Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04d3a8fa18 | ||
|
|
ce536c895a | ||
|
|
cfb43b257b | ||
|
|
5522cf3c89 | ||
|
|
1e0dc96216 | ||
|
|
73d4527033 | ||
|
|
89850be39f |
@@ -1,4 +1,3 @@
|
|||||||
* text eol=lf
|
* text eol=lf
|
||||||
*.png binary
|
*.png binary
|
||||||
*.ico binary
|
*.ico binary
|
||||||
*.woff2 binary
|
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
# Auto-format all files
|
|
||||||
yarn nx format:write
|
|
||||||
|
|
||||||
# Lint all staged files
|
# Lint all staged files
|
||||||
yarn lint-staged
|
yarn lint-staged
|
||||||
|
|||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
# Lint all staged files - this brings more value as pre-commit
|
# Lint all staged files
|
||||||
# yarn nx format:check
|
yarn nx format:check
|
||||||
|
|
||||||
# Test all projects with changes
|
# Test all projects with changes
|
||||||
# yarn nx affected -t test --exclude trading
|
yarn nx affected -t test --exclude trading
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-web
|
|||||||
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
import { DEFAULT_CACHE_CONFIG } from '@vegaprotocol/apollo-client';
|
import { DEFAULT_CACHE_CONFIG } from '@vegaprotocol/apollo-client';
|
||||||
import { RouterProvider } from 'react-router-dom';
|
import { RouterProvider } from 'react-router-dom';
|
||||||
import { useRouterConfig } from './routes/router-config';
|
import { router } from './routes/router-config';
|
||||||
import { createBrowserRouter } from 'react-router-dom';
|
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
@@ -39,10 +38,7 @@ function App() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Suspense fallback={splashLoading}>
|
<Suspense fallback={splashLoading}>
|
||||||
<RouterProvider
|
<RouterProvider router={router} fallbackElement={splashLoading} />
|
||||||
router={createBrowserRouter(useRouterConfig())}
|
|
||||||
fallbackElement={splashLoading}
|
|
||||||
/>
|
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</NodeGuard>
|
</NodeGuard>
|
||||||
<NodeSwitcherDialog
|
<NodeSwitcherDialog
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { Routes } from '../../routes/route-names';
|
|||||||
import { NetworkSwitcher } from '@vegaprotocol/environment';
|
import { NetworkSwitcher } from '@vegaprotocol/environment';
|
||||||
import type { Navigable } from '../../routes/router-config';
|
import type { Navigable } from '../../routes/router-config';
|
||||||
import { isNavigable } from '../../routes/router-config';
|
import { isNavigable } from '../../routes/router-config';
|
||||||
import { useRouterConfig } from '../../routes/router-config';
|
import { routerConfig } from '../../routes/router-config';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import compact from 'lodash/compact';
|
import compact from 'lodash/compact';
|
||||||
import { Search } from '../search';
|
import { Search } from '../search';
|
||||||
@@ -26,7 +26,6 @@ const routeToNavigationItem = (r: Navigable) => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const Header = () => {
|
export const Header = () => {
|
||||||
const routerConfig = useRouterConfig();
|
|
||||||
const isHome = Boolean(useMatch(Routes.HOME));
|
const isHome = Boolean(useMatch(Routes.HOME));
|
||||||
const pages = routerConfig[0].children || [];
|
const pages = routerConfig[0].children || [];
|
||||||
const mainItems = compact(
|
const mainItems = compact(
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { Time } from '../time';
|
|||||||
import { sideText, statusText, tifFull, tifShort } from './lib/order-labels';
|
import { sideText, statusText, tifFull, tifShort } from './lib/order-labels';
|
||||||
import SizeInMarket from '../size-in-market/size-in-market';
|
import SizeInMarket from '../size-in-market/size-in-market';
|
||||||
import { TxOrderPeggedReference } from '../txs/details/order/tx-order-peg';
|
import { TxOrderPeggedReference } from '../txs/details/order/tx-order-peg';
|
||||||
import { OrderTypeMapping } from '@vegaprotocol/types';
|
|
||||||
|
|
||||||
export interface DeterministicOrderDetailsProps {
|
export interface DeterministicOrderDetailsProps {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -70,7 +69,7 @@ const DeterministicOrderDetails = ({
|
|||||||
<span className="mx-5 text-base">@</span>
|
<span className="mx-5 text-base">@</span>
|
||||||
<PriceInMarket price={o.price} marketId={o.market.id} />
|
<PriceInMarket price={o.price} marketId={o.market.id} />
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-400 dark:text-gray-600">
|
<p className="text-gray-200">
|
||||||
In <MarketLink id={o.market.id} /> at <Time date={o.createdAt} />.
|
In <MarketLink id={o.market.id} /> at <Time date={o.createdAt} />.
|
||||||
</p>
|
</p>
|
||||||
{o.peggedOrder ? (
|
{o.peggedOrder ? (
|
||||||
@@ -84,12 +83,13 @@ const DeterministicOrderDetails = ({
|
|||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{o.reference ? (
|
{o.reference ? (
|
||||||
<p className="text-gray-500 mt-4">
|
<p className="text-gray-500 mt-4">
|
||||||
<span>{t('Reference')}</span>: {o.reference}
|
<span>{t('Reference')}</span>: {o.reference}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="grid md:grid-cols-5 gap-x-6 mt-4">
|
<div className="grid md:grid-cols-4 gap-x-6 mt-4">
|
||||||
<div className="mb-12 md:mb-0">
|
<div className="mb-12 md:mb-0">
|
||||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
<h2 className="text-2xl font-bold text-dark mb-4">
|
||||||
{t('Status')}
|
{t('Status')}
|
||||||
@@ -114,16 +114,6 @@ const DeterministicOrderDetails = ({
|
|||||||
{o.version}
|
{o.version}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
{o.type ? (
|
|
||||||
<div className="">
|
|
||||||
<h2 className="text-2xl font-bold text-dark 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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -80,12 +80,6 @@ export function getLabelForOrderType(
|
|||||||
if (command.orderSubmission.icebergOpts) {
|
if (command.orderSubmission.icebergOpts) {
|
||||||
return 'Iceberg';
|
return 'Iceberg';
|
||||||
}
|
}
|
||||||
if (command.orderSubmission.type === 'TYPE_MARKET') {
|
|
||||||
return 'Market order';
|
|
||||||
}
|
|
||||||
if (command.orderSubmission.type === 'TYPE_LIMIT') {
|
|
||||||
return 'Limit order';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return 'Order';
|
return 'Order';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const Block = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
{blockData && 'result' in blockData && (
|
{blockData && (
|
||||||
<>
|
<>
|
||||||
<TableWithTbody className="mb-8">
|
<TableWithTbody className="mb-8">
|
||||||
<TableRow modifier="bordered">
|
<TableRow modifier="bordered">
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { t } from '@vegaprotocol/i18n';
|
|||||||
import { Routes } from './route-names';
|
import { Routes } from './route-names';
|
||||||
import { NetworkParameters } from './network-parameters';
|
import { NetworkParameters } from './network-parameters';
|
||||||
import type { Params, RouteObject } from 'react-router-dom';
|
import type { Params, RouteObject } from 'react-router-dom';
|
||||||
|
import { createBrowserRouter } from 'react-router-dom';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { MarketPage, MarketsPage } from './markets';
|
import { MarketPage, MarketsPage } from './markets';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
@@ -28,7 +29,7 @@ import { truncateMiddle } from '@vegaprotocol/ui-toolkit';
|
|||||||
import { remove0x } from '@vegaprotocol/utils';
|
import { remove0x } from '@vegaprotocol/utils';
|
||||||
import { PartyAccountsByAsset } from './parties/id/accounts';
|
import { PartyAccountsByAsset } from './parties/id/accounts';
|
||||||
import { Disclaimer } from './pages/disclaimer';
|
import { Disclaimer } from './pages/disclaimer';
|
||||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
import RestrictedPage from './restricted';
|
import RestrictedPage from './restricted';
|
||||||
|
|
||||||
export type Navigable = {
|
export type Navigable = {
|
||||||
@@ -59,315 +60,311 @@ type Route = RouteItem & {
|
|||||||
children?: RouteItem[];
|
children?: RouteItem[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useRouterConfig = () => {
|
const partiesRoutes: Route[] = FLAGS.EXPLORER_PARTIES
|
||||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
? [
|
||||||
|
{
|
||||||
const partiesRoutes: Route[] = featureFlags.EXPLORER_PARTIES
|
path: Routes.PARTIES,
|
||||||
? [
|
element: <Party />,
|
||||||
{
|
handle: {
|
||||||
path: Routes.PARTIES,
|
name: t('Parties'),
|
||||||
element: <Party />,
|
text: t('Parties'),
|
||||||
handle: {
|
breadcrumb: () => <Link to={Routes.PARTIES}>{t('Parties')}</Link>,
|
||||||
name: t('Parties'),
|
},
|
||||||
text: t('Parties'),
|
children: [
|
||||||
breadcrumb: () => <Link to={Routes.PARTIES}>{t('Parties')}</Link>,
|
{
|
||||||
|
index: true,
|
||||||
|
element: <Parties />,
|
||||||
},
|
},
|
||||||
children: [
|
{
|
||||||
{
|
path: ':party',
|
||||||
index: true,
|
element: <Party />,
|
||||||
element: <Parties />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: ':party',
|
|
||||||
element: <Party />,
|
|
||||||
|
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
index: true,
|
index: true,
|
||||||
element: <PartySingle />,
|
element: <PartySingle />,
|
||||||
handle: {
|
handle: {
|
||||||
breadcrumb: (params: Params<string>) => (
|
breadcrumb: (params: Params<string>) => (
|
||||||
<Link to={linkTo(Routes.PARTIES, params.party)}>
|
<Link to={linkTo(Routes.PARTIES, params.party)}>
|
||||||
{truncateMiddle(params.party as string)}
|
{truncateMiddle(params.party as string)}
|
||||||
</Link>
|
</Link>
|
||||||
),
|
),
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
path: 'assets',
|
{
|
||||||
element: <Party />,
|
path: 'assets',
|
||||||
handle: {
|
element: <Party />,
|
||||||
breadcrumb: (params: Params<string>) => (
|
handle: {
|
||||||
<Link to={linkTo(Routes.PARTIES, params.party)}>
|
breadcrumb: (params: Params<string>) => (
|
||||||
{truncateMiddle(params.party as string)}
|
<Link to={linkTo(Routes.PARTIES, params.party)}>
|
||||||
</Link>
|
{truncateMiddle(params.party as string)}
|
||||||
),
|
</Link>
|
||||||
},
|
),
|
||||||
children: [
|
},
|
||||||
{
|
children: [
|
||||||
index: true,
|
{
|
||||||
element: <PartyAccountsByAsset />,
|
index: true,
|
||||||
handle: {
|
element: <PartyAccountsByAsset />,
|
||||||
breadcrumb: () => {
|
handle: {
|
||||||
return t('Assets');
|
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 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
|
const assetsRoutes: Route[] = FLAGS.EXPLORER_ASSETS
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
path: Routes.GOVERNANCE,
|
path: Routes.ASSETS,
|
||||||
handle: {
|
handle: {
|
||||||
name: t('Governance proposals'),
|
name: t('Assets'),
|
||||||
text: t('Governance Proposals'),
|
text: t('Assets'),
|
||||||
breadcrumb: () => (
|
breadcrumb: () => <Link to={Routes.ASSETS}>{t('Assets')}</Link>,
|
||||||
<Link to={Routes.GOVERNANCE}>{t('Governance Proposals')}</Link>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
element: <Proposals />,
|
|
||||||
},
|
},
|
||||||
]
|
children: [
|
||||||
: [];
|
|
||||||
|
|
||||||
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: Routes.NETWORK_PARAMETERS,
|
index: true,
|
||||||
|
element: <AssetsPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ':assetId',
|
||||||
|
element: <AssetPage />,
|
||||||
handle: {
|
handle: {
|
||||||
name: t('NetworkParameters'),
|
breadcrumb: (params: Params<string>) => (
|
||||||
text: t('Network Parameters'),
|
<AssetLink assetId={params.assetId as string} />
|
||||||
breadcrumb: () => (
|
),
|
||||||
<Link to={Routes.NETWORK_PARAMETERS}>
|
},
|
||||||
{t('Network Parameters')}
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
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 />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ':marketId',
|
||||||
|
element: <MarketPage />,
|
||||||
|
handle: {
|
||||||
|
breadcrumb: (params: Params<string>) => (
|
||||||
|
<MarketLink id={params.marketId as string} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
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: <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>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
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>,
|
||||||
|
},
|
||||||
|
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>
|
</Link>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
element: <NetworkParameters />,
|
|
||||||
},
|
},
|
||||||
]
|
{
|
||||||
: [];
|
index: true,
|
||||||
|
element: <TxsList />,
|
||||||
const validators: Route[] = featureFlags.EXPLORER_VALIDATORS
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
path: Routes.VALIDATORS,
|
|
||||||
handle: {
|
|
||||||
name: t('Validators'),
|
|
||||||
text: t('Validators'),
|
|
||||||
breadcrumb: () => (
|
|
||||||
<Link to={Routes.VALIDATORS}>{t('Validators')}</Link>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
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: [
|
path: Routes.BLOCKS,
|
||||||
{
|
handle: {
|
||||||
index: true,
|
name: t('Blocks'),
|
||||||
element: <Home />,
|
text: t('Blocks'),
|
||||||
|
breadcrumb: () => <Link to={Routes.BLOCKS}>{t('Blocks')}</Link>,
|
||||||
},
|
},
|
||||||
{
|
element: <BlockPage />,
|
||||||
path: Routes.TX,
|
children: [
|
||||||
handle: {
|
{
|
||||||
name: t('Txs'),
|
index: true,
|
||||||
text: t('Transactions'),
|
element: <Blocks />,
|
||||||
breadcrumb: () => <Link to={Routes.TX}>{t('Transactions')}</Link>,
|
|
||||||
},
|
},
|
||||||
children: [
|
{
|
||||||
{
|
path: ':block',
|
||||||
path: ':txHash',
|
element: <Block />,
|
||||||
element: <Tx />,
|
handle: {
|
||||||
handle: {
|
breadcrumb: (params: Params<string>) => (
|
||||||
breadcrumb: (params: Params<string>) => (
|
<Link to={linkTo(Routes.BLOCKS, params.block)}>
|
||||||
<Link to={linkTo(Routes.TX, params.txHash)}>
|
{params.block}
|
||||||
{truncateMiddle(remove0x(params.txHash as string))}
|
</Link>
|
||||||
</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'),
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
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'),
|
||||||
},
|
},
|
||||||
];
|
},
|
||||||
return routerConfig;
|
];
|
||||||
};
|
|
||||||
|
export const router = createBrowserRouter(routerConfig);
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ CYPRESS_FAIRGROUND=false
|
|||||||
|
|
||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
NX_METAMASK_SNAPS=true
|
NX_METAMASK_SNAPS=false
|
||||||
NX_PRODUCT_PERPETUALS=true
|
NX_PRODUCT_PERPETUALS=true
|
||||||
NX_UPDATE_MARKET_STATE=true
|
NX_UPDATE_MARKET_STATE=true
|
||||||
NX_REFERRALS=true
|
NX_REFERRALS=true
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
|||||||
|
|
||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
NX_METAMASK_SNAPS=true
|
NX_METAMASK_SNAPS=false
|
||||||
NX_PRODUCT_PERPETUALS=true
|
NX_PRODUCT_PERPETUALS=true
|
||||||
NX_UPDATE_MARKET_STATE=true
|
NX_UPDATE_MARKET_STATE=true
|
||||||
NX_REFERRALS=true
|
NX_REFERRALS=true
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
|
|||||||
|
|
||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
NX_METAMASK_SNAPS=true
|
NX_METAMASK_SNAPS=false
|
||||||
NX_PRODUCT_PERPETUALS=true
|
NX_PRODUCT_PERPETUALS=true
|
||||||
NX_UPDATE_MARKET_STATE=true
|
NX_UPDATE_MARKET_STATE=true
|
||||||
NX_REFERRALS=true
|
NX_REFERRALS=true
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.
|
|||||||
|
|
||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
NX_METAMASK_SNAPS=true
|
NX_METAMASK_SNAPS=false
|
||||||
NX_PRODUCT_PERPETUALS=true
|
NX_PRODUCT_PERPETUALS=true
|
||||||
NX_UPDATE_MARKET_STATE=true
|
NX_UPDATE_MARKET_STATE=true
|
||||||
NX_REFERRALS=true
|
NX_REFERRALS=true
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as Sentry from '@sentry/react';
|
|||||||
import { toBigNum } from '@vegaprotocol/utils';
|
import { toBigNum } from '@vegaprotocol/utils';
|
||||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useVegaWallet, useEagerConnect } from '@vegaprotocol/wallet';
|
import { useVegaWallet, useEagerConnect } from '@vegaprotocol/wallet';
|
||||||
import { useFeatureFlags, useEnvironment } from '@vegaprotocol/environment';
|
import { FLAGS, useEnvironment } from '@vegaprotocol/environment';
|
||||||
import { useWeb3React } from '@web3-react/core';
|
import { useWeb3React } from '@web3-react/core';
|
||||||
import React, { Suspense } from 'react';
|
import React, { Suspense } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -15,23 +15,21 @@ import {
|
|||||||
} from './contexts/app-state/app-state-context';
|
} from './contexts/app-state/app-state-context';
|
||||||
import { useContracts } from './contexts/contracts/contracts-context';
|
import { useContracts } from './contexts/contracts/contracts-context';
|
||||||
import { useRefreshAssociatedBalances } from './hooks/use-refresh-associated-balances';
|
import { useRefreshAssociatedBalances } from './hooks/use-refresh-associated-balances';
|
||||||
import { useConnectors } from './lib/vega-connectors';
|
import { Connectors } from './lib/vega-connectors';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
const useVegaWalletEagerConnect = () => {
|
const useVegaWalletEagerConnect = () => {
|
||||||
const connectors = useConnectors();
|
const vegaConnecting = useEagerConnect(Connectors);
|
||||||
const vegaConnecting = useEagerConnect(connectors);
|
|
||||||
const { pubKey, connect } = useVegaWallet();
|
const { pubKey, connect } = useVegaWallet();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const [query] = React.useState(searchParams.get('address'));
|
const [query] = React.useState(searchParams.get('address'));
|
||||||
if (query && !pubKey) {
|
if (query && !pubKey) {
|
||||||
connect(connectors.view);
|
connect(Connectors['view']);
|
||||||
}
|
}
|
||||||
return vegaConnecting;
|
return vegaConnecting;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { account } = useWeb3React();
|
const { account } = useWeb3React();
|
||||||
const { VEGA_URL } = useEnvironment();
|
const { VEGA_URL } = useEnvironment();
|
||||||
@@ -81,16 +79,10 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!featureFlags.GOVERNANCE_NETWORK_DOWN) {
|
if (!FLAGS.GOVERNANCE_NETWORK_DOWN) {
|
||||||
run();
|
run();
|
||||||
}
|
}
|
||||||
}, [
|
}, [token, appDispatch, staking, vesting]);
|
||||||
token,
|
|
||||||
appDispatch,
|
|
||||||
staking,
|
|
||||||
vesting,
|
|
||||||
featureFlags.GOVERNANCE_NETWORK_DOWN,
|
|
||||||
]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (account && pubKey) {
|
if (account && pubKey) {
|
||||||
@@ -155,16 +147,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
|
// Only begin polling if network limits flag is set, as this is a new API not yet on mainnet 7/3/22
|
||||||
if (featureFlags.GOVERNANCE_NETWORK_LIMITS) {
|
if (FLAGS.GOVERNANCE_NETWORK_LIMITS) {
|
||||||
getNetworkLimits();
|
getNetworkLimits();
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
stopPoll();
|
stopPoll();
|
||||||
};
|
};
|
||||||
}, [appDispatch, VEGA_URL, t, featureFlags.GOVERNANCE_NETWORK_LIMITS]);
|
}, [appDispatch, VEGA_URL, t]);
|
||||||
|
|
||||||
if (featureFlags.GOVERNANCE_NETWORK_DOWN) {
|
if (FLAGS.GOVERNANCE_NETWORK_DOWN) {
|
||||||
return (
|
return (
|
||||||
<Splash>
|
<Splash>
|
||||||
<SplashError />
|
<SplashError />
|
||||||
|
|||||||
@@ -23,13 +23,10 @@ export const Heading = ({
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<h1
|
<h1
|
||||||
className={classNames(
|
className={classNames('font-alpha calt text-5xl break-words', {
|
||||||
'font-alpha calt text-5xl [word-break:break-word]',
|
'mt-0': !marginTop,
|
||||||
{
|
'mb-0': !marginBottom,
|
||||||
'mt-0': !marginTop,
|
})}
|
||||||
'mb-0': !marginBottom,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</h1>
|
</h1>
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ import {
|
|||||||
AppStateActionType,
|
AppStateActionType,
|
||||||
useAppState,
|
useAppState,
|
||||||
} from '../../contexts/app-state/app-state-context';
|
} from '../../contexts/app-state/app-state-context';
|
||||||
import { useConnectors } from '../../lib/vega-connectors';
|
import { Connectors } from '../../lib/vega-connectors';
|
||||||
import { RiskMessage } from './risk-message';
|
import { RiskMessage } from './risk-message';
|
||||||
|
|
||||||
export const VegaWalletDialogs = () => {
|
export const VegaWalletDialogs = () => {
|
||||||
const { appState, appDispatch } = useAppState();
|
const { appState, appDispatch } = useAppState();
|
||||||
const connectors = useConnectors();
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<VegaConnectDialog
|
<VegaConnectDialog
|
||||||
connectors={connectors}
|
connectors={Connectors}
|
||||||
riskMessage={<RiskMessage />}
|
riskMessage={<RiskMessage />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ export const VegaWalletDialogs = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ViewAsDialog connector={connectors.view} />
|
<ViewAsDialog connector={Connectors.view} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -49,8 +49,12 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
|
|||||||
? activeProvider
|
? activeProvider
|
||||||
: defaultProvider;
|
: defaultProvider;
|
||||||
|
|
||||||
if (account && provider && typeof provider.getSigner === 'function') {
|
if (
|
||||||
signer = provider.getSigner(account);
|
account &&
|
||||||
|
activeProvider &&
|
||||||
|
typeof activeProvider.getSigner === 'function'
|
||||||
|
) {
|
||||||
|
signer = provider.getSigner();
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokenVestingAddress =
|
const tokenVestingAddress =
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
import { useMemo } from 'react';
|
|
||||||
import {
|
import {
|
||||||
JsonRpcConnector,
|
JsonRpcConnector,
|
||||||
ViewConnector,
|
ViewConnector,
|
||||||
@@ -14,17 +13,13 @@ export const jsonRpc = new JsonRpcConnector();
|
|||||||
export const injected = new InjectedConnector();
|
export const injected = new InjectedConnector();
|
||||||
export const view = new ViewConnector(urlParams.get('address'));
|
export const view = new ViewConnector(urlParams.get('address'));
|
||||||
|
|
||||||
export const snap = new SnapConnector(DEFAULT_SNAP_ID);
|
export const snap = FLAGS.METAMASK_SNAPS
|
||||||
|
? new SnapConnector(DEFAULT_SNAP_ID)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
export const useConnectors = () => {
|
export const Connectors = {
|
||||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
injected,
|
||||||
return useMemo(
|
jsonRpc,
|
||||||
() => ({
|
view,
|
||||||
injected,
|
snap,
|
||||||
jsonRpc,
|
|
||||||
view,
|
|
||||||
snap: featureFlags.METAMASK_SNAPS ? snap : undefined,
|
|
||||||
}),
|
|
||||||
[featureFlags.METAMASK_SNAPS]
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { useRefreshAfterEpoch } from '../../hooks/use-refresh-after-epoch';
|
|||||||
import { ProposalsListItem } from '../proposals/components/proposals-list-item';
|
import { ProposalsListItem } from '../proposals/components/proposals-list-item';
|
||||||
import { ProtocolUpgradeProposalsListItem } from '../proposals/components/protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item';
|
import { ProtocolUpgradeProposalsListItem } from '../proposals/components/protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item';
|
||||||
import Routes from '../routes';
|
import Routes from '../routes';
|
||||||
import { ExternalLinks, useFeatureFlags } from '@vegaprotocol/environment';
|
import { ExternalLinks, FLAGS } from '@vegaprotocol/environment';
|
||||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||||
import { useNodesQuery } from '../staking/home/__generated__/Nodes';
|
import { useNodesQuery } from '../staking/home/__generated__/Nodes';
|
||||||
import { useProposalsQuery } from '../proposals/proposals/__generated__/Proposals';
|
import { useProposalsQuery } from '../proposals/proposals/__generated__/Proposals';
|
||||||
@@ -175,7 +175,6 @@ export const ValidatorDetailsLink = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const GovernanceHome = ({ name }: RouteChildProps) => {
|
const GovernanceHome = ({ name }: RouteChildProps) => {
|
||||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
|
||||||
useDocumentTitle(name);
|
useDocumentTitle(name);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const {
|
const {
|
||||||
@@ -187,9 +186,9 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
|
|||||||
fetchPolicy: 'network-only',
|
fetchPolicy: 'network-only',
|
||||||
errorPolicy: 'ignore',
|
errorPolicy: 'ignore',
|
||||||
variables: {
|
variables: {
|
||||||
includeNewMarketProductFields: !!featureFlags.PRODUCT_PERPETUALS,
|
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
|
||||||
includeUpdateMarketStates: !!featureFlags.UPDATE_MARKET_STATE,
|
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
||||||
includeUpdateReferralPrograms: !!featureFlags.REFERRALS,
|
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
-3
@@ -7,10 +7,8 @@ import type { AssetFieldsFragment } from '@vegaprotocol/assets';
|
|||||||
|
|
||||||
export const ProposalAssetDetails = ({
|
export const ProposalAssetDetails = ({
|
||||||
asset,
|
asset,
|
||||||
originalAsset,
|
|
||||||
}: {
|
}: {
|
||||||
asset: AssetFieldsFragment;
|
asset: AssetFieldsFragment;
|
||||||
originalAsset?: AssetFieldsFragment;
|
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [showAssetDetails, setShowAssetDetails] = useState(false);
|
const [showAssetDetails, setShowAssetDetails] = useState(false);
|
||||||
@@ -29,7 +27,6 @@ export const ProposalAssetDetails = ({
|
|||||||
<div className="mb-10 pb-4">
|
<div className="mb-10 pb-4">
|
||||||
<AssetDetailsTable
|
<AssetDetailsTable
|
||||||
asset={asset}
|
asset={asset}
|
||||||
originalAsset={originalAsset}
|
|
||||||
omitRows={[
|
omitRows={[
|
||||||
AssetDetail.STATUS,
|
AssetDetail.STATUS,
|
||||||
AssetDetail.INFRASTRUCTURE_FEE_ACCOUNT_BALANCE,
|
AssetDetail.INFRASTRUCTURE_FEE_ACCOUNT_BALANCE,
|
||||||
|
|||||||
+3
-2
@@ -19,7 +19,7 @@ import {
|
|||||||
mockWalletContext,
|
mockWalletContext,
|
||||||
createUserVoteQueryMock,
|
createUserVoteQueryMock,
|
||||||
} from '../../test-helpers/mocks';
|
} from '../../test-helpers/mocks';
|
||||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import { VoteState } from '../vote-details/use-user-vote';
|
import { VoteState } from '../vote-details/use-user-vote';
|
||||||
import { useNewTransferProposalDetails } from '@vegaprotocol/proposals';
|
import { useNewTransferProposalDetails } from '@vegaprotocol/proposals';
|
||||||
@@ -62,7 +62,8 @@ describe('Proposal header', () => {
|
|||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
it('Renders New market proposal', () => {
|
it('Renders New market proposal', () => {
|
||||||
useFeatureFlags.setState({ flags: { SUCCESSOR_MARKETS: true } });
|
const mockedFlags = jest.mocked(FLAGS);
|
||||||
|
mockedFlags.SUCCESSOR_MARKETS = true;
|
||||||
renderComponent(
|
renderComponent(
|
||||||
generateProposal({
|
generateProposal({
|
||||||
rationale: {
|
rationale: {
|
||||||
|
|||||||
+7
-9
@@ -12,7 +12,7 @@ import {
|
|||||||
useNewTransferProposalDetails,
|
useNewTransferProposalDetails,
|
||||||
useSuccessorMarketProposalDetails,
|
useSuccessorMarketProposalDetails,
|
||||||
} from '@vegaprotocol/proposals';
|
} from '@vegaprotocol/proposals';
|
||||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
import Routes from '../../../routes';
|
import Routes from '../../../routes';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import type { VoteState } from '../vote-details/use-user-vote';
|
import type { VoteState } from '../vote-details/use-user-vote';
|
||||||
@@ -28,7 +28,6 @@ export const ProposalHeader = ({
|
|||||||
isListItem?: boolean;
|
isListItem?: boolean;
|
||||||
voteState?: VoteState | null;
|
voteState?: VoteState | null;
|
||||||
}) => {
|
}) => {
|
||||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const change = proposal?.terms.change;
|
const change = proposal?.terms.change;
|
||||||
|
|
||||||
@@ -55,14 +54,13 @@ export const ProposalHeader = ({
|
|||||||
switch (change?.__typename) {
|
switch (change?.__typename) {
|
||||||
case 'NewMarket': {
|
case 'NewMarket': {
|
||||||
proposalType =
|
proposalType =
|
||||||
featureFlags.PRODUCT_PERPETUALS &&
|
FLAGS.PRODUCT_PERPETUALS && change?.instrument?.product?.__typename
|
||||||
change?.instrument?.product?.__typename
|
|
||||||
? `NewMarket${change?.instrument?.product?.__typename}`
|
? `NewMarket${change?.instrument?.product?.__typename}`
|
||||||
: 'NewMarket';
|
: 'NewMarket';
|
||||||
fallbackTitle = t('NewMarketProposal');
|
fallbackTitle = t('NewMarketProposal');
|
||||||
details = (
|
details = (
|
||||||
<>
|
<>
|
||||||
{featureFlags.SUCCESSOR_MARKETS && (
|
{FLAGS.SUCCESSOR_MARKETS && (
|
||||||
<SuccessorCode proposalId={proposal?.id} />
|
<SuccessorCode proposalId={proposal?.id} />
|
||||||
)}
|
)}
|
||||||
<span>
|
<span>
|
||||||
@@ -84,13 +82,13 @@ export const ProposalHeader = ({
|
|||||||
}
|
}
|
||||||
case 'UpdateMarketState': {
|
case 'UpdateMarketState': {
|
||||||
proposalType =
|
proposalType =
|
||||||
featureFlags.UPDATE_MARKET_STATE && change?.updateType
|
FLAGS.UPDATE_MARKET_STATE && change?.updateType
|
||||||
? t(change.updateType)
|
? t(change.updateType)
|
||||||
: 'UpdateMarketState';
|
: 'UpdateMarketState';
|
||||||
fallbackTitle = t('UpdateMarketStateProposal');
|
fallbackTitle = t('UpdateMarketStateProposal');
|
||||||
details = (
|
details = (
|
||||||
<span>
|
<span>
|
||||||
{featureFlags.UPDATE_MARKET_STATE &&
|
{FLAGS.UPDATE_MARKET_STATE &&
|
||||||
change?.market?.id &&
|
change?.market?.id &&
|
||||||
change.updateType ? (
|
change.updateType ? (
|
||||||
<>
|
<>
|
||||||
@@ -179,14 +177,14 @@ export const ProposalHeader = ({
|
|||||||
case 'NewTransfer':
|
case 'NewTransfer':
|
||||||
proposalType = 'NewTransfer';
|
proposalType = 'NewTransfer';
|
||||||
fallbackTitle = t('NewTransferProposal');
|
fallbackTitle = t('NewTransferProposal');
|
||||||
details = featureFlags.GOVERNANCE_TRANSFERS ? (
|
details = FLAGS.GOVERNANCE_TRANSFERS ? (
|
||||||
<NewTransferSummary proposalId={proposal?.id} />
|
<NewTransferSummary proposalId={proposal?.id} />
|
||||||
) : null;
|
) : null;
|
||||||
break;
|
break;
|
||||||
case 'CancelTransfer':
|
case 'CancelTransfer':
|
||||||
proposalType = 'CancelTransfer';
|
proposalType = 'CancelTransfer';
|
||||||
fallbackTitle = t('CancelTransferProposal');
|
fallbackTitle = t('CancelTransferProposal');
|
||||||
details = featureFlags.GOVERNANCE_TRANSFERS ? (
|
details = FLAGS.GOVERNANCE_TRANSFERS ? (
|
||||||
<CancelTransferSummary proposalId={proposal?.id} />
|
<CancelTransferSummary proposalId={proposal?.id} />
|
||||||
) : null;
|
) : null;
|
||||||
break;
|
break;
|
||||||
|
|||||||
+2
-2
@@ -54,8 +54,8 @@ export const ProposalReferralProgramDetails = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const benefitTiers = proposal?.terms?.change?.benefitTiers.slice();
|
const benefitTiers = proposal?.terms?.change?.benefitTiers;
|
||||||
const stakingTiers = proposal?.terms?.change?.stakingTiers.slice();
|
const stakingTiers = proposal?.terms?.change?.stakingTiers;
|
||||||
const windowLength = proposal?.terms?.change?.windowLength;
|
const windowLength = proposal?.terms?.change?.windowLength;
|
||||||
const endOfProgramTimestamp = proposal?.terms?.change?.endOfProgram;
|
const endOfProgramTimestamp = proposal?.terms?.change?.endOfProgram;
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import {
|
|||||||
ProposalCancelTransferDetails,
|
ProposalCancelTransferDetails,
|
||||||
ProposalTransferDetails,
|
ProposalTransferDetails,
|
||||||
} from '../proposal-transfer';
|
} from '../proposal-transfer';
|
||||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
import { ProposalUpdateBenefitTiers } from '../proposal-update-benefit-tiers';
|
import { ProposalUpdateBenefitTiers } from '../proposal-update-benefit-tiers';
|
||||||
|
|
||||||
export interface ProposalProps {
|
export interface ProposalProps {
|
||||||
@@ -53,7 +53,6 @@ export const Proposal = ({
|
|||||||
originalMarketProposalRestData,
|
originalMarketProposalRestData,
|
||||||
mostRecentlyEnactedAssociatedMarketProposal,
|
mostRecentlyEnactedAssociatedMarketProposal,
|
||||||
}: ProposalProps) => {
|
}: ProposalProps) => {
|
||||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { submit, Dialog, finalizedVote, transaction } = useVoteSubmit();
|
const { submit, Dialog, finalizedVote, transaction } = useVoteSubmit();
|
||||||
const { voteState, voteDatetime } = useUserVote(proposal?.id, finalizedVote);
|
const { voteState, voteDatetime } = useUserVote(proposal?.id, finalizedVote);
|
||||||
@@ -66,13 +65,10 @@ export const Proposal = ({
|
|||||||
? removePaginationWrapper(assetData.assetsConnection?.edges)[0]
|
? removePaginationWrapper(assetData.assetsConnection?.edges)[0]
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const originalAsset = asset;
|
|
||||||
|
|
||||||
if (proposal.terms.change.__typename === 'UpdateAsset' && asset) {
|
if (proposal.terms.change.__typename === 'UpdateAsset' && asset) {
|
||||||
asset = {
|
asset = {
|
||||||
...asset,
|
...asset,
|
||||||
quantum: proposal.terms.change.quantum,
|
quantum: proposal.terms.change.quantum,
|
||||||
source: { ...asset.source },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (asset.source.__typename === 'ERC20') {
|
if (asset.source.__typename === 'ERC20') {
|
||||||
@@ -133,7 +129,7 @@ export const Proposal = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Show governance transfer details only if the GOVERNANCE_TRANSFERS flag is on.
|
// Show governance transfer details only if the GOVERNANCE_TRANSFERS flag is on.
|
||||||
const governanceTransferDetails = featureFlags.GOVERNANCE_TRANSFERS && (
|
const governanceTransferDetails = FLAGS.GOVERNANCE_TRANSFERS && (
|
||||||
<>
|
<>
|
||||||
{proposal.terms.change.__typename === 'NewTransfer' && (
|
{proposal.terms.change.__typename === 'NewTransfer' && (
|
||||||
/** Governance New Transfer Details */
|
/** Governance New Transfer Details */
|
||||||
@@ -232,7 +228,7 @@ export const Proposal = ({
|
|||||||
proposal.terms.change.__typename === 'UpdateAsset') &&
|
proposal.terms.change.__typename === 'UpdateAsset') &&
|
||||||
asset && (
|
asset && (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<ProposalAssetDetails asset={asset} originalAsset={originalAsset} />
|
<ProposalAssetDetails asset={asset} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -281,8 +281,8 @@ describe('VoteBreakdown', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Progress bar displays status - LP majority', () => {
|
it('Progress bar displays status - LP majority', () => {
|
||||||
const yesVotesLP = 0.8;
|
const yesVotesLP = 800;
|
||||||
const noVotesLP = 0.2;
|
const noVotesLP = 200;
|
||||||
const expectedProgress = (yesVotesLP / (yesVotesLP + noVotesLP)) * 100; // 80%
|
const expectedProgress = (yesVotesLP / (yesVotesLP + noVotesLP)) * 100; // 80%
|
||||||
|
|
||||||
renderComponent(
|
renderComponent(
|
||||||
|
|||||||
@@ -105,6 +105,8 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
|||||||
yesLPPercentage,
|
yesLPPercentage,
|
||||||
yesTokens,
|
yesTokens,
|
||||||
noTokens,
|
noTokens,
|
||||||
|
yesEquityLikeShareWeight,
|
||||||
|
noEquityLikeShareWeight,
|
||||||
totalEquityLikeShareWeight,
|
totalEquityLikeShareWeight,
|
||||||
requiredMajorityPercentage,
|
requiredMajorityPercentage,
|
||||||
requiredMajorityLPPercentage,
|
requiredMajorityLPPercentage,
|
||||||
@@ -133,7 +135,6 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
|||||||
.multipliedBy(100),
|
.multipliedBy(100),
|
||||||
new BigNumber(100)
|
new BigNumber(100)
|
||||||
);
|
);
|
||||||
|
|
||||||
const willPass = willPassByTokenVote || willPassByLPVote;
|
const willPass = willPassByTokenVote || willPassByLPVote;
|
||||||
const updateMarketVotePassMethod = willPassByTokenVote
|
const updateMarketVotePassMethod = willPassByTokenVote
|
||||||
? t('byTokenVote')
|
? t('byTokenVote')
|
||||||
@@ -201,24 +202,50 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
|||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span>{t('liquidityProviderVotesFor')}:</span>
|
<span>{t('liquidityProviderVotesFor')}:</span>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
description={
|
description={formatNumber(
|
||||||
<span>{yesLPPercentage.toFixed(defaultDP)}%</span>
|
yesEquityLikeShareWeight,
|
||||||
}
|
defaultDP
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<button>{yesLPPercentage.toFixed(1)}%</button>
|
<button>
|
||||||
|
<CompactVotes number={yesEquityLikeShareWeight} />
|
||||||
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
<span>
|
||||||
|
(
|
||||||
|
<Tooltip
|
||||||
|
description={
|
||||||
|
<span>{yesLPPercentage.toFixed(defaultDP)}%</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<button>{yesLPPercentage.toFixed(0)}%</button>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span>{t('liquidityProviderVotesAgainst')}:</span>
|
<span>{t('liquidityProviderVotesAgainst')}:</span>
|
||||||
|
<Tooltip
|
||||||
|
description={formatNumber(
|
||||||
|
noEquityLikeShareWeight,
|
||||||
|
defaultDP
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<button>
|
||||||
|
<CompactVotes number={noEquityLikeShareWeight} />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
<span>
|
<span>
|
||||||
|
(
|
||||||
<Tooltip
|
<Tooltip
|
||||||
description={
|
description={
|
||||||
<span>{noLPPercentage.toFixed(defaultDP)}%</span>
|
<span>{noLPPercentage.toFixed(defaultDP)}%</span>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<button>{noLPPercentage.toFixed(1)}%</button>
|
<button>{noLPPercentage.toFixed(0)}%</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -255,8 +282,13 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
|||||||
defaultDP
|
defaultDP
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span>{totalEquityLikeShareWeight.toFixed(1)}%</span>
|
<button>
|
||||||
|
<CompactVotes number={totalEquityLikeShareWeight} />
|
||||||
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
<span>
|
||||||
|
({totalEquityLikeShareWeight.toFixed(defaultDP)}%)
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ describe('use-vote-information', () => {
|
|||||||
it('returns all required vote information', () => {
|
it('returns all required vote information', () => {
|
||||||
const yesVotes = 40;
|
const yesVotes = 40;
|
||||||
const noVotes = 60;
|
const noVotes = 60;
|
||||||
const yesEquityLikeShareWeight = '0.30';
|
const yesEquityLikeShareWeight = '30';
|
||||||
const noEquityLikeShareWeight = '0.70';
|
const noEquityLikeShareWeight = '70';
|
||||||
// Note - giving a fixedTokenValue of 1 means a ratio of 1:1 votes to tokens, making sums easier :)
|
// Note - giving a fixedTokenValue of 1 means a ratio of 1:1 votes to tokens, making sums easier :)
|
||||||
const fixedTokenValue = 1000000000000000000;
|
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', () => {
|
it('correctly shows whether an update market proposal will pass by token or LP vote - both failing', () => {
|
||||||
const yesVotes = 0.2;
|
const yesVotes = 20;
|
||||||
const noVotes = 0.7;
|
const noVotes = 70;
|
||||||
const yesEquityLikeShareWeight = '0.30';
|
const yesEquityLikeShareWeight = '30';
|
||||||
const noEquityLikeShareWeight = '0.60';
|
const noEquityLikeShareWeight = '60';
|
||||||
const fixedTokenValue = 1000000000000000000;
|
const fixedTokenValue = 1000000000000000000;
|
||||||
|
|
||||||
const proposal = generateProposal({
|
const proposal = generateProposal({
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export const useVoteInformation = ({
|
|||||||
const noEquityLikeShareWeight = !proposal?.votes.no
|
const noEquityLikeShareWeight = !proposal?.votes.no
|
||||||
.totalEquityLikeShareWeight
|
.totalEquityLikeShareWeight
|
||||||
? new BigNumber(0)
|
? new BigNumber(0)
|
||||||
: new BigNumber(proposal.votes.no.totalEquityLikeShareWeight).times(100);
|
: new BigNumber(proposal.votes.no.totalEquityLikeShareWeight);
|
||||||
|
|
||||||
const yesTokens = new BigNumber(
|
const yesTokens = new BigNumber(
|
||||||
addDecimal(proposal?.votes.yes.totalTokens ?? 0, decimals)
|
addDecimal(proposal?.votes.yes.totalTokens ?? 0, decimals)
|
||||||
@@ -70,7 +70,7 @@ export const useVoteInformation = ({
|
|||||||
const yesEquityLikeShareWeight = !proposal?.votes.yes
|
const yesEquityLikeShareWeight = !proposal?.votes.yes
|
||||||
.totalEquityLikeShareWeight
|
.totalEquityLikeShareWeight
|
||||||
? new BigNumber(0)
|
? new BigNumber(0)
|
||||||
: new BigNumber(proposal.votes.yes.totalEquityLikeShareWeight).times(100);
|
: new BigNumber(proposal.votes.yes.totalEquityLikeShareWeight);
|
||||||
|
|
||||||
const totalTokensVoted = yesTokens.plus(noTokens);
|
const totalTokensVoted = yesTokens.plus(noTokens);
|
||||||
|
|
||||||
@@ -81,7 +81,12 @@ export const useVoteInformation = ({
|
|||||||
const yesPercentage = totalTokensVoted.isZero()
|
const yesPercentage = totalTokensVoted.isZero()
|
||||||
? new BigNumber(0)
|
? new BigNumber(0)
|
||||||
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
|
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
|
||||||
const yesLPPercentage = yesEquityLikeShareWeight;
|
|
||||||
|
const yesLPPercentage = totalEquityLikeShareWeight.isZero()
|
||||||
|
? new BigNumber(0)
|
||||||
|
: yesEquityLikeShareWeight
|
||||||
|
.multipliedBy(100)
|
||||||
|
.dividedBy(totalEquityLikeShareWeight);
|
||||||
|
|
||||||
const noPercentage = totalTokensVoted.isZero()
|
const noPercentage = totalTokensVoted.isZero()
|
||||||
? new BigNumber(0)
|
? new BigNumber(0)
|
||||||
@@ -98,7 +103,9 @@ export const useVoteInformation = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const participationLPMet = requiredParticipationLP
|
const participationLPMet = requiredParticipationLP
|
||||||
? totalEquityLikeShareWeight.isGreaterThan(requiredParticipationLP)
|
? totalEquityLikeShareWeight.isGreaterThan(
|
||||||
|
totalSupply.multipliedBy(requiredParticipationLP)
|
||||||
|
)
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
const majorityMet = yesPercentage.isGreaterThanOrEqualTo(
|
const majorityMet = yesPercentage.isGreaterThanOrEqualTo(
|
||||||
@@ -113,7 +120,9 @@ export const useVoteInformation = ({
|
|||||||
.multipliedBy(100)
|
.multipliedBy(100)
|
||||||
.dividedBy(totalSupply);
|
.dividedBy(totalSupply);
|
||||||
|
|
||||||
const totalLPTokensPercentage = totalEquityLikeShareWeight;
|
const totalLPTokensPercentage = totalEquityLikeShareWeight
|
||||||
|
.multipliedBy(100)
|
||||||
|
.dividedBy(totalSupply);
|
||||||
|
|
||||||
const willPassByTokenVote =
|
const willPassByTokenVote =
|
||||||
participationMet &&
|
participationMet &&
|
||||||
|
|||||||
@@ -15,11 +15,10 @@ import {
|
|||||||
useNetworkParams,
|
useNetworkParams,
|
||||||
} from '@vegaprotocol/network-parameters';
|
} from '@vegaprotocol/network-parameters';
|
||||||
import { useParentMarketIdQuery } from '@vegaprotocol/markets';
|
import { useParentMarketIdQuery } from '@vegaprotocol/markets';
|
||||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
import { useSuccessorMarketProposalDetails } from '@vegaprotocol/proposals';
|
import { useSuccessorMarketProposalDetails } from '@vegaprotocol/proposals';
|
||||||
|
|
||||||
export const ProposalContainer = () => {
|
export const ProposalContainer = () => {
|
||||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
|
||||||
const [
|
const [
|
||||||
mostRecentlyEnactedAssociatedMarketProposal,
|
mostRecentlyEnactedAssociatedMarketProposal,
|
||||||
setMostRecentlyEnactedAssociatedMarketProposal,
|
setMostRecentlyEnactedAssociatedMarketProposal,
|
||||||
@@ -60,9 +59,9 @@ export const ProposalContainer = () => {
|
|||||||
errorPolicy: 'ignore',
|
errorPolicy: 'ignore',
|
||||||
variables: {
|
variables: {
|
||||||
proposalId: params.proposalId || '',
|
proposalId: params.proposalId || '',
|
||||||
includeNewMarketProductField: !!featureFlags.PRODUCT_PERPETUALS,
|
includeNewMarketProductField: !!FLAGS.PRODUCT_PERPETUALS,
|
||||||
includeUpdateMarketState: !!featureFlags.UPDATE_MARKET_STATE,
|
includeUpdateMarketState: !!FLAGS.UPDATE_MARKET_STATE,
|
||||||
includeUpdateReferralProgram: !!featureFlags.REFERRALS,
|
includeUpdateReferralProgram: !!FLAGS.REFERRALS,
|
||||||
},
|
},
|
||||||
skip: !params.proposalId,
|
skip: !params.proposalId,
|
||||||
});
|
});
|
||||||
@@ -121,7 +120,7 @@ export const ProposalContainer = () => {
|
|||||||
variables: {
|
variables: {
|
||||||
marketId: marketData?.id || '',
|
marketId: marketData?.id || '',
|
||||||
},
|
},
|
||||||
skip: !featureFlags.SUCCESSOR_MARKETS || !isSuccessor || !marketData?.id,
|
skip: !FLAGS.SUCCESSOR_MARKETS || !isSuccessor || !marketData?.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -134,7 +133,7 @@ export const ProposalContainer = () => {
|
|||||||
variables: {
|
variables: {
|
||||||
marketId: parentMarketId?.market?.parentMarketID || '',
|
marketId: parentMarketId?.market?.parentMarketID || '',
|
||||||
skip:
|
skip:
|
||||||
!featureFlags.SUCCESSOR_MARKETS ||
|
!FLAGS.SUCCESSOR_MARKETS ||
|
||||||
!isSuccessor ||
|
!isSuccessor ||
|
||||||
!parentMarketId?.market?.parentMarketID,
|
!parentMarketId?.market?.parentMarketID,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
} from './__generated__/Proposals';
|
} from './__generated__/Proposals';
|
||||||
import { type ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
import { type ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||||
import { useProtocolUpgradeProposalsQuery } from '@vegaprotocol/proposals';
|
import { useProtocolUpgradeProposalsQuery } from '@vegaprotocol/proposals';
|
||||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
|
|
||||||
export function getNotRejectedProposals(data?: ProposalFieldsFragment[]) {
|
export function getNotRejectedProposals(data?: ProposalFieldsFragment[]) {
|
||||||
return flow([
|
return flow([
|
||||||
@@ -43,16 +43,15 @@ export function getNotRejectedProtocolUpgradeProposals<
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ProposalsContainer = () => {
|
export const ProposalsContainer = () => {
|
||||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { data, loading, error } = useProposalsQuery({
|
const { data, loading, error } = useProposalsQuery({
|
||||||
pollInterval: 5000,
|
pollInterval: 5000,
|
||||||
fetchPolicy: 'network-only',
|
fetchPolicy: 'network-only',
|
||||||
errorPolicy: 'ignore',
|
errorPolicy: 'ignore',
|
||||||
variables: {
|
variables: {
|
||||||
includeNewMarketProductFields: !!featureFlags.PRODUCT_PERPETUALS,
|
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
|
||||||
includeUpdateMarketStates: !!featureFlags.UPDATE_MARKET_STATE,
|
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
||||||
includeUpdateReferralPrograms: !!featureFlags.REFERRALS,
|
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -95,7 +95,7 @@ export const ProtocolUpgradeProposalContainer = () => {
|
|||||||
time={
|
time={
|
||||||
pending && time ? (
|
pending && time ? (
|
||||||
convertToCountdownString(time, '0:00:00:00')
|
convertToCountdownString(time, '0:00:00:00')
|
||||||
) : blockInfo && 'result' in blockInfo && blockInfo?.result ? (
|
) : blockInfo?.result ? (
|
||||||
<span title={blockInfo.result.block.header.time}>
|
<span title={blockInfo.result.block.header.time}>
|
||||||
{formatDateWithLocalTimezone(
|
{formatDateWithLocalTimezone(
|
||||||
new Date(blockInfo.result.block.header.time)
|
new Date(blockInfo.result.block.header.time)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { removePaginationWrapper } from '@vegaprotocol/utils';
|
|||||||
import flow from 'lodash/flow';
|
import flow from 'lodash/flow';
|
||||||
import orderBy from 'lodash/orderBy';
|
import orderBy from 'lodash/orderBy';
|
||||||
import { ProposalState } from '@vegaprotocol/types';
|
import { ProposalState } from '@vegaprotocol/types';
|
||||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
|
|
||||||
const orderByDate = (arr: ProposalFieldsFragment[]) =>
|
const orderByDate = (arr: ProposalFieldsFragment[]) =>
|
||||||
orderBy(
|
orderBy(
|
||||||
@@ -33,16 +33,15 @@ export function getRejectedProposals(data?: ProposalFieldsFragment[] | null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const RejectedProposalsContainer = () => {
|
export const RejectedProposalsContainer = () => {
|
||||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { data, loading, error } = useProposalsQuery({
|
const { data, loading, error } = useProposalsQuery({
|
||||||
pollInterval: 5000,
|
pollInterval: 5000,
|
||||||
fetchPolicy: 'network-only',
|
fetchPolicy: 'network-only',
|
||||||
errorPolicy: 'ignore',
|
errorPolicy: 'ignore',
|
||||||
variables: {
|
variables: {
|
||||||
includeNewMarketProductFields: !!featureFlags.PRODUCT_PERPETUALS,
|
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
|
||||||
includeUpdateMarketStates: !!featureFlags.UPDATE_MARKET_STATE,
|
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
||||||
includeUpdateReferralPrograms: !!featureFlags.REFERRALS,
|
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -116,8 +116,7 @@ export const generateYesVotes = (
|
|||||||
fixedTokenValue?: number,
|
fixedTokenValue?: number,
|
||||||
totalEquityLikeShareWeight?: string
|
totalEquityLikeShareWeight?: string
|
||||||
): Votes => {
|
): Votes => {
|
||||||
const votes = [];
|
const votes = Array.from(Array(numberOfVotes)).map(() => {
|
||||||
for (let i = 0; i < numberOfVotes; i++) {
|
|
||||||
const vote: Vote = {
|
const vote: Vote = {
|
||||||
__typename: 'Vote',
|
__typename: 'Vote',
|
||||||
value: Schema.VoteValue.VALUE_YES,
|
value: Schema.VoteValue.VALUE_YES,
|
||||||
@@ -153,9 +152,8 @@ export const generateYesVotes = (
|
|||||||
datetime: faker.date.past().toISOString(),
|
datetime: faker.date.past().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
votes.push(vote);
|
return vote;
|
||||||
}
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
__typename: 'ProposalVoteSide',
|
__typename: 'ProposalVoteSide',
|
||||||
totalNumber: votes.length.toString(),
|
totalNumber: votes.length.toString(),
|
||||||
@@ -174,8 +172,7 @@ export const generateNoVotes = (
|
|||||||
fixedTokenValue?: number,
|
fixedTokenValue?: number,
|
||||||
totalEquityLikeShareWeight?: string
|
totalEquityLikeShareWeight?: string
|
||||||
): Votes => {
|
): Votes => {
|
||||||
const votes = [];
|
const votes = Array.from(Array(numberOfVotes)).map(() => {
|
||||||
for (let i = 0; i < numberOfVotes; i++) {
|
|
||||||
const vote: Vote = {
|
const vote: Vote = {
|
||||||
__typename: 'Vote',
|
__typename: 'Vote',
|
||||||
value: Schema.VoteValue.VALUE_NO,
|
value: Schema.VoteValue.VALUE_NO,
|
||||||
@@ -210,9 +207,8 @@ export const generateNoVotes = (
|
|||||||
},
|
},
|
||||||
datetime: faker.date.past().toISOString(),
|
datetime: faker.date.past().toISOString(),
|
||||||
};
|
};
|
||||||
votes.push(vote);
|
return vote;
|
||||||
}
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
__typename: 'ProposalVoteSide',
|
__typename: 'ProposalVoteSide',
|
||||||
totalNumber: votes.length.toString(),
|
totalNumber: votes.length.toString(),
|
||||||
|
|||||||
@@ -104,10 +104,6 @@
|
|||||||
list-style: circle;
|
list-style: circle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-markdown-container a {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.jsondiffpatch-delta,
|
.jsondiffpatch-delta,
|
||||||
.jsondiffpatch-delta pre {
|
.jsondiffpatch-delta pre {
|
||||||
font-family: 'Roboto Mono', monospace !important;
|
font-family: 'Roboto Mono', monospace !important;
|
||||||
|
|||||||
@@ -22,9 +22,10 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
|||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
NX_STOP_ORDERS=true
|
NX_STOP_ORDERS=true
|
||||||
NX_ICEBERG_ORDERS=true
|
NX_ICEBERG_ORDERS=true
|
||||||
|
# NX_PRODUCT_PERPETUALS
|
||||||
NX_METAMASK_SNAPS=true
|
NX_METAMASK_SNAPS=true
|
||||||
NX_REFERRALS=true
|
NX_REFERRALS=false
|
||||||
# NX_DISABLE_CLOSE_POSITION=false
|
|
||||||
|
|
||||||
NX_TENDERMINT_URL=https://be.vega.community
|
NX_TENDERMINT_URL=https://be.vega.community
|
||||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||||
|
NX_DISABLE_CLOSE_POSITION=true
|
||||||
|
|||||||
@@ -24,7 +24,4 @@ NX_STOP_ORDERS=true
|
|||||||
NX_ICEBERG_ORDERS=true
|
NX_ICEBERG_ORDERS=true
|
||||||
# NX_PRODUCT_PERPETUALS
|
# NX_PRODUCT_PERPETUALS
|
||||||
NX_METAMASK_SNAPS=true
|
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=
|
|
||||||
@@ -23,11 +23,9 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
|||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
NX_STOP_ORDERS=true
|
NX_STOP_ORDERS=true
|
||||||
NX_ICEBERG_ORDERS=true
|
NX_ICEBERG_ORDERS=true
|
||||||
|
# NX_PRODUCT_PERPETUALS
|
||||||
NX_METAMASK_SNAPS=true
|
NX_METAMASK_SNAPS=true
|
||||||
NX_REFERRALS=true
|
NX_REFERRALS=true
|
||||||
|
|
||||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
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=
|
|
||||||
|
|||||||
@@ -188,11 +188,12 @@ const useNow = () => {
|
|||||||
return now;
|
return now;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useEvery = (marketId: string, skip: boolean) => {
|
const useEvery = (marketId: string) => {
|
||||||
|
const { data: marketTradingMode } = useMarketTradingMode(marketId);
|
||||||
const { data: marketInfo } = useDataProvider({
|
const { data: marketInfo } = useDataProvider({
|
||||||
dataProvider: marketInfoProvider,
|
dataProvider: marketInfoProvider,
|
||||||
variables: { marketId },
|
variables: { marketId },
|
||||||
skip,
|
skip: !marketTradingMode || isMarketInAuction(marketTradingMode),
|
||||||
});
|
});
|
||||||
let every: number | undefined = undefined;
|
let every: number | undefined = undefined;
|
||||||
const sourceType =
|
const sourceType =
|
||||||
@@ -210,10 +211,8 @@ const useEvery = (marketId: string, skip: boolean) => {
|
|||||||
return every;
|
return every;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStartTime = (marketId: string, skip: boolean) => {
|
const useStartTime = (marketId: string) => {
|
||||||
const { data: fundingPeriods } = useFundingPeriodsQuery({
|
const { data: fundingPeriods } = useFundingPeriodsQuery({
|
||||||
pollInterval: 5000,
|
|
||||||
skip,
|
|
||||||
variables: {
|
variables: {
|
||||||
marketId: marketId,
|
marketId: marketId,
|
||||||
pagination: { first: 1 },
|
pagination: { first: 1 },
|
||||||
@@ -247,10 +246,8 @@ const useFormatCountdown = (
|
|||||||
|
|
||||||
export const FundingCountdown = ({ marketId }: { marketId: string }) => {
|
export const FundingCountdown = ({ marketId }: { marketId: string }) => {
|
||||||
const now = useNow();
|
const now = useNow();
|
||||||
const { data: marketTradingMode } = useMarketTradingMode(marketId);
|
const startTime = useStartTime(marketId);
|
||||||
const skip = !marketTradingMode || isMarketInAuction(marketTradingMode);
|
const every = useEvery(marketId);
|
||||||
const startTime = useStartTime(marketId, skip);
|
|
||||||
const every = useEvery(marketId, skip);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-testid="funding-countdown">
|
<div data-testid="funding-countdown">
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { TradeGrid } from './trade-grid';
|
|||||||
import { TradePanels } from './trade-panels';
|
import { TradePanels } from './trade-panels';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { Links } from '../../lib/links';
|
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 { useT, ns } from '../../lib/use-t';
|
||||||
import { Trans } from 'react-i18next';
|
import { Trans } from 'react-i18next';
|
||||||
|
|
||||||
@@ -59,6 +61,9 @@ export const MarketPage = () => {
|
|||||||
const t = useT();
|
const t = useT();
|
||||||
const { marketId } = useParams();
|
const { marketId } = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const currentRouteId = useGetCurrentRouteId();
|
||||||
|
const { setViews, getView } = useSidebar();
|
||||||
|
const view = getView(currentRouteId);
|
||||||
const { screenSize } = useScreenDimensions();
|
const { screenSize } = useScreenDimensions();
|
||||||
const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize);
|
const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize);
|
||||||
const update = useGlobalStore((store) => store.update);
|
const update = useGlobalStore((store) => store.update);
|
||||||
@@ -67,11 +72,20 @@ export const MarketPage = () => {
|
|||||||
const { data, loading } = useMarket(marketId);
|
const { data, loading } = useMarket(marketId);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data?.id && data.id !== lastMarketId) {
|
if (data?.id && data.id !== lastMarketId && !closed) {
|
||||||
update({ marketId: data.id });
|
update({ marketId: data.id });
|
||||||
}
|
}
|
||||||
}, [update, lastMarketId, 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 pinnedAsset = data && getAsset(data);
|
||||||
|
|
||||||
const tradeView = useMemo(() => {
|
const tradeView = useMemo(() => {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
MarketSuccessorProposalBanner,
|
MarketSuccessorProposalBanner,
|
||||||
MarketTerminationBanner,
|
MarketTerminationBanner,
|
||||||
} from '../../components/market-banner';
|
} from '../../components/market-banner';
|
||||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
import { useT } from '../../lib/use-t';
|
import { useT } from '../../lib/use-t';
|
||||||
import { ErrorBoundary } from '../../components/error-boundary';
|
import { ErrorBoundary } from '../../components/error-boundary';
|
||||||
|
|
||||||
@@ -35,7 +35,6 @@ const MainGrid = memo(
|
|||||||
marketId: string;
|
marketId: string;
|
||||||
pinnedAsset?: PinnedAsset;
|
pinnedAsset?: PinnedAsset;
|
||||||
}) => {
|
}) => {
|
||||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const { data: market } = useMarket(marketId);
|
const { data: market } = useMarket(marketId);
|
||||||
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'top' });
|
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'top' });
|
||||||
@@ -62,10 +61,10 @@ const MainGrid = memo(
|
|||||||
id="chart"
|
id="chart"
|
||||||
overflowHidden
|
overflowHidden
|
||||||
name={t('Chart')}
|
name={t('Chart')}
|
||||||
menu={<TradingViews.chart.menu />}
|
menu={<TradingViews.candles.menu />}
|
||||||
>
|
>
|
||||||
<ErrorBoundary feature="chart">
|
<ErrorBoundary feature="chart">
|
||||||
<TradingViews.chart.component marketId={marketId} />
|
<TradingViews.candles.component marketId={marketId} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="depth" name={t('Depth')}>
|
<Tab id="depth" name={t('Depth')}>
|
||||||
@@ -166,7 +165,7 @@ const MainGrid = memo(
|
|||||||
<TradingViews.orders.component />
|
<TradingViews.orders.component />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</Tab>
|
</Tab>
|
||||||
{featureFlags.STOP_ORDERS ? (
|
{FLAGS.STOP_ORDERS ? (
|
||||||
<Tab id="stop-orders" name={t('Stop orders')}>
|
<Tab id="stop-orders" name={t('Stop orders')}>
|
||||||
<ErrorBoundary feature="stop-orders">
|
<ErrorBoundary feature="stop-orders">
|
||||||
<TradingViews.stopOrders.component />
|
<TradingViews.stopOrders.component />
|
||||||
@@ -197,7 +196,6 @@ const MainGrid = memo(
|
|||||||
MainGrid.displayName = 'MainGrid';
|
MainGrid.displayName = 'MainGrid';
|
||||||
|
|
||||||
export const TradeGrid = ({ market, pinnedAsset }: TradeGridProps) => {
|
export const TradeGrid = ({ market, pinnedAsset }: TradeGridProps) => {
|
||||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
|
||||||
const wrapperClasses = classNames(
|
const wrapperClasses = classNames(
|
||||||
'h-full grid',
|
'h-full grid',
|
||||||
'grid-rows-[min-content_1fr]'
|
'grid-rows-[min-content_1fr]'
|
||||||
@@ -206,7 +204,7 @@ export const TradeGrid = ({ market, pinnedAsset }: TradeGridProps) => {
|
|||||||
return (
|
return (
|
||||||
<div className={wrapperClasses}>
|
<div className={wrapperClasses}>
|
||||||
<div>
|
<div>
|
||||||
{featureFlags.SUCCESSOR_MARKETS && (
|
{FLAGS.SUCCESSOR_MARKETS && (
|
||||||
<>
|
<>
|
||||||
<MarketSuccessorBanner market={market} />
|
<MarketSuccessorBanner market={market} />
|
||||||
<MarketSuccessorProposalBanner marketId={market?.id} />
|
<MarketSuccessorProposalBanner marketId={market?.id} />
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { OracleBanner } from '@vegaprotocol/markets';
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useT } from '../../lib/use-t';
|
import { useT } from '../../lib/use-t';
|
||||||
import {
|
import {
|
||||||
@@ -14,7 +15,6 @@ import {
|
|||||||
import { ErrorBoundary } from '../../components/error-boundary';
|
import { ErrorBoundary } from '../../components/error-boundary';
|
||||||
import { type TradingView } from './trade-views';
|
import { type TradingView } from './trade-views';
|
||||||
import { TradingViews } from './trade-views';
|
import { TradingViews } from './trade-views';
|
||||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
|
||||||
|
|
||||||
interface TradePanelsProps {
|
interface TradePanelsProps {
|
||||||
market: Market | null;
|
market: Market | null;
|
||||||
@@ -22,8 +22,7 @@ interface TradePanelsProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
||||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
const [view, setView] = useState<TradingView>('candles');
|
||||||
const [view, setView] = useState<TradingView>('chart');
|
|
||||||
|
|
||||||
const renderView = () => {
|
const renderView = () => {
|
||||||
const Component = TradingViews[view].component;
|
const Component = TradingViews[view].component;
|
||||||
@@ -50,7 +49,7 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
|||||||
const Menu = viewCfg.menu;
|
const Menu = viewCfg.menu;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-end gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
|
<div className="flex gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
|
||||||
<Menu />
|
<Menu />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -62,7 +61,7 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
|||||||
return (
|
return (
|
||||||
<div className="h-full grid grid-rows-[min-content_min-content_1fr_min-content]">
|
<div className="h-full grid grid-rows-[min-content_min-content_1fr_min-content]">
|
||||||
<div>
|
<div>
|
||||||
{featureFlags.SUCCESSOR_MARKETS && (
|
{FLAGS.SUCCESSOR_MARKETS && (
|
||||||
<>
|
<>
|
||||||
<MarketSuccessorBanner market={market} />
|
<MarketSuccessorBanner market={market} />
|
||||||
<MarketSuccessorProposalBanner marketId={market?.id} />
|
<MarketSuccessorProposalBanner marketId={market?.id} />
|
||||||
@@ -149,7 +148,7 @@ const useViewLabel = (view: TradingView) => {
|
|||||||
const t = useT();
|
const t = useT();
|
||||||
|
|
||||||
const labels = {
|
const labels = {
|
||||||
chart: t('Chart'),
|
candles: t('Candles'),
|
||||||
depth: t('Depth'),
|
depth: t('Depth'),
|
||||||
liquidity: t('Liquidity'),
|
liquidity: t('Liquidity'),
|
||||||
funding: t('Funding'),
|
funding: t('Funding'),
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { DepthChartContainer } from '@vegaprotocol/market-depth';
|
import { DepthChartContainer } from '@vegaprotocol/market-depth';
|
||||||
|
import {
|
||||||
|
CandlesChartContainer,
|
||||||
|
CandlesMenu,
|
||||||
|
} from '@vegaprotocol/candles-chart';
|
||||||
import { Filter, OpenOrdersMenu } from '@vegaprotocol/orders';
|
import { Filter, OpenOrdersMenu } from '@vegaprotocol/orders';
|
||||||
import { TradesContainer } from '../../components/trades-container';
|
import { TradesContainer } from '../../components/trades-container';
|
||||||
import { OrderbookContainer } from '../../components/orderbook-container';
|
import { OrderbookContainer } from '../../components/orderbook-container';
|
||||||
@@ -12,14 +16,13 @@ import { OrdersContainer } from '../../components/orders-container';
|
|||||||
import { StopOrdersContainer } from '../../components/stop-orders-container';
|
import { StopOrdersContainer } from '../../components/stop-orders-container';
|
||||||
import { AccountsMenu } from '../../components/accounts-menu';
|
import { AccountsMenu } from '../../components/accounts-menu';
|
||||||
import { PositionsMenu } from '../../components/positions-menu';
|
import { PositionsMenu } from '../../components/positions-menu';
|
||||||
import { ChartContainer, ChartMenu } from '../../components/chart-container';
|
|
||||||
|
|
||||||
export type TradingView = keyof typeof TradingViews;
|
export type TradingView = keyof typeof TradingViews;
|
||||||
|
|
||||||
export const TradingViews = {
|
export const TradingViews = {
|
||||||
chart: {
|
candles: {
|
||||||
component: ChartContainer,
|
component: CandlesChartContainer,
|
||||||
menu: ChartMenu,
|
menu: CandlesMenu,
|
||||||
},
|
},
|
||||||
depth: {
|
depth: {
|
||||||
component: DepthChartContainer,
|
component: DepthChartContainer,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { act, render, screen, waitFor, within } from '@testing-library/react';
|
import { act, render, screen, waitFor, within } from '@testing-library/react';
|
||||||
// import userEvent from '@testing-library/user-event';
|
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { Closed } from './closed';
|
import { Closed } from './closed';
|
||||||
import { MarketStateMapping, PropertyKeyType } from '@vegaprotocol/types';
|
import { MarketStateMapping, PropertyKeyType } from '@vegaprotocol/types';
|
||||||
@@ -27,7 +26,6 @@ import {
|
|||||||
marketsDataQuery,
|
marketsDataQuery,
|
||||||
createMarketsDataFragment,
|
createMarketsDataFragment,
|
||||||
} from '@vegaprotocol/mock';
|
} from '@vegaprotocol/mock';
|
||||||
import userEvent from '@testing-library/user-event';
|
|
||||||
|
|
||||||
describe('Closed', () => {
|
describe('Closed', () => {
|
||||||
let originalNow: typeof Date.now;
|
let originalNow: typeof Date.now;
|
||||||
@@ -170,11 +168,14 @@ describe('Closed', () => {
|
|||||||
Date.now = originalNow;
|
Date.now = originalNow;
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderComponent = async (mocks: MockedResponse[]) => {
|
// eslint-disable-next-line jest/no-disabled-tests
|
||||||
|
it.skip('renders correctly formatted and filtered rows', async () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
render(
|
render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<MockedProvider mocks={mocks}>
|
<MockedProvider
|
||||||
|
mocks={[marketsMock, marketsDataMock, oracleDataMock]}
|
||||||
|
>
|
||||||
<VegaWalletContext.Provider
|
<VegaWalletContext.Provider
|
||||||
value={{ pubKey } as VegaWalletContextShape}
|
value={{ pubKey } as VegaWalletContextShape}
|
||||||
>
|
>
|
||||||
@@ -184,10 +185,6 @@ describe('Closed', () => {
|
|||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
it('renders correct headers', async () => {
|
|
||||||
await renderComponent([marketsMock, marketsDataMock, oracleDataMock]);
|
|
||||||
|
|
||||||
const headers = screen.getAllByRole('columnheader');
|
const headers = screen.getAllByRole('columnheader');
|
||||||
const expectedHeaders = [
|
const expectedHeaders = [
|
||||||
@@ -203,10 +200,6 @@ describe('Closed', () => {
|
|||||||
];
|
];
|
||||||
expect(headers).toHaveLength(expectedHeaders.length);
|
expect(headers).toHaveLength(expectedHeaders.length);
|
||||||
expect(headers.map((h) => h.textContent?.trim())).toEqual(expectedHeaders);
|
expect(headers.map((h) => h.textContent?.trim())).toEqual(expectedHeaders);
|
||||||
});
|
|
||||||
|
|
||||||
it('renders correctly formatted and filtered rows', async () => {
|
|
||||||
await renderComponent([marketsMock, marketsDataMock, oracleDataMock]);
|
|
||||||
|
|
||||||
const assetSymbol = getAsset(market).symbol;
|
const assetSymbol = getAsset(market).symbol;
|
||||||
|
|
||||||
@@ -280,8 +273,21 @@ describe('Closed', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
await act(async () => {
|
||||||
await renderComponent([mixedMarketsMock, marketsDataMock, oracleDataMock]);
|
render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<MockedProvider
|
||||||
|
mocks={[mixedMarketsMock, marketsDataMock, oracleDataMock]}
|
||||||
|
>
|
||||||
|
<VegaWalletContext.Provider
|
||||||
|
value={{ pubKey } as VegaWalletContextShape}
|
||||||
|
>
|
||||||
|
<Closed />
|
||||||
|
</VegaWalletContext.Provider>
|
||||||
|
</MockedProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// check that the number of rows in datagrid is 2
|
// check that the number of rows in datagrid is 2
|
||||||
const container = within(
|
const container = within(
|
||||||
@@ -313,67 +319,8 @@ describe('Closed', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('display market actions', async () => {
|
// eslint-disable-next-line jest/no-disabled-tests
|
||||||
// Use market with a succcessor Id as the actions dropdown will optionally
|
it.skip('successor marked should be visible', async () => {
|
||||||
// show a link to the successor market
|
|
||||||
const marketsWithSuccessorAndParent = [
|
|
||||||
{
|
|
||||||
__typename: 'MarketEdge' as const,
|
|
||||||
node: createMarketFragment({
|
|
||||||
id: 'include-0',
|
|
||||||
state: MarketState.STATE_SETTLED,
|
|
||||||
successorMarketID: 'successor',
|
|
||||||
parentMarketID: 'parent',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const mockWithSuccessorAndParent: MockedResponse<MarketsQuery> = {
|
|
||||||
request: {
|
|
||||||
query: MarketsDocument,
|
|
||||||
},
|
|
||||||
result: {
|
|
||||||
data: {
|
|
||||||
marketsConnection: {
|
|
||||||
__typename: 'MarketConnection',
|
|
||||||
edges: marketsWithSuccessorAndParent,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
await renderComponent([
|
|
||||||
mockWithSuccessorAndParent,
|
|
||||||
marketsDataMock,
|
|
||||||
oracleDataMock,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const actionCell = screen
|
|
||||||
.getAllByRole('gridcell')
|
|
||||||
.find((el) => el.getAttribute('col-id') === 'market-actions');
|
|
||||||
|
|
||||||
await userEvent.click(
|
|
||||||
within(actionCell as HTMLElement).getByTestId('dropdown-menu')
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.getByRole('menu')).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(
|
|
||||||
screen.getByRole('menuitem', { name: 'Copy Market ID' })
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.getByRole('menuitem', { name: 'View on Explorer' })
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.getByRole('menuitem', { name: 'View settlement asset details' })
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.getByRole('menuitem', { name: 'View parent market' })
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.getByRole('menuitem', { name: 'View successor market' })
|
|
||||||
).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('successor market should be visible', async () => {
|
|
||||||
const marketsWithSuccessorID = [
|
const marketsWithSuccessorID = [
|
||||||
{
|
{
|
||||||
__typename: 'MarketEdge' as const,
|
__typename: 'MarketEdge' as const,
|
||||||
@@ -398,11 +345,21 @@ describe('Closed', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
await renderComponent([
|
await act(async () => {
|
||||||
mockWithSuccessors,
|
render(
|
||||||
marketsDataMock,
|
<MemoryRouter>
|
||||||
oracleDataMock,
|
<MockedProvider
|
||||||
]);
|
mocks={[mockWithSuccessors, marketsDataMock, oracleDataMock]}
|
||||||
|
>
|
||||||
|
<VegaWalletContext.Provider
|
||||||
|
value={{ pubKey } as VegaWalletContextShape}
|
||||||
|
>
|
||||||
|
<Closed />
|
||||||
|
</VegaWalletContext.Provider>
|
||||||
|
</MockedProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const container = within(
|
const container = within(
|
||||||
document.querySelector('.ag-center-cols-container') as HTMLElement
|
document.querySelector('.ag-center-cols-container') as HTMLElement
|
||||||
|
|||||||
@@ -4,26 +4,9 @@ import {
|
|||||||
SidebarButton,
|
SidebarButton,
|
||||||
SidebarDivider,
|
SidebarDivider,
|
||||||
ViewType,
|
ViewType,
|
||||||
useSidebar,
|
|
||||||
} from '../../components/sidebar';
|
} from '../../components/sidebar';
|
||||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||||
import { useT } from '../../lib/use-t';
|
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 = () => {
|
export const MarketsSidebar = () => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
@@ -54,7 +37,6 @@ export const MarketsSidebar = () => {
|
|||||||
path=":marketId"
|
path=":marketId"
|
||||||
element={
|
element={
|
||||||
<>
|
<>
|
||||||
<ViewInitializer />
|
|
||||||
<SidebarDivider />
|
<SidebarDivider />
|
||||||
<SidebarButton
|
<SidebarButton
|
||||||
view={ViewType.Order}
|
view={ViewType.Order}
|
||||||
|
|||||||
@@ -1,145 +0,0 @@
|
|||||||
import { act, render, screen, within } from '@testing-library/react';
|
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
|
||||||
import { OpenMarkets } from './open-markets';
|
|
||||||
import { Interval } from '@vegaprotocol/types';
|
|
||||||
import type { MockedResponse } from '@apollo/client/testing';
|
|
||||||
import { MockedProvider } from '@apollo/client/testing';
|
|
||||||
import type {
|
|
||||||
MarketsDataQuery,
|
|
||||||
MarketsQuery,
|
|
||||||
MarketCandlesQuery,
|
|
||||||
MarketFieldsFragment,
|
|
||||||
} from '@vegaprotocol/markets';
|
|
||||||
import {
|
|
||||||
MarketsDataDocument,
|
|
||||||
MarketsDocument,
|
|
||||||
MarketsCandlesDocument,
|
|
||||||
} from '@vegaprotocol/markets';
|
|
||||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
|
||||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
|
||||||
import {
|
|
||||||
marketsQuery,
|
|
||||||
marketsDataQuery,
|
|
||||||
marketsCandlesQuery,
|
|
||||||
} from '@vegaprotocol/mock';
|
|
||||||
import userEvent from '@testing-library/user-event';
|
|
||||||
|
|
||||||
describe('Open', () => {
|
|
||||||
let originalNow: typeof Date.now;
|
|
||||||
const mockNowTimestamp = 1672531200000;
|
|
||||||
const pubKey = 'pubKey';
|
|
||||||
|
|
||||||
const marketsQueryData = marketsQuery();
|
|
||||||
const marketsMock: MockedResponse<MarketsQuery> = {
|
|
||||||
request: {
|
|
||||||
query: MarketsDocument,
|
|
||||||
},
|
|
||||||
result: {
|
|
||||||
data: marketsQueryData,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const marketsCandlesQueryData = marketsCandlesQuery();
|
|
||||||
const marketsCandlesMock: MockedResponse<MarketCandlesQuery> = {
|
|
||||||
request: {
|
|
||||||
query: MarketsCandlesDocument,
|
|
||||||
variables: {
|
|
||||||
interval: Interval.INTERVAL_I1H,
|
|
||||||
since: '2022-12-31T00:00:00.000Z',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
result: {
|
|
||||||
data: marketsCandlesQueryData,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const marketsDataQueryData = marketsDataQuery();
|
|
||||||
|
|
||||||
const marketsDataMock: MockedResponse<MarketsDataQuery> = {
|
|
||||||
request: {
|
|
||||||
query: MarketsDataDocument,
|
|
||||||
},
|
|
||||||
result: {
|
|
||||||
data: marketsDataQueryData,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
originalNow = Date.now;
|
|
||||||
Date.now = jest.fn().mockReturnValue(mockNowTimestamp);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
Date.now = originalNow;
|
|
||||||
});
|
|
||||||
|
|
||||||
const renderComponent = async () => {
|
|
||||||
await act(async () => {
|
|
||||||
render(
|
|
||||||
<MemoryRouter>
|
|
||||||
<MockedProvider
|
|
||||||
mocks={[marketsMock, marketsCandlesMock, marketsDataMock]}
|
|
||||||
>
|
|
||||||
<VegaWalletContext.Provider
|
|
||||||
value={{ pubKey } as VegaWalletContextShape}
|
|
||||||
>
|
|
||||||
<OpenMarkets />
|
|
||||||
</VegaWalletContext.Provider>
|
|
||||||
</MockedProvider>
|
|
||||||
</MemoryRouter>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
it('renders correct headers', async () => {
|
|
||||||
await renderComponent();
|
|
||||||
|
|
||||||
const headers = screen.getAllByRole('columnheader');
|
|
||||||
const expectedHeaders = [
|
|
||||||
'Market',
|
|
||||||
'Description',
|
|
||||||
'Settlement asset',
|
|
||||||
'Trading mode',
|
|
||||||
'Status',
|
|
||||||
'Mark price',
|
|
||||||
'24h volume',
|
|
||||||
'Open Interest',
|
|
||||||
'Spread',
|
|
||||||
'', // Action row
|
|
||||||
];
|
|
||||||
expect(headers).toHaveLength(expectedHeaders.length);
|
|
||||||
expect(headers.map((h) => h.textContent?.trim())).toEqual(expectedHeaders);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('sort columns', async () => {
|
|
||||||
await renderComponent();
|
|
||||||
|
|
||||||
const headers = screen.getAllByRole('columnheader');
|
|
||||||
const marketHeader = headers.find(
|
|
||||||
(h) => h.getAttribute('col-id') === 'tradableInstrument.instrument.code'
|
|
||||||
);
|
|
||||||
if (!marketHeader) {
|
|
||||||
throw new Error('No market header found');
|
|
||||||
}
|
|
||||||
expect(marketHeader).toHaveAttribute('aria-sort', 'none');
|
|
||||||
await userEvent.click(within(marketHeader).getByText(/market/i));
|
|
||||||
// 6001-MARK-064
|
|
||||||
expect(marketHeader).toHaveAttribute('aria-sort', 'ascending');
|
|
||||||
});
|
|
||||||
|
|
||||||
// eslint-disable-next-line jest/no-disabled-tests, jest/expect-expect
|
|
||||||
it('renders row', async () => {
|
|
||||||
await renderComponent();
|
|
||||||
|
|
||||||
const container = within(
|
|
||||||
document.querySelector('.ag-center-cols-container') as HTMLElement
|
|
||||||
);
|
|
||||||
|
|
||||||
const markets = marketsQueryData.marketsConnection?.edges.map(
|
|
||||||
(e) => e.node
|
|
||||||
) as MarketFieldsFragment[];
|
|
||||||
|
|
||||||
const rows = container.getAllByRole('row');
|
|
||||||
expect(rows).toHaveLength(markets.length);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -39,21 +39,11 @@ const WithdrawalsIndicator = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SidebarViewInitializer = () => {
|
export const Portfolio = () => {
|
||||||
|
const t = useT();
|
||||||
const currentRouteId = useGetCurrentRouteId();
|
const currentRouteId = useGetCurrentRouteId();
|
||||||
const { getView, setViews } = useSidebar();
|
const { getView, setViews } = useSidebar();
|
||||||
const view = getView(currentRouteId);
|
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) => ({
|
const { updateTitle } = usePageTitleStore((store) => ({
|
||||||
updateTitle: store.updateTitle,
|
updateTitle: store.updateTitle,
|
||||||
@@ -63,11 +53,17 @@ export const Portfolio = () => {
|
|||||||
updateTitle(titlefy([t('Portfolio')]));
|
updateTitle(titlefy([t('Portfolio')]));
|
||||||
}, [updateTitle, t]);
|
}, [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 [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'portfolio' });
|
||||||
const wrapperClasses = 'p-0.5 h-full max-h-full flex flex-col';
|
const wrapperClasses = 'p-0.5 h-full max-h-full flex flex-col';
|
||||||
return (
|
return (
|
||||||
<div className={wrapperClasses}>
|
<div className={wrapperClasses}>
|
||||||
<SidebarViewInitializer />
|
|
||||||
<ResizableGrid vertical onChange={handleOnLayoutChange}>
|
<ResizableGrid vertical onChange={handleOnLayoutChange}>
|
||||||
<ResizableGridPanel minSize={75}>
|
<ResizableGridPanel minSize={75}>
|
||||||
<PortfolioGridChild>
|
<PortfolioGridChild>
|
||||||
|
|||||||
@@ -13,40 +13,15 @@ import type { ButtonHTMLAttributes, MouseEventHandler } from 'react';
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { RainbowButton } from './buttons';
|
import { RainbowButton } from './buttons';
|
||||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||||
import { useIsInReferralSet, useReferral } from './hooks/use-referral';
|
import { useReferral } from './hooks/use-referral';
|
||||||
import { Routes } from '../../lib/links';
|
import { Routes } from '../../lib/links';
|
||||||
import { useTransactionEventSubscription } from '@vegaprotocol/web3';
|
import { useTransactionEventSubscription } from '@vegaprotocol/web3';
|
||||||
import { Statistics, useStats } from './referral-statistics';
|
import { Statistics, useStats } from './referral-statistics';
|
||||||
import { useReferralProgram } from './hooks/use-referral-program';
|
import { useReferralProgram } from './hooks/use-referral-program';
|
||||||
import { ns, useT } from '../../lib/use-t';
|
import { 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 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 validateCode = (value: string, t: ReturnType<typeof useT>) => {
|
||||||
const number = +`0x${value}`;
|
const number = +`0x${value}`;
|
||||||
if (!value || value.length !== 64) {
|
if (!value || value.length !== 64) {
|
||||||
@@ -57,23 +32,20 @@ const validateCode = (value: string, t: ReturnType<typeof useT>) => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ApplyCodeFormContainer = ({
|
export const ApplyCodeFormContainer = () => {
|
||||||
onSuccess,
|
|
||||||
}: {
|
|
||||||
onSuccess?: () => void;
|
|
||||||
}) => {
|
|
||||||
const { pubKey } = useVegaWallet();
|
const { pubKey } = useVegaWallet();
|
||||||
const isInReferralSet = useIsInReferralSet(pubKey);
|
const { data: referee } = useReferral({ pubKey, role: 'referee' });
|
||||||
|
const { data: referrer } = useReferral({ pubKey, role: 'referrer' });
|
||||||
|
|
||||||
// Navigate to the index page when already in the referral set.
|
// go to main page if the current pubkey is already a referrer or referee
|
||||||
if (isInReferralSet) {
|
if (referee || referrer) {
|
||||||
return <Navigate to={Routes.REFERRALS} />;
|
return <Navigate to={Routes.REFERRALS} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ApplyCodeForm onSuccess={onSuccess} />;
|
return <ApplyCodeForm />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
export const ApplyCodeForm = () => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const program = useReferralProgram();
|
const program = useReferralProgram();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -82,15 +54,10 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const [status, setStatus] = useState<
|
const [status, setStatus] = useState<
|
||||||
'requested' | 'no-funds' | 'successful' | null
|
'requested' | 'failed' | 'successful' | null
|
||||||
>(null);
|
>(null);
|
||||||
const txHash = useRef<string | null>(null);
|
const txHash = useRef<string | null>(null);
|
||||||
const { isReadOnly, pubKey, sendTx } = useVegaWallet();
|
const { isReadOnly, pubKey, sendTx } = useVegaWallet();
|
||||||
const { isEligible, requiredFunds } = useFundsAvailable();
|
|
||||||
|
|
||||||
const currentRouteId = useGetCurrentRouteId();
|
|
||||||
const setViews = useSidebar((s) => s.setViews);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@@ -106,17 +73,6 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
|||||||
code: validateCode(codeField, t) ? codeField : undefined,
|
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.
|
* Validates the set a user tries to apply to.
|
||||||
*/
|
*/
|
||||||
@@ -140,15 +96,6 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
|||||||
if (code) setValue('code', code);
|
if (code) setValue('code', code);
|
||||||
}, [params, setValue]);
|
}, [params, setValue]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const err = validateFundsAvailable();
|
|
||||||
if (err !== true) {
|
|
||||||
setStatus('no-funds');
|
|
||||||
} else {
|
|
||||||
setStatus(null);
|
|
||||||
}
|
|
||||||
}, [isEligible, validateFundsAvailable]);
|
|
||||||
|
|
||||||
const onSubmit = ({ code }: FieldValues) => {
|
const onSubmit = ({ code }: FieldValues) => {
|
||||||
if (isReadOnly || !pubKey || !code || code.length === 0) {
|
if (isReadOnly || !pubKey || !code || code.length === 0) {
|
||||||
return;
|
return;
|
||||||
@@ -220,11 +167,10 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status === 'successful') {
|
if (status === 'successful') {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (onSuccess) onSuccess();
|
|
||||||
navigate(Routes.REFERRALS);
|
navigate(Routes.REFERRALS);
|
||||||
}, RELOAD_DELAY);
|
}, RELOAD_DELAY);
|
||||||
}
|
}
|
||||||
}, [navigate, onSuccess, status]);
|
}, [navigate, status]);
|
||||||
|
|
||||||
// show "code applied" message when successfully applied
|
// show "code applied" message when successfully applied
|
||||||
if (status === 'successful') {
|
if (status === 'successful') {
|
||||||
@@ -261,18 +207,6 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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') {
|
if (status === 'requested') {
|
||||||
return {
|
return {
|
||||||
disabled: true,
|
disabled: true,
|
||||||
@@ -302,9 +236,7 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
|||||||
{t('Apply a referral code')}
|
{t('Apply a referral code')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="mb-4 text-center text-base">
|
<p className="mb-4 text-center text-base">
|
||||||
{t(
|
{t('Enter a referral code to get trading discounts.')}
|
||||||
'Apply a referral code to access the discount benefits of the current program.'
|
|
||||||
)}
|
|
||||||
</p>
|
</p>
|
||||||
<form
|
<form
|
||||||
className={classNames('flex w-full flex-col gap-4', {
|
className={classNames('flex w-full flex-col gap-4', {
|
||||||
@@ -319,10 +251,8 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
|||||||
{...register('code', {
|
{...register('code', {
|
||||||
required: t('You have to provide a code to apply it.'),
|
required: t('You have to provide a code to apply it.'),
|
||||||
validate: (value) => {
|
validate: (value) => {
|
||||||
const codeErr = validateCode(value, t);
|
const err = validateCode(value, t);
|
||||||
if (codeErr !== true) return codeErr;
|
if (err !== true) return err;
|
||||||
const fundsErr = validateFundsAvailable();
|
|
||||||
if (fundsErr !== true) return fundsErr;
|
|
||||||
return validateSet();
|
return validateSet();
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
@@ -332,26 +262,10 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
|||||||
</label>
|
</label>
|
||||||
<RainbowButton variant="border" {...getButtonProps()} />
|
<RainbowButton variant="border" {...getButtonProps()} />
|
||||||
</form>
|
</form>
|
||||||
{status === 'no-funds' ? (
|
{errors.code && (
|
||||||
<InputError intent="warning" className="overflow-auto break-words">
|
<InputError className="overflow-auto break-words">
|
||||||
<span>
|
{errors.code.message?.toString()}
|
||||||
<SpamProtectionErr requiredFunds={requiredFunds?.toString()} />
|
|
||||||
</span>
|
|
||||||
</InputError>
|
</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>
|
</div>
|
||||||
{validateCode(codeField, t) === true && previewLoading && !previewData ? (
|
{validateCode(codeField, t) === true && previewLoading && !previewData ? (
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ 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-[40%_0px] bg-[length:1440px] bg-no-repeat bg-local';
|
||||||
|
|
||||||
// TODO: Update the links to use the correct referral related pages
|
// TODO: Update the links to use the correct referral related pages
|
||||||
export const REFERRAL_DOCS_LINK =
|
export const REFERRAL_DOCS_LINK = 'https://docs.vega.xyz/';
|
||||||
'https://docs.vega.xyz/mainnet/concepts/trading-on-vega/discounts-rewards#referral-program';
|
export const ABOUT_REFERRAL_DOCS_LINK = 'https://docs.vega.xyz/';
|
||||||
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/';
|
export const DISCLAIMER_REFERRAL_DOCS_LINK = 'https://docs.vega.xyz/';
|
||||||
|
|||||||
@@ -19,22 +19,14 @@ import {
|
|||||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||||
import { DApp, TokenStaticLinks, useLinks } from '@vegaprotocol/environment';
|
import { DApp, TokenStaticLinks, useLinks } from '@vegaprotocol/environment';
|
||||||
import { useStakeAvailable } from './hooks/use-stake-available';
|
import { useStakeAvailable } from './hooks/use-stake-available';
|
||||||
import { ABOUT_REFERRAL_DOCS_LINK } from './constants';
|
import {
|
||||||
import { useIsInReferralSet, useReferral } from './hooks/use-referral';
|
ABOUT_REFERRAL_DOCS_LINK,
|
||||||
|
DISCLAIMER_REFERRAL_DOCS_LINK,
|
||||||
|
} from './constants';
|
||||||
|
import { useReferral } from './hooks/use-referral';
|
||||||
import { useT } from '../../lib/use-t';
|
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 = () => {
|
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 />;
|
return <CreateCodeForm />;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -56,7 +48,7 @@ export const CreateCodeForm = () => {
|
|||||||
</h3>
|
</h3>
|
||||||
<p className="mb-4 text-center text-base">
|
<p className="mb-4 text-center text-base">
|
||||||
{t(
|
{t(
|
||||||
'Generate a referral code to share with your friends and access the commission benefits of the current program.'
|
'Generate a referral code to share with your friends and start earning commission.'
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -106,7 +98,10 @@ const CreateCodeDialog = ({
|
|||||||
const { stakeAvailable: currentStakeAvailable, requiredStake } =
|
const { stakeAvailable: currentStakeAvailable, requiredStake } =
|
||||||
useStakeAvailable();
|
useStakeAvailable();
|
||||||
|
|
||||||
const { details: programDetails } = useReferralProgram();
|
const { data: referralSets } = useReferral({
|
||||||
|
pubKey,
|
||||||
|
role: 'referrer',
|
||||||
|
});
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
if (isReadOnly || !pubKey) {
|
if (isReadOnly || !pubKey) {
|
||||||
@@ -206,7 +201,7 @@ const CreateCodeDialog = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!programDetails) {
|
if (!referralSets) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{(status === 'idle' || status === 'loading' || status === 'error') && (
|
{(status === 'idle' || status === 'loading' || status === 'error') && (
|
||||||
@@ -242,9 +237,7 @@ const CreateCodeDialog = ({
|
|||||||
intent={Intent.Primary}
|
intent={Intent.Primary}
|
||||||
onClick={() => onSubmit()}
|
onClick={() => onSubmit()}
|
||||||
{...getButtonProps()}
|
{...getButtonProps()}
|
||||||
>
|
></TradingButton>
|
||||||
{t('Yes')}
|
|
||||||
</TradingButton>
|
|
||||||
{status === 'idle' && (
|
{status === 'idle' && (
|
||||||
<TradingButton
|
<TradingButton
|
||||||
fill={true}
|
fill={true}
|
||||||
@@ -262,6 +255,9 @@ const CreateCodeDialog = ({
|
|||||||
<ExternalLink href={ABOUT_REFERRAL_DOCS_LINK}>
|
<ExternalLink href={ABOUT_REFERRAL_DOCS_LINK}>
|
||||||
{t('About the referral program')}
|
{t('About the referral program')}
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
|
<ExternalLink href={DISCLAIMER_REFERRAL_DOCS_LINK}>
|
||||||
|
{t('Disclaimer')}
|
||||||
|
</ExternalLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -272,7 +268,7 @@ const CreateCodeDialog = ({
|
|||||||
{(status === 'idle' || status === 'loading' || status === 'error') && (
|
{(status === 'idle' || status === 'loading' || status === 'error') && (
|
||||||
<p>
|
<p>
|
||||||
{t(
|
{t(
|
||||||
'Generate a referral code to share with your friends and access the commission benefits of the current program.'
|
'Generate a referral code to share with your friends and start earning commission.'
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -303,6 +299,9 @@ const CreateCodeDialog = ({
|
|||||||
<ExternalLink href={ABOUT_REFERRAL_DOCS_LINK}>
|
<ExternalLink href={ABOUT_REFERRAL_DOCS_LINK}>
|
||||||
{t('About the referral program')}
|
{t('About the referral program')}
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
|
<ExternalLink href={DISCLAIMER_REFERRAL_DOCS_LINK}>
|
||||||
|
{t('Disclaimer')}
|
||||||
|
</ExternalLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export const NotFound = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LayoutWithSky className="pt-32">
|
<div className="pt-32">
|
||||||
<div
|
<div
|
||||||
aria-hidden
|
aria-hidden
|
||||||
className="absolute top-64 right-[220px] md:right-[340px] max-sm: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')}
|
{t('Go back and try again')}
|
||||||
</RainbowButton>
|
</RainbowButton>
|
||||||
</p>
|
</p>
|
||||||
</LayoutWithSky>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
query FundsAvailable($partyId: ID!) {
|
|
||||||
party(id: $partyId) {
|
|
||||||
accountsConnection {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
balance
|
|
||||||
asset {
|
|
||||||
decimals
|
|
||||||
symbol
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
networkParameter(key: "spam.protection.applyReferral.min.funds") {
|
|
||||||
key
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import * as Types from '@vegaprotocol/types';
|
|
||||||
|
|
||||||
import { gql } from '@apollo/client';
|
|
||||||
import * as Apollo from '@apollo/client';
|
|
||||||
const defaultOptions = {} as const;
|
|
||||||
export type 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>;
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
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,8 +1,14 @@
|
|||||||
import { getNumberFormat } from '@vegaprotocol/utils';
|
import { getNumberFormat } from '@vegaprotocol/utils';
|
||||||
|
import { addDays } from 'date-fns';
|
||||||
import sortBy from 'lodash/sortBy';
|
import sortBy from 'lodash/sortBy';
|
||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
import { useReferralProgramQuery } from './__generated__/CurrentReferralProgram';
|
import { useReferralProgramQuery } from './__generated__/CurrentReferralProgram';
|
||||||
import BigNumber from 'bignumber.js';
|
|
||||||
|
const STAKING_TIERS_MAPPING: Record<number, string> = {
|
||||||
|
1: 'Tradestarter',
|
||||||
|
2: 'Mid level degen',
|
||||||
|
3: 'Reward hoarder',
|
||||||
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const MOCK = {
|
const MOCK = {
|
||||||
@@ -10,76 +16,46 @@ const MOCK = {
|
|||||||
currentReferralProgram: {
|
currentReferralProgram: {
|
||||||
id: 'abc',
|
id: 'abc',
|
||||||
version: 1,
|
version: 1,
|
||||||
|
endOfProgramTimestamp: addDays(new Date(), 10).toISOString(),
|
||||||
|
windowLength: 10,
|
||||||
benefitTiers: [
|
benefitTiers: [
|
||||||
{
|
{
|
||||||
minimumEpochs: 1,
|
minimumEpochs: 5,
|
||||||
minimumRunningNotionalTakerVolume: '100000',
|
minimumRunningNotionalTakerVolume: '30000',
|
||||||
referralDiscountFactor: '0.1',
|
referralDiscountFactor: '0.01',
|
||||||
|
referralRewardFactor: '0.01',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minimumEpochs: 5,
|
||||||
|
minimumRunningNotionalTakerVolume: '20000',
|
||||||
|
referralDiscountFactor: '0.05',
|
||||||
referralRewardFactor: '0.05',
|
referralRewardFactor: '0.05',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
minimumEpochs: 1,
|
minimumEpochs: 5,
|
||||||
minimumRunningNotionalTakerVolume: '1000000',
|
minimumRunningNotionalTakerVolume: '10000',
|
||||||
referralDiscountFactor: '0.1',
|
referralDiscountFactor: '0.001',
|
||||||
referralRewardFactor: '0.075',
|
referralRewardFactor: '0.001',
|
||||||
},
|
|
||||||
{
|
|
||||||
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: [
|
stakingTiers: [
|
||||||
{
|
{
|
||||||
minimumStakedTokens: '100000000000000000000',
|
minimumStakedTokens: '10000',
|
||||||
referralRewardMultiplier: '1.025',
|
referralRewardMultiplier: '1',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
minimumStakedTokens: '1000000000000000000000',
|
minimumStakedTokens: '20000',
|
||||||
referralRewardMultiplier: '1.05',
|
referralRewardMultiplier: '2',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
minimumStakedTokens: '5000000000000000000000',
|
minimumStakedTokens: '30000',
|
||||||
referralRewardMultiplier: '1.1',
|
referralRewardMultiplier: '3',
|
||||||
},
|
|
||||||
{
|
|
||||||
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 = () => {
|
export const useReferralProgram = () => {
|
||||||
@@ -103,9 +79,9 @@ export const useReferralProgram = () => {
|
|||||||
return {
|
return {
|
||||||
tier: i + 1, // sorted in asc order, hence first is the lowest tier
|
tier: i + 1, // sorted in asc order, hence first is the lowest tier
|
||||||
rewardFactor: Number(t.referralRewardFactor),
|
rewardFactor: Number(t.referralRewardFactor),
|
||||||
commission: BigNumber(t.referralRewardFactor).times(100).toFixed(2) + '%',
|
commission: Number(t.referralRewardFactor) * 100 + '%',
|
||||||
discountFactor: Number(t.referralDiscountFactor),
|
discountFactor: Number(t.referralDiscountFactor),
|
||||||
discount: BigNumber(t.referralDiscountFactor).times(100).toFixed(2) + '%',
|
discount: Number(t.referralDiscountFactor) * 100 + '%',
|
||||||
minimumVolume: Number(t.minimumRunningNotionalTakerVolume),
|
minimumVolume: Number(t.minimumRunningNotionalTakerVolume),
|
||||||
volume: getNumberFormat(0).format(
|
volume: getNumberFormat(0).format(
|
||||||
Number(t.minimumRunningNotionalTakerVolume)
|
Number(t.minimumRunningNotionalTakerVolume)
|
||||||
@@ -114,11 +90,13 @@ export const useReferralProgram = () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const stakingTiers = sortBy(data.currentReferralProgram.stakingTiers, (t) =>
|
const stakingTiers = sortBy(
|
||||||
parseFloat(t.referralRewardMultiplier)
|
data.currentReferralProgram.stakingTiers,
|
||||||
|
(t) => t.referralRewardMultiplier
|
||||||
).map((t, i) => {
|
).map((t, i) => {
|
||||||
return {
|
return {
|
||||||
tier: i + 1,
|
tier: i + 1,
|
||||||
|
label: STAKING_TIERS_MAPPING[i + 1],
|
||||||
...t,
|
...t,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -75,7 +75,10 @@ export const useReferralToasts = () => {
|
|||||||
data-testid="toast-apply-code"
|
data-testid="toast-apply-code"
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const matched = matchPath(Routes.REFERRALS, pathname);
|
const matched = matchPath(
|
||||||
|
Routes.REFERRALS_APPLY_CODE,
|
||||||
|
pathname
|
||||||
|
);
|
||||||
if (!matched) navigate(Routes.REFERRALS_APPLY_CODE);
|
if (!matched) navigate(Routes.REFERRALS_APPLY_CODE);
|
||||||
updateToast(NON_ELIGIBLE_REFERRAL_SET_TOAST_ID + epoch, {
|
updateToast(NON_ELIGIBLE_REFERRAL_SET_TOAST_ID + epoch, {
|
||||||
hidden: true,
|
hidden: true,
|
||||||
|
|||||||
@@ -2,11 +2,7 @@ import { removePaginationWrapper } from '@vegaprotocol/utils';
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useRefereesQuery } from './__generated__/Referees';
|
import { useRefereesQuery } from './__generated__/Referees';
|
||||||
import compact from 'lodash/compact';
|
import compact from 'lodash/compact';
|
||||||
import pick from 'lodash/pick';
|
import type { ReferralSetsQueryVariables } from './__generated__/ReferralSets';
|
||||||
import type {
|
|
||||||
ReferralSetsQuery,
|
|
||||||
ReferralSetsQueryVariables,
|
|
||||||
} from './__generated__/ReferralSets';
|
|
||||||
import { useReferralSetsQuery } from './__generated__/ReferralSets';
|
import { useReferralSetsQuery } from './__generated__/ReferralSets';
|
||||||
import { useStakeAvailable } from './use-stake-available';
|
import { useStakeAvailable } from './use-stake-available';
|
||||||
|
|
||||||
@@ -122,96 +118,3 @@ export const useReferral = (args: UseReferralArgs) => {
|
|||||||
refetch,
|
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,6 +13,7 @@ export const useStakeAvailable = (pubKey?: string) => {
|
|||||||
const { data } = useStakeAvailableQuery({
|
const { data } = useStakeAvailableQuery({
|
||||||
variables: { partyId: partyId || '' },
|
variables: { partyId: partyId || '' },
|
||||||
skip: !partyId,
|
skip: !partyId,
|
||||||
|
// TODO: remove when network params available
|
||||||
errorPolicy: 'ignore',
|
errorPolicy: 'ignore',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -15,16 +15,11 @@ export const LandingBanner = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="pt-20 sm:w-[50%]">
|
<div className="pt-20 sm:w-[50%]">
|
||||||
<h1 className="text-6xl font-alpha calt mb-10">
|
<h1 className="text-6xl font-alpha calt mb-10">
|
||||||
{t('Vega community referrals')}
|
{t('Earn commission & stake rewards')}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-lg mb-1">
|
|
||||||
{t(
|
|
||||||
'Referral programs can be proposed and created via community governance.'
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p className="text-lg mb-10">
|
<p className="text-lg mb-10">
|
||||||
{t(
|
{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.'
|
'Invite friends and earn rewards from the trading fees they pay. Stake those rewards to earn multipliers on future rewards.'
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import { DocsLinks } from '@vegaprotocol/environment';
|
|
||||||
import { ExternalLink, Tooltip } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { useT } from '../../lib/use-t';
|
|
||||||
|
|
||||||
export const QUSDTooltip = () => {
|
|
||||||
const t = useT();
|
|
||||||
return (
|
|
||||||
<Tooltip
|
|
||||||
description={
|
|
||||||
<>
|
|
||||||
<p className="mb-1">
|
|
||||||
{t(
|
|
||||||
'qUSD provides a rough USD equivalent of balances across all assets using the value of "Quantum" for that asset'
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
{DocsLinks && (
|
|
||||||
<ExternalLink href={DocsLinks.QUANTUM}>
|
|
||||||
{t('Find out more')}
|
|
||||||
</ExternalLink>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
underline={true}
|
|
||||||
>
|
|
||||||
<span>{t('qUSD')}</span>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -151,27 +151,6 @@ const MOCK_REFEREES: RefereesQuery = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const MOCK_REFEREES_30: RefereesQuery = {
|
|
||||||
referralSetReferees: {
|
|
||||||
__typename: 'ReferralSetRefereeConnection',
|
|
||||||
edges: [
|
|
||||||
{
|
|
||||||
node: {
|
|
||||||
atEpoch: 1,
|
|
||||||
joinedAt: '2023-11-21T14:17:09.257235Z',
|
|
||||||
refereeId:
|
|
||||||
'0987654321098765432109876543210987654321098765432109876543219876',
|
|
||||||
referralSetId:
|
|
||||||
'3772e570fbab89e50e563036b01dd949c554e5b5fe7908449672dfce9a8adffa',
|
|
||||||
totalRefereeGeneratedRewards: '12340',
|
|
||||||
totalRefereeNotionalTakerVolume: '56780',
|
|
||||||
__typename: 'ReferralSetReferee',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const programMock: MockedResponse<ReferralProgramQuery> = {
|
const programMock: MockedResponse<ReferralProgramQuery> = {
|
||||||
request: {
|
request: {
|
||||||
query: ReferralProgramDocument,
|
query: ReferralProgramDocument,
|
||||||
@@ -283,19 +262,6 @@ const refereesMock: MockedResponse<RefereesQuery, RefereesQueryVariables> = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const refereesMock30: MockedResponse<RefereesQuery, RefereesQueryVariables> = {
|
|
||||||
request: {
|
|
||||||
query: RefereesDocument,
|
|
||||||
variables: {
|
|
||||||
code: MOCK_REFERRER_SET.referralSets.edges[0]?.node.id as string,
|
|
||||||
aggregationEpochs: 30,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
result: {
|
|
||||||
data: MOCK_REFEREES_30,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
jest.mock('@vegaprotocol/wallet', () => {
|
jest.mock('@vegaprotocol/wallet', () => {
|
||||||
return {
|
return {
|
||||||
...jest.requireActual('@vegaprotocol/wallet'),
|
...jest.requireActual('@vegaprotocol/wallet'),
|
||||||
@@ -309,35 +275,30 @@ jest.mock('@vegaprotocol/wallet', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('ReferralStatistics', () => {
|
describe('ReferralStatistics', () => {
|
||||||
it('displays apply code when no data has been found for given pubkey', () => {
|
it('displays create code when no data has been found for given pubkey', () => {
|
||||||
const { queryByTestId } = render(
|
const { queryByTestId } = render(
|
||||||
<MemoryRouter>
|
<MockedProvider mocks={[]} showWarnings={false}>
|
||||||
<MockedProvider mocks={[]} showWarnings={false}>
|
<ReferralStatistics />
|
||||||
<ReferralStatistics />
|
</MockedProvider>
|
||||||
</MockedProvider>
|
|
||||||
</MemoryRouter>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(queryByTestId('referral-apply-code-form')).toBeInTheDocument();
|
expect(queryByTestId('referral-create-code-form')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('displays referrer stats when given pubkey is a referrer', async () => {
|
it('displays referrer stats when given pubkey is a referrer', async () => {
|
||||||
const { queryByTestId } = render(
|
const { queryByTestId } = render(
|
||||||
<MemoryRouter>
|
<MockedProvider
|
||||||
<MockedProvider
|
mocks={[
|
||||||
mocks={[
|
programMock,
|
||||||
programMock,
|
referralSetAsReferrerMock,
|
||||||
referralSetAsReferrerMock,
|
noReferralSetAsRefereeMock,
|
||||||
noReferralSetAsRefereeMock,
|
stakeAvailableMock,
|
||||||
stakeAvailableMock,
|
refereesMock,
|
||||||
refereesMock,
|
]}
|
||||||
refereesMock30,
|
showWarnings={false}
|
||||||
]}
|
>
|
||||||
showWarnings={false}
|
<ReferralStatistics />
|
||||||
>
|
</MockedProvider>
|
||||||
<ReferralStatistics />
|
|
||||||
</MockedProvider>
|
|
||||||
</MemoryRouter>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -348,10 +309,6 @@ describe('ReferralStatistics', () => {
|
|||||||
expect(queryByTestId('referral-statistics')?.dataset.as).toEqual(
|
expect(queryByTestId('referral-statistics')?.dataset.as).toEqual(
|
||||||
'referrer'
|
'referrer'
|
||||||
);
|
);
|
||||||
// gets commision from 30 epochs query
|
|
||||||
expect(queryByTestId('total-commission-value')).toHaveTextContent(
|
|
||||||
'12,340'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,13 @@ import {
|
|||||||
VegaIcon,
|
VegaIcon,
|
||||||
VegaIconNames,
|
VegaIconNames,
|
||||||
truncateMiddle,
|
truncateMiddle,
|
||||||
TextChildrenTooltip as Tooltip,
|
ExternalLink,
|
||||||
|
Tooltip,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import {
|
import { DEFAULT_AGGREGATION_DAYS, useReferral } from './hooks/use-referral';
|
||||||
DEFAULT_AGGREGATION_DAYS,
|
import { CreateCodeContainer } from './create-code-form';
|
||||||
useReferral,
|
|
||||||
useUpdateReferees,
|
|
||||||
} from './hooks/use-referral';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Table } from './table';
|
import { Table } from './table';
|
||||||
import {
|
import {
|
||||||
@@ -28,45 +26,34 @@ import compact from 'lodash/compact';
|
|||||||
import { useReferralProgram } from './hooks/use-referral-program';
|
import { useReferralProgram } from './hooks/use-referral-program';
|
||||||
import { useStakeAvailable } from './hooks/use-stake-available';
|
import { useStakeAvailable } from './hooks/use-stake-available';
|
||||||
import sortBy from 'lodash/sortBy';
|
import sortBy from 'lodash/sortBy';
|
||||||
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
import { useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useCurrentEpochInfoQuery } from './hooks/__generated__/Epoch';
|
import { useCurrentEpochInfoQuery } from './hooks/__generated__/Epoch';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
|
import { DocsLinks } from '@vegaprotocol/environment';
|
||||||
import { useT, ns } from '../../lib/use-t';
|
import { useT, ns } from '../../lib/use-t';
|
||||||
import { Trans } from 'react-i18next';
|
import { Trans } from 'react-i18next';
|
||||||
import { ApplyCodeForm, ApplyCodeFormContainer } from './apply-code-form';
|
import { ApplyCodeForm } from './apply-code-form';
|
||||||
import { QUSDTooltip } from './qusd-tooltip';
|
|
||||||
|
|
||||||
export const ReferralStatistics = () => {
|
export const ReferralStatistics = () => {
|
||||||
const { pubKey } = useVegaWallet();
|
const { pubKey } = useVegaWallet();
|
||||||
|
|
||||||
const program = useReferralProgram();
|
const program = useReferralProgram();
|
||||||
|
|
||||||
const { data: referee, refetch: refereeRefetch } = useReferral({
|
const { data: referee } = useReferral({
|
||||||
pubKey,
|
pubKey,
|
||||||
role: 'referee',
|
role: 'referee',
|
||||||
aggregationEpochs: program.details?.windowLength,
|
aggregationEpochs: program.details?.windowLength,
|
||||||
});
|
});
|
||||||
|
const { data: referrer } = useReferral({
|
||||||
const { data: referrer, refetch: referrerRefetch } = useUpdateReferees(
|
pubKey,
|
||||||
useReferral({
|
role: 'referrer',
|
||||||
pubKey,
|
aggregationEpochs: program.details?.windowLength,
|
||||||
role: 'referrer',
|
});
|
||||||
aggregationEpochs: program.details?.windowLength,
|
|
||||||
}),
|
|
||||||
DEFAULT_AGGREGATION_DAYS,
|
|
||||||
['totalRefereeGeneratedRewards'],
|
|
||||||
DEFAULT_AGGREGATION_DAYS === program.details?.windowLength
|
|
||||||
);
|
|
||||||
|
|
||||||
const refetch = useCallback(() => {
|
|
||||||
refereeRefetch();
|
|
||||||
referrerRefetch();
|
|
||||||
}, [refereeRefetch, referrerRefetch]);
|
|
||||||
|
|
||||||
if (referee?.code) {
|
if (referee?.code) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Statistics data={referee} program={program} as="referee" />
|
<Statistics data={referee} program={program} as="referee" />;
|
||||||
{!referee.isEligible && <ApplyCodeForm />}
|
{!referee.isEligible && <ApplyCodeForm />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -75,26 +62,26 @@ export const ReferralStatistics = () => {
|
|||||||
if (referrer?.code) {
|
if (referrer?.code) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Statistics data={referrer} program={program} as="referrer" />
|
<Statistics data={referrer} program={program} as="referrer" />;
|
||||||
<RefereesTable data={referrer} program={program} />
|
<RefereesTable data={referrer} program={program} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ApplyCodeFormContainer onSuccess={refetch} />;
|
return <CreateCodeContainer />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useStats = ({
|
export const useStats = ({
|
||||||
data,
|
data,
|
||||||
program,
|
program,
|
||||||
|
as,
|
||||||
}: {
|
}: {
|
||||||
data?: NonNullable<ReturnType<typeof useReferral>['data']>;
|
data?: NonNullable<ReturnType<typeof useReferral>['data']>;
|
||||||
program: ReturnType<typeof useReferralProgram>;
|
program: ReturnType<typeof useReferralProgram>;
|
||||||
|
as?: 'referrer' | 'referee';
|
||||||
}) => {
|
}) => {
|
||||||
const { benefitTiers } = program;
|
const { benefitTiers } = program;
|
||||||
const { data: epochData } = useCurrentEpochInfoQuery({
|
const { data: epochData } = useCurrentEpochInfoQuery();
|
||||||
fetchPolicy: 'network-only',
|
|
||||||
});
|
|
||||||
const { data: statsData } = useReferralSetStatsQuery({
|
const { data: statsData } = useReferralSetStatsQuery({
|
||||||
variables: {
|
variables: {
|
||||||
code: data?.code || '',
|
code: data?.code || '',
|
||||||
@@ -128,7 +115,7 @@ export const useStats = ({
|
|||||||
: 1;
|
: 1;
|
||||||
const finalCommissionValue = isNaN(multiplier)
|
const finalCommissionValue = isNaN(multiplier)
|
||||||
? baseCommissionValue
|
? baseCommissionValue
|
||||||
: new BigNumber(multiplier).times(baseCommissionValue).toNumber();
|
: multiplier * baseCommissionValue;
|
||||||
|
|
||||||
const discountFactorValue = refereeStats?.discountFactor
|
const discountFactorValue = refereeStats?.discountFactor
|
||||||
? Number(refereeStats.discountFactor)
|
? Number(refereeStats.discountFactor)
|
||||||
@@ -187,10 +174,9 @@ export const Statistics = ({
|
|||||||
discountFactorValue,
|
discountFactorValue,
|
||||||
currentBenefitTierValue,
|
currentBenefitTierValue,
|
||||||
epochsValue,
|
epochsValue,
|
||||||
nextBenefitTierValue,
|
|
||||||
nextBenefitTierVolumeValue,
|
nextBenefitTierVolumeValue,
|
||||||
nextBenefitTierEpochsValue,
|
nextBenefitTierEpochsValue,
|
||||||
} = useStats({ data, program });
|
} = useStats({ data, program, as });
|
||||||
|
|
||||||
const isApplyCodePreview = useMemo(
|
const isApplyCodePreview = useMemo(
|
||||||
() => data.referee === null,
|
() => data.referee === null,
|
||||||
@@ -221,8 +207,6 @@ export const Statistics = ({
|
|||||||
).toString(),
|
).toString(),
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
testId="base-commission-rate"
|
|
||||||
overrideWithNoProgram={!details}
|
|
||||||
>
|
>
|
||||||
{baseCommissionValue * 100}%
|
{baseCommissionValue * 100}%
|
||||||
</StatTile>
|
</StatTile>
|
||||||
@@ -231,7 +215,6 @@ export const Statistics = ({
|
|||||||
const stakingMultiplierTile = (
|
const stakingMultiplierTile = (
|
||||||
<StatTile
|
<StatTile
|
||||||
title={t('Staking multiplier')}
|
title={t('Staking multiplier')}
|
||||||
testId="staking-multiplier"
|
|
||||||
description={
|
description={
|
||||||
<span
|
<span
|
||||||
className={classNames({
|
className={classNames({
|
||||||
@@ -246,36 +229,27 @@ export const Statistics = ({
|
|||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
overrideWithNoProgram={!details}
|
|
||||||
>
|
>
|
||||||
{multiplier || t('None')}
|
{multiplier || t('None')}
|
||||||
</StatTile>
|
</StatTile>
|
||||||
);
|
);
|
||||||
const baseCommissionFormatted = BigNumber(baseCommissionValue)
|
|
||||||
.times(100)
|
|
||||||
.toString();
|
|
||||||
const finalCommissionFormatted = new BigNumber(finalCommissionValue)
|
|
||||||
.times(100)
|
|
||||||
.toString();
|
|
||||||
const finalCommissionTile = (
|
const finalCommissionTile = (
|
||||||
<StatTile
|
<StatTile
|
||||||
title={t('Final commission rate')}
|
title={t('Final commission rate')}
|
||||||
description={
|
description={
|
||||||
!isNaN(multiplier)
|
!isNaN(multiplier)
|
||||||
? `(${baseCommissionFormatted}% ⨉ ${multiplier} = ${finalCommissionFormatted}%)`
|
? `(${baseCommissionValue * 100}% ⨉ ${multiplier} = ${
|
||||||
|
finalCommissionValue * 100
|
||||||
|
}%)`
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
testId="final-commission-rate"
|
|
||||||
overrideWithNoProgram={!details}
|
|
||||||
>
|
>
|
||||||
{finalCommissionFormatted}%
|
{finalCommissionValue * 100}%
|
||||||
</StatTile>
|
</StatTile>
|
||||||
);
|
);
|
||||||
const numberOfTradersValue = data.referees.length;
|
const numberOfTradersValue = data.referees.length;
|
||||||
const numberOfTradersTile = (
|
const numberOfTradersTile = (
|
||||||
<StatTile title={t('Number of traders')} testId="number-of-traders">
|
<StatTile title={t('Number of traders')}>{numberOfTradersValue}</StatTile>
|
||||||
{numberOfTradersValue}
|
|
||||||
</StatTile>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const codeTile = (
|
const codeTile = (
|
||||||
@@ -290,8 +264,6 @@ export const Statistics = ({
|
|||||||
title={t('myVolume', 'My volume (last {{count}} epochs)', {
|
title={t('myVolume', 'My volume (last {{count}} epochs)', {
|
||||||
count: details?.windowLength || DEFAULT_AGGREGATION_DAYS,
|
count: details?.windowLength || DEFAULT_AGGREGATION_DAYS,
|
||||||
})}
|
})}
|
||||||
testId="my-volume"
|
|
||||||
overrideWithNoProgram={!details}
|
|
||||||
>
|
>
|
||||||
{compactNumFormat.format(referrerVolumeValue)}
|
{compactNumFormat.format(referrerVolumeValue)}
|
||||||
</StatTile>
|
</StatTile>
|
||||||
@@ -302,26 +274,9 @@ export const Statistics = ({
|
|||||||
.reduce((all, r) => all.plus(r), new BigNumber(0));
|
.reduce((all, r) => all.plus(r), new BigNumber(0));
|
||||||
const totalCommissionTile = (
|
const totalCommissionTile = (
|
||||||
<StatTile
|
<StatTile
|
||||||
testId="total-commission"
|
title={t('totalCommission', 'Total commission (last {{count}}} epochs)', {
|
||||||
title={
|
count: details?.windowLength || DEFAULT_AGGREGATION_DAYS,
|
||||||
<Trans
|
})}
|
||||||
i18nKey="totalCommission"
|
|
||||||
defaults="Total commission (<0>last {{count}} epochs</0>)"
|
|
||||||
values={{
|
|
||||||
count: DEFAULT_AGGREGATION_DAYS,
|
|
||||||
}}
|
|
||||||
components={[
|
|
||||||
<Tooltip
|
|
||||||
key="1"
|
|
||||||
description={t(
|
|
||||||
'Depending on data node retention you may not be able see the full 30 days'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
last 30 epochs
|
|
||||||
</Tooltip>,
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
description={<QUSDTooltip />}
|
description={<QUSDTooltip />}
|
||||||
>
|
>
|
||||||
{getNumberFormat(0).format(Number(totalCommissionValue))}
|
{getNumberFormat(0).format(Number(totalCommissionValue))}
|
||||||
@@ -346,30 +301,15 @@ export const Statistics = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const currentBenefitTierTile = (
|
const currentBenefitTierTile = (
|
||||||
<StatTile
|
<StatTile title={t('Current tier')}>
|
||||||
title={t('Current tier')}
|
|
||||||
testId="current-tier"
|
|
||||||
description={
|
|
||||||
nextBenefitTierValue?.tier
|
|
||||||
? t('(Next tier: {{nextTier}})', {
|
|
||||||
nextTier: nextBenefitTierValue?.tier,
|
|
||||||
})
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
overrideWithNoProgram={!details}
|
|
||||||
>
|
|
||||||
{isApplyCodePreview
|
{isApplyCodePreview
|
||||||
? currentBenefitTierValue?.tier || benefitTiers[0]?.tier || 'None'
|
? currentBenefitTierValue?.tier || benefitTiers[0]?.tier || 'None'
|
||||||
: currentBenefitTierValue?.tier || 'None'}
|
: currentBenefitTierValue?.tier || 'None'}
|
||||||
</StatTile>
|
</StatTile>
|
||||||
);
|
);
|
||||||
const discountFactorTile = (
|
const discountFactorTile = (
|
||||||
<StatTile
|
<StatTile title={t('Discount')}>
|
||||||
title={t('Discount')}
|
{isApplyCodePreview
|
||||||
testId="discount"
|
|
||||||
overrideWithNoProgram={!details}
|
|
||||||
>
|
|
||||||
{isApplyCodePreview && benefitTiers.length >= 1
|
|
||||||
? benefitTiers[0].discountFactor * 100
|
? benefitTiers[0].discountFactor * 100
|
||||||
: discountFactorValue * 100}
|
: discountFactorValue * 100}
|
||||||
%
|
%
|
||||||
@@ -384,34 +324,22 @@ export const Statistics = ({
|
|||||||
count: details?.windowLength,
|
count: details?.windowLength,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
testId="combined-volume"
|
|
||||||
overrideWithNoProgram={!details}
|
|
||||||
>
|
>
|
||||||
{compactNumFormat.format(runningVolumeValue)}
|
{compactNumFormat.format(runningVolumeValue)}
|
||||||
</StatTile>
|
</StatTile>
|
||||||
);
|
);
|
||||||
const epochsTile = (
|
const epochsTile = (
|
||||||
<StatTile title={t('Epochs in set')} testId="epochs-in-set">
|
<StatTile title={t('Epochs in set')}>{epochsValue}</StatTile>
|
||||||
{epochsValue}
|
|
||||||
</StatTile>
|
|
||||||
);
|
);
|
||||||
const nextTierVolumeTile = (
|
const nextTierVolumeTile = (
|
||||||
<StatTile
|
<StatTile title={t('Volume to next tier')}>
|
||||||
title={t('Volume to next tier')}
|
|
||||||
testId="vol-to-next-tier"
|
|
||||||
overrideWithNoProgram={!details}
|
|
||||||
>
|
|
||||||
{nextBenefitTierVolumeValue <= 0
|
{nextBenefitTierVolumeValue <= 0
|
||||||
? '0'
|
? '0'
|
||||||
: compactNumFormat.format(nextBenefitTierVolumeValue)}
|
: compactNumFormat.format(nextBenefitTierVolumeValue)}
|
||||||
</StatTile>
|
</StatTile>
|
||||||
);
|
);
|
||||||
const nextTierEpochsTile = (
|
const nextTierEpochsTile = (
|
||||||
<StatTile
|
<StatTile title={t('Epochs to next tier')}>
|
||||||
title={t('Epochs to next tier')}
|
|
||||||
testId="epochs-to-next-tier"
|
|
||||||
overrideWithNoProgram={!details}
|
|
||||||
>
|
|
||||||
{nextBenefitTierEpochsValue <= 0 ? '0' : nextBenefitTierEpochsValue}
|
{nextBenefitTierEpochsValue <= 0 ? '0' : nextBenefitTierEpochsValue}
|
||||||
</StatTile>
|
</StatTile>
|
||||||
);
|
);
|
||||||
@@ -528,20 +456,10 @@ export const RefereesTable = ({
|
|||||||
displayName: (
|
displayName: (
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="referralStatisticsCommission"
|
i18nKey="referralStatisticsCommission"
|
||||||
defaults="Commission earned in <0>qUSD</0> (<1>last {{count}} epochs</1>)"
|
defaults="Commission earned in <0>qUSD</0> (last {{count}} epochs)"
|
||||||
components={[
|
|
||||||
<QUSDTooltip key="0" />,
|
|
||||||
<Tooltip
|
|
||||||
key="1"
|
|
||||||
description={t(
|
|
||||||
'Depending on data node retention you may not be able see the full 30 days'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
last 30 epochs
|
|
||||||
</Tooltip>,
|
|
||||||
]}
|
|
||||||
values={{
|
values={{
|
||||||
count: DEFAULT_AGGREGATION_DAYS,
|
count:
|
||||||
|
details?.windowLength || DEFAULT_AGGREGATION_DAYS,
|
||||||
}}
|
}}
|
||||||
ns={ns}
|
ns={ns}
|
||||||
/>
|
/>
|
||||||
@@ -574,3 +492,28 @@ export const RefereesTable = ({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const QUSDTooltip = () => {
|
||||||
|
const t = useT();
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
description={
|
||||||
|
<>
|
||||||
|
<p className="mb-1">
|
||||||
|
{t(
|
||||||
|
'qUSD provides a rough USD equivalent of balances across all assets using the value of "Quantum" for that asset'
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
{DocsLinks && (
|
||||||
|
<ExternalLink href={DocsLinks.QUANTUM}>
|
||||||
|
{t('Find out more')}
|
||||||
|
</ExternalLink>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
underline={true}
|
||||||
|
>
|
||||||
|
<span>{t('qUSD')}</span>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import {
|
|||||||
VegaIcon,
|
VegaIcon,
|
||||||
VegaIconNames,
|
VegaIconNames,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { HowItWorksTable } from './how-it-works-table';
|
||||||
import { LandingBanner } from './landing-banner';
|
import { LandingBanner } from './landing-banner';
|
||||||
import { TiersContainer } from './tiers';
|
import { TiersContainer } from './tiers';
|
||||||
import { TabLink } from './buttons';
|
import { TabLink } from './buttons';
|
||||||
import { Outlet, useMatch } from 'react-router-dom';
|
import { Outlet } from 'react-router-dom';
|
||||||
import { Routes } from '../../lib/links';
|
import { Routes } from '../../lib/links';
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import { useReferral } from './hooks/use-referral';
|
import { useReferral } from './hooks/use-referral';
|
||||||
@@ -21,13 +22,12 @@ import { ErrorBoundary } from '../../components/error-boundary';
|
|||||||
|
|
||||||
const Nav = () => {
|
const Nav = () => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const match = useMatch(Routes.REFERRALS_APPLY_CODE);
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center border-b border-vega-cdark-500">
|
<div className="flex justify-center border-b border-vega-cdark-500">
|
||||||
<TabLink end to={match ? Routes.REFERRALS_APPLY_CODE : Routes.REFERRALS}>
|
<TabLink end to={Routes.REFERRALS}>
|
||||||
{t('Apply code')}
|
{t('I want a code')}
|
||||||
</TabLink>
|
</TabLink>
|
||||||
<TabLink to={Routes.REFERRALS_CREATE_CODE}>{t('Create code')}</TabLink>
|
<TabLink to={Routes.REFERRALS_APPLY_CODE}>{t('I have a code')}</TabLink>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -96,13 +96,15 @@ export const Referrals = () => {
|
|||||||
<h2 className="text-2xl">{t('How it works')}</h2>
|
<h2 className="text-2xl">{t('How it works')}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="md:w-[60%] mx-auto">
|
<div className="md:w-[60%] mx-auto">
|
||||||
|
<HowItWorksTable />
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<TradingAnchorButton
|
<TradingAnchorButton
|
||||||
className="mx-auto w-max"
|
className="mx-auto w-max"
|
||||||
href={REFERRAL_DOCS_LINK}
|
href={REFERRAL_DOCS_LINK}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
{t('Read the docs')} <VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
{t('Read the terms')}{' '}
|
||||||
|
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
||||||
</TradingAnchorButton>
|
</TradingAnchorButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,10 +14,8 @@ export const Tag = ({
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
'w-max border rounded-[1rem] py-[0.125rem] px-2 text-xs',
|
'w-max border rounded-[1rem] py-[0.125rem] px-2 text-xs',
|
||||||
{
|
{
|
||||||
'border-vega-yellow-550 text-vega-yellow-550 dark:border-vega-yellow-500 dark:text-vega-yellow-500':
|
'border-vega-yellow-500 text-vega-yellow-500': color === 'yellow',
|
||||||
color === 'yellow',
|
'border-vega-green-500 text-vega-green-500': color === 'green',
|
||||||
'border-vega-green-550 text-vega-green-550 dark:border-vega-green-500 dark:text-vega-green-500':
|
|
||||||
color === 'green',
|
|
||||||
'border-vega-blue-500 text-vega-blue-500': color === 'blue',
|
'border-vega-blue-500 text-vega-blue-500': color === 'blue',
|
||||||
'border-vega-purple-500 text-vega-purple-500': color === 'purple',
|
'border-vega-purple-500 text-vega-purple-500': color === 'purple',
|
||||||
'border-vega-pink-500 text-vega-pink-500': color === 'pink',
|
'border-vega-pink-500 text-vega-pink-500': color === 'pink',
|
||||||
|
|||||||
@@ -1,43 +1,20 @@
|
|||||||
import {
|
import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||||
addDecimalsFormatNumber,
|
|
||||||
getDateTimeFormat,
|
|
||||||
} from '@vegaprotocol/utils';
|
|
||||||
import { useReferralProgram } from './hooks/use-referral-program';
|
import { useReferralProgram } from './hooks/use-referral-program';
|
||||||
import { Table } from './table';
|
import { Table } from './table';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { BORDER_COLOR, GRADIENT } from './constants';
|
import { BORDER_COLOR, GRADIENT } from './constants';
|
||||||
import { Tag } from './tag';
|
import { Tag } from './tag';
|
||||||
import type { ComponentProps, ReactNode } from 'react';
|
import type { ComponentProps, ReactNode } from 'react';
|
||||||
import { ExternalLink, truncateMiddle } from '@vegaprotocol/ui-toolkit';
|
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||||
import {
|
import {
|
||||||
DApp,
|
DApp,
|
||||||
DocsLinks,
|
DocsLinks,
|
||||||
TOKEN_PROPOSAL,
|
|
||||||
TOKEN_PROPOSALS,
|
TOKEN_PROPOSALS,
|
||||||
useLinks,
|
useLinks,
|
||||||
} from '@vegaprotocol/environment';
|
} from '@vegaprotocol/environment';
|
||||||
import { useT, ns } from '../../lib/use-t';
|
import { useT, ns } from '../../lib/use-t';
|
||||||
import { Trans } from 'react-i18next';
|
import { Trans } from 'react-i18next';
|
||||||
|
|
||||||
// rainbow-ish order
|
|
||||||
const TIER_COLORS: Array<ComponentProps<typeof Tag>['color']> = [
|
|
||||||
'pink',
|
|
||||||
'orange',
|
|
||||||
'yellow',
|
|
||||||
'green',
|
|
||||||
'blue',
|
|
||||||
'purple',
|
|
||||||
];
|
|
||||||
|
|
||||||
const getTierColor = (tier: number) => {
|
|
||||||
const tiers = Object.keys(TIER_COLORS).length;
|
|
||||||
let index = Math.abs(tier - 1);
|
|
||||||
if (tier >= tiers) {
|
|
||||||
index = index % tiers;
|
|
||||||
}
|
|
||||||
return TIER_COLORS[index];
|
|
||||||
};
|
|
||||||
|
|
||||||
const Loading = ({ variant }: { variant: 'large' | 'inline' }) => (
|
const Loading = ({ variant }: { variant: 'large' | 'inline' }) => (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
@@ -51,63 +28,51 @@ const Loading = ({ variant }: { variant: 'large' | 'inline' }) => (
|
|||||||
|
|
||||||
const StakingTier = ({
|
const StakingTier = ({
|
||||||
tier,
|
tier,
|
||||||
|
label,
|
||||||
referralRewardMultiplier,
|
referralRewardMultiplier,
|
||||||
minimumStakedTokens,
|
minimumStakedTokens,
|
||||||
}: {
|
}: {
|
||||||
tier: number;
|
tier: number;
|
||||||
|
label: string;
|
||||||
referralRewardMultiplier: string;
|
referralRewardMultiplier: string;
|
||||||
minimumStakedTokens: string;
|
minimumStakedTokens: string;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const minimum = addDecimalsFormatNumber(minimumStakedTokens, 18);
|
const color: Record<number, ComponentProps<typeof Tag>['color']> = {
|
||||||
|
1: 'green',
|
||||||
// TODO: Decide what to do with the multiplier images
|
2: 'blue',
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
3: 'pink',
|
||||||
const multiplierImage = (
|
};
|
||||||
<div
|
|
||||||
aria-hidden
|
|
||||||
className={classNames(
|
|
||||||
'w-full max-w-[80px] h-full min-h-[80px]',
|
|
||||||
'bg-cover bg-right-bottom',
|
|
||||||
{
|
|
||||||
"bg-[url('/1x.png')]": tier === 1,
|
|
||||||
"bg-[url('/2x.png')]": tier === 2,
|
|
||||||
"bg-[url('/3x.png')]": tier === 3,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span className="sr-only">{`${referralRewardMultiplier}x multiplier`}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'overflow-hidden',
|
'overflow-hidden',
|
||||||
'border rounded-md w-full',
|
'border rounded-md w-full',
|
||||||
'flex flex-row',
|
'flex flex-row',
|
||||||
'bg-white dark:bg-vega-cdark-900',
|
|
||||||
GRADIENT,
|
GRADIENT,
|
||||||
BORDER_COLOR
|
BORDER_COLOR
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div aria-hidden className="max-w-[120px]">
|
||||||
className={classNames(
|
{tier < 4 && (
|
||||||
'p-3 flex flex-row min-h-[80px] h-full items-center'
|
// eslint-disable-next-line @next/next/no-img-element
|
||||||
|
<img
|
||||||
|
src={`/${tier}x.png`}
|
||||||
|
alt={`${referralRewardMultiplier}x multiplier`}
|
||||||
|
width={240}
|
||||||
|
height={240}
|
||||||
|
className="w-full h-full"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
>
|
</div>
|
||||||
<div>
|
<div className={classNames('p-3')}>
|
||||||
<Tag color={getTierColor(tier)}>
|
<Tag color={color[tier]}>Multiplier {referralRewardMultiplier}x</Tag>
|
||||||
{t('Multiplier')} {referralRewardMultiplier}x
|
<h3 className="mt-1 mb-1 text-base">{label}</h3>
|
||||||
</Tag>
|
<p className="text-sm text-vega-clight-100 dark:text-vega-cdark-100">
|
||||||
<p className="mt-1 text-sm text-vega-clight-100 dark:text-vega-cdark-100">
|
{t('Stake a minimum of {{minimumStakedTokens}} $VEGA tokens', {
|
||||||
<Trans
|
minimumStakedTokens,
|
||||||
defaults="Stake a minimum of <0>{{minimum}}</0> $VEGA tokens"
|
})}
|
||||||
values={{ minimum }}
|
</p>
|
||||||
components={[<b key={minimum}></b>]}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -126,29 +91,21 @@ export const TiersContainer = () => {
|
|||||||
|
|
||||||
if ((!loading && !details) || error) {
|
if ((!loading && !details) || error) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-vega-clight-800 dark:bg-vega-cdark-800 text-black dark:text-white rounded-lg p-6 mt-1 mb-20 text-sm text-center">
|
<div className="text-base px-5 py-10 text-center">
|
||||||
<Trans
|
<Trans
|
||||||
defaults="There are currently no active referral programs. Check the <0>Governance App</0> to see if there are any proposals in progress and vote."
|
defaults="There are currently no active referral programs. Check the <0>Governance App</0> to see if there are any proposals in progress and vote."
|
||||||
components={[
|
components={[
|
||||||
<ExternalLink
|
<ExternalLink href={governanceLink(TOKEN_PROPOSALS)} key="link">
|
||||||
href={governanceLink(TOKEN_PROPOSALS)}
|
|
||||||
key="link"
|
|
||||||
className="underline"
|
|
||||||
>
|
|
||||||
{t('Governance App')}
|
{t('Governance App')}
|
||||||
</ExternalLink>,
|
</ExternalLink>,
|
||||||
]}
|
]}
|
||||||
ns={ns}
|
ns={ns}
|
||||||
/>{' '}
|
/>
|
||||||
<Trans
|
<Trans
|
||||||
defaults="Use the <0>docs</0> tutorial to propose a new program."
|
defaults="You can propose a new program via the <0>Docs</0>."
|
||||||
components={[
|
components={[
|
||||||
<ExternalLink
|
<ExternalLink href={DocsLinks?.REFERRALS} key="link">
|
||||||
href={DocsLinks?.REFERRALS}
|
{t('Docs')}
|
||||||
key="link"
|
|
||||||
className="underline"
|
|
||||||
>
|
|
||||||
{t('docs')}
|
|
||||||
</ExternalLink>,
|
</ExternalLink>,
|
||||||
]}
|
]}
|
||||||
ns={ns}
|
ns={ns}
|
||||||
@@ -159,93 +116,47 @@ export const TiersContainer = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h2 className="text-3xl mt-10">{t('Current program details')}</h2>
|
{/* Benefit tiers */}
|
||||||
{details?.id && (
|
<div className="flex flex-col items-baseline justify-between mt-10 mb-5">
|
||||||
<p>
|
<h2 className="text-2xl">{t('Referral tiers')}</h2>
|
||||||
<Trans
|
|
||||||
defaults="As a result of governance proposal <0>{{proposal}}</0> the program below is currently active on the Vega network."
|
|
||||||
values={{ proposal: truncateMiddle(details.id) }}
|
|
||||||
components={[
|
|
||||||
<ExternalLink
|
|
||||||
key="referral-program-proposal-link"
|
|
||||||
href={governanceLink(TOKEN_PROPOSAL.replace(':id', details.id))}
|
|
||||||
className="underline"
|
|
||||||
>
|
|
||||||
proposal
|
|
||||||
</ExternalLink>,
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Meta */}
|
|
||||||
<div className="mt-10 flex flex-row items-baseline justify-between text-xs text-vega-clight-100 dark:text-vega-cdark-100 font-alpha calt">
|
|
||||||
{details?.id && (
|
|
||||||
<span>
|
|
||||||
{t('Proposal ID:')}{' '}
|
|
||||||
<ExternalLink
|
|
||||||
href={governanceLink(TOKEN_PROPOSAL.replace(':id', details.id))}
|
|
||||||
>
|
|
||||||
<span>{truncateMiddle(details.id)}</span>
|
|
||||||
</ExternalLink>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{ends && (
|
{ends && (
|
||||||
<span>
|
<span className="text-sm text-vega-clight-200 dark:text-vega-cdark-200">
|
||||||
{t('Program ends:')} {ends}
|
{t('Program ends:')} {ends}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mb-20">
|
||||||
|
{loading || !benefitTiers || benefitTiers.length === 0 ? (
|
||||||
|
<Loading variant="large" />
|
||||||
|
) : (
|
||||||
|
<TiersTable
|
||||||
|
windowLength={details?.windowLength}
|
||||||
|
data={benefitTiers.map((bt) => ({
|
||||||
|
...bt,
|
||||||
|
tierElement: (
|
||||||
|
<div className="rounded-full bg-vega-clight-900 dark:bg-vega-cdark-900 p-1 w-8 h-8 text-center">
|
||||||
|
{bt.tier}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Container */}
|
{/* Staking tiers */}
|
||||||
<div className="bg-vega-clight-800 dark:bg-vega-cdark-800 text-black dark:text-white rounded-lg p-6 mt-1 mb-20">
|
<div className="flex flex-row items-baseline justify-between mb-5">
|
||||||
{/* Benefit tiers */}
|
<h2 className="text-2xl">{t('Staking multipliers')}</h2>
|
||||||
<div className="flex flex-col mb-5">
|
</div>
|
||||||
<h3 className="text-2xl calt">{t('Benefit tiers')}</h3>
|
<div className="mb-20 flex flex-col justify-items-stretch lg:flex-row gap-5">
|
||||||
<p className="text-sm text-vega-clight-200 dark:text-vega-cdark-200">
|
{loading || !stakingTiers || stakingTiers.length === 0 ? (
|
||||||
{t(
|
<>
|
||||||
'Members of a referral group can access the increasing commission and discount benefits defined in the program based on their combined running volume.'
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="mb-10">
|
|
||||||
{loading || !benefitTiers || benefitTiers.length === 0 ? (
|
|
||||||
<Loading variant="large" />
|
<Loading variant="large" />
|
||||||
) : (
|
<Loading variant="large" />
|
||||||
<TiersTable
|
<Loading variant="large" />
|
||||||
windowLength={details?.windowLength}
|
</>
|
||||||
data={benefitTiers.map((bt) => ({
|
) : (
|
||||||
...bt,
|
<StakingTiers data={stakingTiers} />
|
||||||
tierElement: (
|
)}
|
||||||
<div className="rounded-full bg-vega-clight-900 dark:bg-vega-cdark-900 p-1 w-8 h-8 text-center">
|
|
||||||
{bt.tier}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Staking tiers */}
|
|
||||||
<div className="flex flex-col mb-5">
|
|
||||||
<h3 className="text-2xl calt">{t('Staking multipliers')}</h3>
|
|
||||||
<p className="text-sm text-vega-clight-200 dark:text-vega-cdark-200">
|
|
||||||
{t(
|
|
||||||
'Referrers can access the commission multipliers defined in the program by staking VEGA tokens in the amounts shown.'
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="gap-5 grid lg:grid-cols-3">
|
|
||||||
{loading || !stakingTiers || stakingTiers.length === 0 ? (
|
|
||||||
<>
|
|
||||||
<Loading variant="large" />
|
|
||||||
<Loading variant="large" />
|
|
||||||
<Loading variant="large" />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<StakingTiers data={stakingTiers} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -257,14 +168,17 @@ const StakingTiers = ({
|
|||||||
data: ReturnType<typeof useReferralProgram>['stakingTiers'];
|
data: ReturnType<typeof useReferralProgram>['stakingTiers'];
|
||||||
}) => (
|
}) => (
|
||||||
<>
|
<>
|
||||||
{data.map(({ tier, referralRewardMultiplier, minimumStakedTokens }, i) => (
|
{data.map(
|
||||||
<StakingTier
|
({ tier, label, referralRewardMultiplier, minimumStakedTokens }, i) => (
|
||||||
key={i}
|
<StakingTier
|
||||||
tier={tier}
|
key={i}
|
||||||
referralRewardMultiplier={referralRewardMultiplier}
|
tier={tier}
|
||||||
minimumStakedTokens={minimumStakedTokens}
|
label={label}
|
||||||
/>
|
referralRewardMultiplier={referralRewardMultiplier}
|
||||||
))}
|
minimumStakedTokens={minimumStakedTokens}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -289,17 +203,9 @@ const TiersTable = ({
|
|||||||
{
|
{
|
||||||
name: 'commission',
|
name: 'commission',
|
||||||
displayName: t('Referrer commission'),
|
displayName: t('Referrer commission'),
|
||||||
tooltip: t(
|
tooltip: t('A percentage of commission earned by the referrer'),
|
||||||
"The proportion of the referee's taker fees to be rewarded to the referrer"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'discount',
|
|
||||||
displayName: t('Referee trading discount'),
|
|
||||||
tooltip: t(
|
|
||||||
"The proportion of the referee's taker fees to be discounted"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
|
{ name: 'discount', displayName: t('Referrer trading discount') },
|
||||||
{
|
{
|
||||||
name: 'volume',
|
name: 'volume',
|
||||||
displayName: t(
|
displayName: t(
|
||||||
@@ -309,34 +215,20 @@ const TiersTable = ({
|
|||||||
count: windowLength,
|
count: windowLength,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
tooltip: t('The minimum running notional for the given benefit tier'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'epochs',
|
|
||||||
displayName: t('Min. epochs'),
|
|
||||||
tooltip: t(
|
|
||||||
'The minimum number of epochs the party needs to be in the referral set to be eligible for the benefit'
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
|
{ name: 'epochs', displayName: t('Min. epochs') },
|
||||||
]}
|
]}
|
||||||
className="bg-white dark:bg-vega-cdark-900"
|
|
||||||
data={data.map((d) => ({
|
data={data.map((d) => ({
|
||||||
...d,
|
...d,
|
||||||
className: classNames({
|
className: classNames({
|
||||||
'from-vega-yellow-400 dark:from-vega-yellow-600 to-20% bg-highlight':
|
|
||||||
'yellow' === getTierColor(d.tier),
|
|
||||||
'from-vega-green-400 dark:from-vega-green-600 to-20% bg-highlight':
|
|
||||||
'green' === getTierColor(d.tier),
|
|
||||||
'from-vega-blue-400 dark:from-vega-blue-600 to-20% bg-highlight':
|
|
||||||
'blue' === getTierColor(d.tier),
|
|
||||||
'from-vega-purple-400 dark:from-vega-purple-600 to-20% bg-highlight':
|
|
||||||
'purple' === getTierColor(d.tier),
|
|
||||||
'from-vega-pink-400 dark:from-vega-pink-600 to-20% bg-highlight':
|
'from-vega-pink-400 dark:from-vega-pink-600 to-20% bg-highlight':
|
||||||
'pink' === getTierColor(d.tier),
|
d.tier >= 3,
|
||||||
|
'from-vega-purple-400 dark:from-vega-purple-600 to-20% bg-highlight':
|
||||||
|
d.tier === 2,
|
||||||
|
'from-vega-blue-400 dark:from-vega-blue-600 to-20% bg-highlight':
|
||||||
|
d.tier === 1,
|
||||||
'from-vega-orange-400 dark:from-vega-orange-600 to-20% bg-highlight':
|
'from-vega-orange-400 dark:from-vega-orange-600 to-20% bg-highlight':
|
||||||
'orange' === getTierColor(d.tier),
|
d.tier == 0,
|
||||||
'from-vega-clight-200 dark:from-vega-cdark-200 to-20% bg-highlight':
|
|
||||||
'none' === getTierColor(d.tier),
|
|
||||||
}),
|
}),
|
||||||
}))}
|
}))}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -31,52 +31,22 @@ export const Tile = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
type StatTileProps = {
|
type StatTileProps = {
|
||||||
title: ReactNode;
|
title: string;
|
||||||
testId?: string;
|
|
||||||
description?: ReactNode;
|
description?: ReactNode;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
overrideWithNoProgram?: boolean;
|
|
||||||
};
|
};
|
||||||
export const StatTile = ({
|
export const StatTile = ({ title, description, children }: StatTileProps) => {
|
||||||
title,
|
|
||||||
description,
|
|
||||||
children,
|
|
||||||
testId,
|
|
||||||
overrideWithNoProgram = false,
|
|
||||||
}: StatTileProps) => {
|
|
||||||
if (overrideWithNoProgram) {
|
|
||||||
return <NoProgramTile title={title} />;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Tile>
|
|
||||||
<h3
|
|
||||||
data-testid={testId}
|
|
||||||
className="mb-1 text-sm text-vega-clight-100 dark:text-vega-cdark-100 calt"
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</h3>
|
|
||||||
<div data-testid={`${testId}-value`} className="text-5xl text-left">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
{description && (
|
|
||||||
<div className="text-sm text-left text-vega-clight-100 dark:text-vega-cdark-100">
|
|
||||||
{description}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Tile>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NoProgramTile = ({ title }: Pick<StatTileProps, 'title'>) => {
|
|
||||||
const t = useT();
|
|
||||||
return (
|
return (
|
||||||
<Tile>
|
<Tile>
|
||||||
<h3 className="mb-1 text-sm text-vega-clight-100 dark:text-vega-cdark-100 calt">
|
<h3 className="mb-1 text-sm text-vega-clight-100 dark:text-vega-cdark-100 calt">
|
||||||
{title}
|
{title}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="text-xs text-vega-clight-300 dark:text-vega-cdark-300 leading-[3rem]">
|
<div className="text-5xl text-left">{children}</div>
|
||||||
{t('No active program')}
|
{description && (
|
||||||
</div>
|
<div className="text-sm text-left text-vega-clight-100 dark:text-vega-cdark-100">
|
||||||
|
{description}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Tile>
|
</Tile>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { ViewType, useSidebar } from '../sidebar';
|
|||||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||||
import { useT } from '../../lib/use-t';
|
import { useT } from '../../lib/use-t';
|
||||||
import { usePersistentDepositStore } from '@vegaprotocol/deposits';
|
|
||||||
|
|
||||||
export const AccountsContainer = ({
|
export const AccountsContainer = ({
|
||||||
pinnedAsset,
|
pinnedAsset,
|
||||||
@@ -26,7 +25,6 @@ export const AccountsContainer = ({
|
|||||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||||
const currentRouteId = useGetCurrentRouteId();
|
const currentRouteId = useGetCurrentRouteId();
|
||||||
const setViews = useSidebar((store) => store.setViews);
|
const setViews = useSidebar((store) => store.setViews);
|
||||||
const setDepositAsset = usePersistentDepositStore((store) => store.saveValue);
|
|
||||||
|
|
||||||
const gridStore = useAccountStore((store) => store.gridStore);
|
const gridStore = useAccountStore((store) => store.gridStore);
|
||||||
const updateGridStore = useAccountStore((store) => store.updateGridStore);
|
const updateGridStore = useAccountStore((store) => store.updateGridStore);
|
||||||
@@ -57,10 +55,7 @@ export const AccountsContainer = ({
|
|||||||
setViews({ type: ViewType.Withdraw, assetId }, currentRouteId);
|
setViews({ type: ViewType.Withdraw, assetId }, currentRouteId);
|
||||||
}}
|
}}
|
||||||
onClickDeposit={(assetId) => {
|
onClickDeposit={(assetId) => {
|
||||||
setViews({ type: ViewType.Deposit }, currentRouteId);
|
setViews({ type: ViewType.Deposit, assetId }, currentRouteId);
|
||||||
if (assetId) {
|
|
||||||
setDepositAsset({ assetId });
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
onClickTransfer={(assetId) => {
|
onClickTransfer={(assetId) => {
|
||||||
setViews({ type: ViewType.Transfer, assetId }, currentRouteId);
|
setViews({ type: ViewType.Transfer, assetId }, currentRouteId);
|
||||||
|
|||||||
@@ -8,18 +8,15 @@ export const Card = ({
|
|||||||
className,
|
className,
|
||||||
loading = false,
|
loading = false,
|
||||||
highlight = false,
|
highlight = false,
|
||||||
testId,
|
|
||||||
}: {
|
}: {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
title: string;
|
title: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
highlight?: boolean;
|
highlight?: boolean;
|
||||||
testId?: string;
|
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-testid={testId}
|
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'bg-vega-clight-800 dark:bg-vega-cdark-800 col-span-full p-0.5 lg:col-auto',
|
'bg-vega-clight-800 dark:bg-vega-cdark-800 col-span-full p-0.5 lg:col-auto',
|
||||||
'rounded-lg',
|
'rounded-lg',
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
|
||||||
import { ChartContainer } from './chart-container';
|
|
||||||
import { useChartSettingsStore } from './use-chart-settings';
|
|
||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
|
||||||
|
|
||||||
jest.mock('@vegaprotocol/candles-chart', () => ({
|
|
||||||
...jest.requireActual('@vegaprotocol/candles-chart'),
|
|
||||||
CandlesChartContainer: ({ marketId }: { marketId: string }) => (
|
|
||||||
<div data-testid="pennant">{marketId}</div>
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('@vegaprotocol/trading-view', () => ({
|
|
||||||
...jest.requireActual('@vegaprotocol/trading-view'),
|
|
||||||
TradingViewContainer: ({ marketId }: { marketId: string }) => (
|
|
||||||
<div data-testid="tradingview">{marketId}</div>
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('ChartContainer', () => {
|
|
||||||
it('renders pennant if no library path is set', () => {
|
|
||||||
useChartSettingsStore.setState({
|
|
||||||
chartlib: 'tradingview',
|
|
||||||
});
|
|
||||||
|
|
||||||
useEnvironment.setState({
|
|
||||||
CHARTING_LIBRARY_PATH: undefined,
|
|
||||||
CHARTING_LIBRARY_HASH: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const marketId = 'market-id';
|
|
||||||
|
|
||||||
render(<ChartContainer marketId={marketId} />);
|
|
||||||
|
|
||||||
expect(screen.getByTestId('pennant')).toHaveTextContent(marketId);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders trading view if library path is set', () => {
|
|
||||||
useChartSettingsStore.setState({
|
|
||||||
chartlib: 'tradingview',
|
|
||||||
});
|
|
||||||
|
|
||||||
useEnvironment.setState({
|
|
||||||
CHARTING_LIBRARY_PATH: 'dummy-path',
|
|
||||||
CHARTING_LIBRARY_HASH: 'hash',
|
|
||||||
});
|
|
||||||
|
|
||||||
const marketId = 'market-id';
|
|
||||||
|
|
||||||
render(<ChartContainer marketId={marketId} />);
|
|
||||||
|
|
||||||
expect(screen.getByTestId('tradingview')).toHaveTextContent(marketId);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders pennant chart if stored in settings', () => {
|
|
||||||
useChartSettingsStore.setState({
|
|
||||||
chartlib: 'pennant',
|
|
||||||
});
|
|
||||||
|
|
||||||
const marketId = 'market-id';
|
|
||||||
|
|
||||||
render(<ChartContainer marketId={marketId} />);
|
|
||||||
|
|
||||||
expect(screen.getByTestId('pennant')).toHaveTextContent(marketId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
import invert from 'lodash/invert';
|
|
||||||
import { type Interval } from '@vegaprotocol/types';
|
|
||||||
import {
|
|
||||||
TradingViewContainer,
|
|
||||||
ALLOWED_TRADINGVIEW_HOSTNAMES,
|
|
||||||
TRADINGVIEW_INTERVAL_MAP,
|
|
||||||
} from '@vegaprotocol/trading-view';
|
|
||||||
import {
|
|
||||||
CandlesChartContainer,
|
|
||||||
PENNANT_INTERVAL_MAP,
|
|
||||||
} from '@vegaprotocol/candles-chart';
|
|
||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
|
||||||
import { useChartSettings, STUDY_SIZE } from './use-chart-settings';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders either the pennant chart or the tradingview chart
|
|
||||||
*/
|
|
||||||
export const ChartContainer = ({ marketId }: { marketId: string }) => {
|
|
||||||
const { CHARTING_LIBRARY_PATH, CHARTING_LIBRARY_HASH } = useEnvironment();
|
|
||||||
|
|
||||||
const {
|
|
||||||
chartlib,
|
|
||||||
interval,
|
|
||||||
chartType,
|
|
||||||
overlays,
|
|
||||||
studies,
|
|
||||||
studySizes,
|
|
||||||
tradingViewStudies,
|
|
||||||
setInterval,
|
|
||||||
setStudies,
|
|
||||||
setStudySizes,
|
|
||||||
setOverlays,
|
|
||||||
setTradingViewStudies,
|
|
||||||
} = useChartSettings();
|
|
||||||
|
|
||||||
const pennantChart = (
|
|
||||||
<CandlesChartContainer
|
|
||||||
marketId={marketId}
|
|
||||||
interval={toPennantInterval(interval)}
|
|
||||||
chartType={chartType}
|
|
||||||
overlays={overlays}
|
|
||||||
studies={studies}
|
|
||||||
studySizes={studySizes}
|
|
||||||
setStudySizes={setStudySizes}
|
|
||||||
setStudies={setStudies}
|
|
||||||
setOverlays={setOverlays}
|
|
||||||
defaultStudySize={STUDY_SIZE}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!ALLOWED_TRADINGVIEW_HOSTNAMES.includes(window.location.hostname)) {
|
|
||||||
return pennantChart;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CHARTING_LIBRARY_PATH || !CHARTING_LIBRARY_HASH) {
|
|
||||||
return pennantChart;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (chartlib) {
|
|
||||||
case 'tradingview': {
|
|
||||||
return (
|
|
||||||
<TradingViewContainer
|
|
||||||
libraryPath={CHARTING_LIBRARY_PATH}
|
|
||||||
libraryHash={CHARTING_LIBRARY_HASH}
|
|
||||||
marketId={marketId}
|
|
||||||
interval={toTradingViewResolution(interval)}
|
|
||||||
studies={tradingViewStudies}
|
|
||||||
onIntervalChange={(newInterval) => {
|
|
||||||
setInterval(fromTradingViewResolution(newInterval));
|
|
||||||
}}
|
|
||||||
onAutoSaveNeeded={(data: { studies: string[] }) => {
|
|
||||||
setTradingViewStudies(data.studies);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case 'pennant': {
|
|
||||||
return pennantChart;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
throw new Error('invalid chart lib');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const toTradingViewResolution = (interval: Interval) => {
|
|
||||||
const resolution = TRADINGVIEW_INTERVAL_MAP[interval];
|
|
||||||
|
|
||||||
if (!resolution) {
|
|
||||||
throw new Error(
|
|
||||||
`failed to convert interval: ${interval} to valid resolution`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolution;
|
|
||||||
};
|
|
||||||
|
|
||||||
const fromTradingViewResolution = (resolution: string) => {
|
|
||||||
const interval = invert(TRADINGVIEW_INTERVAL_MAP)[resolution];
|
|
||||||
|
|
||||||
if (!interval) {
|
|
||||||
throw new Error(
|
|
||||||
`failed to convert resolution: ${resolution} to valid interval`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return interval as Interval;
|
|
||||||
};
|
|
||||||
|
|
||||||
const toPennantInterval = (interval: Interval) => {
|
|
||||||
const pennantInterval = PENNANT_INTERVAL_MAP[interval];
|
|
||||||
|
|
||||||
if (!pennantInterval) {
|
|
||||||
throw new Error(
|
|
||||||
`failed to convert interval: ${interval} to valid pennant interval`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return pennantInterval;
|
|
||||||
};
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
import { render, screen, within } from '@testing-library/react';
|
|
||||||
import userEvent from '@testing-library/user-event';
|
|
||||||
import { ChartMenu } from './chart-menu';
|
|
||||||
import {
|
|
||||||
useChartSettingsStore,
|
|
||||||
DEFAULT_CHART_SETTINGS,
|
|
||||||
} from './use-chart-settings';
|
|
||||||
import { Overlay, Study, overlayLabels, studyLabels } from 'pennant';
|
|
||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
|
||||||
|
|
||||||
describe('ChartMenu', () => {
|
|
||||||
it('doesnt show trading view option if library path undefined', () => {
|
|
||||||
useEnvironment.setState({ CHARTING_LIBRARY_PATH: undefined });
|
|
||||||
|
|
||||||
render(<ChartMenu />);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
screen.queryByRole('button', { name: 'TradingView' })
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
screen.queryByRole('button', { name: 'Vega chart' })
|
|
||||||
).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can switch between charts if library path', async () => {
|
|
||||||
useEnvironment.setState({ CHARTING_LIBRARY_PATH: 'dummy' });
|
|
||||||
|
|
||||||
render(<ChartMenu />);
|
|
||||||
|
|
||||||
await userEvent.click(screen.getByRole('button', { name: 'TradingView' }));
|
|
||||||
expect(useChartSettingsStore.getState().chartlib).toEqual('tradingview');
|
|
||||||
|
|
||||||
await userEvent.click(screen.getByRole('button', { name: 'Vega chart' }));
|
|
||||||
expect(useChartSettingsStore.getState().chartlib).toEqual('pennant');
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('tradingview', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
useEnvironment.setState({ CHARTING_LIBRARY_PATH: 'dummy-path' });
|
|
||||||
|
|
||||||
// clear store each time to avoid conditional testing of defaults
|
|
||||||
useChartSettingsStore.setState({
|
|
||||||
chartlib: 'tradingview',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('only shows chartlib switch and attribution', () => {
|
|
||||||
render(<ChartMenu />);
|
|
||||||
|
|
||||||
const buttons = screen.getAllByRole('button');
|
|
||||||
expect(buttons).toHaveLength(1);
|
|
||||||
expect(buttons[0]).toHaveTextContent('Vega chart');
|
|
||||||
|
|
||||||
expect(screen.getByText('Chart by')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('pennant', () => {
|
|
||||||
const openDropdown = async () => {
|
|
||||||
await userEvent.click(
|
|
||||||
screen.getByRole('button', {
|
|
||||||
name: 'Indicators',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// clear store each time to avoid conditional testing of defaults
|
|
||||||
useChartSettingsStore.setState({
|
|
||||||
chartlib: 'pennant',
|
|
||||||
overlays: [],
|
|
||||||
studies: [],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each(Object.values(Overlay))('can set %s overlay', async (overlay) => {
|
|
||||||
render(<ChartMenu />);
|
|
||||||
|
|
||||||
await openDropdown();
|
|
||||||
|
|
||||||
const menu = within(await screen.findByRole('menu'));
|
|
||||||
|
|
||||||
await userEvent.click(menu.getByText(overlayLabels[overlay as Overlay]));
|
|
||||||
|
|
||||||
// re-open the dropdown
|
|
||||||
await openDropdown();
|
|
||||||
|
|
||||||
expect(
|
|
||||||
screen.getByText(overlayLabels[overlay as Overlay])
|
|
||||||
).toHaveAttribute('data-state', 'checked');
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each(Object.values(Study))('can set %s study', async (study) => {
|
|
||||||
render(<ChartMenu />);
|
|
||||||
|
|
||||||
await openDropdown();
|
|
||||||
|
|
||||||
const menu = within(await screen.findByRole('menu'));
|
|
||||||
|
|
||||||
await userEvent.click(menu.getByText(studyLabels[study as Study]));
|
|
||||||
|
|
||||||
// re-open the dropdown
|
|
||||||
await openDropdown();
|
|
||||||
|
|
||||||
expect(screen.getByText(studyLabels[study as Study])).toHaveAttribute(
|
|
||||||
'data-state',
|
|
||||||
'checked'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render with the correct default studies and overlays', async () => {
|
|
||||||
useChartSettingsStore.setState({
|
|
||||||
...DEFAULT_CHART_SETTINGS,
|
|
||||||
chartlib: 'pennant',
|
|
||||||
});
|
|
||||||
|
|
||||||
render(<ChartMenu />);
|
|
||||||
|
|
||||||
await userEvent.click(
|
|
||||||
screen.getByRole('button', {
|
|
||||||
name: 'Indicators',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const menu = within(await screen.findByRole('menu'));
|
|
||||||
|
|
||||||
expect(menu.getByText(studyLabels.volume)).toHaveAttribute(
|
|
||||||
'data-state',
|
|
||||||
'checked'
|
|
||||||
);
|
|
||||||
expect(menu.getByText(studyLabels.macd)).toHaveAttribute(
|
|
||||||
'data-state',
|
|
||||||
'checked'
|
|
||||||
);
|
|
||||||
expect(menu.getByText(overlayLabels.movingAverage)).toHaveAttribute(
|
|
||||||
'data-state',
|
|
||||||
'checked'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export { ChartContainer } from './chart-container';
|
|
||||||
export { ChartMenu } from './chart-menu';
|
|
||||||
@@ -84,10 +84,7 @@ export const FeesContainer = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="grid auto-rows-min grid-cols-4 gap-3">
|
||||||
className="grid auto-rows-min grid-cols-4 gap-3"
|
|
||||||
data-testid="fees-container"
|
|
||||||
>
|
|
||||||
{isConnected && (
|
{isConnected && (
|
||||||
<>
|
<>
|
||||||
<Card
|
<Card
|
||||||
@@ -127,10 +124,7 @@ export const FeesContainer = () => {
|
|||||||
windowLength={volumeDiscountWindowLength}
|
windowLength={volumeDiscountWindowLength}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<p
|
<p className="text-muted pt-3 text-sm">
|
||||||
className="text-muted pt-3 text-sm"
|
|
||||||
data-testid="no-volume-discount"
|
|
||||||
>
|
|
||||||
{t('No volume discount program active')}
|
{t('No volume discount program active')}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -139,22 +133,17 @@ export const FeesContainer = () => {
|
|||||||
title={t('Referral benefits')}
|
title={t('Referral benefits')}
|
||||||
className="sm:col-span-2"
|
className="sm:col-span-2"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
data-testid="referral-benefits-card"
|
|
||||||
>
|
>
|
||||||
{isReferrer ? (
|
{isReferrer ? (
|
||||||
<ReferrerInfo code={code} data-testid="referrer-info" />
|
<ReferrerInfo code={code} />
|
||||||
) : isReferralProgramRunning ? (
|
) : isReferralProgramRunning ? (
|
||||||
<ReferralBenefits
|
<ReferralBenefits
|
||||||
setRunningNotionalTakerVolume={referralVolumeInWindow}
|
setRunningNotionalTakerVolume={referralVolumeInWindow}
|
||||||
epochsInSet={epochsInSet}
|
epochsInSet={epochsInSet}
|
||||||
epochs={referralDiscountWindowLength}
|
epochs={referralDiscountWindowLength}
|
||||||
data-testid="referral-benefits"
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<p
|
<p className="text-muted pt-3 text-sm">
|
||||||
className="text-muted pt-3 text-sm"
|
|
||||||
data-testid="no-referral-program"
|
|
||||||
>
|
|
||||||
{t('No referral program active')}
|
{t('No referral program active')}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -165,7 +154,6 @@ export const FeesContainer = () => {
|
|||||||
title={t('Volume discount')}
|
title={t('Volume discount')}
|
||||||
className="lg:col-span-full xl:col-span-2"
|
className="lg:col-span-full xl:col-span-2"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
data-testid="volume-discount-card"
|
|
||||||
>
|
>
|
||||||
<VolumeTiers
|
<VolumeTiers
|
||||||
tiers={volumeTiers}
|
tiers={volumeTiers}
|
||||||
@@ -178,7 +166,6 @@ export const FeesContainer = () => {
|
|||||||
title={t('Referral discount')}
|
title={t('Referral discount')}
|
||||||
className="lg:col-span-full xl:col-span-2"
|
className="lg:col-span-full xl:col-span-2"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
data-testid="referral-discount-card"
|
|
||||||
>
|
>
|
||||||
<ReferralTiers
|
<ReferralTiers
|
||||||
tiers={referralTiers}
|
tiers={referralTiers}
|
||||||
@@ -191,7 +178,6 @@ export const FeesContainer = () => {
|
|||||||
title={t('Fees by market')}
|
title={t('Fees by market')}
|
||||||
className="lg:col-span-full"
|
className="lg:col-span-full"
|
||||||
loading={marketsLoading}
|
loading={marketsLoading}
|
||||||
data-testid="fees-by-market-card"
|
|
||||||
>
|
>
|
||||||
<MarketFees
|
<MarketFees
|
||||||
markets={markets}
|
markets={markets}
|
||||||
@@ -259,7 +245,7 @@ export const TradingFees = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pt-4" data-testid="trading-fees">
|
<div className="pt-4">
|
||||||
<div className="leading-none">
|
<div className="leading-none">
|
||||||
<p className="block text-3xl leading-none" data-testid="adjusted-fees">
|
<p className="block text-3xl leading-none" data-testid="adjusted-fees">
|
||||||
{minAdjustedTotal !== undefined && maxAdjustedTotal !== undefined
|
{minAdjustedTotal !== undefined && maxAdjustedTotal !== undefined
|
||||||
@@ -269,7 +255,7 @@ export const TradingFees = ({
|
|||||||
: `${formatPercentage(adjustedTotal)}%`}
|
: `${formatPercentage(adjustedTotal)}%`}
|
||||||
</p>
|
</p>
|
||||||
<CardTable>
|
<CardTable>
|
||||||
<tr className="text-default" data-testid="total-fee-before-discount">
|
<tr className="text-default">
|
||||||
<CardTableTH>{t('Total fee before discount')}</CardTableTH>
|
<CardTableTH>{t('Total fee before discount')}</CardTableTH>
|
||||||
<CardTableTD>
|
<CardTableTD>
|
||||||
{minTotal !== undefined && maxTotal !== undefined
|
{minTotal !== undefined && maxTotal !== undefined
|
||||||
@@ -279,7 +265,7 @@ export const TradingFees = ({
|
|||||||
: `${formatPercentage(total.toNumber())}%`}
|
: `${formatPercentage(total.toNumber())}%`}
|
||||||
</CardTableTD>
|
</CardTableTD>
|
||||||
</tr>
|
</tr>
|
||||||
<tr data-testid="infrastructure-fees">
|
<tr>
|
||||||
<CardTableTH>{t('Infrastructure')}</CardTableTH>
|
<CardTableTH>{t('Infrastructure')}</CardTableTH>
|
||||||
<CardTableTD>
|
<CardTableTD>
|
||||||
{formatPercentage(
|
{formatPercentage(
|
||||||
@@ -288,14 +274,14 @@ export const TradingFees = ({
|
|||||||
%
|
%
|
||||||
</CardTableTD>
|
</CardTableTD>
|
||||||
</tr>
|
</tr>
|
||||||
<tr data-testid="maker-fees">
|
<tr>
|
||||||
<CardTableTH>{t('Maker')}</CardTableTH>
|
<CardTableTH>{t('Maker')}</CardTableTH>
|
||||||
<CardTableTD>
|
<CardTableTD>
|
||||||
{formatPercentage(Number(params.market_fee_factors_makerFee))}%
|
{formatPercentage(Number(params.market_fee_factors_makerFee))}%
|
||||||
</CardTableTD>
|
</CardTableTD>
|
||||||
</tr>
|
</tr>
|
||||||
{minLiq && maxLiq && (
|
{minLiq && maxLiq && (
|
||||||
<tr data-testid="liquidity-fees">
|
<tr>
|
||||||
<CardTableTH>{t('Liquidity')}</CardTableTH>
|
<CardTableTH>{t('Liquidity')}</CardTableTH>
|
||||||
<CardTableTD>
|
<CardTableTD>
|
||||||
{formatPercentage(Number(minLiq.fees.factors.liquidityFee))}%
|
{formatPercentage(Number(minLiq.fees.factors.liquidityFee))}%
|
||||||
@@ -324,26 +310,19 @@ export const CurrentVolume = ({
|
|||||||
const t = useT();
|
const t = useT();
|
||||||
const nextTier = tiers[tierIndex + 1];
|
const nextTier = tiers[tierIndex + 1];
|
||||||
const requiredForNextTier = nextTier
|
const requiredForNextTier = nextTier
|
||||||
? new BigNumber(nextTier.minimumRunningNotionalTakerVolume).minus(
|
? Number(nextTier.minimumRunningNotionalTakerVolume) - windowLengthVolume
|
||||||
windowLengthVolume
|
: 0;
|
||||||
)
|
|
||||||
: new BigNumber(0);
|
|
||||||
const currentVolume = new BigNumber(windowLengthVolume);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-3 pt-4" data-testid="current-volume">
|
<div className="flex flex-col gap-3 pt-4">
|
||||||
<CardStat
|
<CardStat
|
||||||
value={formatNumberRounded(currentVolume)}
|
value={formatNumberRounded(new BigNumber(windowLengthVolume))}
|
||||||
text={t('pastEpochs', 'Past {{count}} epochs', {
|
text={t('pastEpochs', 'Past {{count}} epochs', { count: windowLength })}
|
||||||
count: windowLength,
|
|
||||||
})}
|
|
||||||
testId="past-epochs-volume"
|
|
||||||
/>
|
/>
|
||||||
{requiredForNextTier.isGreaterThan(0) && (
|
{requiredForNextTier > 0 && (
|
||||||
<CardStat
|
<CardStat
|
||||||
value={formatNumber(requiredForNextTier)}
|
value={formatNumber(requiredForNextTier)}
|
||||||
text={t('Required for next tier')}
|
text={t('Required for next tier')}
|
||||||
testId="required-for-next-tier"
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -361,7 +340,7 @@ const ReferralBenefits = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-3 pt-4" data-testid="referral-benefits">
|
<div className="flex flex-col gap-3 pt-4">
|
||||||
<CardStat
|
<CardStat
|
||||||
// all sets volume (not just current party)
|
// all sets volume (not just current party)
|
||||||
value={formatNumber(setRunningNotionalTakerVolume)}
|
value={formatNumber(setRunningNotionalTakerVolume)}
|
||||||
@@ -372,13 +351,8 @@ const ReferralBenefits = ({
|
|||||||
count: epochs,
|
count: epochs,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
testId="running-notional-taker-volume"
|
|
||||||
/>
|
|
||||||
<CardStat
|
|
||||||
value={epochsInSet}
|
|
||||||
text={t('epochs in referral set')}
|
|
||||||
testId="epochs-in-referral-set"
|
|
||||||
/>
|
/>
|
||||||
|
<CardStat value={epochsInSet} text={t('epochs in referral set')} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -406,7 +380,7 @@ const TotalDiscount = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pt-4" data-testid="total-discount-card-stats">
|
<div className="pt-4">
|
||||||
<CardStat
|
<CardStat
|
||||||
description={
|
description={
|
||||||
<>
|
<>
|
||||||
@@ -416,10 +390,9 @@ const TotalDiscount = ({
|
|||||||
}
|
}
|
||||||
value={formatPercentage(totalDiscount) + '%'}
|
value={formatPercentage(totalDiscount) + '%'}
|
||||||
highlight={true}
|
highlight={true}
|
||||||
testId="total-discount"
|
|
||||||
/>
|
/>
|
||||||
<CardTable>
|
<CardTable>
|
||||||
<tr data-testid="volume-discount-row">
|
<tr>
|
||||||
<CardTableTH>{t('Volume discount')}</CardTableTH>
|
<CardTableTH>{t('Volume discount')}</CardTableTH>
|
||||||
<CardTableTD>
|
<CardTableTD>
|
||||||
{formatPercentage(volumeDiscount)}%
|
{formatPercentage(volumeDiscount)}%
|
||||||
@@ -433,7 +406,7 @@ const TotalDiscount = ({
|
|||||||
)}
|
)}
|
||||||
</CardTableTD>
|
</CardTableTD>
|
||||||
</tr>
|
</tr>
|
||||||
<tr data-testid="referral-discount-row">
|
<tr>
|
||||||
<CardTableTH>{t('Referral discount')}</CardTableTH>
|
<CardTableTH>{t('Referral discount')}</CardTableTH>
|
||||||
<CardTableTD>
|
<CardTableTD>
|
||||||
{formatPercentage(referralDiscount)}%
|
{formatPercentage(referralDiscount)}%
|
||||||
@@ -479,37 +452,29 @@ const VolumeTiers = ({
|
|||||||
<div>
|
<div>
|
||||||
<Table>
|
<Table>
|
||||||
<THead>
|
<THead>
|
||||||
<Tr>
|
<tr>
|
||||||
<Th data-testid="tier-header">{t('Tier')}</Th>
|
<Th>{t('Tier')}</Th>
|
||||||
<Th data-testid="discount-header">{t('Discount')}</Th>
|
<Th>{t('Discount')}</Th>
|
||||||
<Th data-testid="min-volume-header">{t('Min. trading volume')}</Th>
|
<Th>{t('Min. trading volume')}</Th>
|
||||||
<Th data-testid="my-volume-header">
|
<Th>
|
||||||
{t('myVolume', 'My volume (last {{count}} epochs)', {
|
{t('myVolume', 'My volume (last {{count}} epochs)', {
|
||||||
count: windowLength,
|
count: windowLength,
|
||||||
})}
|
})}
|
||||||
</Th>
|
</Th>
|
||||||
<Th data-testid="actions-header" />
|
<Th />
|
||||||
</Tr>
|
</tr>
|
||||||
</THead>
|
</THead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{Array.from(tiers).map((tier, i) => {
|
{Array.from(tiers).map((tier, i) => {
|
||||||
const isUserTier = tierIndex === i;
|
const isUserTier = tierIndex === i;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tr key={i} data-testid={`tier-row-${i}`}>
|
<Tr key={i}>
|
||||||
<Td data-testid={`tier-value-${i}`}>{i + 1}</Td>
|
<Td>{i + 1}</Td>
|
||||||
<Td data-testid={`discount-value-${i}`}>
|
<Td>{formatPercentage(Number(tier.volumeDiscountFactor))}%</Td>
|
||||||
{formatPercentage(Number(tier.volumeDiscountFactor))}%
|
<Td>{formatNumber(tier.minimumRunningNotionalTakerVolume)}</Td>
|
||||||
</Td>
|
<Td>{isUserTier ? formatNumber(lastEpochVolume) : ''}</Td>
|
||||||
<Td data-testid={`min-volume-value-${i}`}>
|
<Td>{isUserTier ? <YourTier /> : null}</Td>
|
||||||
{formatNumber(tier.minimumRunningNotionalTakerVolume)}
|
|
||||||
</Td>
|
|
||||||
<Td data-testid={`my-volume-value-${i}`}>
|
|
||||||
{isUserTier ? formatNumber(lastEpochVolume) : ''}
|
|
||||||
</Td>
|
|
||||||
<Td data-testid={`your-tier-${i}`}>
|
|
||||||
{isUserTier ? <YourTier /> : null}
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
</Tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -546,53 +511,39 @@ const ReferralTiers = ({
|
|||||||
<div>
|
<div>
|
||||||
<Table>
|
<Table>
|
||||||
<THead>
|
<THead>
|
||||||
<Tr>
|
<tr>
|
||||||
<Th data-testid="tier-header">{t('Tier')}</Th>
|
<Th>{t('Tier')}</Th>
|
||||||
<Th data-testid="discount-header">{t('Discount')}</Th>
|
<Th>{t('Discount')}</Th>
|
||||||
<Th data-testid="min-volume-header">{t('Min. trading volume')}</Th>
|
<Th>{t('Min. trading volume')}</Th>
|
||||||
<Th data-testid="required-epochs-header">{t('Required epochs')}</Th>
|
<Th>{t('Required epochs')}</Th>
|
||||||
<Th data-testid="extra-header" />
|
<Th />
|
||||||
</Tr>
|
</tr>
|
||||||
</THead>
|
</THead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{Array.from(tiers).map((tier, i) => {
|
{Array.from(tiers).map((t, i) => {
|
||||||
const isUserTier = tierIndex === i;
|
const isUserTier = tierIndex === i;
|
||||||
|
|
||||||
const requiredVolume = Number(
|
const requiredVolume = Number(t.minimumRunningNotionalTakerVolume);
|
||||||
tier.minimumRunningNotionalTakerVolume
|
|
||||||
);
|
|
||||||
let unlocksIn = null;
|
let unlocksIn = null;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
referralVolumeInWindow >= requiredVolume &&
|
referralVolumeInWindow >= requiredVolume &&
|
||||||
epochsInSet < tier.minimumEpochs
|
epochsInSet < t.minimumEpochs
|
||||||
) {
|
) {
|
||||||
unlocksIn = (
|
unlocksIn = (
|
||||||
<span className="text-muted">
|
<span className="text-muted">
|
||||||
Unlocks in {tier.minimumEpochs - epochsInSet} epochs
|
Unlocks in {t.minimumEpochs - epochsInSet} epochs
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tr key={i} data-testid={`tier-row-${i}`}>
|
<Tr key={i}>
|
||||||
<Td data-testid={`tier-value-${i}`}>{i + 1}</Td>
|
<Td>{i + 1}</Td>
|
||||||
<Td data-testid={`discount-value-${i}`}>
|
<Td>{formatPercentage(Number(t.referralDiscountFactor))}%</Td>
|
||||||
{formatPercentage(Number(tier.referralDiscountFactor))}%
|
<Td>{formatNumber(t.minimumRunningNotionalTakerVolume)}</Td>
|
||||||
</Td>
|
<Td>{t.minimumEpochs}</Td>
|
||||||
<Td data-testid={`min-volume-value-${i}`}>
|
<Td>{isUserTier ? <YourTier /> : unlocksIn}</Td>
|
||||||
{formatNumber(tier.minimumRunningNotionalTakerVolume)}
|
|
||||||
</Td>
|
|
||||||
<Td data-testid={`required-epochs-value-${i}`}>
|
|
||||||
{tier.minimumEpochs}
|
|
||||||
</Td>
|
|
||||||
<Td data-testid={`user-tier-or-unlocks-${i}`}>
|
|
||||||
{isUserTier ? (
|
|
||||||
<YourTier testId={`your-tier-${i}`} />
|
|
||||||
) : (
|
|
||||||
unlocksIn
|
|
||||||
)}
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
</Tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -602,18 +553,11 @@ const ReferralTiers = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface YourTierProps {
|
const YourTier = () => {
|
||||||
testId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const YourTier = ({ testId }: YourTierProps) => {
|
|
||||||
const t = useT();
|
const t = useT();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span className="bg-rainbow whitespace-nowrap rounded-xl px-4 py-1.5 text-white">
|
||||||
className="bg-rainbow whitespace-nowrap rounded-xl px-4 py-1.5 text-white"
|
|
||||||
data-testid={testId}
|
|
||||||
>
|
|
||||||
{t('Your tier')}
|
{t('Your tier')}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,31 +3,21 @@ import type { ReactNode } from 'react';
|
|||||||
|
|
||||||
const cellClass = 'px-4 py-2 text-xs font-normal text-left last:text-right';
|
const cellClass = 'px-4 py-2 text-xs font-normal text-left last:text-right';
|
||||||
|
|
||||||
export const Th = ({ children, ...props }: { children?: ReactNode }) => {
|
export const Th = ({ children }: { children?: ReactNode }) => {
|
||||||
return (
|
return (
|
||||||
<th
|
<th className={classNames(cellClass, 'text-secondary leading-none py-3')}>
|
||||||
className={classNames(cellClass, 'text-secondary leading-none py-3')}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</th>
|
</th>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Td = ({ children, ...props }: { children?: ReactNode }) => {
|
export const Td = ({ children }: { children?: ReactNode }) => {
|
||||||
return (
|
return <th className={cellClass}>{children}</th>;
|
||||||
<th className={cellClass} {...props}>
|
|
||||||
{children}
|
|
||||||
</th>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Tr = ({ children, ...props }: { children?: ReactNode }) => {
|
export const Tr = ({ children }: { children?: ReactNode }) => {
|
||||||
return (
|
return (
|
||||||
<tr
|
<tr className="hover:bg-vega-clight-600 dark:hover:bg-vega-cdark-700">
|
||||||
className="hover:bg-vega-clight-600 dark:hover:bg-vega-cdark-700"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -40,9 +40,6 @@ const DateRange = {
|
|||||||
RANGE_ALL: 'All',
|
RANGE_ALL: 'All',
|
||||||
};
|
};
|
||||||
|
|
||||||
const priceFormat = (fundingRate: number) =>
|
|
||||||
`${(fundingRate * 100).toFixed(4)}%`;
|
|
||||||
|
|
||||||
export const FundingContainer = ({ marketId }: { marketId: string }) => {
|
export const FundingContainer = ({ marketId }: { marketId: string }) => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const { theme } = useThemeSwitcher();
|
const { theme } = useThemeSwitcher();
|
||||||
@@ -85,7 +82,7 @@ export const FundingContainer = ({ marketId }: { marketId: string }) => {
|
|||||||
<LineChart
|
<LineChart
|
||||||
data={values}
|
data={values}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
priceFormat={priceFormat}
|
priceFormat={(fundingRate) => `${(fundingRate * 100).toFixed(4)}%`}
|
||||||
yAxisTickFormat="%"
|
yAxisTickFormat="%"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
|||||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||||
import { Navbar } from './navbar';
|
import { Navbar } from './navbar';
|
||||||
import { useGlobalStore } from '../../stores';
|
import { useGlobalStore } from '../../stores';
|
||||||
import { ENV, useFeatureFlags } from '@vegaprotocol/environment';
|
import { ENV, FLAGS } from '@vegaprotocol/environment';
|
||||||
|
|
||||||
jest.mock('@vegaprotocol/proposals', () => ({
|
jest.mock('@vegaprotocol/proposals', () => ({
|
||||||
ProtocolUpgradeCountdown: () => null,
|
ProtocolUpgradeCountdown: () => null,
|
||||||
@@ -48,7 +48,8 @@ describe('Navbar', () => {
|
|||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
useGlobalStore.setState({ marketId });
|
useGlobalStore.setState({ marketId });
|
||||||
useFeatureFlags.setState({ flags: { REFERRALS: true } });
|
const mockedFLAGS = jest.mocked(FLAGS);
|
||||||
|
mockedFLAGS.REFERRALS = true;
|
||||||
const mockedENV = jest.mocked(ENV);
|
const mockedENV = jest.mocked(ENV);
|
||||||
mockedENV.VEGA_TOKEN_URL = 'governance';
|
mockedENV.VEGA_TOKEN_URL = 'governance';
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
Networks,
|
Networks,
|
||||||
DApp,
|
DApp,
|
||||||
useLinks,
|
useLinks,
|
||||||
useFeatureFlags,
|
FLAGS,
|
||||||
useEnvNameMapping,
|
useEnvNameMapping,
|
||||||
} from '@vegaprotocol/environment';
|
} from '@vegaprotocol/environment';
|
||||||
import { useGlobalStore } from '../../stores';
|
import { useGlobalStore } from '../../stores';
|
||||||
@@ -157,7 +157,6 @@ export const Navbar = ({
|
|||||||
* of the navigation
|
* of the navigation
|
||||||
*/
|
*/
|
||||||
const NavbarMenu = ({ onClick }: { onClick: () => void }) => {
|
const NavbarMenu = ({ onClick }: { onClick: () => void }) => {
|
||||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const envNameMapping = useEnvNameMapping();
|
const envNameMapping = useEnvNameMapping();
|
||||||
const { VEGA_ENV, VEGA_NETWORKS, GITHUB_FEEDBACK_URL } = useEnvironment();
|
const { VEGA_ENV, VEGA_NETWORKS, GITHUB_FEEDBACK_URL } = useEnvironment();
|
||||||
@@ -202,7 +201,7 @@ const NavbarMenu = ({ onClick }: { onClick: () => void }) => {
|
|||||||
{t('Portfolio')}
|
{t('Portfolio')}
|
||||||
</NavbarLink>
|
</NavbarLink>
|
||||||
</NavbarItem>
|
</NavbarItem>
|
||||||
{featureFlags.REFERRALS && (
|
{FLAGS.REFERRALS && (
|
||||||
<NavbarItem>
|
<NavbarItem>
|
||||||
<NavbarLink end={false} to={Links.REFERRALS()} onClick={onClick}>
|
<NavbarLink end={false} to={Links.REFERRALS()} onClick={onClick}>
|
||||||
{t('Referrals')}
|
{t('Referrals')}
|
||||||
|
|||||||
@@ -69,13 +69,11 @@ describe('RewardPot', () => {
|
|||||||
balance: '100',
|
balance: '100',
|
||||||
asset: rewardAsset,
|
asset: rewardAsset,
|
||||||
},
|
},
|
||||||
// should include this in total:
|
|
||||||
{
|
{
|
||||||
type: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
type: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||||
balance: '100',
|
balance: '100',
|
||||||
asset: rewardAsset,
|
asset: rewardAsset,
|
||||||
},
|
},
|
||||||
// should include this in total:
|
|
||||||
{
|
{
|
||||||
type: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
type: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||||
balance: '50',
|
balance: '50',
|
||||||
@@ -140,20 +138,20 @@ describe('RewardPot', () => {
|
|||||||
|
|
||||||
renderComponent(props);
|
renderComponent(props);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('total-rewards')).toHaveTextContent(
|
||||||
|
`7.00 ${rewardAsset.symbol}`
|
||||||
|
);
|
||||||
|
|
||||||
expect(screen.getByText(/Locked/).nextElementSibling).toHaveTextContent(
|
expect(screen.getByText(/Locked/).nextElementSibling).toHaveTextContent(
|
||||||
'2.50'
|
'2.50'
|
||||||
);
|
);
|
||||||
expect(screen.getByText(/Vesting/).nextElementSibling).toHaveTextContent(
|
expect(screen.getByText(/Vesting/).nextElementSibling).toHaveTextContent(
|
||||||
'4.50'
|
'4.50'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
screen.getByText(/Available to withdraw/).nextElementSibling
|
screen.getByText(/Available to withdraw/).nextElementSibling
|
||||||
).toHaveTextContent('1.50');
|
).toHaveTextContent('1.50');
|
||||||
|
|
||||||
// should be sum of the above
|
|
||||||
expect(screen.getByTestId('total-rewards')).toHaveTextContent(
|
|
||||||
`8.50 ${rewardAsset.symbol}`
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import groupBy from 'lodash/groupBy';
|
import groupBy from 'lodash/groupBy';
|
||||||
import uniq from 'lodash/uniq';
|
|
||||||
import type { Account } from '@vegaprotocol/accounts';
|
import type { Account } from '@vegaprotocol/accounts';
|
||||||
import { useAccounts } from '@vegaprotocol/accounts';
|
import { useAccounts } from '@vegaprotocol/accounts';
|
||||||
import {
|
import {
|
||||||
@@ -32,12 +31,6 @@ import { ViewType, useSidebar } from '../sidebar';
|
|||||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||||
import { RewardsHistoryContainer } from './rewards-history';
|
import { RewardsHistoryContainer } from './rewards-history';
|
||||||
import { useT } from '../../lib/use-t';
|
import { useT } from '../../lib/use-t';
|
||||||
import { useAssetsMapProvider } from '@vegaprotocol/assets';
|
|
||||||
|
|
||||||
const ASSETS_WITH_INCORRECT_VESTING_REWARD_DATA = [
|
|
||||||
'bf1e88d19db4b3ca0d1d5bdb73718a01686b18cf731ca26adedf3c8b83802bba', // USDT mainnet
|
|
||||||
'8ba0b10971f0c4747746cd01ff05a53ae75ca91eba1d4d050b527910c983e27e', // USDT testnet
|
|
||||||
];
|
|
||||||
|
|
||||||
export const RewardsContainer = () => {
|
export const RewardsContainer = () => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
@@ -47,71 +40,34 @@ export const RewardsContainer = () => {
|
|||||||
NetworkParams.rewards_activityStreak_benefitTiers,
|
NetworkParams.rewards_activityStreak_benefitTiers,
|
||||||
NetworkParams.rewards_vesting_baseRate,
|
NetworkParams.rewards_vesting_baseRate,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { data: accounts, loading: accountsLoading } = useAccounts(pubKey);
|
const { data: accounts, loading: accountsLoading } = useAccounts(pubKey);
|
||||||
|
|
||||||
const { data: assetMap } = useAssetsMapProvider();
|
|
||||||
|
|
||||||
const { data: epochData } = useRewardsEpochQuery();
|
const { data: epochData } = useRewardsEpochQuery();
|
||||||
|
|
||||||
// No need to specify the fromEpoch as it will by default give you the last
|
// No need to specify the fromEpoch as it will by default give you the last
|
||||||
// Note activityStreak in query will fail
|
|
||||||
const { data: rewardsData, loading: rewardsLoading } = useRewardsPageQuery({
|
const { data: rewardsData, loading: rewardsLoading } = useRewardsPageQuery({
|
||||||
variables: {
|
variables: {
|
||||||
partyId: pubKey || '',
|
partyId: pubKey || '',
|
||||||
},
|
},
|
||||||
// Inclusion of activity streak in query currently fails
|
|
||||||
errorPolicy: 'ignore',
|
|
||||||
// polling here so that as rewards are are moved to ACCOUNT_TYPE_VESTED_REWARDS the vesting stats information stays
|
|
||||||
// almost up to sync with accounts updating from subscriptions. There is a chance the data could be out
|
|
||||||
// of sync for 10s if you happen to be on the page at the end of an epoch
|
|
||||||
pollInterval: 10000,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!epochData?.epoch || !assetMap) return null;
|
if (!epochData?.epoch) return null;
|
||||||
|
|
||||||
const loading = paramsLoading || accountsLoading || rewardsLoading;
|
const loading = paramsLoading || accountsLoading || rewardsLoading;
|
||||||
|
|
||||||
const rewardAccounts = accounts
|
const rewardAccounts = accounts
|
||||||
? accounts
|
? accounts.filter((a) =>
|
||||||
.filter((a) =>
|
[
|
||||||
[
|
AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||||
AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
AccountType.ACCOUNT_TYPE_VESTING_REWARDS,
|
||||||
AccountType.ACCOUNT_TYPE_VESTING_REWARDS,
|
].includes(a.type)
|
||||||
].includes(a.type)
|
|
||||||
)
|
|
||||||
.filter((a) => new BigNumber(a.balance).isGreaterThan(0))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const rewardAccountsAssetMap = groupBy(rewardAccounts, 'asset.id');
|
|
||||||
|
|
||||||
const lockedBalances = rewardsData?.party?.vestingBalancesSummary
|
|
||||||
.lockedBalances
|
|
||||||
? rewardsData.party.vestingBalancesSummary.lockedBalances.filter((b) =>
|
|
||||||
new BigNumber(b.balance).isGreaterThan(0)
|
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
const lockedAssetMap = groupBy(lockedBalances, 'asset.id');
|
|
||||||
|
|
||||||
const vestingBalances = rewardsData?.party?.vestingBalancesSummary
|
const rewardAssetsMap = groupBy(
|
||||||
.vestingBalances
|
rewardAccounts.filter((a) => a.asset.id !== params.reward_asset),
|
||||||
? rewardsData.party.vestingBalancesSummary.vestingBalances.filter((b) =>
|
'asset.id'
|
||||||
new BigNumber(b.balance).isGreaterThan(0)
|
);
|
||||||
)
|
|
||||||
: [];
|
|
||||||
const vestingAssetMap = groupBy(vestingBalances, 'asset.id');
|
|
||||||
|
|
||||||
// each asset reward pot is made up of:
|
|
||||||
// available to withdraw - ACCOUNT_TYPE_VESTED_REWARDS
|
|
||||||
// vesting - vestingBalancesSummary.vestingBalances
|
|
||||||
// locked - vestingBalancesSummary.lockedBalances
|
|
||||||
//
|
|
||||||
// there can be entires for the same asset in each list so we need a uniq list of assets
|
|
||||||
const assets = uniq([
|
|
||||||
...Object.keys(rewardAccountsAssetMap),
|
|
||||||
...Object.keys(lockedAssetMap),
|
|
||||||
...Object.keys(vestingAssetMap),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid auto-rows-min grid-cols-6 gap-3">
|
<div className="grid auto-rows-min grid-cols-6 gap-3">
|
||||||
@@ -161,72 +117,28 @@ export const RewardsContainer = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Show all other reward pots, most of the time users will not have other rewards */}
|
{/* Show all other reward pots, most of the time users will not have other rewards */}
|
||||||
{assets
|
{Object.keys(rewardAssetsMap).map((assetId) => {
|
||||||
.filter((assetId) => assetId !== params.reward_asset)
|
const asset = rewardAssetsMap[assetId][0].asset;
|
||||||
.map((assetId) => {
|
return (
|
||||||
const asset = assetMap ? assetMap[assetId] : null;
|
<Card
|
||||||
|
key={assetId}
|
||||||
if (!asset) return null;
|
title={t('{{assetSymbol}} Reward pot', {
|
||||||
|
assetSymbol: asset.symbol,
|
||||||
// Following code is for mitigating an issue due to a core bug where locked and vesting
|
})}
|
||||||
// balances were incorrectly increased for infrastructure rewards for USDT on mainnet
|
className="lg:col-span-3 xl:col-span-2"
|
||||||
//
|
loading={loading}
|
||||||
// We don't want to incorrectly show the wring locked/vesting values, but we DO want to
|
>
|
||||||
// show the user that they have rewards available to withdraw
|
<RewardPot
|
||||||
if (ASSETS_WITH_INCORRECT_VESTING_REWARD_DATA.includes(asset.id)) {
|
pubKey={pubKey}
|
||||||
const accountsForAsset = rewardAccountsAssetMap[asset.id];
|
accounts={accounts}
|
||||||
const vestedAccount = accountsForAsset?.find(
|
assetId={assetId}
|
||||||
(a) => a.type === AccountType.ACCOUNT_TYPE_VESTED_REWARDS
|
vestingBalancesSummary={
|
||||||
);
|
rewardsData?.party?.vestingBalancesSummary
|
||||||
|
}
|
||||||
// No vested rewards available to withdraw, so skip over USDT
|
/>
|
||||||
if (!vestedAccount || Number(vestedAccount.balance) <= 0) {
|
</Card>
|
||||||
return null;
|
);
|
||||||
}
|
})}
|
||||||
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
key={assetId}
|
|
||||||
title={t('{{assetSymbol}} Reward pot', {
|
|
||||||
assetSymbol: asset.symbol,
|
|
||||||
})}
|
|
||||||
className="lg:col-span-3 xl:col-span-2"
|
|
||||||
loading={loading}
|
|
||||||
>
|
|
||||||
<RewardPot
|
|
||||||
pubKey={pubKey}
|
|
||||||
accounts={accounts}
|
|
||||||
assetId={assetId}
|
|
||||||
// Ensure that these values are shown as 0
|
|
||||||
vestingBalancesSummary={{
|
|
||||||
lockedBalances: [],
|
|
||||||
vestingBalances: [],
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
key={assetId}
|
|
||||||
title={t('{{assetSymbol}} Reward pot', {
|
|
||||||
assetSymbol: asset.symbol,
|
|
||||||
})}
|
|
||||||
className="lg:col-span-3 xl:col-span-2"
|
|
||||||
loading={loading}
|
|
||||||
>
|
|
||||||
<RewardPot
|
|
||||||
pubKey={pubKey}
|
|
||||||
accounts={accounts}
|
|
||||||
assetId={assetId}
|
|
||||||
vestingBalancesSummary={
|
|
||||||
rewardsData?.party?.vestingBalancesSummary
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<Card
|
<Card
|
||||||
title={t('Rewards history')}
|
title={t('Rewards history')}
|
||||||
className="lg:col-span-full"
|
className="lg:col-span-full"
|
||||||
@@ -235,7 +147,6 @@ export const RewardsContainer = () => {
|
|||||||
<RewardsHistoryContainer
|
<RewardsHistoryContainer
|
||||||
epoch={Number(epochData?.epoch.id)}
|
epoch={Number(epochData?.epoch.id)}
|
||||||
pubKey={pubKey}
|
pubKey={pubKey}
|
||||||
assets={assetMap}
|
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
@@ -299,9 +210,7 @@ export const RewardPot = ({
|
|||||||
: [0];
|
: [0];
|
||||||
const totalVesting = BigNumber.sum.apply(null, vestingBalances);
|
const totalVesting = BigNumber.sum.apply(null, vestingBalances);
|
||||||
|
|
||||||
const totalRewards = totalLocked
|
const totalRewards = totalLocked.plus(totalVesting);
|
||||||
.plus(totalVesting)
|
|
||||||
.plus(totalVestedRewardsByRewardAsset);
|
|
||||||
|
|
||||||
let rewardAsset = undefined;
|
let rewardAsset = undefined;
|
||||||
|
|
||||||
@@ -404,14 +313,14 @@ export const RewardPot = ({
|
|||||||
export const Vesting = ({
|
export const Vesting = ({
|
||||||
pubKey,
|
pubKey,
|
||||||
baseRate,
|
baseRate,
|
||||||
multiplier,
|
multiplier = '1',
|
||||||
}: {
|
}: {
|
||||||
pubKey: string | null;
|
pubKey: string | null;
|
||||||
baseRate: string;
|
baseRate: string;
|
||||||
multiplier?: string;
|
multiplier?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const rate = new BigNumber(baseRate).times(multiplier || 1);
|
const rate = new BigNumber(baseRate).times(multiplier);
|
||||||
const rateFormatted = formatPercentage(Number(rate));
|
const rateFormatted = formatPercentage(Number(rate));
|
||||||
const baseRateFormatted = formatPercentage(Number(baseRate));
|
const baseRateFormatted = formatPercentage(Number(baseRate));
|
||||||
|
|
||||||
@@ -426,7 +335,7 @@ export const Vesting = ({
|
|||||||
{pubKey && (
|
{pubKey && (
|
||||||
<tr>
|
<tr>
|
||||||
<CardTableTH>{t('Vesting multiplier')}</CardTableTH>
|
<CardTableTH>{t('Vesting multiplier')}</CardTableTH>
|
||||||
<CardTableTD>{multiplier ? `${multiplier}x` : '-'}</CardTableTD>
|
<CardTableTD>{multiplier}x</CardTableTD>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
</CardTable>
|
</CardTable>
|
||||||
@@ -436,16 +345,16 @@ export const Vesting = ({
|
|||||||
|
|
||||||
export const Multipliers = ({
|
export const Multipliers = ({
|
||||||
pubKey,
|
pubKey,
|
||||||
streakMultiplier,
|
streakMultiplier = '1',
|
||||||
hoarderMultiplier,
|
hoarderMultiplier = '1',
|
||||||
}: {
|
}: {
|
||||||
pubKey: string | null;
|
pubKey: string | null;
|
||||||
streakMultiplier?: string;
|
streakMultiplier?: string;
|
||||||
hoarderMultiplier?: string;
|
hoarderMultiplier?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const combinedMultiplier = new BigNumber(streakMultiplier || 1).times(
|
const combinedMultiplier = new BigNumber(streakMultiplier).times(
|
||||||
hoarderMultiplier || 1
|
hoarderMultiplier
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!pubKey) {
|
if (!pubKey) {
|
||||||
@@ -466,15 +375,11 @@ export const Multipliers = ({
|
|||||||
<CardTable>
|
<CardTable>
|
||||||
<tr>
|
<tr>
|
||||||
<CardTableTH>{t('Streak reward multiplier')}</CardTableTH>
|
<CardTableTH>{t('Streak reward multiplier')}</CardTableTH>
|
||||||
<CardTableTD>
|
<CardTableTD>{streakMultiplier}x</CardTableTD>
|
||||||
{streakMultiplier ? `${streakMultiplier}x` : '-'}
|
|
||||||
</CardTableTD>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<CardTableTH>{t('Hoarder reward multiplier')}</CardTableTH>
|
<CardTableTH>{t('Hoarder reward multiplier')}</CardTableTH>
|
||||||
<CardTableTD>
|
<CardTableTD>{hoarderMultiplier}x</CardTableTD>
|
||||||
{hoarderMultiplier ? `${hoarderMultiplier}x` : '-'}
|
|
||||||
</CardTableTD>
|
|
||||||
</tr>
|
</tr>
|
||||||
</CardTable>
|
</CardTable>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -61,14 +61,6 @@ const rewardSummaries = [
|
|||||||
rewardType: AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
|
rewardType: AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
node: {
|
|
||||||
epoch: 7,
|
|
||||||
assetId: assets.asset2.id,
|
|
||||||
amount: '300',
|
|
||||||
rewardType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const getCell = (cells: HTMLElement[], colId: string) => {
|
const getCell = (cells: HTMLElement[], colId: string) => {
|
||||||
@@ -77,7 +69,7 @@ const getCell = (cells: HTMLElement[], colId: string) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('RewardsHistoryTable', () => {
|
describe('RewarsHistoryTable', () => {
|
||||||
const props = {
|
const props = {
|
||||||
epochRewardSummaries: {
|
epochRewardSummaries: {
|
||||||
edges: rewardSummaries,
|
edges: rewardSummaries,
|
||||||
@@ -96,7 +88,7 @@ describe('RewardsHistoryTable', () => {
|
|||||||
loading: false,
|
loading: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
it('renders table with accounts summed up by asset', () => {
|
it('Renders table with accounts summed up by asset', () => {
|
||||||
render(<RewardHistoryTable {...props} />);
|
render(<RewardHistoryTable {...props} />);
|
||||||
|
|
||||||
const container = within(
|
const container = within(
|
||||||
@@ -118,27 +110,17 @@ describe('RewardsHistoryTable', () => {
|
|||||||
assets.asset2.name
|
assets.asset2.name
|
||||||
);
|
);
|
||||||
|
|
||||||
// First row
|
|
||||||
const marketCreationCell = getCell(cells, 'marketCreation');
|
const marketCreationCell = getCell(cells, 'marketCreation');
|
||||||
expect(
|
expect(
|
||||||
marketCreationCell.getByTestId('stack-cell-primary')
|
marketCreationCell.getByTestId('stack-cell-primary')
|
||||||
).toHaveTextContent('300');
|
).toHaveTextContent('300');
|
||||||
expect(
|
expect(
|
||||||
marketCreationCell.getByTestId('stack-cell-secondary')
|
marketCreationCell.getByTestId('stack-cell-secondary')
|
||||||
).toHaveTextContent('50.00%');
|
).toHaveTextContent('100.00%');
|
||||||
|
|
||||||
const infrastructureFeesCell = getCell(cells, 'infrastructureFees');
|
|
||||||
expect(
|
|
||||||
infrastructureFeesCell.getByTestId('stack-cell-primary')
|
|
||||||
).toHaveTextContent('300');
|
|
||||||
expect(
|
|
||||||
infrastructureFeesCell.getByTestId('stack-cell-secondary')
|
|
||||||
).toHaveTextContent('50.00%');
|
|
||||||
|
|
||||||
let totalCell = getCell(cells, 'total');
|
let totalCell = getCell(cells, 'total');
|
||||||
expect(totalCell.getByText('600.00')).toBeInTheDocument();
|
expect(totalCell.getByText('300.00')).toBeInTheDocument();
|
||||||
|
|
||||||
// Second row
|
|
||||||
row = within(rows[1]);
|
row = within(rows[1]);
|
||||||
cells = row.getAllByRole('gridcell');
|
cells = row.getAllByRole('gridcell');
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import debounce from 'lodash/debounce';
|
|||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import type { ColDef, ValueFormatterFunc } from 'ag-grid-community';
|
import type { ColDef, ValueFormatterFunc } from 'ag-grid-community';
|
||||||
import { type AssetFieldsFragment } from '@vegaprotocol/assets';
|
import {
|
||||||
|
useAssetsMapProvider,
|
||||||
|
type AssetFieldsFragment,
|
||||||
|
} from '@vegaprotocol/assets';
|
||||||
import {
|
import {
|
||||||
addDecimalsFormatNumberQuantum,
|
addDecimalsFormatNumberQuantum,
|
||||||
formatNumberPercentage,
|
formatNumberPercentage,
|
||||||
@@ -23,17 +26,17 @@ import { useT } from '../../lib/use-t';
|
|||||||
export const RewardsHistoryContainer = ({
|
export const RewardsHistoryContainer = ({
|
||||||
epoch,
|
epoch,
|
||||||
pubKey,
|
pubKey,
|
||||||
assets,
|
|
||||||
}: {
|
}: {
|
||||||
pubKey: string | null;
|
pubKey: string | null;
|
||||||
epoch: number;
|
epoch: number;
|
||||||
assets: Record<string, AssetFieldsFragment>;
|
|
||||||
}) => {
|
}) => {
|
||||||
const [epochVariables, setEpochVariables] = useState(() => ({
|
const [epochVariables, setEpochVariables] = useState(() => ({
|
||||||
from: epoch - 1,
|
from: epoch - 1,
|
||||||
to: epoch,
|
to: epoch,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const { data: assets } = useAssetsMapProvider();
|
||||||
|
|
||||||
// No need to specify the fromEpoch as it will by default give you the last
|
// No need to specify the fromEpoch as it will by default give you the last
|
||||||
const { refetch, data, loading } = useRewardsHistoryQuery({
|
const { refetch, data, loading } = useRewardsHistoryQuery({
|
||||||
variables: {
|
variables: {
|
||||||
@@ -151,12 +154,10 @@ export const RewardHistoryTable = ({
|
|||||||
const rewardValueFormatter: ValueFormatterFunc<RewardRow> = ({
|
const rewardValueFormatter: ValueFormatterFunc<RewardRow> = ({
|
||||||
data,
|
data,
|
||||||
value,
|
value,
|
||||||
...rest
|
|
||||||
}) => {
|
}) => {
|
||||||
if (!value || !data) {
|
if (!value || !data) {
|
||||||
return '-';
|
return '-';
|
||||||
}
|
}
|
||||||
|
|
||||||
return addDecimalsFormatNumberQuantum(
|
return addDecimalsFormatNumberQuantum(
|
||||||
value,
|
value,
|
||||||
data.asset.decimals,
|
data.asset.decimals,
|
||||||
@@ -196,11 +197,6 @@ export const RewardHistoryTable = ({
|
|||||||
},
|
},
|
||||||
sort: 'desc',
|
sort: 'desc',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
field: 'infrastructureFees',
|
|
||||||
valueFormatter: rewardValueFormatter,
|
|
||||||
cellRenderer: rewardCellRenderer,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
field: 'staking',
|
field: 'staking',
|
||||||
valueFormatter: rewardValueFormatter,
|
valueFormatter: rewardValueFormatter,
|
||||||
|
|||||||
@@ -1,159 +0,0 @@
|
|||||||
import { type AssetFieldsFragment } from '@vegaprotocol/assets';
|
|
||||||
import { getRewards } from './use-reward-row-data';
|
|
||||||
import * as Schema from '@vegaprotocol/types';
|
|
||||||
|
|
||||||
const asset1 = {
|
|
||||||
id: 'asset1',
|
|
||||||
name: 'USD (KRW)',
|
|
||||||
symbol: 'USD-KRW',
|
|
||||||
decimals: 6,
|
|
||||||
quantum: '1000000',
|
|
||||||
status: Schema.AssetStatus.STATUS_ENABLED,
|
|
||||||
// @ts-ignore not needed
|
|
||||||
source: {},
|
|
||||||
} as AssetFieldsFragment;
|
|
||||||
|
|
||||||
const asset2 = {
|
|
||||||
id: 'asset2',
|
|
||||||
name: 'tDAI TEST',
|
|
||||||
symbol: 'tDAI',
|
|
||||||
decimals: 5,
|
|
||||||
quantum: '1',
|
|
||||||
status: Schema.AssetStatus.STATUS_ENABLED,
|
|
||||||
// @ts-ignore not needed
|
|
||||||
source: {},
|
|
||||||
} as AssetFieldsFragment;
|
|
||||||
|
|
||||||
const asset3 = {
|
|
||||||
id: 'asset3',
|
|
||||||
name: 'Tether USD',
|
|
||||||
symbol: 'USDT',
|
|
||||||
decimals: 6,
|
|
||||||
quantum: '1000000',
|
|
||||||
status: Schema.AssetStatus.STATUS_ENABLED,
|
|
||||||
// @ts-ignore not needed
|
|
||||||
source: {},
|
|
||||||
} as AssetFieldsFragment;
|
|
||||||
|
|
||||||
const asset4 = {
|
|
||||||
id: 'asset4',
|
|
||||||
name: 'USDT-T',
|
|
||||||
symbol: 'USDT-T',
|
|
||||||
decimals: 18,
|
|
||||||
quantum: '1',
|
|
||||||
status: Schema.AssetStatus.STATUS_ENABLED,
|
|
||||||
// @ts-ignore not needed
|
|
||||||
source: {},
|
|
||||||
} as AssetFieldsFragment;
|
|
||||||
|
|
||||||
const assets: Record<string, AssetFieldsFragment> = {
|
|
||||||
asset1,
|
|
||||||
asset2,
|
|
||||||
asset3,
|
|
||||||
asset4,
|
|
||||||
};
|
|
||||||
|
|
||||||
const testData = {
|
|
||||||
rewards: [
|
|
||||||
{
|
|
||||||
rewardType: Schema.AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
|
||||||
assetId: 'asset1',
|
|
||||||
amount: '31897424',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rewardType: Schema.AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
|
||||||
assetId: 'asset2',
|
|
||||||
amount: '57',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rewardType: Schema.AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
|
||||||
assetId: 'asset3',
|
|
||||||
amount: '5501',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rewardType: Schema.AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION,
|
|
||||||
assetId: 'asset3',
|
|
||||||
amount: '5501',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rewardType: Schema.AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
|
|
||||||
assetId: 'asset4',
|
|
||||||
amount: '5501',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rewardType: Schema.AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
|
||||||
assetId: 'asset4',
|
|
||||||
amount: '456',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rewardType: Schema.AccountType.ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING,
|
|
||||||
assetId: 'asset4',
|
|
||||||
amount: '4565',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
assets,
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('getRewards', () => {
|
|
||||||
it('should return the correct rewards when infra fees are included', () => {
|
|
||||||
const rewards = getRewards(testData.rewards, testData.assets);
|
|
||||||
expect(rewards).toEqual([
|
|
||||||
{
|
|
||||||
asset: asset1,
|
|
||||||
infrastructureFees: 31897424,
|
|
||||||
staking: 0,
|
|
||||||
priceTaking: 0,
|
|
||||||
priceMaking: 0,
|
|
||||||
liquidityProvision: 0,
|
|
||||||
marketCreation: 0,
|
|
||||||
averagePosition: 0,
|
|
||||||
relativeReturns: 0,
|
|
||||||
returnsVolatility: 0,
|
|
||||||
validatorRanking: 0,
|
|
||||||
total: 31897424,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
asset: asset2,
|
|
||||||
infrastructureFees: 57,
|
|
||||||
staking: 0,
|
|
||||||
priceTaking: 0,
|
|
||||||
priceMaking: 0,
|
|
||||||
liquidityProvision: 0,
|
|
||||||
marketCreation: 0,
|
|
||||||
averagePosition: 0,
|
|
||||||
relativeReturns: 0,
|
|
||||||
returnsVolatility: 0,
|
|
||||||
validatorRanking: 0,
|
|
||||||
total: 57,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
asset: asset3,
|
|
||||||
infrastructureFees: 5501,
|
|
||||||
staking: 0,
|
|
||||||
priceTaking: 0,
|
|
||||||
priceMaking: 0,
|
|
||||||
liquidityProvision: 0,
|
|
||||||
marketCreation: 0,
|
|
||||||
averagePosition: 5501,
|
|
||||||
relativeReturns: 0,
|
|
||||||
returnsVolatility: 0,
|
|
||||||
validatorRanking: 0,
|
|
||||||
total: 11002,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
asset: asset4,
|
|
||||||
infrastructureFees: 0,
|
|
||||||
staking: 0,
|
|
||||||
priceTaking: 0,
|
|
||||||
priceMaking: 5501,
|
|
||||||
liquidityProvision: 456,
|
|
||||||
marketCreation: 0,
|
|
||||||
averagePosition: 0,
|
|
||||||
relativeReturns: 0,
|
|
||||||
returnsVolatility: 0,
|
|
||||||
validatorRanking: 4565,
|
|
||||||
total: 10522,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -16,10 +16,9 @@ const REWARD_ACCOUNT_TYPES = [
|
|||||||
AccountType.ACCOUNT_TYPE_REWARD_RELATIVE_RETURN,
|
AccountType.ACCOUNT_TYPE_REWARD_RELATIVE_RETURN,
|
||||||
AccountType.ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY,
|
AccountType.ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY,
|
||||||
AccountType.ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING,
|
AccountType.ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING,
|
||||||
AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const getRewards = (
|
const getRewards = (
|
||||||
rewards: Array<{
|
rewards: Array<{
|
||||||
rewardType: AccountType;
|
rewardType: AccountType;
|
||||||
assetId: string;
|
assetId: string;
|
||||||
@@ -57,9 +56,6 @@ export const getRewards = (
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
asset,
|
asset,
|
||||||
infrastructureFees: totals.get(
|
|
||||||
AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE
|
|
||||||
),
|
|
||||||
staking: totals.get(AccountType.ACCOUNT_TYPE_GLOBAL_REWARD),
|
staking: totals.get(AccountType.ACCOUNT_TYPE_GLOBAL_REWARD),
|
||||||
priceTaking: totals.get(AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES),
|
priceTaking: totals.get(AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES),
|
||||||
priceMaking: totals.get(
|
priceMaking: totals.get(
|
||||||
@@ -105,8 +101,7 @@ export const useRewardsRowData = ({
|
|||||||
assetId: r.asset.id,
|
assetId: r.asset.id,
|
||||||
amount: r.amount,
|
amount: r.amount,
|
||||||
}));
|
}));
|
||||||
const result = getRewards(rewards, assets);
|
return getRewards(rewards, assets);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const rewards = removePaginationWrapper(epochRewardSummaries?.edges);
|
const rewards = removePaginationWrapper(epochRewardSummaries?.edges);
|
||||||
|
|||||||
@@ -10,18 +10,6 @@ import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
|
|||||||
import { useState, type ReactNode } from 'react';
|
import { useState, type ReactNode } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useT } from '../../lib/use-t';
|
import { useT } from '../../lib/use-t';
|
||||||
import { useFeatureFlags, type FeatureFlags } from '@vegaprotocol/environment';
|
|
||||||
|
|
||||||
export const FeatureFlagSwitch = ({ flag }: { flag: keyof FeatureFlags }) => {
|
|
||||||
const flags = useFeatureFlags((state) => state.flags);
|
|
||||||
const setFeatureFlag = useFeatureFlags((state) => state.setFeatureFlag);
|
|
||||||
return (
|
|
||||||
<Switch
|
|
||||||
onCheckedChange={(checked) => setFeatureFlag(flag, !!checked)}
|
|
||||||
checked={flags[flag]}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Settings = () => {
|
export const Settings = () => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
@@ -132,7 +120,7 @@ const SettingsGroup = ({
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div className={classNames({ 'w-3/4': inline, 'mb-2': !inline })}>
|
<div className={classNames({ 'w-3/4': inline, 'mb-2': !inline })}>
|
||||||
<div className="text-sm">{label}</div>
|
<label className="text-sm">{label}</label>
|
||||||
{helpText && <p className="text-xs text-muted">{helpText}</p>}
|
{helpText && <p className="text-xs text-muted">{helpText}</p>}
|
||||||
</div>
|
</div>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -24,13 +24,7 @@ import classNames from 'classnames';
|
|||||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||||
import { useT } from '../../lib/use-t';
|
import { useT } from '../../lib/use-t';
|
||||||
|
|
||||||
export const VegaWalletConnectButton = ({
|
export const VegaWalletConnectButton = () => {
|
||||||
intent = Intent.None,
|
|
||||||
onClick,
|
|
||||||
}: {
|
|
||||||
intent?: Intent;
|
|
||||||
onClick?: () => void;
|
|
||||||
}) => {
|
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||||
@@ -123,12 +117,9 @@ export const VegaWalletConnectButton = ({
|
|||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
data-testid="connect-vega-wallet"
|
data-testid="connect-vega-wallet"
|
||||||
onClick={() => {
|
onClick={openVegaWalletDialog}
|
||||||
onClick?.();
|
|
||||||
openVegaWalletDialog();
|
|
||||||
}}
|
|
||||||
size="small"
|
size="small"
|
||||||
intent={intent}
|
intent={Intent.None}
|
||||||
icon={<VegaIcon name={VegaIconNames.ARROW_RIGHT} size={14} />}
|
icon={<VegaIcon name={VegaIconNames.ARROW_RIGHT} size={14} />}
|
||||||
>
|
>
|
||||||
<span className="whitespace-nowrap uppercase">
|
<span className="whitespace-nowrap uppercase">
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { positionsDataProvider } from '@vegaprotocol/positions';
|
|||||||
import { useGlobalStore } from '../../stores';
|
import { useGlobalStore } from '../../stores';
|
||||||
|
|
||||||
const ONBOARDING_STORAGE_KEY = 'vega_onboarding';
|
const ONBOARDING_STORAGE_KEY = 'vega_onboarding';
|
||||||
|
|
||||||
export const useOnboardingStore = create<{
|
export const useOnboardingStore = create<{
|
||||||
dialogOpen: boolean;
|
dialogOpen: boolean;
|
||||||
walletDialogOpen: boolean;
|
walletDialogOpen: boolean;
|
||||||
@@ -21,7 +20,7 @@ export const useOnboardingStore = create<{
|
|||||||
}>()(
|
}>()(
|
||||||
persist(
|
persist(
|
||||||
(set) => ({
|
(set) => ({
|
||||||
dialogOpen: false,
|
dialogOpen: true,
|
||||||
walletDialogOpen: false,
|
walletDialogOpen: false,
|
||||||
dismissed: false,
|
dismissed: false,
|
||||||
dismiss: () => set({ dismissed: true }),
|
dismiss: () => set({ dismissed: true }),
|
||||||
|
|||||||
@@ -1,30 +1,18 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { matchPath, useLocation } from 'react-router-dom';
|
|
||||||
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
|
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
import { useEnvironment } from '@vegaprotocol/environment';
|
||||||
import { VegaConnectDialog } from '@vegaprotocol/wallet';
|
|
||||||
import { useConnectors } from '../../lib/vega-connectors';
|
|
||||||
import { useT } from '../../lib/use-t';
|
|
||||||
import { Routes } from '../../lib/links';
|
|
||||||
import { RiskMessage } from './risk-message';
|
|
||||||
import { WelcomeDialogContent } from './welcome-dialog-content';
|
import { WelcomeDialogContent } from './welcome-dialog-content';
|
||||||
import { useOnboardingStore } from './use-get-onboarding-step';
|
import { useOnboardingStore } from './use-get-onboarding-step';
|
||||||
import { ensureSuffix } from '@vegaprotocol/utils';
|
import { VegaConnectDialog } from '@vegaprotocol/wallet';
|
||||||
|
import { Connectors } from '../../lib/vega-connectors';
|
||||||
/**
|
import { RiskMessage } from './risk-message';
|
||||||
* A list of paths on which the welcome dialog should be omitted.
|
import { useT } from '../../lib/use-t';
|
||||||
*/
|
|
||||||
const OMIT_ON_LIST = [ensureSuffix(Routes.REFERRALS, '/*')];
|
|
||||||
|
|
||||||
export const WelcomeDialog = () => {
|
export const WelcomeDialog = () => {
|
||||||
const { pathname } = useLocation();
|
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const { VEGA_ENV } = useEnvironment();
|
const { VEGA_ENV } = useEnvironment();
|
||||||
const connectors = useConnectors();
|
|
||||||
const dismissed = useOnboardingStore((store) => store.dismissed);
|
const dismissed = useOnboardingStore((store) => store.dismissed);
|
||||||
const dialogOpen = useOnboardingStore((store) => store.dialogOpen);
|
const dialogOpen = useOnboardingStore((store) => store.dialogOpen);
|
||||||
const dismiss = useOnboardingStore((store) => store.dismiss);
|
const dismiss = useOnboardingStore((store) => store.dismiss);
|
||||||
const setDialogOpen = useOnboardingStore((store) => store.setDialogOpen);
|
|
||||||
const walletDialogOpen = useOnboardingStore(
|
const walletDialogOpen = useOnboardingStore(
|
||||||
(store) => store.walletDialogOpen
|
(store) => store.walletDialogOpen
|
||||||
);
|
);
|
||||||
@@ -32,19 +20,9 @@ export const WelcomeDialog = () => {
|
|||||||
(store) => store.setWalletDialogOpen
|
(store) => store.setWalletDialogOpen
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const shouldOmit = OMIT_ON_LIST.map((path) =>
|
|
||||||
matchPath(path, pathname)
|
|
||||||
).some((m) => !!m);
|
|
||||||
|
|
||||||
if (dismissed || shouldOmit) return;
|
|
||||||
|
|
||||||
setDialogOpen(true);
|
|
||||||
}, [dismissed, pathname, setDialogOpen]);
|
|
||||||
|
|
||||||
const content = walletDialogOpen ? (
|
const content = walletDialogOpen ? (
|
||||||
<VegaConnectDialog
|
<VegaConnectDialog
|
||||||
connectors={connectors}
|
connectors={Connectors}
|
||||||
riskMessage={<RiskMessage />}
|
riskMessage={<RiskMessage />}
|
||||||
onClose={() => setWalletDialogOpen(false)}
|
onClose={() => setWalletDialogOpen(false)}
|
||||||
contentOnly
|
contentOnly
|
||||||
@@ -53,12 +31,7 @@ export const WelcomeDialog = () => {
|
|||||||
<WelcomeDialogContent />
|
<WelcomeDialogContent />
|
||||||
);
|
);
|
||||||
|
|
||||||
const onClose = walletDialogOpen
|
const onClose = walletDialogOpen ? () => setWalletDialogOpen(false) : dismiss;
|
||||||
? () => setWalletDialogOpen(false)
|
|
||||||
: () => {
|
|
||||||
setDialogOpen(false);
|
|
||||||
dismiss();
|
|
||||||
};
|
|
||||||
|
|
||||||
const title = walletDialogOpen ? null : (
|
const title = walletDialogOpen ? null : (
|
||||||
<span className="font-alpha calt" data-testid="welcome-title">
|
<span className="font-alpha calt" data-testid="welcome-title">
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest
|
CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest
|
||||||
VEGA_VERSION=v0.73.9
|
VEGA_VERSION=v0.73.6
|
||||||
LOCAL_SERVER=false
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
CONSOLE_IMAGE_NAME=vegaprotocol/trading:develop
|
CONSOLE_IMAGE_NAME=vegaprotocol/trading:develop
|
||||||
VEGA_VERSION=v0.73.9
|
VEGA_VERSION=v0.73.6
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
CONSOLE_IMAGE_NAME=vegaprotocol/trading:main
|
CONSOLE_IMAGE_NAME=vegaprotocol/trading:main
|
||||||
VEGA_VERSION=v0.73.8
|
VEGA_VERSION=v0.73.6
|
||||||
LOCAL_SERVER=false
|
|
||||||
|
|||||||
@@ -24,12 +24,6 @@ poetry shell
|
|||||||
|
|
||||||
5. **Install python dependencies**
|
5. **Install python dependencies**
|
||||||
|
|
||||||
To make sure you are on the latest version of our market-sim branch.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
poetry update vega-sim
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
poetry install
|
poetry install
|
||||||
```
|
```
|
||||||
@@ -93,9 +87,10 @@ docker build -f docker/node-outside-docker.Dockerfile --build-arg APP=trading --
|
|||||||
|
|
||||||
## Running Tests 🧪
|
## Running Tests 🧪
|
||||||
|
|
||||||
Before running make sure the docker daemon is running.
|
Before running make sure the docker daemon is runnign so that the app can be served.
|
||||||
|
|
||||||
To run a specific test, use the `-k` option followed by the name of the test.
|
To run a specific test, use the `-k` option followed by the name of the test.
|
||||||
|
|
||||||
Run all tests:
|
Run all tests:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -114,21 +109,6 @@ Run from anywhere:
|
|||||||
yarn trading:test -- "test_name" -s --headed
|
yarn trading:test -- "test_name" -s --headed
|
||||||
```
|
```
|
||||||
|
|
||||||
Run using your locally served console:
|
|
||||||
|
|
||||||
Within one terminal
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yarn nx build trading
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yarn nx serve trading
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Once console is served you can update the .env file to have local_server to true.
|
|
||||||
|
|
||||||
## Running Tests in Parallel 🔢
|
## Running Tests in Parallel 🔢
|
||||||
|
|
||||||
To run tests in parallel, use the `--numprocesses auto` option. The `--dist loadfile` setting ensures that multiple runners are not assigned to a single test file.
|
To run tests in parallel, use the `--numprocesses auto` option. The `--dist loadfile` setting ensures that multiple runners are not assigned to a single test file.
|
||||||
|
|||||||
@@ -6,27 +6,23 @@ from typing import Optional
|
|||||||
WalletConfig = namedtuple("WalletConfig", ["name", "passphrase"])
|
WalletConfig = namedtuple("WalletConfig", ["name", "passphrase"])
|
||||||
ASSET_NAME = "tDAI"
|
ASSET_NAME = "tDAI"
|
||||||
|
|
||||||
|
|
||||||
def wait_for_toast_confirmation(page: Page, timeout: int = 30000):
|
def wait_for_toast_confirmation(page: Page, timeout: int = 30000):
|
||||||
page.wait_for_function("""
|
page.wait_for_function("""
|
||||||
document.querySelector('[data-testid="toast-content"]') &&
|
document.querySelector('[data-testid="toast-content"]') &&
|
||||||
document.querySelector('[data-testid="toast-content"]').innerText.includes('AWAITING CONFIRMATION')
|
document.querySelector('[data-testid="toast-content"]').innerText.includes('AWAITING CONFIRMATION')
|
||||||
""", timeout=timeout)
|
""", timeout=timeout)
|
||||||
|
|
||||||
|
|
||||||
def create_and_faucet_wallet(
|
def create_and_faucet_wallet(
|
||||||
vega: VegaServiceNull,
|
vega: VegaServiceNull,
|
||||||
wallet: WalletConfig,
|
wallet: WalletConfig,
|
||||||
symbol: Optional[str] = None,
|
symbol: Optional[str] = None,
|
||||||
amount: float = 1e4,
|
amount: float = 1e4,
|
||||||
|
|
||||||
):
|
):
|
||||||
asset_id = vega.find_asset_id(
|
asset_id = vega.find_asset_id(symbol=symbol if symbol is not None else ASSET_NAME)
|
||||||
symbol=symbol if symbol is not None else ASSET_NAME)
|
|
||||||
vega.create_key(wallet.name)
|
vega.create_key(wallet.name)
|
||||||
vega.mint(wallet.name, asset_id, amount)
|
vega.mint(wallet.name, asset_id, amount)
|
||||||
|
|
||||||
|
|
||||||
def next_epoch(vega: VegaServiceNull):
|
def next_epoch(vega: VegaServiceNull):
|
||||||
forwards = 0
|
forwards = 0
|
||||||
epoch_seq = vega.statistics().epoch_seq
|
epoch_seq = vega.statistics().epoch_seq
|
||||||
@@ -40,35 +36,13 @@ def next_epoch(vega: VegaServiceNull):
|
|||||||
vega.wait_fn(1)
|
vega.wait_fn(1)
|
||||||
vega.wait_for_total_catchup()
|
vega.wait_for_total_catchup()
|
||||||
|
|
||||||
|
|
||||||
def truncate_middle(market_id, start=6, end=4):
|
def truncate_middle(market_id, start=6, end=4):
|
||||||
if len(market_id) < 11:
|
if len(market_id) < 11:
|
||||||
return market_id
|
return market_id
|
||||||
return market_id[:start] + '\u2026' + market_id[-end:]
|
return market_id[:start] + '\u2026' + market_id[-end:]
|
||||||
|
|
||||||
|
def change_keys(page: Page, vega:VegaServiceNull, key_name):
|
||||||
def change_keys(page: Page, vega: VegaServiceNull, key_name):
|
|
||||||
page.get_by_test_id("manage-vega-wallet").click()
|
page.get_by_test_id("manage-vega-wallet").click()
|
||||||
page.get_by_test_id("key-" + vega.wallet.public_key(key_name)).click()
|
page.get_by_test_id("key-" + vega.wallet.public_key(key_name)).click()
|
||||||
page.click(
|
page.click(f'data-testid=key-{vega.wallet.public_key(key_name)} >> .inline-flex')
|
||||||
f'data-testid=key-{vega.wallet.public_key(key_name)} >> .inline-flex')
|
|
||||||
page.reload()
|
page.reload()
|
||||||
|
|
||||||
|
|
||||||
def forward_time(vega:VegaServiceNull, forward_epoch: bool = False):
|
|
||||||
|
|
||||||
vega.wait_fn(1)
|
|
||||||
vega.wait_for_total_catchup()
|
|
||||||
|
|
||||||
if forward_epoch:
|
|
||||||
next_epoch(vega)
|
|
||||||
|
|
||||||
# This is for when the element will initially load but contain an outdated value. It will wait for the element to contain the expected text, returning False after a timeout or exception
|
|
||||||
def selector_contains_text(page: Page, selector, expected_text, timeout=5000):
|
|
||||||
try:
|
|
||||||
page.wait_for_selector(
|
|
||||||
f'{selector} >> text={expected_text}', timeout=timeout)
|
|
||||||
return True
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from typing import List, Tuple, Optional
|
from typing import List, Tuple, Optional
|
||||||
from vega_sim.service import VegaService, PeggedOrder
|
from vega_sim.service import VegaService, PeggedOrder
|
||||||
|
|
||||||
|
|
||||||
def submit_order(
|
def submit_order(
|
||||||
vega: VegaService,
|
vega: VegaService,
|
||||||
wallet_name: str,
|
wallet_name: str,
|
||||||
@@ -36,7 +35,7 @@ def submit_multiple_orders(
|
|||||||
submit_order(vega, wallet_name, market_id, side, volume, price)
|
submit_order(vega, wallet_name, market_id, side, volume, price)
|
||||||
|
|
||||||
|
|
||||||
def submit_liquidity(vega: VegaService, wallet_name: str, market_id: str, buy_vol=99, sell_vol=99, custom_price=None):
|
def submit_liquidity(vega: VegaService, wallet_name: str, market_id: str):
|
||||||
vega.submit_simple_liquidity(
|
vega.submit_simple_liquidity(
|
||||||
key_name=wallet_name,
|
key_name=wallet_name,
|
||||||
market_id=market_id,
|
market_id=market_id,
|
||||||
@@ -52,7 +51,7 @@ def submit_liquidity(vega: VegaService, wallet_name: str, market_id: str, buy_vo
|
|||||||
pegged_order=PeggedOrder(reference="PEGGED_REFERENCE_MID", offset=1),
|
pegged_order=PeggedOrder(reference="PEGGED_REFERENCE_MID", offset=1),
|
||||||
wait=False,
|
wait=False,
|
||||||
time_in_force="TIME_IN_FORCE_GTC",
|
time_in_force="TIME_IN_FORCE_GTC",
|
||||||
volume=buy_vol,
|
volume=99,
|
||||||
)
|
)
|
||||||
vega.submit_order(
|
vega.submit_order(
|
||||||
market_id=market_id,
|
market_id=market_id,
|
||||||
@@ -62,5 +61,5 @@ def submit_liquidity(vega: VegaService, wallet_name: str, market_id: str, buy_vo
|
|||||||
pegged_order=PeggedOrder(reference="PEGGED_REFERENCE_MID", offset=1),
|
pegged_order=PeggedOrder(reference="PEGGED_REFERENCE_MID", offset=1),
|
||||||
wait=False,
|
wait=False,
|
||||||
time_in_force="TIME_IN_FORCE_GTC",
|
time_in_force="TIME_IN_FORCE_GTC",
|
||||||
volume=sell_vol,
|
volume=99,
|
||||||
)
|
)
|
||||||
@@ -6,13 +6,12 @@ import requests
|
|||||||
import time
|
import time
|
||||||
import docker
|
import docker
|
||||||
import http.server
|
import http.server
|
||||||
import sys
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from vega_sim.null_service import VegaServiceNull, Ports
|
from vega_sim.null_service import VegaServiceNull
|
||||||
from playwright.sync_api import Browser, Page
|
from playwright.sync_api import Browser, Page
|
||||||
from config import console_image_name, vega_version
|
from config import console_image_name, vega_version
|
||||||
from datetime import datetime, timedelta
|
|
||||||
from fixtures.market import (
|
from fixtures.market import (
|
||||||
setup_simple_market,
|
setup_simple_market,
|
||||||
setup_opening_auction_market,
|
setup_opening_auction_market,
|
||||||
@@ -20,6 +19,7 @@ from fixtures.market import (
|
|||||||
setup_perps_market,
|
setup_perps_market,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
# Workaround for current xdist issue with displaying live logs from multiple workers
|
# Workaround for current xdist issue with displaying live logs from multiple workers
|
||||||
# https://github.com/pytest-dev/pytest-xdist/issues/402
|
# https://github.com/pytest-dev/pytest-xdist/issues/402
|
||||||
@@ -28,8 +28,6 @@ sys.stdout = sys.stderr
|
|||||||
docker_client = docker.from_env()
|
docker_client = docker.from_env()
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
|
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(tryfirst=True)
|
@pytest.hookimpl(tryfirst=True)
|
||||||
def pytest_runtest_makereport(item, call):
|
def pytest_runtest_makereport(item, call):
|
||||||
@@ -51,24 +49,16 @@ def pytest_configure(config):
|
|||||||
level=config.getini("log_file_level"),
|
level=config.getini("log_file_level"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CustomHttpRequestHandler(http.server.SimpleHTTPRequestHandler):
|
class CustomHttpRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
# Set the path to your website's directory here
|
# Set the path to your website's directory here
|
||||||
if self.path == "/":
|
if self.path == '/':
|
||||||
self.path = "dist/apps/trading/exported/index.html"
|
self.path = 'dist/apps/trading/exported/index.html'
|
||||||
return http.server.SimpleHTTPRequestHandler.do_GET(self)
|
return http.server.SimpleHTTPRequestHandler.do_GET(self)
|
||||||
|
|
||||||
|
|
||||||
# Start VegaServiceNull
|
# Start VegaServiceNull
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def init_vega(request=None):
|
def init_vega(request=None):
|
||||||
local_server = os.getenv("LOCAL_SERVER", "false").lower() == "true"
|
|
||||||
port_config = None
|
|
||||||
if local_server:
|
|
||||||
port_config = {
|
|
||||||
Ports.DATA_NODE_REST: 8001,
|
|
||||||
}
|
|
||||||
default_seconds = 1
|
default_seconds = 1
|
||||||
seconds_per_block = default_seconds
|
seconds_per_block = default_seconds
|
||||||
if request and hasattr(request, "param"):
|
if request and hasattr(request, "param"):
|
||||||
@@ -80,26 +70,20 @@ def init_vega(request=None):
|
|||||||
)
|
)
|
||||||
logger.info(f"Using console image: {console_image_name}")
|
logger.info(f"Using console image: {console_image_name}")
|
||||||
logger.info(f"Using vega version: {vega_version}")
|
logger.info(f"Using vega version: {vega_version}")
|
||||||
|
with VegaServiceNull(
|
||||||
vega_service_args = {
|
run_with_console=False,
|
||||||
"run_with_console": False,
|
launch_graphql=False,
|
||||||
"launch_graphql": False,
|
retain_log_files=True,
|
||||||
"retain_log_files": True,
|
use_full_vega_wallet=True,
|
||||||
"use_full_vega_wallet": True,
|
store_transactions=True,
|
||||||
"store_transactions": True,
|
transactions_per_block=1000,
|
||||||
"transactions_per_block": 1000,
|
seconds_per_block=seconds_per_block,
|
||||||
"seconds_per_block": seconds_per_block,
|
) as vega:
|
||||||
"genesis_time": datetime.now() - timedelta(days=1),
|
|
||||||
}
|
|
||||||
|
|
||||||
if port_config is not None:
|
|
||||||
vega_service_args["port_config"] = port_config
|
|
||||||
|
|
||||||
with VegaServiceNull(**vega_service_args) as vega:
|
|
||||||
try:
|
try:
|
||||||
container = docker_client.containers.run(
|
container = docker_client.containers.run(
|
||||||
console_image_name, detach=True, ports={"80/tcp": vega.console_port}
|
console_image_name, detach=True, ports={"80/tcp": vega.console_port}
|
||||||
)
|
)
|
||||||
|
# docker setup
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Container {container.id} started",
|
f"Container {container.id} started",
|
||||||
extra={"worker_id": os.environ.get("PYTEST_XDIST_WORKER")},
|
extra={"worker_id": os.environ.get("PYTEST_XDIST_WORKER")},
|
||||||
@@ -112,17 +96,15 @@ def init_vega(request=None):
|
|||||||
finally:
|
finally:
|
||||||
logger.info(f"Stopping container {container.id}")
|
logger.info(f"Stopping container {container.id}")
|
||||||
container.stop()
|
container.stop()
|
||||||
|
# Remove the container
|
||||||
logger.info(f"Removing container {container.id}")
|
logger.info(f"Removing container {container.id}")
|
||||||
container.remove()
|
container.remove()
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def init_page(vega: VegaServiceNull, browser: Browser, request: pytest.FixtureRequest):
|
def init_page(vega: VegaServiceNull, browser: Browser, request: pytest.FixtureRequest):
|
||||||
local_server = os.getenv("LOCAL_SERVER", "false").lower() == "true"
|
|
||||||
server_port = "4200" if local_server else str(vega.console_port)
|
|
||||||
with browser.new_context(
|
with browser.new_context(
|
||||||
viewport={"width": 1920, "height": 1080},
|
viewport={"width": 1920, "height": 1080},
|
||||||
base_url=f"http://localhost:{server_port}",
|
base_url=f"http://localhost:{vega.console_port}",
|
||||||
) as context, context.new_page() as page:
|
) as context, context.new_page() as page:
|
||||||
context.tracing.start(screenshots=True, snapshots=True, sources=True)
|
context.tracing.start(screenshots=True, snapshots=True, sources=True)
|
||||||
try:
|
try:
|
||||||
@@ -130,7 +112,9 @@ def init_page(vega: VegaServiceNull, browser: Browser, request: pytest.FixtureRe
|
|||||||
attempts = 0
|
attempts = 0
|
||||||
while attempts < 100:
|
while attempts < 100:
|
||||||
try:
|
try:
|
||||||
code = requests.get(f"http://localhost:{server_port}/").status_code
|
code = requests.get(
|
||||||
|
f"http://localhost:{vega.console_port}/"
|
||||||
|
).status_code
|
||||||
if code == 200:
|
if code == 200:
|
||||||
break
|
break
|
||||||
except requests.exceptions.ConnectionError as e:
|
except requests.exceptions.ConnectionError as e:
|
||||||
|
|||||||
@@ -8,17 +8,12 @@ logger = logging.getLogger()
|
|||||||
mint_amount: float = 10e5
|
mint_amount: float = 10e5
|
||||||
market_name = "BTC:DAI_2023"
|
market_name = "BTC:DAI_2023"
|
||||||
|
|
||||||
default_sell_orders = [[1, 110], [1, 105]]
|
|
||||||
default_buy_orders = [[1, 90], [1, 95]]
|
|
||||||
|
|
||||||
|
|
||||||
def setup_simple_market(
|
def setup_simple_market(
|
||||||
vega: VegaService,
|
vega: VegaService,
|
||||||
approve_proposal=True,
|
approve_proposal=True,
|
||||||
custom_market_name=market_name,
|
custom_market_name=market_name,
|
||||||
custom_asset_name="tDAI",
|
custom_asset_name="tDAI",
|
||||||
custom_asset_symbol="tDAI",
|
custom_asset_symbol="tDAI",
|
||||||
custom_quantum=1
|
|
||||||
):
|
):
|
||||||
for wallet in wallets:
|
for wallet in wallets:
|
||||||
vega.create_key(wallet.name)
|
vega.create_key(wallet.name)
|
||||||
@@ -42,7 +37,6 @@ def setup_simple_market(
|
|||||||
symbol=custom_asset_symbol,
|
symbol=custom_asset_symbol,
|
||||||
decimals=5,
|
decimals=5,
|
||||||
max_faucet_amount=1e10,
|
max_faucet_amount=1e10,
|
||||||
quantum=custom_quantum,
|
|
||||||
)
|
)
|
||||||
vega.wait_fn(1)
|
vega.wait_fn(1)
|
||||||
vega.wait_for_total_catchup()
|
vega.wait_for_total_catchup()
|
||||||
@@ -117,17 +111,16 @@ def setup_simple_successor_market(
|
|||||||
return market_id
|
return market_id
|
||||||
|
|
||||||
|
|
||||||
def setup_opening_auction_market(vega: VegaService, market_id: str = None, buy_orders=default_buy_orders, sell_orders=default_sell_orders, add_liquidity=True, **kwargs):
|
def setup_opening_auction_market(vega: VegaService, market_id: str = None, **kwargs):
|
||||||
if not market_exists(vega, market_id):
|
if market_id is None or market_id not in vega.all_markets():
|
||||||
market_id = setup_simple_market(vega, **kwargs)
|
market_id = setup_simple_market(vega, **kwargs)
|
||||||
|
|
||||||
if add_liquidity:
|
submit_liquidity(vega, MM_WALLET.name, market_id)
|
||||||
submit_liquidity(vega, MM_WALLET.name, market_id)
|
|
||||||
submit_multiple_orders(
|
submit_multiple_orders(
|
||||||
vega, MM_WALLET.name, market_id, "SIDE_SELL", sell_orders
|
vega, MM_WALLET.name, market_id, "SIDE_SELL", [[1, 110], [1, 105]]
|
||||||
)
|
)
|
||||||
submit_multiple_orders(
|
submit_multiple_orders(
|
||||||
vega, MM_WALLET2.name, market_id, "SIDE_BUY", buy_orders
|
vega, MM_WALLET2.name, market_id, "SIDE_BUY", [[1, 90], [1, 95]]
|
||||||
)
|
)
|
||||||
|
|
||||||
vega.forward("10s")
|
vega.forward("10s")
|
||||||
@@ -137,22 +130,11 @@ def setup_opening_auction_market(vega: VegaService, market_id: str = None, buy_o
|
|||||||
return market_id
|
return market_id
|
||||||
|
|
||||||
|
|
||||||
def market_exists(vega: VegaService, market_id: str):
|
def setup_continuous_market(vega: VegaService, market_id: str = None, **kwargs):
|
||||||
if market_id is None:
|
if market_id is None or market_id not in vega.all_markets():
|
||||||
return False
|
market_id = setup_opening_auction_market(vega, **kwargs)
|
||||||
all_markets = vega.all_markets()
|
|
||||||
market_ids = [market.id for market in all_markets]
|
|
||||||
return market_id in market_ids
|
|
||||||
|
|
||||||
|
submit_order(vega, "Key 1", market_id, "SIDE_BUY", 1, 110)
|
||||||
# Add sell orders and buy orders to put on the book
|
|
||||||
def setup_continuous_market(vega: VegaService, market_id: str = None, buy_orders=default_buy_orders, sell_orders=default_sell_orders, add_liquidity=True, **kwargs):
|
|
||||||
if not market_exists(vega, market_id) or buy_orders != default_buy_orders or sell_orders != default_sell_orders:
|
|
||||||
market_id = setup_opening_auction_market(
|
|
||||||
vega, market_id, buy_orders, sell_orders, add_liquidity, **kwargs)
|
|
||||||
|
|
||||||
submit_order(vega, "Key 1", market_id, "SIDE_BUY",
|
|
||||||
sell_orders[0][0], sell_orders[0][1])
|
|
||||||
|
|
||||||
vega.forward("10s")
|
vega.forward("10s")
|
||||||
vega.wait_fn(1)
|
vega.wait_fn(1)
|
||||||
@@ -160,7 +142,6 @@ def setup_continuous_market(vega: VegaService, market_id: str = None, buy_orders
|
|||||||
|
|
||||||
return market_id
|
return market_id
|
||||||
|
|
||||||
|
|
||||||
def setup_perps_market(
|
def setup_perps_market(
|
||||||
vega: VegaService,
|
vega: VegaService,
|
||||||
custom_asset_name="tDAI",
|
custom_asset_name="tDAI",
|
||||||
@@ -229,7 +210,7 @@ def setup_perps_market(
|
|||||||
settlement_data_key=TERMINATE_WALLET.name,
|
settlement_data_key=TERMINATE_WALLET.name,
|
||||||
funding_payment_frequency_in_seconds=10,
|
funding_payment_frequency_in_seconds=10,
|
||||||
market_decimals=5,
|
market_decimals=5,
|
||||||
)
|
)
|
||||||
vega.wait_for_total_catchup()
|
vega.wait_for_total_catchup()
|
||||||
|
|
||||||
submit_liquidity(vega, MM_WALLET.name, market_id)
|
submit_liquidity(vega, MM_WALLET.name, market_id)
|
||||||
@@ -244,12 +225,4 @@ def setup_perps_market(
|
|||||||
vega.wait_fn(1)
|
vega.wait_fn(1)
|
||||||
vega.wait_for_total_catchup()
|
vega.wait_for_total_catchup()
|
||||||
|
|
||||||
return market_id
|
return market_id
|
||||||
|
|
||||||
|
|
||||||
def market_exists(vega: VegaService, market_id: str):
|
|
||||||
if market_id is None:
|
|
||||||
return False
|
|
||||||
all_markets = vega.all_markets()
|
|
||||||
market_ids = [market.id for market in all_markets]
|
|
||||||
return market_id in market_ids
|
|
||||||
Generated
+5
-5
@@ -1,4 +1,4 @@
|
|||||||
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
@@ -1159,9 +1159,9 @@ profile = ["pytest-profiling", "snakeviz"]
|
|||||||
|
|
||||||
[package.source]
|
[package.source]
|
||||||
type = "git"
|
type = "git"
|
||||||
url = "https://github.com/vegaprotocol/vega-market-sim.git/"
|
url = "https://github.com/vegaprotocol/vega-market-sim.git"
|
||||||
reference = "fix/genesis_panic"
|
reference = "HEAD"
|
||||||
resolved_reference = "de30d2d4c7a1b81a830527ca76473e23ef59de12"
|
resolved_reference = "e93f7dfa8463c59cfd0e299362b845511cebeef6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "websocket-client"
|
name = "websocket-client"
|
||||||
@@ -1342,4 +1342,4 @@ files = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = ">=3.9,<3.11"
|
python-versions = ">=3.9,<3.11"
|
||||||
content-hash = "68ed0de55290a3b929d47eb7f7b031fb7e172261c7bbeb4f554b7c27a4462754"
|
content-hash = "d1231fe591b774e34b8f94a54cd02e4d7dae924c57785263841c3b0b0feed505"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ packages = [{include = "trading market-sim e2e"}]
|
|||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = ">=3.9,<3.11"
|
python = ">=3.9,<3.11"
|
||||||
psutil = "^5.9.5"
|
psutil = "^5.9.5"
|
||||||
vega-sim = {git = "https://github.com/vegaprotocol/vega-market-sim.git/", branch = "fix/genesis_panic"}
|
vega-sim = {git = "https://github.com/vegaprotocol/vega-market-sim.git"}
|
||||||
pytest-playwright = "^0.4.2"
|
pytest-playwright = "^0.4.2"
|
||||||
docker = "^6.1.3"
|
docker = "^6.1.3"
|
||||||
pytest-xdist = "^3.3.1"
|
pytest-xdist = "^3.3.1"
|
||||||
|
|||||||
@@ -56,12 +56,11 @@ label_value_tooltip_pairs = [
|
|||||||
|
|
||||||
def tooltip(page: Page, index: int, test_id: str, tooltip: str):
|
def tooltip(page: Page, index: int, test_id: str, tooltip: str):
|
||||||
page.locator(f"data-testid={index}_{test_id}").hover()
|
page.locator(f"data-testid={index}_{test_id}").hover()
|
||||||
expect(page.locator('[role="tooltip"]').locator(
|
expect(page.locator('[role="tooltip"]').locator("div")).to_have_text(tooltip)
|
||||||
"div")).to_have_text(tooltip)
|
|
||||||
page.get_by_test_id("dialog-title").click()
|
page.get_by_test_id("dialog-title").click()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("continuous_market", "auth", "risk_accepted")
|
@pytest.mark.usefixtures("page", "continuous_market", "auth", "risk_accepted")
|
||||||
def test_asset_details(page: Page):
|
def test_asset_details(page: Page):
|
||||||
page.goto("/#/portfolio")
|
page.goto("/#/portfolio")
|
||||||
page.locator('[data-testid="tab-collateral"] >> text=tDAI').click()
|
page.locator('[data-testid="tab-collateral"] >> text=tDAI').click()
|
||||||
@@ -74,22 +73,17 @@ def test_asset_details(page: Page):
|
|||||||
value = pair.get("value", "")
|
value = pair.get("value", "")
|
||||||
label_tooltip = pair.get("labelTooltip", "")
|
label_tooltip = pair.get("labelTooltip", "")
|
||||||
value_tooltip = pair.get("valueToolTip", "")
|
value_tooltip = pair.get("valueToolTip", "")
|
||||||
|
|
||||||
if label == "ID":
|
if label == "ID":
|
||||||
expect(page.get_by_role(
|
expect(page.get_by_role("button", name="Copy id to clipboard")).to_be_visible()
|
||||||
"button", name="Copy id to clipboard")).to_be_visible()
|
asset_id_text = page.locator(f"[data-testid='{index}_value']").inner_text()
|
||||||
asset_id_text = page.locator(
|
|
||||||
f"[data-testid='{index}_value']").inner_text()
|
|
||||||
pattern = r"^[0-9a-f]{6}\u2026[0-9a-f]{4}"
|
pattern = r"^[0-9a-f]{6}\u2026[0-9a-f]{4}"
|
||||||
|
|
||||||
assert re.match(
|
assert re.match(pattern, asset_id_text), f"Expected ID to match pattern but got {asset_id_text}"
|
||||||
pattern, asset_id_text), f"Expected ID to match pattern but got {asset_id_text}"
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
expect(page.locator(
|
expect(page.locator(f"[data-testid='{index}_label']")).to_have_text(label)
|
||||||
f"[data-testid='{index}_label']")).to_have_text(label)
|
expect(page.locator(f"[data-testid='{index}_value']")).to_have_text(value)
|
||||||
expect(page.locator(
|
|
||||||
f"[data-testid='{index}_value']")).to_have_text(value)
|
|
||||||
|
|
||||||
if label_tooltip:
|
if label_tooltip:
|
||||||
tooltip(page, index, "label", label_tooltip)
|
tooltip(page, index, "label", label_tooltip)
|
||||||
|
|||||||
@@ -14,16 +14,19 @@ market_order = "order-type-Market"
|
|||||||
tif = "order-tif"
|
tif = "order-tif"
|
||||||
expire = "expire"
|
expire = "expire"
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def vega(request):
|
def vega(request):
|
||||||
with init_vega(request) as vega:
|
with init_vega(request) as vega:
|
||||||
yield vega
|
yield vega
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def continuous_market(vega):
|
def continuous_market(vega):
|
||||||
return setup_continuous_market(vega)
|
return setup_continuous_market(vega)
|
||||||
|
|
||||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
|
||||||
|
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
|
||||||
def test_limit_buy_order_GTT(continuous_market, vega: VegaService, page: Page):
|
def test_limit_buy_order_GTT(continuous_market, vega: VegaService, page: Page):
|
||||||
page.goto(f"/#/markets/{continuous_market}")
|
page.goto(f"/#/markets/{continuous_market}")
|
||||||
page.get_by_test_id(tif).select_option("Good 'til Time (GTT)")
|
page.get_by_test_id(tif).select_option("Good 'til Time (GTT)")
|
||||||
@@ -51,7 +54,8 @@ def test_limit_buy_order_GTT(continuous_market, vega: VegaService, page: Page):
|
|||||||
"BTC:DAI_2023Futr10+10LimitFilled120.00GTT:"
|
"BTC:DAI_2023Futr10+10LimitFilled120.00GTT:"
|
||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
|
||||||
|
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
|
||||||
def test_limit_buy_order(continuous_market, vega: VegaService, page: Page):
|
def test_limit_buy_order(continuous_market, vega: VegaService, page: Page):
|
||||||
page.goto(f"/#/markets/{continuous_market}")
|
page.goto(f"/#/markets/{continuous_market}")
|
||||||
|
|
||||||
@@ -68,7 +72,8 @@ def test_limit_buy_order(continuous_market, vega: VegaService, page: Page):
|
|||||||
"BTC:DAI_2023Futr10+10LimitFilled120.00GTC"
|
"BTC:DAI_2023Futr10+10LimitFilled120.00GTC"
|
||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
|
||||||
|
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
|
||||||
def test_limit_sell_order(continuous_market, vega: VegaService, page: Page):
|
def test_limit_sell_order(continuous_market, vega: VegaService, page: Page):
|
||||||
page.goto(f"/#/markets/{continuous_market}")
|
page.goto(f"/#/markets/{continuous_market}")
|
||||||
page.get_by_test_id(order_size).fill("10")
|
page.get_by_test_id(order_size).fill("10")
|
||||||
@@ -92,7 +97,8 @@ def test_limit_sell_order(continuous_market, vega: VegaService, page: Page):
|
|||||||
"BTC:DAI_2023Futr10-10LimitFilled100.00GFN"
|
"BTC:DAI_2023Futr10-10LimitFilled100.00GFN"
|
||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
|
||||||
|
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
|
||||||
def test_market_sell_order(continuous_market, vega: VegaService, page: Page):
|
def test_market_sell_order(continuous_market, vega: VegaService, page: Page):
|
||||||
page.goto(f"/#/markets/{continuous_market}")
|
page.goto(f"/#/markets/{continuous_market}")
|
||||||
page.get_by_test_id(market_order).click()
|
page.get_by_test_id(market_order).click()
|
||||||
@@ -116,7 +122,8 @@ def test_market_sell_order(continuous_market, vega: VegaService, page: Page):
|
|||||||
"BTC:DAI_2023Futr10-10MarketFilled-IOC"
|
"BTC:DAI_2023Futr10-10MarketFilled-IOC"
|
||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
|
||||||
|
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
|
||||||
def test_market_buy_order(continuous_market, vega: VegaService, page: Page):
|
def test_market_buy_order(continuous_market, vega: VegaService, page: Page):
|
||||||
page.goto(f"/#/markets/{continuous_market}")
|
page.goto(f"/#/markets/{continuous_market}")
|
||||||
page.get_by_test_id(market_order).click()
|
page.get_by_test_id(market_order).click()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ def continuous_market(vega):
|
|||||||
return setup_continuous_market(vega)
|
return setup_continuous_market(vega)
|
||||||
|
|
||||||
@pytest.mark.skip("We currently can't approve wallet connection through Sim")
|
@pytest.mark.skip("We currently can't approve wallet connection through Sim")
|
||||||
@pytest.mark.usefixtures("risk_accepted")
|
@pytest.mark.usefixtures("page", "risk_accepted")
|
||||||
def test_connect_vega_wallet(continuous_market, page: Page):
|
def test_connect_vega_wallet(continuous_market, page: Page):
|
||||||
page.goto(f"/#/markets/{continuous_market}")
|
page.goto(f"/#/markets/{continuous_market}")
|
||||||
page.get_by_test_id("order-price").fill("101")
|
page.get_by_test_id("order-price").fill("101")
|
||||||
@@ -25,7 +25,7 @@ def test_connect_vega_wallet(continuous_market, page: Page):
|
|||||||
expect(page.get_by_test_id("order-type-Limit")).to_be_checked()
|
expect(page.get_by_test_id("order-type-Limit")).to_be_checked()
|
||||||
expect(page.get_by_test_id("order-price")).to_have_value("101")
|
expect(page.get_by_test_id("order-price")).to_have_value("101")
|
||||||
|
|
||||||
@pytest.mark.usefixtures("risk_accepted")
|
@pytest.mark.usefixtures("page", "risk_accepted")
|
||||||
def test_sidebar_should_be_open_after_reload(continuous_market, page: Page):
|
def test_sidebar_should_be_open_after_reload(continuous_market, page: Page):
|
||||||
page.goto(f"/#/markets/{continuous_market}")
|
page.goto(f"/#/markets/{continuous_market}")
|
||||||
expect(page.get_by_test_id("deal-ticket-form")).to_be_visible()
|
expect(page.get_by_test_id("deal-ticket-form")).to_be_visible()
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ market_trading_mode = "market-trading-mode"
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip("tbd")
|
@pytest.mark.skip("tbd")
|
||||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
@pytest.mark.usefixtures("page", "vega", "continuous_market", "auth", "risk_accepted")
|
||||||
def test_margin_and_fees_estimations(continuous_market, vega: VegaService, page: Page):
|
def test_margin_and_fees_estimations(continuous_market, vega: VegaService, page: Page):
|
||||||
# setup continuous trading market with one user buy trade
|
# setup continuous trading market with one user buy trade
|
||||||
market_id = continuous_market
|
market_id = continuous_market
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user