Compare commits

..
Author SHA1 Message Date
Ben 5ce2c117be chore(trading): black formatting and removed double fixtures (#5506) 2023-12-19 14:49:41 +00:00
Art 3beeb0140c feat(wallet): disconnect if wallet unreachable (#5501) 2023-12-18 10:39:18 +00:00
m.ray 4b208e90bf fix(trading): internal timestamp date unix formatting (#5505) 2023-12-14 15:10:17 +00:00
Bartłomiej Głownia ec837854cf chore(trading): avoid portfolio and market page re-rendering on sidebar view change (#5499) 2023-12-14 14:12:19 +00:00
Bartłomiej Głownia 8b94a75ba4 feat(positions): improve tooltips (#5481) 2023-12-13 14:36:57 +00:00
Ben 08c57b6759 chore(trading): run against local console (#5500) 2023-12-13 12:46:16 +00:00
Ben bb2184498f chore(trading): update vega version (#5498) 2023-12-13 11:16:52 +00:00
Bartłomiej Głownia 8b07ce3024 fix(trading): fix tabs covered by orderbook splash (#5497) 2023-12-13 09:10:54 +00:00
Bartłomiej Głownia 8a110584dd feat(trading): improve desposit and withraw asset selection (#5493) 2023-12-13 08:47:22 +00:00
Bartłomiej Głownia b82615a3d8 fix(trading): do not add candles if they are older than requested date range (#5484) 2023-12-13 08:06:02 +00:00
Matthew Russell f178b85846 feat(trading): trading view (#5348) 2023-12-12 17:33:41 -08:00
Bartłomiej Głownia 0796f2b31f feat(environment): users controlled feature flags (#5425) 2023-12-12 13:53:20 +01:00
dalebennett1992 67be224138 Merge pull request #5492 from vegaprotocol/test/referrals2
test: add referrals tests
2023-12-12 10:33:57 +00:00
151 changed files with 3648 additions and 1631 deletions
+6 -2
View File
@@ -12,7 +12,8 @@ import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-web
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
import { DEFAULT_CACHE_CONFIG } from '@vegaprotocol/apollo-client';
import { RouterProvider } from 'react-router-dom';
import { router } from './routes/router-config';
import { useRouterConfig } from './routes/router-config';
import { createBrowserRouter } from 'react-router-dom';
import { t } from '@vegaprotocol/i18n';
import { Suspense } from 'react';
@@ -38,7 +39,10 @@ function App() {
}
>
<Suspense fallback={splashLoading}>
<RouterProvider router={router} fallbackElement={splashLoading} />
<RouterProvider
router={createBrowserRouter(useRouterConfig())}
fallbackElement={splashLoading}
/>
</Suspense>
</NodeGuard>
<NodeSwitcherDialog
@@ -14,7 +14,7 @@ import { Routes } from '../../routes/route-names';
import { NetworkSwitcher } from '@vegaprotocol/environment';
import type { Navigable } from '../../routes/router-config';
import { isNavigable } from '../../routes/router-config';
import { routerConfig } from '../../routes/router-config';
import { useRouterConfig } from '../../routes/router-config';
import { useMemo } from 'react';
import compact from 'lodash/compact';
import { Search } from '../search';
@@ -26,6 +26,7 @@ const routeToNavigationItem = (r: Navigable) => (
);
export const Header = () => {
const routerConfig = useRouterConfig();
const isHome = Boolean(useMatch(Routes.HOME));
const pages = routerConfig[0].children || [];
const mainItems = compact(
+292 -289
View File
@@ -18,7 +18,6 @@ import { t } from '@vegaprotocol/i18n';
import { Routes } from './route-names';
import { NetworkParameters } from './network-parameters';
import type { Params, RouteObject } from 'react-router-dom';
import { createBrowserRouter } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { MarketPage, MarketsPage } from './markets';
import type { ReactNode } from 'react';
@@ -29,7 +28,7 @@ import { truncateMiddle } from '@vegaprotocol/ui-toolkit';
import { remove0x } from '@vegaprotocol/utils';
import { PartyAccountsByAsset } from './parties/id/accounts';
import { Disclaimer } from './pages/disclaimer';
import { FLAGS } from '@vegaprotocol/environment';
import { useFeatureFlags } from '@vegaprotocol/environment';
import RestrictedPage from './restricted';
export type Navigable = {
@@ -60,311 +59,315 @@ type Route = RouteItem & {
children?: RouteItem[];
};
const partiesRoutes: Route[] = FLAGS.EXPLORER_PARTIES
? [
{
path: Routes.PARTIES,
element: <Party />,
handle: {
name: t('Parties'),
text: t('Parties'),
breadcrumb: () => <Link to={Routes.PARTIES}>{t('Parties')}</Link>,
},
children: [
{
index: true,
element: <Parties />,
},
{
path: ':party',
element: <Party />,
export const useRouterConfig = () => {
const featureFlags = useFeatureFlags((state) => state.flags);
children: [
{
index: true,
element: <PartySingle />,
handle: {
breadcrumb: (params: Params<string>) => (
<Link to={linkTo(Routes.PARTIES, params.party)}>
{truncateMiddle(params.party as string)}
</Link>
),
const partiesRoutes: Route[] = featureFlags.EXPLORER_PARTIES
? [
{
path: Routes.PARTIES,
element: <Party />,
handle: {
name: t('Parties'),
text: t('Parties'),
breadcrumb: () => <Link to={Routes.PARTIES}>{t('Parties')}</Link>,
},
children: [
{
index: true,
element: <Parties />,
},
{
path: ':party',
element: <Party />,
children: [
{
index: true,
element: <PartySingle />,
handle: {
breadcrumb: (params: Params<string>) => (
<Link to={linkTo(Routes.PARTIES, params.party)}>
{truncateMiddle(params.party as string)}
</Link>
),
},
},
},
{
path: 'assets',
element: <Party />,
handle: {
breadcrumb: (params: Params<string>) => (
<Link to={linkTo(Routes.PARTIES, params.party)}>
{truncateMiddle(params.party as string)}
</Link>
),
},
children: [
{
index: true,
element: <PartyAccountsByAsset />,
handle: {
breadcrumb: () => {
return t('Assets');
{
path: 'assets',
element: <Party />,
handle: {
breadcrumb: (params: Params<string>) => (
<Link to={linkTo(Routes.PARTIES, params.party)}>
{truncateMiddle(params.party as string)}
</Link>
),
},
children: [
{
index: true,
element: <PartyAccountsByAsset />,
handle: {
breadcrumb: () => {
return t('Assets');
},
},
},
},
],
],
},
],
},
],
},
]
: [];
const assetsRoutes: Route[] = featureFlags.EXPLORER_ASSETS
? [
{
path: Routes.ASSETS,
handle: {
name: t('Assets'),
text: t('Assets'),
breadcrumb: () => <Link to={Routes.ASSETS}>{t('Assets')}</Link>,
},
children: [
{
index: true,
element: <AssetsPage />,
},
{
path: ':assetId',
element: <AssetPage />,
handle: {
breadcrumb: (params: Params<string>) => (
<AssetLink assetId={params.assetId as string} />
),
},
],
},
],
},
]
: [];
const assetsRoutes: Route[] = FLAGS.EXPLORER_ASSETS
? [
{
path: Routes.ASSETS,
handle: {
name: t('Assets'),
text: t('Assets'),
breadcrumb: () => <Link to={Routes.ASSETS}>{t('Assets')}</Link>,
},
],
},
children: [
{
index: true,
element: <AssetsPage />,
]
: [];
const genesisRoutes: Route[] = featureFlags.EXPLORER_GENESIS
? [
{
path: Routes.GENESIS,
handle: {
name: t('Genesis'),
text: t('Genesis Parameters'),
breadcrumb: () => (
<Link to={Routes.GENESIS}>{t('Genesis Parameters')}</Link>
),
},
element: <Genesis />,
},
]
: [];
const governanceRoutes: Route[] = featureFlags.EXPLORER_GOVERNANCE
? [
{
path: Routes.GOVERNANCE,
handle: {
name: t('Governance proposals'),
text: t('Governance Proposals'),
breadcrumb: () => (
<Link to={Routes.GOVERNANCE}>{t('Governance Proposals')}</Link>
),
},
element: <Proposals />,
},
]
: [];
const marketsRoutes: Route[] = featureFlags.EXPLORER_MARKETS
? [
{
path: Routes.MARKETS,
handle: {
name: t('Markets'),
text: t('Markets'),
breadcrumb: () => <Link to={Routes.MARKETS}>{t('Markets')}</Link>,
},
children: [
{
index: true,
element: <MarketsPage />,
},
{
path: ':marketId',
element: <MarketPage />,
handle: {
breadcrumb: (params: Params<string>) => (
<MarketLink id={params.marketId as string} />
),
},
},
],
},
]
: [];
const networkParametersRoutes: Route[] =
featureFlags.EXPLORER_NETWORK_PARAMETERS
? [
{
path: ':assetId',
element: <AssetPage />,
path: Routes.NETWORK_PARAMETERS,
handle: {
breadcrumb: (params: Params<string>) => (
<AssetLink assetId={params.assetId as string} />
name: t('NetworkParameters'),
text: t('Network Parameters'),
breadcrumb: () => (
<Link to={Routes.NETWORK_PARAMETERS}>
{t('Network Parameters')}
</Link>
),
},
element: <NetworkParameters />,
},
],
},
]
: [];
]
: [];
const genesisRoutes: Route[] = FLAGS.EXPLORER_GENESIS
? [
{
path: Routes.GENESIS,
handle: {
name: t('Genesis'),
text: t('Genesis Parameters'),
breadcrumb: () => (
<Link to={Routes.GENESIS}>{t('Genesis Parameters')}</Link>
),
},
element: <Genesis />,
},
]
: [];
const governanceRoutes: Route[] = FLAGS.EXPLORER_GOVERNANCE
? [
{
path: Routes.GOVERNANCE,
handle: {
name: t('Governance proposals'),
text: t('Governance Proposals'),
breadcrumb: () => (
<Link to={Routes.GOVERNANCE}>{t('Governance Proposals')}</Link>
),
},
element: <Proposals />,
},
]
: [];
const marketsRoutes: Route[] = FLAGS.EXPLORER_MARKETS
? [
{
path: Routes.MARKETS,
handle: {
name: t('Markets'),
text: t('Markets'),
breadcrumb: () => <Link to={Routes.MARKETS}>{t('Markets')}</Link>,
},
children: [
{
index: true,
element: <MarketsPage />,
const validators: Route[] = featureFlags.EXPLORER_VALIDATORS
? [
{
path: Routes.VALIDATORS,
handle: {
name: t('Validators'),
text: t('Validators'),
breadcrumb: () => (
<Link to={Routes.VALIDATORS}>{t('Validators')}</Link>
),
},
{
path: ':marketId',
element: <MarketPage />,
handle: {
breadcrumb: (params: Params<string>) => (
<MarketLink id={params.marketId as string} />
),
element: <ValidatorsPage />,
},
]
: [];
const linkTo = (...segments: (string | undefined)[]) =>
compact(segments).join('/');
const routerConfig: Route[] = [
{
path: Routes.HOME,
element: <Layout />,
handle: {
name: t('Home'),
text: t('Home'),
breadcrumb: () => <Link to={Routes.HOME}>{t('Home')}</Link>,
},
errorElement: <ErrorBoundary />,
children: [
{
index: true,
element: <Home />,
},
{
path: Routes.TX,
handle: {
name: t('Txs'),
text: t('Transactions'),
breadcrumb: () => <Link to={Routes.TX}>{t('Transactions')}</Link>,
},
children: [
{
path: ':txHash',
element: <Tx />,
handle: {
breadcrumb: (params: Params<string>) => (
<Link to={linkTo(Routes.TX, params.txHash)}>
{truncateMiddle(remove0x(params.txHash as string))}
</Link>
),
},
},
{
index: true,
element: <TxsList />,
},
],
},
{
path: Routes.BLOCKS,
handle: {
name: t('Blocks'),
text: t('Blocks'),
breadcrumb: () => <Link to={Routes.BLOCKS}>{t('Blocks')}</Link>,
},
],
},
]
: [];
const networkParametersRoutes: Route[] = FLAGS.EXPLORER_NETWORK_PARAMETERS
? [
{
path: Routes.NETWORK_PARAMETERS,
handle: {
name: t('NetworkParameters'),
text: t('Network Parameters'),
breadcrumb: () => (
<Link to={Routes.NETWORK_PARAMETERS}>
{t('Network Parameters')}
</Link>
),
element: <BlockPage />,
children: [
{
index: true,
element: <Blocks />,
},
{
path: ':block',
element: <Block />,
handle: {
breadcrumb: (params: Params<string>) => (
<Link to={linkTo(Routes.BLOCKS, params.block)}>
{params.block}
</Link>
),
},
},
],
},
element: <NetworkParameters />,
},
]
: [];
const validators: Route[] = FLAGS.EXPLORER_VALIDATORS
? [
{
path: Routes.VALIDATORS,
handle: {
name: t('Validators'),
text: t('Validators'),
breadcrumb: () => (
<Link to={Routes.VALIDATORS}>{t('Validators')}</Link>
),
{
path: Routes.ORACLES,
handle: {
name: t('Oracles'),
text: t('Oracles'),
breadcrumb: () => <Link to={Routes.ORACLES}>{t('Oracles')}</Link>,
},
element: <OraclePage />,
children: [
{
index: true,
element: <Oracles />,
},
{
path: ':id',
element: <Oracle />,
handle: {
breadcrumb: (params: Params<string>) => (
<Link to={linkTo(Routes.ORACLES, params.id)}>
{truncateMiddle(params.id as string)}
</Link>
),
},
},
],
},
element: <ValidatorsPage />,
},
]
: [];
const linkTo = (...segments: (string | undefined)[]) =>
compact(segments).join('/');
export const routerConfig: Route[] = [
{
path: Routes.HOME,
element: <Layout />,
handle: {
name: t('Home'),
text: t('Home'),
breadcrumb: () => <Link to={Routes.HOME}>{t('Home')}</Link>,
{
path: Routes.DISCLAIMER,
element: <Disclaimer />,
handle: {
name: t('Disclaimer'),
text: t('Disclaimer'),
breadcrumb: () => (
<Link to={Routes.DISCLAIMER}>{t('Disclaimer')}</Link>
),
},
},
...partiesRoutes,
...assetsRoutes,
...genesisRoutes,
...governanceRoutes,
...marketsRoutes,
...networkParametersRoutes,
...validators,
],
},
errorElement: <ErrorBoundary />,
children: [
{
index: true,
element: <Home />,
{
path: Routes.RESTRICTED,
element: <RestrictedPage />,
handle: {
name: t('Restricted'),
text: t('Restricted'),
},
{
path: Routes.TX,
handle: {
name: t('Txs'),
text: t('Transactions'),
breadcrumb: () => <Link to={Routes.TX}>{t('Transactions')}</Link>,
},
children: [
{
path: ':txHash',
element: <Tx />,
handle: {
breadcrumb: (params: Params<string>) => (
<Link to={linkTo(Routes.TX, params.txHash)}>
{truncateMiddle(remove0x(params.txHash as string))}
</Link>
),
},
},
{
index: true,
element: <TxsList />,
},
],
},
{
path: Routes.BLOCKS,
handle: {
name: t('Blocks'),
text: t('Blocks'),
breadcrumb: () => <Link to={Routes.BLOCKS}>{t('Blocks')}</Link>,
},
element: <BlockPage />,
children: [
{
index: true,
element: <Blocks />,
},
{
path: ':block',
element: <Block />,
handle: {
breadcrumb: (params: Params<string>) => (
<Link to={linkTo(Routes.BLOCKS, params.block)}>
{params.block}
</Link>
),
},
},
],
},
{
path: Routes.ORACLES,
handle: {
name: t('Oracles'),
text: t('Oracles'),
breadcrumb: () => <Link to={Routes.ORACLES}>{t('Oracles')}</Link>,
},
element: <OraclePage />,
children: [
{
index: true,
element: <Oracles />,
},
{
path: ':id',
element: <Oracle />,
handle: {
breadcrumb: (params: Params<string>) => (
<Link to={linkTo(Routes.ORACLES, params.id)}>
{truncateMiddle(params.id as string)}
</Link>
),
},
},
],
},
{
path: Routes.DISCLAIMER,
element: <Disclaimer />,
handle: {
name: t('Disclaimer'),
text: t('Disclaimer'),
breadcrumb: () => (
<Link to={Routes.DISCLAIMER}>{t('Disclaimer')}</Link>
),
},
},
...partiesRoutes,
...assetsRoutes,
...genesisRoutes,
...governanceRoutes,
...marketsRoutes,
...networkParametersRoutes,
...validators,
],
},
{
path: Routes.RESTRICTED,
element: <RestrictedPage />,
handle: {
name: t('Restricted'),
text: t('Restricted'),
},
},
];
export const router = createBrowserRouter(routerConfig);
];
return routerConfig;
};
+17 -9
View File
@@ -2,7 +2,7 @@ import * as Sentry from '@sentry/react';
import { toBigNum } from '@vegaprotocol/utils';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet, useEagerConnect } from '@vegaprotocol/wallet';
import { FLAGS, useEnvironment } from '@vegaprotocol/environment';
import { useFeatureFlags, useEnvironment } from '@vegaprotocol/environment';
import { useWeb3React } from '@web3-react/core';
import React, { Suspense } from 'react';
import { useTranslation } from 'react-i18next';
@@ -15,21 +15,23 @@ import {
} from './contexts/app-state/app-state-context';
import { useContracts } from './contexts/contracts/contracts-context';
import { useRefreshAssociatedBalances } from './hooks/use-refresh-associated-balances';
import { Connectors } from './lib/vega-connectors';
import { useConnectors } from './lib/vega-connectors';
import { useSearchParams } from 'react-router-dom';
const useVegaWalletEagerConnect = () => {
const vegaConnecting = useEagerConnect(Connectors);
const connectors = useConnectors();
const vegaConnecting = useEagerConnect(connectors);
const { pubKey, connect } = useVegaWallet();
const [searchParams] = useSearchParams();
const [query] = React.useState(searchParams.get('address'));
if (query && !pubKey) {
connect(Connectors['view']);
connect(connectors.view);
}
return vegaConnecting;
};
export const AppLoader = ({ children }: { children: React.ReactElement }) => {
const featureFlags = useFeatureFlags((state) => state.flags);
const { t } = useTranslation();
const { account } = useWeb3React();
const { VEGA_URL } = useEnvironment();
@@ -79,10 +81,16 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
}
};
if (!FLAGS.GOVERNANCE_NETWORK_DOWN) {
if (!featureFlags.GOVERNANCE_NETWORK_DOWN) {
run();
}
}, [token, appDispatch, staking, vesting]);
}, [
token,
appDispatch,
staking,
vesting,
featureFlags.GOVERNANCE_NETWORK_DOWN,
]);
React.useEffect(() => {
if (account && pubKey) {
@@ -147,16 +155,16 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
};
// Only begin polling if network limits flag is set, as this is a new API not yet on mainnet 7/3/22
if (FLAGS.GOVERNANCE_NETWORK_LIMITS) {
if (featureFlags.GOVERNANCE_NETWORK_LIMITS) {
getNetworkLimits();
}
return () => {
stopPoll();
};
}, [appDispatch, VEGA_URL, t]);
}, [appDispatch, VEGA_URL, t, featureFlags.GOVERNANCE_NETWORK_LIMITS]);
if (FLAGS.GOVERNANCE_NETWORK_DOWN) {
if (featureFlags.GOVERNANCE_NETWORK_DOWN) {
return (
<Splash>
<SplashError />
@@ -7,16 +7,16 @@ import {
AppStateActionType,
useAppState,
} from '../../contexts/app-state/app-state-context';
import { Connectors } from '../../lib/vega-connectors';
import { useConnectors } from '../../lib/vega-connectors';
import { RiskMessage } from './risk-message';
export const VegaWalletDialogs = () => {
const { appState, appDispatch } = useAppState();
const connectors = useConnectors();
return (
<>
<VegaConnectDialog
connectors={Connectors}
connectors={connectors}
riskMessage={<RiskMessage />}
/>
@@ -30,7 +30,7 @@ export const VegaWalletDialogs = () => {
}
/>
<ViewAsDialog connector={Connectors.view} />
<ViewAsDialog connector={connectors.view} />
</>
);
};
+14 -9
View File
@@ -1,4 +1,5 @@
import { FLAGS } from '@vegaprotocol/environment';
import { useFeatureFlags } from '@vegaprotocol/environment';
import { useMemo } from 'react';
import {
JsonRpcConnector,
ViewConnector,
@@ -13,13 +14,17 @@ export const jsonRpc = new JsonRpcConnector();
export const injected = new InjectedConnector();
export const view = new ViewConnector(urlParams.get('address'));
export const snap = FLAGS.METAMASK_SNAPS
? new SnapConnector(DEFAULT_SNAP_ID)
: undefined;
export const snap = new SnapConnector(DEFAULT_SNAP_ID);
export const Connectors = {
injected,
jsonRpc,
view,
snap,
export const useConnectors = () => {
const featureFlags = useFeatureFlags((state) => state.flags);
return useMemo(
() => ({
injected,
jsonRpc,
view,
snap: featureFlags.METAMASK_SNAPS ? snap : undefined,
}),
[featureFlags.METAMASK_SNAPS]
);
};
+5 -4
View File
@@ -12,7 +12,7 @@ import { useRefreshAfterEpoch } from '../../hooks/use-refresh-after-epoch';
import { ProposalsListItem } from '../proposals/components/proposals-list-item';
import { ProtocolUpgradeProposalsListItem } from '../proposals/components/protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item';
import Routes from '../routes';
import { ExternalLinks, FLAGS } from '@vegaprotocol/environment';
import { ExternalLinks, useFeatureFlags } from '@vegaprotocol/environment';
import { removePaginationWrapper } from '@vegaprotocol/utils';
import { useNodesQuery } from '../staking/home/__generated__/Nodes';
import { useProposalsQuery } from '../proposals/proposals/__generated__/Proposals';
@@ -175,6 +175,7 @@ export const ValidatorDetailsLink = ({
};
const GovernanceHome = ({ name }: RouteChildProps) => {
const featureFlags = useFeatureFlags((state) => state.flags);
useDocumentTitle(name);
const { t } = useTranslation();
const {
@@ -186,9 +187,9 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
fetchPolicy: 'network-only',
errorPolicy: 'ignore',
variables: {
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
includeNewMarketProductFields: !!featureFlags.PRODUCT_PERPETUALS,
includeUpdateMarketStates: !!featureFlags.UPDATE_MARKET_STATE,
includeUpdateReferralPrograms: !!featureFlags.REFERRALS,
},
});
@@ -19,7 +19,7 @@ import {
mockWalletContext,
createUserVoteQueryMock,
} from '../../test-helpers/mocks';
import { FLAGS } from '@vegaprotocol/environment';
import { useFeatureFlags } from '@vegaprotocol/environment';
import { BrowserRouter } from 'react-router-dom';
import { VoteState } from '../vote-details/use-user-vote';
import { useNewTransferProposalDetails } from '@vegaprotocol/proposals';
@@ -62,8 +62,7 @@ describe('Proposal header', () => {
jest.clearAllMocks();
});
it('Renders New market proposal', () => {
const mockedFlags = jest.mocked(FLAGS);
mockedFlags.SUCCESSOR_MARKETS = true;
useFeatureFlags.setState({ flags: { SUCCESSOR_MARKETS: true } });
renderComponent(
generateProposal({
rationale: {
@@ -12,7 +12,7 @@ import {
useNewTransferProposalDetails,
useSuccessorMarketProposalDetails,
} from '@vegaprotocol/proposals';
import { FLAGS } from '@vegaprotocol/environment';
import { useFeatureFlags } from '@vegaprotocol/environment';
import Routes from '../../../routes';
import { Link } from 'react-router-dom';
import type { VoteState } from '../vote-details/use-user-vote';
@@ -28,6 +28,7 @@ export const ProposalHeader = ({
isListItem?: boolean;
voteState?: VoteState | null;
}) => {
const featureFlags = useFeatureFlags((state) => state.flags);
const { t } = useTranslation();
const change = proposal?.terms.change;
@@ -54,13 +55,14 @@ export const ProposalHeader = ({
switch (change?.__typename) {
case 'NewMarket': {
proposalType =
FLAGS.PRODUCT_PERPETUALS && change?.instrument?.product?.__typename
featureFlags.PRODUCT_PERPETUALS &&
change?.instrument?.product?.__typename
? `NewMarket${change?.instrument?.product?.__typename}`
: 'NewMarket';
fallbackTitle = t('NewMarketProposal');
details = (
<>
{FLAGS.SUCCESSOR_MARKETS && (
{featureFlags.SUCCESSOR_MARKETS && (
<SuccessorCode proposalId={proposal?.id} />
)}
<span>
@@ -82,13 +84,13 @@ export const ProposalHeader = ({
}
case 'UpdateMarketState': {
proposalType =
FLAGS.UPDATE_MARKET_STATE && change?.updateType
featureFlags.UPDATE_MARKET_STATE && change?.updateType
? t(change.updateType)
: 'UpdateMarketState';
fallbackTitle = t('UpdateMarketStateProposal');
details = (
<span>
{FLAGS.UPDATE_MARKET_STATE &&
{featureFlags.UPDATE_MARKET_STATE &&
change?.market?.id &&
change.updateType ? (
<>
@@ -177,14 +179,14 @@ export const ProposalHeader = ({
case 'NewTransfer':
proposalType = 'NewTransfer';
fallbackTitle = t('NewTransferProposal');
details = FLAGS.GOVERNANCE_TRANSFERS ? (
details = featureFlags.GOVERNANCE_TRANSFERS ? (
<NewTransferSummary proposalId={proposal?.id} />
) : null;
break;
case 'CancelTransfer':
proposalType = 'CancelTransfer';
fallbackTitle = t('CancelTransferProposal');
details = FLAGS.GOVERNANCE_TRANSFERS ? (
details = featureFlags.GOVERNANCE_TRANSFERS ? (
<CancelTransferSummary proposalId={proposal?.id} />
) : null;
break;
@@ -26,7 +26,7 @@ import {
ProposalCancelTransferDetails,
ProposalTransferDetails,
} from '../proposal-transfer';
import { FLAGS } from '@vegaprotocol/environment';
import { useFeatureFlags } from '@vegaprotocol/environment';
import { ProposalUpdateBenefitTiers } from '../proposal-update-benefit-tiers';
export interface ProposalProps {
@@ -53,6 +53,7 @@ export const Proposal = ({
originalMarketProposalRestData,
mostRecentlyEnactedAssociatedMarketProposal,
}: ProposalProps) => {
const featureFlags = useFeatureFlags((state) => state.flags);
const { t } = useTranslation();
const { submit, Dialog, finalizedVote, transaction } = useVoteSubmit();
const { voteState, voteDatetime } = useUserVote(proposal?.id, finalizedVote);
@@ -132,7 +133,7 @@ export const Proposal = ({
}
// Show governance transfer details only if the GOVERNANCE_TRANSFERS flag is on.
const governanceTransferDetails = FLAGS.GOVERNANCE_TRANSFERS && (
const governanceTransferDetails = featureFlags.GOVERNANCE_TRANSFERS && (
<>
{proposal.terms.change.__typename === 'NewTransfer' && (
/** Governance New Transfer Details */
@@ -105,8 +105,6 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
yesLPPercentage,
yesTokens,
noTokens,
yesEquityLikeShareWeight,
noEquityLikeShareWeight,
totalEquityLikeShareWeight,
requiredMajorityPercentage,
requiredMajorityLPPercentage,
@@ -202,42 +200,17 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
<div className="flex items-center gap-1">
<span>{t('liquidityProviderVotesFor')}:</span>
<Tooltip
description={formatNumber(
yesEquityLikeShareWeight,
defaultDP
)}
description={
<span>{yesLPPercentage.toFixed(defaultDP)}%</span>
}
>
<button>
<CompactVotes number={yesEquityLikeShareWeight} />
</button>
<button>{yesLPPercentage.toFixed(0)}%</button>
</Tooltip>
<span>
(
<Tooltip
description={
<span>{yesLPPercentage.toFixed(defaultDP)}%</span>
}
>
<button>{yesLPPercentage.toFixed(0)}%</button>
</Tooltip>
)
</span>
</div>
<div className="flex items-center gap-1">
<span>{t('liquidityProviderVotesAgainst')}:</span>
<Tooltip
description={formatNumber(
noEquityLikeShareWeight,
defaultDP
)}
>
<button>
<CompactVotes number={noEquityLikeShareWeight} />
</button>
</Tooltip>
<span>
(
<Tooltip
description={
<span>{noLPPercentage.toFixed(defaultDP)}%</span>
@@ -245,7 +218,6 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
>
<button>{noLPPercentage.toFixed(0)}%</button>
</Tooltip>
)
</span>
</div>
</div>
@@ -282,13 +254,10 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
defaultDP
)}
>
<button>
<CompactVotes number={totalEquityLikeShareWeight} />
</button>
<span>
{totalEquityLikeShareWeight.times(100).toString()}%
</span>
</Tooltip>
<span>
({totalEquityLikeShareWeight.toFixed(defaultDP)}%)
</span>
</div>
</div>
</section>
@@ -81,12 +81,7 @@ export const useVoteInformation = ({
const yesPercentage = totalTokensVoted.isZero()
? new BigNumber(0)
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
const yesLPPercentage = totalEquityLikeShareWeight.isZero()
? new BigNumber(0)
: yesEquityLikeShareWeight
.multipliedBy(100)
.dividedBy(totalEquityLikeShareWeight);
const yesLPPercentage = yesEquityLikeShareWeight.multipliedBy(100);
const noPercentage = totalTokensVoted.isZero()
? new BigNumber(0)
@@ -15,10 +15,11 @@ import {
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { useParentMarketIdQuery } from '@vegaprotocol/markets';
import { FLAGS } from '@vegaprotocol/environment';
import { useFeatureFlags } from '@vegaprotocol/environment';
import { useSuccessorMarketProposalDetails } from '@vegaprotocol/proposals';
export const ProposalContainer = () => {
const featureFlags = useFeatureFlags((state) => state.flags);
const [
mostRecentlyEnactedAssociatedMarketProposal,
setMostRecentlyEnactedAssociatedMarketProposal,
@@ -59,9 +60,9 @@ export const ProposalContainer = () => {
errorPolicy: 'ignore',
variables: {
proposalId: params.proposalId || '',
includeNewMarketProductField: !!FLAGS.PRODUCT_PERPETUALS,
includeUpdateMarketState: !!FLAGS.UPDATE_MARKET_STATE,
includeUpdateReferralProgram: !!FLAGS.REFERRALS,
includeNewMarketProductField: !!featureFlags.PRODUCT_PERPETUALS,
includeUpdateMarketState: !!featureFlags.UPDATE_MARKET_STATE,
includeUpdateReferralProgram: !!featureFlags.REFERRALS,
},
skip: !params.proposalId,
});
@@ -120,7 +121,7 @@ export const ProposalContainer = () => {
variables: {
marketId: marketData?.id || '',
},
skip: !FLAGS.SUCCESSOR_MARKETS || !isSuccessor || !marketData?.id,
skip: !featureFlags.SUCCESSOR_MARKETS || !isSuccessor || !marketData?.id,
});
const {
@@ -133,7 +134,7 @@ export const ProposalContainer = () => {
variables: {
marketId: parentMarketId?.market?.parentMarketID || '',
skip:
!FLAGS.SUCCESSOR_MARKETS ||
!featureFlags.SUCCESSOR_MARKETS ||
!isSuccessor ||
!parentMarketId?.market?.parentMarketID,
},
@@ -17,7 +17,7 @@ import {
} from './__generated__/Proposals';
import { type ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { useProtocolUpgradeProposalsQuery } from '@vegaprotocol/proposals';
import { FLAGS } from '@vegaprotocol/environment';
import { useFeatureFlags } from '@vegaprotocol/environment';
export function getNotRejectedProposals(data?: ProposalFieldsFragment[]) {
return flow([
@@ -43,15 +43,16 @@ export function getNotRejectedProtocolUpgradeProposals<
}
export const ProposalsContainer = () => {
const featureFlags = useFeatureFlags((state) => state.flags);
const { t } = useTranslation();
const { data, loading, error } = useProposalsQuery({
pollInterval: 5000,
fetchPolicy: 'network-only',
errorPolicy: 'ignore',
variables: {
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
includeNewMarketProductFields: !!featureFlags.PRODUCT_PERPETUALS,
includeUpdateMarketStates: !!featureFlags.UPDATE_MARKET_STATE,
includeUpdateReferralPrograms: !!featureFlags.REFERRALS,
},
});
@@ -10,7 +10,7 @@ import { removePaginationWrapper } from '@vegaprotocol/utils';
import flow from 'lodash/flow';
import orderBy from 'lodash/orderBy';
import { ProposalState } from '@vegaprotocol/types';
import { FLAGS } from '@vegaprotocol/environment';
import { useFeatureFlags } from '@vegaprotocol/environment';
const orderByDate = (arr: ProposalFieldsFragment[]) =>
orderBy(
@@ -33,15 +33,16 @@ export function getRejectedProposals(data?: ProposalFieldsFragment[] | null) {
}
export const RejectedProposalsContainer = () => {
const featureFlags = useFeatureFlags((state) => state.flags);
const { t } = useTranslation();
const { data, loading, error } = useProposalsQuery({
pollInterval: 5000,
fetchPolicy: 'network-only',
errorPolicy: 'ignore',
variables: {
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
includeNewMarketProductFields: !!featureFlags.PRODUCT_PERPETUALS,
includeUpdateMarketStates: !!featureFlags.UPDATE_MARKET_STATE,
includeUpdateReferralPrograms: !!featureFlags.REFERRALS,
},
});
+4 -1
View File
@@ -24,4 +24,7 @@ NX_STOP_ORDERS=true
NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=true
NX_REFERRALS=true
NX_REFERRALS=true
NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=
+3
View File
@@ -28,3 +28,6 @@ NX_REFERRALS=true
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=
+1 -15
View File
@@ -9,8 +9,6 @@ import { TradeGrid } from './trade-grid';
import { TradePanels } from './trade-panels';
import { useNavigate, useParams } from 'react-router-dom';
import { Links } from '../../lib/links';
import { ViewType, useSidebar } from '../../components/sidebar';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
import { useT, ns } from '../../lib/use-t';
import { Trans } from 'react-i18next';
@@ -61,9 +59,6 @@ export const MarketPage = () => {
const t = useT();
const { marketId } = useParams();
const navigate = useNavigate();
const currentRouteId = useGetCurrentRouteId();
const { setViews, getView } = useSidebar();
const view = getView(currentRouteId);
const { screenSize } = useScreenDimensions();
const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize);
const update = useGlobalStore((store) => store.update);
@@ -72,20 +67,11 @@ export const MarketPage = () => {
const { data, loading } = useMarket(marketId);
useEffect(() => {
if (data?.id && data.id !== lastMarketId && !closed) {
if (data?.id && data.id !== lastMarketId) {
update({ marketId: data.id });
}
}, [update, lastMarketId, data?.id]);
useEffect(() => {
if (largeScreen && view === undefined) {
setViews(
{ type: closed ? ViewType.Info : ViewType.Order },
currentRouteId
);
}
}, [setViews, view, currentRouteId, largeScreen]);
const pinnedAsset = data && getAsset(data);
const tradeView = useMemo(() => {
@@ -18,7 +18,7 @@ import {
MarketSuccessorProposalBanner,
MarketTerminationBanner,
} from '../../components/market-banner';
import { FLAGS } from '@vegaprotocol/environment';
import { useFeatureFlags } from '@vegaprotocol/environment';
import { useT } from '../../lib/use-t';
import { ErrorBoundary } from '../../components/error-boundary';
@@ -35,6 +35,7 @@ const MainGrid = memo(
marketId: string;
pinnedAsset?: PinnedAsset;
}) => {
const featureFlags = useFeatureFlags((state) => state.flags);
const t = useT();
const { data: market } = useMarket(marketId);
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'top' });
@@ -61,10 +62,10 @@ const MainGrid = memo(
id="chart"
overflowHidden
name={t('Chart')}
menu={<TradingViews.candles.menu />}
menu={<TradingViews.chart.menu />}
>
<ErrorBoundary feature="chart">
<TradingViews.candles.component marketId={marketId} />
<TradingViews.chart.component marketId={marketId} />
</ErrorBoundary>
</Tab>
<Tab id="depth" name={t('Depth')}>
@@ -165,7 +166,7 @@ const MainGrid = memo(
<TradingViews.orders.component />
</ErrorBoundary>
</Tab>
{FLAGS.STOP_ORDERS ? (
{featureFlags.STOP_ORDERS ? (
<Tab id="stop-orders" name={t('Stop orders')}>
<ErrorBoundary feature="stop-orders">
<TradingViews.stopOrders.component />
@@ -196,6 +197,7 @@ const MainGrid = memo(
MainGrid.displayName = 'MainGrid';
export const TradeGrid = ({ market, pinnedAsset }: TradeGridProps) => {
const featureFlags = useFeatureFlags((state) => state.flags);
const wrapperClasses = classNames(
'h-full grid',
'grid-rows-[min-content_1fr]'
@@ -204,7 +206,7 @@ export const TradeGrid = ({ market, pinnedAsset }: TradeGridProps) => {
return (
<div className={wrapperClasses}>
<div>
{FLAGS.SUCCESSOR_MARKETS && (
{featureFlags.SUCCESSOR_MARKETS && (
<>
<MarketSuccessorBanner market={market} />
<MarketSuccessorProposalBanner marketId={market?.id} />
@@ -4,7 +4,6 @@ import { OracleBanner } from '@vegaprotocol/markets';
import { useState } from 'react';
import AutoSizer from 'react-virtualized-auto-sizer';
import classNames from 'classnames';
import { FLAGS } from '@vegaprotocol/environment';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useT } from '../../lib/use-t';
import {
@@ -15,6 +14,7 @@ import {
import { ErrorBoundary } from '../../components/error-boundary';
import { type TradingView } from './trade-views';
import { TradingViews } from './trade-views';
import { useFeatureFlags } from '@vegaprotocol/environment';
interface TradePanelsProps {
market: Market | null;
@@ -22,7 +22,8 @@ interface TradePanelsProps {
}
export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
const [view, setView] = useState<TradingView>('candles');
const featureFlags = useFeatureFlags((state) => state.flags);
const [view, setView] = useState<TradingView>('chart');
const renderView = () => {
const Component = TradingViews[view].component;
@@ -49,7 +50,7 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
const Menu = viewCfg.menu;
return (
<div className="flex gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
<div className="flex items-center justify-end gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
<Menu />
</div>
);
@@ -61,7 +62,7 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
return (
<div className="h-full grid grid-rows-[min-content_min-content_1fr_min-content]">
<div>
{FLAGS.SUCCESSOR_MARKETS && (
{featureFlags.SUCCESSOR_MARKETS && (
<>
<MarketSuccessorBanner market={market} />
<MarketSuccessorProposalBanner marketId={market?.id} />
@@ -148,7 +149,7 @@ const useViewLabel = (view: TradingView) => {
const t = useT();
const labels = {
candles: t('Candles'),
chart: t('Chart'),
depth: t('Depth'),
liquidity: t('Liquidity'),
funding: t('Funding'),
@@ -1,8 +1,4 @@
import { DepthChartContainer } from '@vegaprotocol/market-depth';
import {
CandlesChartContainer,
CandlesMenu,
} from '@vegaprotocol/candles-chart';
import { Filter, OpenOrdersMenu } from '@vegaprotocol/orders';
import { TradesContainer } from '../../components/trades-container';
import { OrderbookContainer } from '../../components/orderbook-container';
@@ -16,13 +12,14 @@ import { OrdersContainer } from '../../components/orders-container';
import { StopOrdersContainer } from '../../components/stop-orders-container';
import { AccountsMenu } from '../../components/accounts-menu';
import { PositionsMenu } from '../../components/positions-menu';
import { ChartContainer, ChartMenu } from '../../components/chart-container';
export type TradingView = keyof typeof TradingViews;
export const TradingViews = {
candles: {
component: CandlesChartContainer,
menu: CandlesMenu,
chart: {
component: ChartContainer,
menu: ChartMenu,
},
depth: {
component: DepthChartContainer,
@@ -4,9 +4,26 @@ import {
SidebarButton,
SidebarDivider,
ViewType,
useSidebar,
} from '../../components/sidebar';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
import { useT } from '../../lib/use-t';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
import { useEffect } from 'react';
const ViewInitializer = () => {
const currentRouteId = useGetCurrentRouteId();
const { setViews, getView } = useSidebar();
const view = getView(currentRouteId);
const { screenSize } = useScreenDimensions();
const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize);
useEffect(() => {
if (largeScreen && view === undefined) {
setViews({ type: ViewType.Order }, currentRouteId);
}
}, [setViews, view, currentRouteId, largeScreen]);
return null;
};
export const MarketsSidebar = () => {
const t = useT();
@@ -37,6 +54,7 @@ export const MarketsSidebar = () => {
path=":marketId"
element={
<>
<ViewInitializer />
<SidebarDivider />
<SidebarButton
view={ViewType.Order}
@@ -39,11 +39,21 @@ const WithdrawalsIndicator = () => {
);
};
export const Portfolio = () => {
const t = useT();
const SidebarViewInitializer = () => {
const currentRouteId = useGetCurrentRouteId();
const { getView, setViews } = useSidebar();
const view = getView(currentRouteId);
// Make transfer sidebar open by default
useEffect(() => {
if (view === undefined) {
setViews({ type: ViewType.Transfer }, currentRouteId);
}
}, [view, setViews, currentRouteId]);
return null;
};
export const Portfolio = () => {
const t = useT();
const { updateTitle } = usePageTitleStore((store) => ({
updateTitle: store.updateTitle,
@@ -53,17 +63,11 @@ export const Portfolio = () => {
updateTitle(titlefy([t('Portfolio')]));
}, [updateTitle, t]);
// Make transfer sidebar open by default
useEffect(() => {
if (view === undefined) {
setViews({ type: ViewType.Transfer }, currentRouteId);
}
}, [view, setViews, currentRouteId]);
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'portfolio' });
const wrapperClasses = 'p-0.5 h-full max-h-full flex flex-col';
return (
<div className={wrapperClasses}>
<SidebarViewInitializer />
<ResizableGrid vertical onChange={handleOnLayoutChange}>
<ResizableGridPanel minSize={75}>
<PortfolioGridChild>
@@ -13,6 +13,7 @@ import { ViewType, useSidebar } from '../sidebar';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
import { useT } from '../../lib/use-t';
import { usePersistentDepositStore } from '@vegaprotocol/deposits';
export const AccountsContainer = ({
pinnedAsset,
@@ -25,6 +26,7 @@ export const AccountsContainer = ({
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
const currentRouteId = useGetCurrentRouteId();
const setViews = useSidebar((store) => store.setViews);
const setDepositAsset = usePersistentDepositStore((store) => store.saveValue);
const gridStore = useAccountStore((store) => store.gridStore);
const updateGridStore = useAccountStore((store) => store.updateGridStore);
@@ -55,7 +57,10 @@ export const AccountsContainer = ({
setViews({ type: ViewType.Withdraw, assetId }, currentRouteId);
}}
onClickDeposit={(assetId) => {
setViews({ type: ViewType.Deposit, assetId }, currentRouteId);
setViews({ type: ViewType.Deposit }, currentRouteId);
if (assetId) {
setDepositAsset({ assetId });
}
}}
onClickTransfer={(assetId) => {
setViews({ type: ViewType.Transfer, assetId }, currentRouteId);
@@ -0,0 +1,66 @@
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);
});
});
@@ -0,0 +1,120 @@
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;
};
@@ -0,0 +1,140 @@
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,14 +1,12 @@
import 'pennant/dist/style.css';
import {
ChartType,
Interval,
Overlay,
Study,
chartTypeLabels,
intervalLabels,
overlayLabels,
studyLabels,
} from 'pennant';
import { Trans } from 'react-i18next';
import {
TradingButton,
TradingDropdown,
@@ -20,10 +18,21 @@ import {
TradingDropdownTrigger,
Icon,
} from '@vegaprotocol/ui-toolkit';
import { type IconName } from '@blueprintjs/icons';
import { IconNames } from '@blueprintjs/icons';
import { useCandlesChartSettings } from './use-candles-chart-settings';
import { useT } from './use-t';
import { Interval } from '@vegaprotocol/types';
import { useEnvironment } from '@vegaprotocol/environment';
import { ALLOWED_TRADINGVIEW_HOSTNAMES } from '@vegaprotocol/trading-view';
import { IconNames, type IconName } from '@blueprintjs/icons';
import { useChartSettings } from './use-chart-settings';
import { useT } from '../../lib/use-t';
const INTERVALS = [
Interval.INTERVAL_I1M,
Interval.INTERVAL_I5M,
Interval.INTERVAL_I15M,
Interval.INTERVAL_I1H,
Interval.INTERVAL_I6H,
Interval.INTERVAL_I1D,
];
const chartTypeIcon = new Map<ChartType, IconName>([
[ChartType.AREA, IconNames.TIMELINE_AREA_CHART],
@@ -32,30 +41,46 @@ const chartTypeIcon = new Map<ChartType, IconName>([
[ChartType.OHLC, IconNames.WATERFALL_CHART],
]);
export const CandlesMenu = () => {
export const ChartMenu = () => {
const { CHARTING_LIBRARY_PATH } = useEnvironment();
const {
chartlib,
interval,
chartType,
studies,
overlays,
setChartlib,
setInterval,
setType,
setStudies,
setOverlays,
} = useCandlesChartSettings();
} = useChartSettings();
const t = useT();
const triggerClasses = 'text-xs';
const contentAlign = 'end';
const triggerClasses = 'text-xs';
const triggerButtonProps = { size: 'extra-small' } as const;
return (
const isPennant = chartlib === 'pennant';
const commonMenuItems = (
<TradingButton
onClick={() => {
setChartlib(isPennant ? 'tradingview' : 'pennant');
}}
size="extra-small"
>
{isPennant ? 'TradingView' : t('Vega chart')}
</TradingButton>
);
const pennantMenuItems = (
<>
<TradingDropdown
trigger={
<TradingDropdownTrigger className={triggerClasses}>
<TradingButton {...triggerButtonProps}>
{t('Interval: {{interval}}', {
interval: intervalLabels[interval],
interval: t(interval),
})}
</TradingButton>
</TradingDropdownTrigger>
@@ -68,13 +93,13 @@ export const CandlesMenu = () => {
setInterval(value as Interval);
}}
>
{Object.values(Interval).map((timeInterval) => (
{INTERVALS.map((timeInterval) => (
<TradingDropdownRadioItem
key={timeInterval}
inset
value={timeInterval}
>
{intervalLabels[timeInterval]}
{t(timeInterval)}
<TradingDropdownItemIndicator />
</TradingDropdownRadioItem>
))}
@@ -158,4 +183,50 @@ export const CandlesMenu = () => {
</TradingDropdown>
</>
);
const tradingViewMenuItems = (
<p className="text-xs mr-2 whitespace-nowrap">
<Trans
i18nKey="Chart by <0>TradingView</0>"
components={[
// eslint-disable-next-line
<a
className="underline"
target="_blank"
href="https://www.tradingview.com"
/>,
]}
/>
</p>
);
if (!ALLOWED_TRADINGVIEW_HOSTNAMES.includes(window.location.hostname)) {
return pennantMenuItems;
}
if (!CHARTING_LIBRARY_PATH) {
return pennantMenuItems;
}
switch (chartlib) {
case 'tradingview': {
return (
<>
{tradingViewMenuItems}
{commonMenuItems}
</>
);
}
case 'pennant': {
return (
<>
{pennantMenuItems}
{commonMenuItems}
</>
);
}
default: {
throw new Error('invalid chart lib');
}
}
};
@@ -0,0 +1,2 @@
export { ChartContainer } from './chart-container';
export { ChartMenu } from './chart-menu';
@@ -1,18 +1,23 @@
import { getValidItem, getValidSubset } from '@vegaprotocol/react-helpers';
import { ChartType, Interval, Study } from 'pennant';
import { Overlay } from 'pennant';
import { ChartType, Overlay, Study } from 'pennant';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { Interval } from '@vegaprotocol/types';
import { getValidItem, getValidSubset } from '@vegaprotocol/react-helpers';
type StudySizes = { [S in Study]?: number };
export type Chartlib = 'pennant' | 'tradingview';
interface StoredSettings {
chartlib: Chartlib;
// For interval we use the enum from @vegaprotocol/types, this is to make mapping between different
// chart types easier and more consistent
interval: Interval;
type: ChartType;
overlays: Overlay[];
studies: Study[];
studySizes: StudySizes;
tradingViewStudies: string[];
}
export const STUDY_SIZE = 90;
@@ -25,20 +30,24 @@ const STUDY_ORDER: Study[] = [
];
export const DEFAULT_CHART_SETTINGS = {
interval: Interval.I15M,
chartlib: 'pennant' as const,
interval: Interval.INTERVAL_I15M,
type: ChartType.CANDLE,
overlays: [Overlay.MOVING_AVERAGE],
studies: [Study.MACD, Study.VOLUME],
studySizes: {},
tradingViewStudies: ['Volume'],
};
export const useCandlesChartSettingsStore = create<
export const useChartSettingsStore = create<
StoredSettings & {
setType: (type: ChartType) => void;
setInterval: (interval: Interval) => void;
setOverlays: (overlays?: Overlay[]) => void;
setStudies: (studies?: Study[]) => void;
setStudySizes: (sizes: number[]) => void;
setChartlib: (lib: Chartlib) => void;
setTradingViewStudies: (studies: string[]) => void;
}
>()(
persist(
@@ -81,6 +90,16 @@ export const useCandlesChartSettingsStore = create<
});
});
},
setChartlib: (lib) => {
set((state) => {
state.chartlib = lib;
});
},
setTradingViewStudies: (studies: string[]) => {
set((state) => {
state.tradingViewStudies = studies;
});
},
})),
{
name: 'vega_candles_chart_store',
@@ -88,13 +107,13 @@ export const useCandlesChartSettingsStore = create<
)
);
export const useCandlesChartSettings = () => {
const settings = useCandlesChartSettingsStore();
export const useChartSettings = () => {
const settings = useChartSettingsStore();
const interval: Interval = getValidItem(
settings.interval,
Object.values(Interval),
Interval.I15M
Interval.INTERVAL_I15M
);
const chartType: ChartType = getValidItem(
@@ -122,15 +141,19 @@ export const useCandlesChartSettings = () => {
});
return {
chartlib: settings.chartlib,
interval,
chartType,
overlays,
studies,
studySizes,
tradingViewStudies: settings.tradingViewStudies,
setInterval: settings.setInterval,
setType: settings.setType,
setStudies: settings.setStudies,
setOverlays: settings.setOverlays,
setStudySizes: settings.setStudySizes,
setChartlib: settings.setChartlib,
setTradingViewStudies: settings.setTradingViewStudies,
};
};
@@ -5,7 +5,7 @@ import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { Navbar } from './navbar';
import { useGlobalStore } from '../../stores';
import { ENV, FLAGS } from '@vegaprotocol/environment';
import { ENV, useFeatureFlags } from '@vegaprotocol/environment';
jest.mock('@vegaprotocol/proposals', () => ({
ProtocolUpgradeCountdown: () => null,
@@ -48,8 +48,7 @@ describe('Navbar', () => {
beforeAll(() => {
useGlobalStore.setState({ marketId });
const mockedFLAGS = jest.mocked(FLAGS);
mockedFLAGS.REFERRALS = true;
useFeatureFlags.setState({ flags: { REFERRALS: true } });
const mockedENV = jest.mocked(ENV);
mockedENV.VEGA_TOKEN_URL = 'governance';
});
+3 -2
View File
@@ -6,7 +6,7 @@ import {
Networks,
DApp,
useLinks,
FLAGS,
useFeatureFlags,
useEnvNameMapping,
} from '@vegaprotocol/environment';
import { useGlobalStore } from '../../stores';
@@ -157,6 +157,7 @@ export const Navbar = ({
* of the navigation
*/
const NavbarMenu = ({ onClick }: { onClick: () => void }) => {
const featureFlags = useFeatureFlags((state) => state.flags);
const t = useT();
const envNameMapping = useEnvNameMapping();
const { VEGA_ENV, VEGA_NETWORKS, GITHUB_FEEDBACK_URL } = useEnvironment();
@@ -201,7 +202,7 @@ const NavbarMenu = ({ onClick }: { onClick: () => void }) => {
{t('Portfolio')}
</NavbarLink>
</NavbarItem>
{FLAGS.REFERRALS && (
{featureFlags.REFERRALS && (
<NavbarItem>
<NavbarLink end={false} to={Links.REFERRALS()} onClick={onClick}>
{t('Referrals')}
+13 -1
View File
@@ -10,6 +10,18 @@ import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
import { useState, type ReactNode } from 'react';
import classNames from 'classnames';
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 = () => {
const t = useT();
@@ -120,7 +132,7 @@ const SettingsGroup = ({
})}
>
<div className={classNames({ 'w-3/4': inline, 'mb-2': !inline })}>
<label className="text-sm">{label}</label>
<div className="text-sm">{label}</div>
{helpText && <p className="text-xs text-muted">{helpText}</p>}
</div>
{children}
@@ -24,7 +24,13 @@ import classNames from 'classnames';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
import { useT } from '../../lib/use-t';
export const VegaWalletConnectButton = () => {
export const VegaWalletConnectButton = ({
intent = Intent.None,
onClick,
}: {
intent?: Intent;
onClick?: () => void;
}) => {
const t = useT();
const [dropdownOpen, setDropdownOpen] = useState(false);
const openVegaWalletDialog = useVegaWalletDialogStore(
@@ -117,9 +123,12 @@ export const VegaWalletConnectButton = () => {
return (
<Button
data-testid="connect-vega-wallet"
onClick={openVegaWalletDialog}
onClick={() => {
onClick?.();
openVegaWalletDialog();
}}
size="small"
intent={Intent.None}
intent={intent}
icon={<VegaIcon name={VegaIconNames.ARROW_RIGHT} size={14} />}
>
<span className="whitespace-nowrap uppercase">
@@ -3,7 +3,7 @@ import { matchPath, useLocation } from 'react-router-dom';
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/environment';
import { VegaConnectDialog } from '@vegaprotocol/wallet';
import { Connectors } from '../../lib/vega-connectors';
import { useConnectors } from '../../lib/vega-connectors';
import { useT } from '../../lib/use-t';
import { Routes } from '../../lib/links';
import { RiskMessage } from './risk-message';
@@ -20,6 +20,7 @@ export const WelcomeDialog = () => {
const { pathname } = useLocation();
const t = useT();
const { VEGA_ENV } = useEnvironment();
const connectors = useConnectors();
const dismissed = useOnboardingStore((store) => store.dismissed);
const dialogOpen = useOnboardingStore((store) => store.dialogOpen);
const dismiss = useOnboardingStore((store) => store.dismiss);
@@ -43,7 +44,7 @@ export const WelcomeDialog = () => {
const content = walletDialogOpen ? (
<VegaConnectDialog
connectors={Connectors}
connectors={connectors}
riskMessage={<RiskMessage />}
onClose={() => setWalletDialogOpen(false)}
contentOnly
+1 -1
View File
@@ -1,2 +1,2 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest
VEGA_VERSION=v0.73.8
VEGA_VERSION=v0.73.9
+1 -1
View File
@@ -1,2 +1,2 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:develop
VEGA_VERSION=v0.73.8
VEGA_VERSION=v0.73.9
+20 -2
View File
@@ -87,10 +87,9 @@ docker build -f docker/node-outside-docker.Dockerfile --build-arg APP=trading --
## Running Tests 🧪
Before running make sure the docker daemon is runnign so that the app can be served.
Before running make sure the docker daemon is running.
To run a specific test, use the `-k` option followed by the name of the test.
Run all tests:
```bash
@@ -109,6 +108,25 @@ Run from anywhere:
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 use the flag --local-server
```bash
poetry run pytest -k "test_name" -s --headed --local-server
```
## 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.
+17 -6
View File
@@ -7,7 +7,6 @@ import time
import docker
import http.server
from contextlib import contextmanager
from vega_sim.null_service import VegaServiceNull
from playwright.sync_api import Browser, Page
@@ -102,11 +101,23 @@ def init_vega(request=None):
logger.info(f"Removing container {container.id}")
container.remove()
def pytest_addoption(parser):
parser.addoption(
"--local-server", action="store_true", default=False,
help="Build and serve locally instead of using a container"
)
@pytest.fixture(scope="session")
def local_server(pytestconfig):
return pytestconfig.getoption("--local-server")
@contextmanager
def init_page(vega: VegaServiceNull, browser: Browser, request: pytest.FixtureRequest):
def init_page(vega: VegaServiceNull, browser: Browser, request: pytest.FixtureRequest, local_server: bool):
server_port = "4200" if local_server else str(vega.console_port)
with browser.new_context(
viewport={"width": 1920, "height": 1080},
base_url=f"http://localhost:{vega.console_port}",
base_url=f"http://localhost:{server_port}",
) as context, context.new_page() as page:
context.tracing.start(screenshots=True, snapshots=True, sources=True)
try:
@@ -115,7 +126,7 @@ def init_page(vega: VegaServiceNull, browser: Browser, request: pytest.FixtureRe
while attempts < 100:
try:
code = requests.get(
f"http://localhost:{vega.console_port}/"
f"http://localhost:{server_port}/"
).status_code
if code == 200:
break
@@ -161,8 +172,8 @@ def vega(request):
@pytest.fixture
def page(vega, browser, request):
with init_page(vega, browser, request) as page_instance:
def page(vega, browser, request, local_server):
with init_page(vega, browser, request, local_server) as page_instance:
yield page_instance
+14 -8
View File
@@ -56,11 +56,12 @@ label_value_tooltip_pairs = [
def tooltip(page: Page, index: int, test_id: str, tooltip: str):
page.locator(f"data-testid={index}_{test_id}").hover()
expect(page.locator('[role="tooltip"]').locator("div")).to_have_text(tooltip)
expect(page.locator('[role="tooltip"]').locator(
"div")).to_have_text(tooltip)
page.get_by_test_id("dialog-title").click()
@pytest.mark.usefixtures("page", "continuous_market", "auth", "risk_accepted")
@pytest.mark.usefixtures("continuous_market", "auth", "risk_accepted")
def test_asset_details(page: Page):
page.goto("/#/portfolio")
page.locator('[data-testid="tab-collateral"] >> text=tDAI').click()
@@ -73,17 +74,22 @@ def test_asset_details(page: Page):
value = pair.get("value", "")
label_tooltip = pair.get("labelTooltip", "")
value_tooltip = pair.get("valueToolTip", "")
if label == "ID":
expect(page.get_by_role("button", name="Copy id to clipboard")).to_be_visible()
asset_id_text = page.locator(f"[data-testid='{index}_value']").inner_text()
expect(page.get_by_role(
"button", name="Copy id to clipboard")).to_be_visible()
asset_id_text = page.locator(
f"[data-testid='{index}_value']").inner_text()
pattern = r"^[0-9a-f]{6}\u2026[0-9a-f]{4}"
assert re.match(pattern, asset_id_text), f"Expected ID to match pattern but got {asset_id_text}"
assert re.match(
pattern, asset_id_text), f"Expected ID to match pattern but got {asset_id_text}"
else:
expect(page.locator(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}_label']")).to_have_text(label)
expect(page.locator(
f"[data-testid='{index}_value']")).to_have_text(value)
if label_tooltip:
tooltip(page, index, "label", label_tooltip)
@@ -14,19 +14,16 @@ market_order = "order-type-Market"
tif = "order-tif"
expire = "expire"
@pytest.fixture(scope="module")
def vega(request):
with init_vega(request) as vega:
yield vega
@pytest.fixture(scope="module")
def continuous_market(vega):
return setup_continuous_market(vega)
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_limit_buy_order_GTT(continuous_market, vega: VegaService, page: Page):
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id(tif).select_option("Good 'til Time (GTT)")
@@ -54,8 +51,7 @@ def test_limit_buy_order_GTT(continuous_market, vega: VegaService, page: Page):
"BTC:DAI_2023Futr10+10LimitFilled120.00GTT:"
)
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_limit_buy_order(continuous_market, vega: VegaService, page: Page):
page.goto(f"/#/markets/{continuous_market}")
@@ -72,8 +68,7 @@ def test_limit_buy_order(continuous_market, vega: VegaService, page: Page):
"BTC:DAI_2023Futr10+10LimitFilled120.00GTC"
)
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_limit_sell_order(continuous_market, vega: VegaService, page: Page):
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id(order_size).fill("10")
@@ -97,8 +92,7 @@ def test_limit_sell_order(continuous_market, vega: VegaService, page: Page):
"BTC:DAI_2023Futr10-10LimitFilled100.00GFN"
)
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_market_sell_order(continuous_market, vega: VegaService, page: Page):
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id(market_order).click()
@@ -122,8 +116,7 @@ def test_market_sell_order(continuous_market, vega: VegaService, page: Page):
"BTC:DAI_2023Futr10-10MarketFilled-IOC"
)
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_market_buy_order(continuous_market, vega: VegaService, page: Page):
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id(market_order).click()
@@ -13,7 +13,7 @@ def continuous_market(vega):
return setup_continuous_market(vega)
@pytest.mark.skip("We currently can't approve wallet connection through Sim")
@pytest.mark.usefixtures("page", "risk_accepted")
@pytest.mark.usefixtures("risk_accepted")
def test_connect_vega_wallet(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
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-price")).to_have_value("101")
@pytest.mark.usefixtures("page", "risk_accepted")
@pytest.mark.usefixtures("risk_accepted")
def test_sidebar_should_be_open_after_reload(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
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.usefixtures("page", "vega", "continuous_market", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_margin_and_fees_estimations(continuous_market, vega: VegaService, page: Page):
# setup continuous trading market with one user buy trade
market_id = continuous_market
@@ -38,7 +38,6 @@ timeInForce_col = '[col-id="submission.timeInForce"]'
updatedAt_col = '[col-id="updatedAt"]'
close_toast = "toast-close"
def create_position(vega: VegaService, market_id):
submit_order(vega, "Key 1", market_id, "SIDE_SELL", 100, 110)
submit_order(vega, "Key 1", market_id, "SIDE_BUY", 100, 110)
@@ -46,7 +45,7 @@ def create_position(vega: VegaService, market_id):
vega.wait_fn(1)
vega.wait_for_total_catchup
@pytest.mark.usefixtures("page", "continuous_market", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_stop_order_form_error_validation(continuous_market, page: Page):
# 7002-SORD-032
page.goto(f"/#/markets/{continuous_market}")
@@ -69,7 +68,7 @@ def test_stop_order_form_error_validation(continuous_market, page: Page):
)
@pytest.mark.skip("core issue")
@pytest.mark.usefixtures("page", "vega", "continuous_market", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_submit_stop_order_rejected(continuous_market, vega: VegaService, page: Page):
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id(stop_orders_tab).click()
@@ -107,7 +106,7 @@ def test_submit_stop_order_rejected(continuous_market, vega: VegaService, page:
).not_to_be_empty()
@pytest.mark.skip("core issue")
@pytest.mark.usefixtures("page", "vega", "continuous_market", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_submit_stop_market_order_triggered(
continuous_market, vega: VegaService, page: Page
):
@@ -165,7 +164,7 @@ def test_submit_stop_market_order_triggered(
).not_to_be_empty()
@pytest.mark.skip("core issue")
@pytest.mark.usefixtures("continuous_market", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_submit_stop_limit_order_pending(
continuous_market, vega: VegaService, page: Page
):
@@ -226,7 +225,7 @@ def test_submit_stop_limit_order_pending(
).not_to_be_empty()
@pytest.mark.skip("core issue")
@pytest.mark.usefixtures("continuous_market", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_submit_stop_limit_order_cancel(
continuous_market, vega: VegaService, page: Page
):
@@ -270,7 +269,7 @@ class TestStopOcoValidation:
def continuous_market(self, vega):
return setup_continuous_market(vega)
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_stop_market_order_form_validation(self, continuous_market, page: Page):
# 7002-SORD-052
# 7002-SORD-055
@@ -303,7 +302,7 @@ class TestStopOcoValidation:
expect(page.get_by_test_id(order_size)).to_be_empty
expect(page.get_by_test_id(order_price)).not_to_be_visible()
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_stop_limit_order_form_validation(self, continuous_market, page: Page):
# 7002-SORD-020
# 7002-SORD-021
@@ -347,7 +346,7 @@ class TestStopOcoValidation:
expect(page.get_by_test_id(order_price)).to_be_empty()
@pytest.mark.skip("core issue")
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_maximum_number_of_active_stop_orders(
self, continuous_market, vega: VegaService, page: Page
):
@@ -4,7 +4,6 @@ from vega_sim.service import VegaService
from actions.vega import submit_order
from actions.utils import wait_for_toast_confirmation
stop_order_btn = "order-type-Stop"
stop_limit_order_btn = "order-type-StopLimit"
stop_market_order_btn = "order-type-StopMarket"
@@ -50,7 +49,7 @@ def create_position(vega: VegaService, market_id):
vega.wait_for_total_catchup
@pytest.mark.usefixtures("page", "vega", "continuous_market", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_submit_stop_order_market_oco_rejected(
continuous_market, vega: VegaService, page: Page
):
@@ -127,7 +126,7 @@ def test_submit_stop_order_market_oco_rejected(
assert trigger_price_list.sort() == trigger_value_list.sort()
@pytest.mark.usefixtures("page", "vega", "continuous_market", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_submit_stop_oco_market_order_triggered(
continuous_market, vega: VegaService, page: Page
):
@@ -204,7 +203,7 @@ def test_submit_stop_oco_market_order_triggered(
assert trigger_price_list.sort() == trigger_value_list.sort()
@pytest.mark.usefixtures("page", "vega", "continuous_market", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_submit_stop_oco_market_order_pending(
continuous_market, vega: VegaService, page: Page
):
@@ -236,7 +235,7 @@ def test_submit_stop_oco_market_order_pending(
"PendingOCO"
)
@pytest.mark.usefixtures("page", "vega", "continuous_market", "auth", "risk_accepted")
@pytest.mark.usefixtures("page", "continuous_market", "auth", "risk_accepted")
def test_submit_stop_oco_limit_order_pending(
continuous_market, vega: VegaService, page: Page
):
@@ -287,7 +286,7 @@ def test_submit_stop_oco_limit_order_pending(
assert trigger_price_list.sort() == trigger_value_list.sort()
@pytest.mark.usefixtures("page", "vega", "continuous_market", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_submit_stop_oco_limit_order_cancel(
continuous_market, vega: VegaService, page: Page
):
@@ -325,5 +324,3 @@ def test_submit_stop_oco_limit_order_cancel(
expect(
page.locator(".ag-center-cols-container").locator('[col-id="status"]').last
).to_have_text("CancelledOCO")
@@ -5,8 +5,6 @@ from actions.utils import change_keys
from conftest import init_vega
from fixtures.market import setup_continuous_market
order_size = "order-size"
order_price = "order-price"
place_order = "place-order"
@@ -18,13 +16,12 @@ def vega(request):
with init_vega(request) as vega:
yield vega
@pytest.fixture(scope="module")
def continuous_market(vega):
return setup_continuous_market(vega)
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
def test_should_display_info_and_button_for_deposit(continuous_market, vega: VegaService, page: Page):
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_should_display_info_and_button_for_deposit(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id(order_size).fill("200000")
page.get_by_test_id(order_price).fill("20")
@@ -35,7 +32,7 @@ def test_should_display_info_and_button_for_deposit(continuous_market, vega: Veg
page.get_by_test_id(deal_ticket_deposit_dialog_button).nth(0).click()
expect(page.get_by_test_id("sidebar-content")).to_contain_text("DepositFrom")
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_should_show_an_error_if_your_balance_is_zero(continuous_market, vega: VegaService, page: Page):
page.goto(f"/#/markets/{continuous_market}")
vega.create_key("key_empty")
@@ -10,20 +10,16 @@ import logging
logger = logging.getLogger()
@pytest.fixture(scope="class")
def vega():
with init_vega() as vega:
yield vega
# we can reuse vega market-sim service and market in almost all tests
@pytest.fixture(scope="class")
def simple_market(vega: VegaService):
return setup_simple_market(vega)
class TestGetStarted:
@pytest.mark.usefixtures("page")
def test_get_started_interactive(self, vega: VegaService, page: Page):
page.goto("/")
# 0007-FUGS-001
@@ -134,8 +130,7 @@ class TestGetStarted:
# Assert dialog isn't visible
expect(page.get_by_test_id("welcome-dialog")).not_to_be_visible()
@pytest.mark.usefixtures("page", "risk_accepted")
@pytest.mark.usefixtures("risk_accepted")
def test_get_started_seen_already(self, simple_market, page: Page):
page.goto(f"/#/markets/{simple_market}")
get_started_locator = page.get_by_test_id("connect-vega-wallet")
@@ -148,8 +143,6 @@ class TestGetStarted:
# 0007-FUGS-007
expect(page.get_by_test_id("dialog-content").nth(1)).to_be_visible()
@pytest.mark.usefixtures("page")
def test_browser_wallet_installed(self, simple_market, page: Page):
page.add_init_script("window.vega = {}")
page.goto(f"/#/markets/{simple_market}")
@@ -159,14 +152,13 @@ class TestGetStarted:
expect(locator).to_be_visible
expect(locator).to_have_text("Connect")
@pytest.mark.usefixtures("page", "risk_accepted")
@pytest.mark.usefixtures("risk_accepted")
def test_get_started_deal_ticket(self,simple_market, page: Page):
page.goto(f"/#/markets/{simple_market}")
expect(page.get_by_test_id("order-connect-wallet")).to_have_text("Connect wallet")
@pytest.mark.usefixtures("page", "risk_accepted")
@pytest.mark.usefixtures("risk_accepted")
def test_browser_wallet_installed_deal_ticket(simple_market, page: Page):
page.add_init_script("window.vega = {}")
page.goto(f"/#/markets/{simple_market}")
@@ -174,7 +166,6 @@ class TestGetStarted:
page.wait_for_selector('[data-testid="sidebar-content"]', state="visible")
expect(page.get_by_test_id("get-started-banner")).not_to_be_visible()
@pytest.mark.usefixtures("page")
def test_redirect_default_market(self, continuous_market, vega: VegaService, page: Page):
page.goto("/")
# 0007-FUGS-012
@@ -186,7 +177,6 @@ class TestGetStarted:
expect(page.get_by_test_id("welcome-dialog")).not_to_be_visible()
class TestBrowseAll:
@pytest.mark.usefixtures("page")
def test_get_started_browse_all(self, simple_market, vega: VegaService, page: Page):
page.goto("/")
print(simple_market)
@@ -11,7 +11,6 @@ def hover_and_assert_tooltip(page: Page, element_text):
element.hover()
expect(page.get_by_role("tooltip")).to_be_visible()
class TestIcebergOrdersValidations:
@pytest.fixture(scope="class")
def vega(self, request):
@@ -22,7 +21,7 @@ class TestIcebergOrdersValidations:
def continuous_market(self, vega):
return setup_continuous_market(vega)
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_iceberg_submit(self, continuous_market, vega: VegaService, page: Page):
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id("iceberg").click()
@@ -47,7 +46,7 @@ class TestIcebergOrdersValidations:
(page.get_by_role("row").locator('[col-id="type"]')).nth(1)
).to_have_text("Limit (Iceberg)")
@pytest.mark.usefixtures("vega", "page", "continuous_market", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_iceberg_open_order(continuous_market, vega: VegaService, page: Page):
page.goto(f"/#/markets/{continuous_market}")
@@ -17,27 +17,34 @@ def continuous_market(vega):
return setup_continuous_market(vega)
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_liquidity_provision_amendment(continuous_market, vega: VegaService, page: Page):
# TODO Refactor asserting the grid
page.goto(f"/#/liquidity/{continuous_market}")
change_keys(page, vega, "market_maker")
row = page.get_by_test_id("tab-myLP").locator(".ag-center-cols-container .ag-row").first
row = page.get_by_test_id(
"tab-myLP").locator(".ag-center-cols-container .ag-row").first
expect(row).to_contain_text(
"Active"
)
# 5002-LIQP-006
expect(page.get_by_test_id("target-stake")).to_have_text("Target stake5.82757 tDAI")
expect(page.get_by_test_id("target-stake")
).to_have_text("Target stake5.82757 tDAI")
# 5002-LIQP-007
expect(page.get_by_test_id("supplied-stake")).to_have_text("Supplied stake10,000.00 tDAI")
expect(page.get_by_test_id("supplied-stake")
).to_have_text("Supplied stake10,000.00 tDAI")
# 5002-LIQP-008
expect(page.get_by_test_id("liquidity-supplied")).to_have_text("Liquidity supplied 171,598.11%")
expect(page.get_by_test_id("liquidity-supplied")
).to_have_text("Liquidity supplied 171,598.11%")
expect(page.get_by_test_id("fees-paid")).to_have_text("Fees paid-")
# 5002-LIQP-009
expect(page.get_by_test_id("liquidity-market-id")).to_have_text("Market ID" + truncate_middle(continuous_market))
expect(page.get_by_test_id("liquidity-learn-more")).to_have_text("Learn moreProviding liquidity")
expect(page.get_by_test_id("liquidity-market-id")
).to_have_text("Market ID" + truncate_middle(continuous_market))
expect(page.get_by_test_id("liquidity-learn-more")
).to_have_text("Learn moreProviding liquidity")
# 002-LIQP-010
expect(page.get_by_test_id("liquidity-learn-more").get_by_test_id("external-link")).to_have_attribute("href", "https://docs.vega.xyz/testnet/concepts/liquidity/provision")
expect(page.get_by_test_id("liquidity-learn-more").get_by_test_id("external-link")
).to_have_attribute("href", "https://docs.vega.xyz/testnet/concepts/liquidity/provision")
vega.submit_simple_liquidity(
key_name="market_maker",
@@ -50,26 +57,32 @@ def test_liquidity_provision_amendment(continuous_market, vega: VegaService, pag
vega.wait_fn(1)
vega.wait_for_total_catchup()
page.reload()
row = page.get_by_test_id("tab-myLP").locator(".ag-center-cols-container .ag-row").first
row = page.get_by_test_id(
"tab-myLP").locator(".ag-center-cols-container .ag-row").first
expect(row).to_contain_text(
"Updating next epoch"
)
next_epoch(vega=vega)
page.reload()
expect(page.get_by_test_id("supplied-stake")).to_have_text("Supplied stake1.00001 tDAI")
expect(page.get_by_test_id("liquidity-supplied")).to_have_text("Liquidity supplied 17.16%")
row = page.get_by_test_id("tab-myLP").locator(".ag-center-cols-container .ag-row").first
expect(page.get_by_test_id("supplied-stake")
).to_have_text("Supplied stake1.00001 tDAI")
expect(page.get_by_test_id("liquidity-supplied")
).to_have_text("Liquidity supplied 17.16%")
row = page.get_by_test_id(
"tab-myLP").locator(".ag-center-cols-container .ag-row").first
expect(row).to_contain_text(
"Active"
)
@pytest.mark.skip("Waiting for the ability to cancel LP")
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_liquidity_provision_inactive(continuous_market, vega: VegaService, page: Page):
# TODO Refactor asserting the grid
page.goto(f"/#/liquidity/{continuous_market}")
change_keys(page,vega, "market_maker")
row = page.get_by_test_id("tab-myLP").locator(".ag-center-cols-container .ag-row").first
change_keys(page, vega, "market_maker")
row = page.get_by_test_id(
"tab-myLP").locator(".ag-center-cols-container .ag-row").first
expect(row).to_contain_text(
"Active"
)
@@ -82,4 +95,3 @@ def test_liquidity_provision_inactive(continuous_market, vega: VegaService, page
)
vega.wait_fn(1)
vega.wait_for_total_catchup()
@@ -12,6 +12,7 @@ def vega():
with init_vega() as vega:
yield vega
@pytest.fixture(scope="class")
def create_settled_market(vega: VegaService):
market_id = setup_continuous_market(vega)
@@ -73,8 +74,9 @@ class TestSettledMarket:
# 6001-MARK-010
pattern = r"(\d+)\s+(months|hours|days)\s+ago"
date_text = row_selector.locator('[col-id="settlementDate"]').inner_text()
assert re.match(pattern, date_text), f"Expected text to match pattern but got {date_text}"
assert re.match(
pattern, date_text
), f"Expected text to match pattern but got {date_text}"
expected_pattern = re.compile(r"https://.*?/oracles/[a-f0-9]{64}")
actual_href = row_selector.locator(
@@ -87,12 +89,12 @@ class TestSettledMarket:
expect(row_selector.locator('[col-id="bestBidPrice"]')).to_have_text("0.00")
# 6001-MARK-012
expect(row_selector.locator('[col-id="bestOfferPrice"]')).to_have_text("0.00")
# 6001-MARK-013
# 6001-MARK-013
expect(row_selector.locator('[col-id="markPrice"]')).to_have_text("110.00")
# 6001-MARK-014
# 6001-MARK-015
# 6001-MARK-016
#tbd currently we have value unknown
# tbd currently we have value unknown
# expect(row_selector.locator('[col-id="settlementDataOracleId"]')).to_have_text(
# "110.00"
# )
@@ -107,7 +109,9 @@ class TestSettledMarket:
# 6001-MARK-018
expect(row_selector.locator('[col-id="settlementAsset"]')).to_have_text("tDAI")
# 6001-MARK-020
assert re.match(pattern, date_text), f"Expected text to match pattern but got {date_text}"
assert re.match(
pattern, date_text
), f"Expected text to match pattern but got {date_text}"
@pytest.mark.usefixtures("risk_accepted", "auth")
+15 -9
View File
@@ -31,7 +31,7 @@ initial_spread: float = 0.1
market_name = "BTC:DAI_2023"
@pytest.mark.usefixtures("vega", "page", "simple_market", "risk_accepted", "auth")
@pytest.mark.usefixtures("risk_accepted", "auth")
def test_price_monitoring(simple_market, vega: VegaService, page: Page):
page.goto(f"/#/markets/all")
expect(page.locator(table_row_selector).locator(trading_mode_col)).to_have_text(
@@ -75,7 +75,7 @@ def test_price_monitoring(simple_market, vega: VegaService, page: Page):
time_in_force="TIME_IN_FORCE_GTC",
volume=99,
)
#6002-MDET-009
# 6002-MDET-009
expect(
page.get_by_test_id(liquidity_supplied).get_by_test_id(item_value)
).to_have_text("0.00 (0.00%)")
@@ -154,7 +154,7 @@ def test_price_monitoring(simple_market, vega: VegaService, page: Page):
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
vega.wait_for_total_catchup()
expect(
page.get_by_test_id(price_monitoring_bounds_row).first.get_by_text(
"135.44204 BTC"
@@ -191,22 +191,28 @@ def test_price_monitoring(simple_market, vega: VegaService, page: Page):
)
# commented out because we have an issue #4233
# expect(page.get_by_text("Opening auction")).to_be_hidden()
#6002-MDET-009
# 6002-MDET-009
expect(
page.get_by_test_id(liquidity_supplied).get_by_test_id(item_value)
).to_have_text("50.00 (>100%)")
COL_ID_FEE = ".ag-center-cols-container [col-id='fee'] .ag-cell-value"
@pytest.mark.usefixtures("vega", "page", "continuous_market", "risk_accepted", "auth")
@pytest.mark.usefixtures("risk_accepted", "auth")
def test_auction_uncross_fees(continuous_market, vega: VegaService, page: Page):
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id("Fills").click()
expect(page.locator(COL_ID_FEE)).to_have_text("0.00 tDAI")
page.locator(COL_ID_FEE).hover()
expect(page.get_by_test_id("fee-breakdown-tooltip")).to_have_text("If the market was suspendedIf the market is in monitoring auction, half of the infrastructure and liquidity fees will be paid.Infrastructure fee0.00 tDAILiquidity fee0.00 tDAIMaker fee0.00 tDAITotal fees0.00 tDAI")
change_keys(page,vega, "market_maker")
expect(page.get_by_test_id("fee-breakdown-tooltip")).to_have_text(
"If the market was suspendedIf the market is in monitoring auction, half of the infrastructure and liquidity fees will be paid.Infrastructure fee0.00 tDAILiquidity fee0.00 tDAIMaker fee0.00 tDAITotal fees0.00 tDAI"
)
change_keys(page, vega, "market_maker")
expect(page.locator(COL_ID_FEE)).to_have_text("0.00 tDAI")
page.locator(COL_ID_FEE).hover()
expect(page.get_by_test_id("fee-breakdown-tooltip")).to_have_text("If the market was suspendedIf the market is in monitoring auction, half of the infrastructure and liquidity fees will be paid.Infrastructure fee0.00 tDAILiquidity fee0.00 tDAIMaker fee0.00 tDAITotal fees0.00 tDAI")
expect(page.get_by_test_id("fee-breakdown-tooltip")).to_have_text(
"If the market was suspendedIf the market is in monitoring auction, half of the infrastructure and liquidity fees will be paid.Infrastructure fee0.00 tDAILiquidity fee0.00 tDAIMaker fee0.00 tDAITotal fees0.00 tDAI"
)
@@ -3,7 +3,6 @@ import pytest
from playwright.sync_api import Page, expect
from vega_sim.service import VegaService
from fixtures.market import setup_continuous_market
from conftest import init_page, init_vega, risk_accepted_setup
market_title_test_id = "accordion-title"
@@ -15,10 +14,9 @@ def vega():
yield vega
# setting up everything in this single fixture, as all of the tests need the same setup, so no point in creating separate ones
@pytest.fixture(scope="module")
def page(vega, browser, request):
with init_page(vega, browser, request) as page:
def page(vega, browser, request, local_server):
with init_page(vega, browser, request, local_server) as page:
setup_continuous_market(vega)
risk_accepted_setup(page)
page.goto("/")
@@ -122,7 +120,9 @@ def test_market_info_instrument(page: Page):
# @pytest.mark.skip("oracle test to be fixed")
def test_market_info_oracle(page: Page, vega: VegaService):
def test_market_info_oracle(page: Page):
# 6002-MDET-203
page.get_by_test_id(market_title_test_id).get_by_text("Oracle").click()
expect(
@@ -2,7 +2,7 @@ import pytest
from playwright.sync_api import expect, Page
@pytest.mark.usefixtures("page", "continuous_market", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_market_selector(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
expect(page.get_by_test_id("market-selector")).not_to_be_visible()
@@ -27,7 +27,7 @@ def test_market_selector(continuous_market, page: Page):
expect(btc_market.locator('[data-testid="sparkline-svg"]')).not_to_be_visible
@pytest.mark.usefixtures("page", "continuous_market", "simple_market", "auth", "risk_accepted")
@pytest.mark.usefixtures("simple_market", "auth", "risk_accepted")
@pytest.mark.parametrize(
"simple_market",
[
@@ -1,6 +1,5 @@
import pytest
from playwright.sync_api import Page, expect
from vega_sim.service import VegaService
from conftest import init_page, init_vega, risk_accepted_setup
@@ -12,8 +11,8 @@ def vega(request):
@pytest.fixture(scope="module")
def page(vega, browser, request):
with init_page(vega, browser, request) as page:
def page(vega, browser, request, local_server):
with init_page(vega, browser, request, local_server) as page:
risk_accepted_setup(page)
page.goto("/#/markets/all")
yield page
@@ -5,7 +5,7 @@ from playwright.sync_api import Page, expect
from vega_sim.service import VegaService
from conftest import init_vega
from fixtures.market import setup_simple_market
from wallet_config import MM_WALLET, MM_WALLET2, TERMINATE_WALLET, wallets
from wallet_config import MM_WALLET
row_selector = '[data-testid="tab-proposed-markets"] .ag-center-cols-container .ag-row'
col_market_id = '[col-id="market"] [data-testid="stack-cell-primary"]'
@@ -16,6 +16,7 @@ def vega(request):
with init_vega(request) as vega:
yield vega
@pytest.fixture(scope="module")
def proposed_market(vega: VegaService):
# setup market without liquidity provided
@@ -7,6 +7,7 @@ from conftest import init_vega
from actions.utils import wait_for_toast_confirmation
from wallet_config import MM_WALLET, MM_WALLET2
@pytest.fixture(scope="module")
def vega(request):
with init_vega(request) as vega:
@@ -17,6 +18,7 @@ def vega(request):
def simple_market(vega):
return setup_simple_market(vega)
@pytest.fixture(scope="module")
def setup_market_monitoring_auction(vega: VegaService, simple_market):
vega.submit_liquidity(
@@ -48,12 +50,18 @@ def setup_market_monitoring_auction(vega: VegaService, simple_market):
volume=99,
)
# add orders to provide liquidity
submit_order(vega, MM_WALLET.name, simple_market, "SIDE_BUY", 1, 1)
submit_order(vega, MM_WALLET.name, simple_market, "SIDE_SELL", 1, 1)
submit_order(vega,MM_WALLET.name,simple_market, "SIDE_BUY",1,1 + 0.1 / 2,)
submit_order(vega,MM_WALLET.name,simple_market,"SIDE_SELL",1,1 + 0.1 / 2)
submit_order(
vega,
MM_WALLET.name,
simple_market,
"SIDE_BUY",
1,
1 + 0.1 / 2,
)
submit_order(vega, MM_WALLET.name, simple_market, "SIDE_SELL", 1, 1 + 0.1 / 2)
submit_order(vega, MM_WALLET2.name, simple_market, "SIDE_SELL", 1, 1)
vega.forward("10s")
@@ -71,9 +79,11 @@ def setup_market_monitoring_auction(vega: VegaService, simple_market):
vega.wait_fn(1)
vega.wait_for_total_catchup()
@pytest.mark.usefixtures("page", "risk_accepted", "simple_market", "auth", "setup_market_monitoring_auction")
def test_market_monitoring_auction_price_volatility_limit_order(page: Page, simple_market, vega: VegaService):
@pytest.mark.usefixtures("risk_accepted", "auth", "setup_market_monitoring_auction")
def test_market_monitoring_auction_price_volatility_limit_order(
page: Page, simple_market, vega: VegaService
):
page.goto(f"/#/markets/{simple_market}")
page.get_by_test_id("order-size").clear()
page.get_by_test_id("order-size").type("1")
@@ -82,10 +92,14 @@ def test_market_monitoring_auction_price_volatility_limit_order(page: Page, simp
page.get_by_test_id("order-tif").select_option("Fill or Kill (FOK)")
page.get_by_test_id("place-order").click()
expect(page.get_by_test_id("deal-ticket-error-message-tif")).to_have_text("This market is in auction due to high price volatility. Until the auction ends, you can only place GFA, GTT, or GTC limit orders.")
expect(page.get_by_test_id("deal-ticket-error-message-tif")).to_have_text(
"This market is in auction due to high price volatility. Until the auction ends, you can only place GFA, GTT, or GTC limit orders."
)
expect(page.get_by_test_id("deal-ticket-error-message-tif")).to_be_visible()
expect(page.get_by_test_id("deal-ticket-warning-auction")).to_have_text("Any orders placed now will not trade until the auction ends")
expect(page.get_by_test_id("deal-ticket-warning-auction")).to_have_text(
"Any orders placed now will not trade until the auction ends"
)
expect(page.get_by_test_id("deal-ticket-warning-auction")).to_be_visible()
page.get_by_test_id("order-tif").select_option("Good 'til Cancelled (GTC)")
@@ -103,8 +117,11 @@ def test_market_monitoring_auction_price_volatility_limit_order(page: Page, simp
"BTC:DAI_2023Futr0+1LimitActive110.00GTC"
)
@pytest.mark.usefixtures("page", "risk_accepted", "simple_market", "auth", "setup_market_monitoring_auction")
def test_market_monitoring_auction_price_volatility_market_order(page: Page, simple_market):
@pytest.mark.usefixtures("risk_accepted", "auth", "setup_market_monitoring_auction")
def test_market_monitoring_auction_price_volatility_market_order(
page: Page, simple_market
):
page.goto(f"/#/markets/{simple_market}")
page.get_by_test_id("order-type-Market").click()
page.get_by_test_id("order-size").clear()
@@ -112,8 +129,12 @@ def test_market_monitoring_auction_price_volatility_market_order(page: Page, sim
# 7002-SORD-060
page.get_by_test_id("place-order").click()
expect(page.get_by_test_id("deal-ticket-error-message-tif")).to_have_text("This market is in auction due to high price volatility. Until the auction ends, you can only place GFA, GTT, or GTC limit orders.")
expect(page.get_by_test_id("deal-ticket-error-message-tif")).to_have_text(
"This market is in auction due to high price volatility. Until the auction ends, you can only place GFA, GTT, or GTC limit orders."
)
expect(page.get_by_test_id("deal-ticket-error-message-tif")).to_be_visible()
expect(page.get_by_test_id("deal-ticket-error-message-type")).to_have_text("This market is in auction due to high price volatility. Only limit orders are permitted when market is in auction.")
expect(page.get_by_test_id("deal-ticket-error-message-type")).to_have_text(
"This market is in auction due to high price volatility. Only limit orders are permitted when market is in auction."
)
expect(page.get_by_test_id("deal-ticket-error-message-type")).to_be_visible()
@@ -9,8 +9,7 @@ from actions.utils import next_epoch
from wallet_config import MM_WALLET, MM_WALLET2, GOVERNANCE_WALLET
@pytest.mark.usefixtures("vega", "page", "proposed_market", "risk_accepted")
@pytest.mark.usefixtures("risk_accepted")
def test_market_lifecycle(proposed_market, vega: VegaService, page: Page):
# 7002-SORD-001
# 7002-SORD-002
@@ -27,8 +26,12 @@ def test_market_lifecycle(proposed_market, vega: VegaService, page: Page):
# 6002-MDET-002
expect(page.get_by_test_id("market-expiry")).to_have_text("ExpiryNot time-based")
page.get_by_test_id("market-expiry").hover()
expect(page.get_by_test_id("expiry-tooltip").first).to_have_text("This market expires when triggered by its oracle, not on a set date.View oracle specification")
expect(page.get_by_test_id("expiry-tooltip").first.get_by_test_id("link")).to_have_attribute("href", re.compile('.*'))
expect(page.get_by_test_id("expiry-tooltip").first).to_have_text(
"This market expires when triggered by its oracle, not on a set date.View oracle specification"
)
expect(
page.get_by_test_id("expiry-tooltip").first.get_by_test_id("link")
).to_have_attribute("href", re.compile(".*"))
# 6002-MDET-003
expect(page.get_by_test_id("market-price")).to_have_text("Mark Price0.00")
# 6002-MDET-004
@@ -36,18 +39,30 @@ def test_market_lifecycle(proposed_market, vega: VegaService, page: Page):
# 6002-MDET-005
expect(page.get_by_test_id("market-volume")).to_have_text("Volume (24h)-")
# 6002-MDET-008
expect(page.get_by_test_id("market-settlement-asset")).to_have_text("Settlement assettDAI")
expect(page.get_by_test_id("liquidity-supplied")).to_have_text("Liquidity supplied 0.00 (0.00%)")
expect(page.get_by_test_id("market-settlement-asset")).to_have_text(
"Settlement assettDAI"
)
expect(page.get_by_test_id("liquidity-supplied")).to_have_text(
"Liquidity supplied 0.00 (0.00%)"
)
page.get_by_test_id("liquidity-supplied").hover()
expect(page.get_by_test_id("liquidity-supplied-tooltip").first).to_have_text("Supplied stake0.00Target stake0.00View liquidity provision tableLearn about providing liquidity")
expect(page.get_by_test_id("liquidity-supplied-tooltip").first.get_by_test_id("link").first).to_have_text("View liquidity provision table")
expect(page.get_by_test_id("liquidity-supplied-tooltip").first).to_have_text(
"Supplied stake0.00Target stake0.00View liquidity provision tableLearn about providing liquidity"
)
expect(
page.get_by_test_id("liquidity-supplied-tooltip")
.first.get_by_test_id("link")
.first
).to_have_text("View liquidity provision table")
# check that market is in proposed state
# 6002-MDET-006
# 6002-MDET-007
# 7002-SORD-061
expect(trading_mode).to_have_text("No trading")
trading_mode.hover()
expect(page.get_by_test_id("trading-mode-tooltip").first).to_have_text("No trading enabled for this market.")
expect(page.get_by_test_id("trading-mode-tooltip").first).to_have_text(
"No trading enabled for this market."
)
expect(market_state).to_have_text("Proposed")
# approve market
@@ -182,4 +197,4 @@ def test_market_closing_banners(page: Page, continuous_market, vega: VegaService
will_close_pattern = r"TRADING ON MARKET BTC:DAI_2023 WILL STOP ON \d+ \w+\nYou will no longer be able to hold a position on this market when it closes in \d+ days \d+ hours\. The final price will be 107\.00 BTC\."
match_result = re.fullmatch(will_close_pattern, page.locator(".grow").inner_text())
assert match_result is not None
"""
"""
@@ -12,8 +12,8 @@ def vega():
# we can reuse single page instance in all tests
@pytest.fixture(scope="module")
def page(vega, browser, request):
with init_page(vega, browser, request) as page:
def page(vega, browser, request, local_server):
with init_page(vega, browser, request, local_server) as page:
yield page
@@ -48,8 +48,9 @@ def verify_order_value(
else:
expect(element).to_have_text(expected_text)
@pytest.mark.skip("tbd")
@pytest.mark.usefixtures("vega", "page", "continuous_market", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_order_details_are_correctly_displayed(
continuous_market, vega: VegaService, page: Page
):
@@ -8,6 +8,7 @@ from actions.vega import submit_order
logger = logging.getLogger()
# Could be turned into a helper function in the future.
def verify_data_grid(page: Page, data_test_id, expected_pattern):
page.get_by_test_id(data_test_id).click()
@@ -49,9 +50,7 @@ def submit_order(vega: VegaService, wallet_name, market_id, side, volume, price)
)
@pytest.mark.usefixtures(
"vega", "page", "opening_auction_market", "auth", "risk_accepted"
)
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_limit_order_trade_open_order(
opening_auction_market, vega: VegaService, page: Page
):
@@ -80,7 +79,7 @@ def test_limit_order_trade_open_order(
verify_data_grid(page, "Open", expected_open_order)
@pytest.mark.usefixtures("vega", "page", "continuous_market", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_limit_order_trade_open_position(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
@@ -148,7 +147,7 @@ def test_limit_order_trade_open_position(continuous_market, page: Page):
expect(unrealisedPNL).to_have_text(position["unrealised_pnl"])
@pytest.mark.usefixtures("vega", "page", "continuous_market", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_limit_order_trade_order_trade_away(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
# Assert that the order is no longer on the orderbook
@@ -223,8 +223,8 @@ def markets(vega: VegaService):
@pytest.fixture(scope="module")
def page(vega, browser, request):
with init_page(vega, browser, request) as page:
def page(vega, browser, request, local_server):
with init_page(vega, browser, request, local_server) as page:
risk_accepted_setup(page)
auth_setup(vega, page)
page.goto("/")
@@ -6,6 +6,7 @@ from conftest import init_vega
from fixtures.market import setup_simple_market
from wallet_config import MM_WALLET, MM_WALLET2
@pytest.fixture(scope="module")
def vega():
with init_vega() as vega:
@@ -78,8 +79,9 @@ def verify_prices_descending(page: Page):
prices = [float(price.text_content()) for price in prices_locator.all()]
assert prices == sorted(prices, reverse=True)
@pytest.mark.skip("tbd")
@pytest.mark.usefixtures("page", "risk_accepted")
@pytest.mark.usefixtures("risk_accepted")
def test_orderbook_grid_content(setup_market, page: Page):
vega = setup_market[0]
market_id = setup_market[1]
@@ -138,7 +140,7 @@ def test_orderbook_grid_content(setup_market, page: Page):
verify_prices_descending(page)
@pytest.mark.usefixtures("page", "risk_accepted")
@pytest.mark.usefixtures("risk_accepted")
def test_orderbook_resolution_change(setup_market, page: Page):
market_id = setup_market[1]
# 6003-ORDB-008
@@ -188,7 +190,7 @@ def test_orderbook_resolution_change(setup_market, page: Page):
# verify_orderbook_grid(page, resolution[1])
@pytest.mark.usefixtures("page", "risk_accepted")
@pytest.mark.usefixtures("risk_accepted")
def test_orderbook_price_size_copy(setup_market, page: Page):
market_id = setup_market[1]
# 6003-ORDB-009
@@ -206,8 +208,9 @@ def test_orderbook_price_size_copy(setup_market, page: Page):
volume.click()
expect(page.get_by_test_id("order-size")).to_have_value(volume.text_content())
@pytest.mark.skip("tbd")
@pytest.mark.usefixtures("page", "risk_accepted")
@pytest.mark.usefixtures("risk_accepted")
def test_orderbook_price_movement(setup_market, page: Page):
vega = setup_market[0]
market_id = setup_market[1]
@@ -13,8 +13,8 @@ from wallet_config import MM_WALLET, MM_WALLET2, TERMINATE_WALLET
row_selector = '[data-testid="tab-funding-payments"] .ag-center-cols-container .ag-row'
col_amount = '[col-id="amount"]'
class TestPerpetuals:
class TestPerpetuals:
@pytest.fixture(scope="class")
def vega(self, request):
with init_vega(request) as vega:
@@ -53,31 +53,33 @@ class TestPerpetuals:
vega.wait_for_total_catchup()
return perps_market
@pytest.mark.usefixtures("page","risk_accepted", "auth")
@pytest.mark.usefixtures("risk_accepted", "auth")
def test_funding_payment_profit(self, perps_market, page: Page):
page.goto(f"/#/markets/{perps_market}")
page.goto(f"/#/markets/{perps_market}")
page.get_by_test_id("Funding payments").click()
row = page.locator(row_selector)
expect(row.locator(col_amount)).to_have_text("9.00 tDAI")
@pytest.mark.usefixtures("page","risk_accepted", "auth")
@pytest.mark.usefixtures("risk_accepted", "auth")
def test_funding_payment_loss(self, perps_market, page: Page, vega):
page.goto(f"/#/markets/{perps_market}")
page.goto(f"/#/markets/{perps_market}")
change_keys(page, vega, "market_maker")
page.get_by_test_id("Funding payments").click()
row = page.locator(row_selector)
expect(row.locator(col_amount)).to_have_text("-27.00 tDAI")
@pytest.mark.usefixtures("page","risk_accepted", "auth")
@pytest.mark.usefixtures("risk_accepted", "auth")
def test_funding_header(self, perps_market, page: Page):
page.goto(f"/#/markets/{perps_market}")
expect(page.get_by_test_id("market-funding")).to_contain_text("Funding Rate / Countdown-8.1818%")
expect(page.get_by_test_id("market-funding")).to_contain_text(
"Funding Rate / Countdown-8.1818%"
)
expect(page.get_by_test_id("index-price")).to_have_text("Index Price110.00")
@pytest.mark.skip("Skipped due to issue #5421")
@pytest.mark.usefixtures("page","risk_accepted", "auth")
@pytest.mark.usefixtures("risk_accepted", "auth")
def test_funding_payment_history(perps_market, page: Page, vega):
page.goto(f"/#/markets/{perps_market}")
page.goto(f"/#/markets/{perps_market}")
change_keys(page, vega, "market_maker")
page.get_by_test_id("Funding history").click()
element = page.get_by_test_id("tab-funding-history")
@@ -92,30 +94,36 @@ class TestPerpetuals:
else:
print("Bounding box not found for the element")
@pytest.mark.usefixtures("page","risk_accepted", "auth")
@pytest.mark.usefixtures("risk_accepted", "auth")
def test_perps_market_termination_proposed(page: Page, vega: VegaService):
perpetual_market = setup_perps_market(vega)
page.goto(f"/#/markets/{perpetual_market}")
page.goto(f"/#/markets/{perpetual_market}")
vega.update_market_state(
proposal_key=MM_WALLET.name,
market_id=perpetual_market,
market_state=MarketStateUpdateType.Terminate,
price=100,
vote_closing_time = datetime.now() + timedelta(seconds=15),
vote_enactment_time = datetime.now() + timedelta(seconds=60),
approve_proposal = True,
forward_time_to_enactment = False,
vote_closing_time=datetime.now() + timedelta(seconds=15),
vote_enactment_time=datetime.now() + timedelta(seconds=60),
approve_proposal=True,
forward_time_to_enactment=False,
)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
banner_text = page.get_by_test_id(f"termination-warning-banner-{perpetual_market}").text_content()
banner_text = page.get_by_test_id(
f"termination-warning-banner-{perpetual_market}"
).text_content()
pattern = re.compile(
r"Trading on Market BTC:DAI_Perpetual may stop on \d{2} [A-Za-z]+\. There is open proposal to close this market\.Proposed final price is 100\.00 BTC\.View proposal"
r"Trading on Market BTC:DAI_Perpetual may stop on \d{2} [A-Za-z]+\. There is open proposal to close this market\.Proposed final price is 100\.00 BTC\.View proposal"
)
assert pattern.search(banner_text), f"Text did not match pattern. Text was: {banner_text}"
assert pattern.search(
banner_text
), f"Text did not match pattern. Text was: {banner_text}"
@pytest.mark.usefixtures("page","risk_accepted", "auth" )
@pytest.mark.usefixtures("risk_accepted", "auth")
def test_perps_market_terminated(page: Page, vega: VegaService):
perpetual_market = setup_perps_market(vega)
vega.update_market_state(
@@ -123,20 +131,29 @@ def test_perps_market_terminated(page: Page, vega: VegaService):
market_id=perpetual_market,
market_state=MarketStateUpdateType.Terminate,
price=100,
approve_proposal = True,
forward_time_to_enactment = True,
approve_proposal=True,
forward_time_to_enactment=True,
)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
page.goto(f"/#/markets/{perpetual_market}")
page.goto(f"/#/markets/{perpetual_market}")
# TODO change back to have text once bug #5465 is fixed
expect(page.get_by_test_id("market-price")).to_have_text("Mark Price100.00")
expect(page.get_by_test_id("market-change")).to_have_text("Change (24h)-")
expect(page.get_by_test_id("market-volume")).to_have_text("Volume (24h)-")
expect(page.get_by_test_id("market-trading-mode")).to_have_text("Trading modeNo trading")
expect(page.get_by_test_id("market-change")).to_contain_text("Change (24h)-")
expect(page.get_by_test_id("market-volume")).to_contain_text("Volume (24h)-")
expect(page.get_by_test_id("market-trading-mode")).to_have_text(
"Trading modeNo trading"
)
expect(page.get_by_test_id("market-state")).to_have_text("StatusClosed")
expect(page.get_by_test_id("liquidity-supplied")).to_have_text("Liquidity supplied 0.00 (0.00%)")
expect(page.get_by_test_id("market-funding")).to_contain_text("Funding Rate / Countdown")
expect(page.get_by_test_id("liquidity-supplied")).to_have_text(
"Liquidity supplied 0.00 (0.00%)"
)
expect(page.get_by_test_id("market-funding")).to_contain_text(
"Funding Rate / Countdown"
)
expect(page.get_by_test_id("index-price")).to_contain_text("Index Price")
expect(page.get_by_test_id("deal-ticket-error-message-summary")).to_have_text("This market is closed and not accepting orders")
expect(page.get_by_test_id("deal-ticket-error-message-summary")).to_have_text(
"This market is closed and not accepting orders"
)
+9 -3
View File
@@ -4,13 +4,15 @@ from vega_sim.service import VegaService
from actions.vega import submit_order
from actions.utils import change_keys
def check_pnl_color_value(element, expected_color, expected_value):
color = element.evaluate("element => getComputedStyle(element).color")
value = element.inner_text()
assert color == expected_color, f"Unexpected color: {color}"
assert value == expected_value, f"Unexpected value: {value}"
@pytest.mark.usefixtures("vega", "page", "continuous_market", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_pnl(continuous_market, vega: VegaService, page: Page):
page.set_viewport_size({"width": 1748, "height": 977})
submit_order(vega, "Key 1", continuous_market, "SIDE_BUY", 1, 104.50000)
@@ -59,9 +61,13 @@ def test_pnl(continuous_market, vega: VegaService, page: Page):
key_1_unrealised_pnl = key_1.query_selector('xpath=./div[@col-id="unrealisedPNL"]')
key_1_realised_pnl = key_1.query_selector('xpath=./div[@col-id="realisedPNL"]')
key_mm_unrealised_pnl = key_mm.query_selector('xpath=./div[@col-id="unrealisedPNL"]')
key_mm_unrealised_pnl = key_mm.query_selector(
'xpath=./div[@col-id="unrealisedPNL"]'
)
key_mm_realised_pnl = key_mm.query_selector('xpath=./div[@col-id="realisedPNL"]')
key_mm2_unrealised_pnl = key_mm2.query_selector('xpath=./div[@col-id="unrealisedPNL"]')
key_mm2_unrealised_pnl = key_mm2.query_selector(
'xpath=./div[@col-id="unrealisedPNL"]'
)
key_mm2_realised_pnl = key_mm2.query_selector('xpath=./div[@col-id="realisedPNL"]')
check_pnl_color_value(key_1_realised_pnl, "rgb(0, 0, 0)", "0.00")
check_pnl_color_value(key_1_unrealised_pnl, "rgb(236, 0, 60)", "-4.00")
@@ -2,30 +2,31 @@ import os
import pytest
from playwright.sync_api import Page, expect
from actions.utils import wait_for_toast_confirmation
@pytest.mark.usefixtures("page", "auth", "risk_accepted", "continuous_market")
#TODO migrate to jest
@pytest.mark.usefixtures("auth", "risk_accepted", "continuous_market")
def test_ledger_entries_downloads(page: Page):
page.goto("/#/portfolio")
page.get_by_test_id("Ledger entries").click()
expect(page.get_by_test_id("ledger-download-button")).to_be_enabled()
# 7007-LEEN-001
page.get_by_test_id("ledger-download-button").click()
#7007-LEEN-009
# 7007-LEEN-009
expect(page.get_by_test_id("toast-content")).to_contain_text(("Your file is ready"))
# Get the user's Downloads directory
downloads_directory = os.path.expanduser("~") + "/Downloads/"
# Start waiting for the download
with page.expect_download() as download_info:
# Perform the action that initiates download
# Perform the action that initiates download
page.get_by_role("link", name="Get file here").click()
download = download_info.value
# Wait for the download process to complete and save the downloaded file in the Downloads directory
download.save_as(os.path.join(downloads_directory, download.suggested_filename))
# Verify the download by asserting that the file exists
downloaded_file_path = os.path.join(downloads_directory, download.suggested_filename)
assert os.path.exists(downloaded_file_path), f"Download failed! File not found at: {downloaded_file_path}"
downloaded_file_path = os.path.join(
downloads_directory, download.suggested_filename
)
assert os.path.exists(
downloaded_file_path
), f"Download failed! File not found at: {downloaded_file_path}"
@@ -8,44 +8,60 @@ TOOLTIP_LABEL = "margin-health-tooltip-label"
TOOLTIP_VALUE = "margin-health-tooltip-value"
COL_ID_USED = ".ag-center-cols-container [col-id='used'] .ag-cell-value"
@pytest.fixture(scope="module")
def vega(request):
with init_vega(request) as vega:
yield vega
@pytest.fixture(scope="module")
def continuous_market(vega: VegaService):
return setup_continuous_market(vega)
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_usage_breakdown(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id("Collateral").click()
page.locator(".ag-floating-top-container .ag-row [col-id='used']").click()
usage_breakdown = page.get_by_test_id('usage-breakdown')
usage_breakdown = page.get_by_test_id("usage-breakdown")
# Verify headers
headers = ['Market', 'Account type', 'Balance', 'Margin health']
ag_headers = usage_breakdown.locator('.ag-header-cell-text').element_handles()
headers = ["Market", "Account type", "Balance", "Margin health"]
ag_headers = usage_breakdown.locator(".ag-header-cell-text").element_handles()
for i, header_element in enumerate(ag_headers):
header_text = header_element.text_content()
assert header_text == headers[i]
# Other expectations
expect(usage_breakdown.locator('[class="mb-2 text-sm"]')).to_have_text("You have 1,000,000.00 tDAI in total.")
expect(usage_breakdown.locator('[class="mb-2 text-sm"]')).to_have_text(
"You have 1,000,000.00 tDAI in total."
)
expect(usage_breakdown.locator(COL_ID_USED).first).to_have_text("8.50269 (0%)")
expect(usage_breakdown.locator(COL_ID_USED).nth(1)).to_have_text("999,991.49731 (99%)")
expect(usage_breakdown.locator(COL_ID_USED).nth(1)).to_have_text(
"999,991.49731 (99%)"
)
# Maintenance Level
expect(usage_breakdown.locator(".ag-center-cols-container [col-id='market.id'] .ag-cell-value").first).to_have_text("2.85556 above maintenance level")
expect(
usage_breakdown.locator(
".ag-center-cols-container [col-id='market.id'] .ag-cell-value"
).first
).to_have_text("2.85556 above maintenance level")
# Margin health tooltip
usage_breakdown.get_by_test_id("margin-health-chart-track").hover()
tooltip_data = [("maintenance level", "5.64713"), ("search level", "6.21184"), ("initial level", "8.47069"), ("balance", "8.50269"), ("release level", "9.60012")]
tooltip_data = [
("maintenance level", "5.64713"),
("search level", "6.21184"),
("initial level", "8.47069"),
("balance", "8.50269"),
("release level", "9.60012"),
]
for index, (label, value) in enumerate(tooltip_data):
expect(page.get_by_test_id(TOOLTIP_LABEL).nth(index)).to_have_text(label)
expect(page.get_by_test_id(TOOLTIP_VALUE).nth(index)).to_have_text(value)
page.get_by_test_id('dialog-close').click()
page.get_by_test_id("dialog-close").click()
@@ -5,6 +5,7 @@ from fixtures.market import (
setup_continuous_market,
)
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_closed_market_position(vega: VegaService, page: Page):
market_id = setup_continuous_market(vega)
@@ -26,4 +27,3 @@ def test_closed_market_position(vega: VegaService, page: Page):
expect(market.get_by_test_id("stack-cell-primary")).to_have_text("BTC:DAI_2023")
page.get_by_test_id("open-transfer").click()
expect(page.locator(".ag-overlay-panel")).to_have_text("No positions")
@@ -9,10 +9,9 @@ def vega():
yield vega
@pytest.mark.usefixtures("page", "risk_accepted")
@pytest.mark.usefixtures("risk_accepted")
def test_share_usage_data(page: Page):
page.goto("/")
# page.get_by_test_id("icon-cross").click()
page.get_by_test_id("Settings").click()
telemetry_switch = page.locator("#switch-settings-telemetry-switch")
expect(telemetry_switch).to_have_attribute("data-state", "unchecked")
@@ -41,7 +40,7 @@ ICON_TO_TOAST = {
}
@pytest.mark.usefixtures("page", "risk_accepted")
@pytest.mark.usefixtures("risk_accepted")
def test_toast_positions(page: Page):
page.goto("/")
page.get_by_test_id("Settings").click()
@@ -52,7 +51,7 @@ def test_toast_positions(page: Page):
expect(page.locator(f"[{toast_selector}]")).to_be_visible()
@pytest.mark.usefixtures("page", "risk_accepted")
@pytest.mark.usefixtures("risk_accepted")
def test_dark_mode(page: Page):
page.goto("/")
page.get_by_test_id("Settings").click()
@@ -5,7 +5,7 @@ from fixtures.market import setup_continuous_market, setup_simple_successor_mark
@pytest.fixture
@pytest.mark.usefixtures("vega")
@pytest.mark.usefixtures()
def successor_market(vega: VegaService):
parent_market_id = setup_continuous_market(vega)
tdai_id = vega.find_asset_id(symbol="tDAI")
@@ -23,8 +23,7 @@ def successor_market(vega: VegaService):
return successor_market_id
@pytest.mark.usefixtures("page", "risk_accepted")
@pytest.mark.usefixtures("risk_accepted")
def test_succession_line(page: Page, successor_market):
page.goto(f"/#/markets/{successor_market}")
page.get_by_test_id("Info").click()
@@ -45,8 +45,10 @@ def verify_data_grid(page: Page, data_test_id, expected_pattern):
raise AssertionError(f"Pattern does not match: {expected} != {actual}")
@pytest.mark.usefixtures("page", "continuous_market", "auth", "risk_accepted")
def test_limit_order_new_trade_top_of_list(continuous_market, vega: VegaService, page: Page):
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_limit_order_new_trade_top_of_list(
continuous_market, vega: VegaService, page: Page
):
submit_order(vega, "Key 1", continuous_market, "SIDE_BUY", 1, 110)
vega.wait_fn(1)
vega.wait_for_total_catchup()
@@ -67,7 +69,7 @@ def test_limit_order_new_trade_top_of_list(continuous_market, vega: VegaService,
verify_data_grid(page, "Trades", expected_trade)
@pytest.mark.usefixtures("page", "continuous_market", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_price_copied_to_deal_ticket(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id("Trades").click()
@@ -4,10 +4,9 @@ from vega_sim.service import VegaService
from actions.vega import submit_multiple_orders
@pytest.mark.skip("tbd")
@pytest.mark.usefixtures(
"page", "vega", "opening_auction_market", "auth", "risk_accepted"
)
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_trade_match_table(opening_auction_market: str, vega: VegaService, page: Page):
row_locator = ".ag-center-cols-container .ag-row"
page.goto(f"/#/markets/{opening_auction_market}")
@@ -12,7 +12,7 @@
# InfoItem = namedtuple('InfoItem', ['name', 'infoText'])
# @pytest.mark.skip("temporary skip")
# @pytest.mark.parametrize("vega", [120], indirect=True)
# @pytest.mark.parametrize(, [120], indirect=True)
# @pytest.mark.usefixtures("continuous_market","risk_accepted", "auth")
# def test_trading_chart(continuous_market, vega: VegaService, page: Page):
# page.goto(f"/#/markets/{continuous_market}")
@@ -2,7 +2,13 @@ import pytest
import re
from playwright.sync_api import Page, expect
from vega_sim.service import VegaService
from actions.utils import wait_for_toast_confirmation, create_and_faucet_wallet, WalletConfig, next_epoch, change_keys
from actions.utils import (
wait_for_toast_confirmation,
create_and_faucet_wallet,
WalletConfig,
next_epoch,
change_keys,
)
import vega_sim.proto.vega as vega_protos
LIQ = WalletConfig("liq", "liq")
@@ -10,7 +16,8 @@ PARTY_A = WalletConfig("party_a", "party_a")
PARTY_B = WalletConfig("party_b", "party_b")
PARTY_C = WalletConfig("party_c", "party_c")
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_transfer_submit(continuous_market, vega: VegaService, page: Page):
# 1003-TRAN-001
# 1003-TRAN-006
@@ -19,38 +26,50 @@ def test_transfer_submit(continuous_market, vega: VegaService, page: Page):
# 1003-TRAN-009
# 1003-TRAN-010
# 1003-TRAN-023
page.goto('/#/portfolio')
page.goto("/#/portfolio")
expect(page.get_by_test_id('transfer-form')).to_be_visible
page.get_by_test_id('select-asset').click()
expect(page.get_by_test_id('rich-select-option')).to_have_count(1)
expect(page.get_by_test_id("transfer-form")).to_be_visible
page.get_by_test_id("select-asset").click()
expect(page.get_by_test_id("rich-select-option")).to_have_count(1)
page.get_by_test_id('rich-select-option').click()
page.get_by_test_id("rich-select-option").click()
page.select_option('[data-testid=transfer-form] [name="toVegaKey"]', index=2)
page.select_option('[data-testid=transfer-form] [name="fromAccount"]', index=1)
expected_asset_text = re.compile(r"tDAI tDAI999991.49731 tDAI.{6}….{4}")
actual_asset_text = page.get_by_test_id('select-asset').text_content().strip()
actual_asset_text = page.get_by_test_id("select-asset").text_content().strip()
assert expected_asset_text.search(actual_asset_text), f"Expected pattern not found in {actual_asset_text}"
assert expected_asset_text.search(
actual_asset_text
), f"Expected pattern not found in {actual_asset_text}"
page.locator('[data-testid=transfer-form] input[name="amount"]').fill('1')
expect(page.locator('[data-testid=transfer-form] input[name="amount"]')).not_to_be_empty()
page.locator('[data-testid=transfer-form] input[name="amount"]').fill("1")
expect(
page.locator('[data-testid=transfer-form] input[name="amount"]')
).not_to_be_empty()
page.locator('[data-testid=transfer-form] [type="submit"]').click()
wait_for_toast_confirmation(page)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
expected_confirmation_text = re.compile(r"Transfer completeYour transaction has been confirmedView in block explorerTransferTo .{6}….{6}1\.00 tDAI")
actual_confirmation_text = page.get_by_test_id('toast-content').text_content()
assert expected_confirmation_text.search(actual_confirmation_text), f"Expected pattern not found in {actual_confirmation_text}"
expected_confirmation_text = re.compile(
r"Transfer completeYour transaction has been confirmedView in block explorerTransferTo .{6}….{6}1\.00 tDAI"
)
actual_confirmation_text = page.get_by_test_id("toast-content").text_content()
assert expected_confirmation_text.search(
actual_confirmation_text
), f"Expected pattern not found in {actual_confirmation_text}"
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
def test_transfer_vesting_below_minimum(continuous_market, vega: VegaService, page: Page):
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_transfer_vesting_below_minimum(
continuous_market, vega: VegaService, page: Page
):
vega.update_network_parameter(
"market_maker", parameter="transfer.minTransferQuantumMultiple", new_value="100000"
"market_maker",
parameter="transfer.minTransferQuantumMultiple",
new_value="100000",
)
vega.wait_for_total_catchup()
@@ -94,28 +113,34 @@ def test_transfer_vesting_below_minimum(continuous_market, vega: VegaService, pa
vega.wait_for_total_catchup()
next_epoch(vega=vega)
next_epoch(vega=vega)
page.goto('/#/portfolio')
expect(page.get_by_test_id('transfer-form')).to_be_visible
page.goto("/#/portfolio")
expect(page.get_by_test_id("transfer-form")).to_be_visible
change_keys(page, vega, "party_b")
page.get_by_test_id('select-asset').click()
page.get_by_test_id('rich-select-option').click()
page.get_by_test_id("select-asset").click()
page.get_by_test_id("rich-select-option").click()
option_value = page.locator('[data-testid="transfer-form"] [name="fromAccount"] option[value^="ACCOUNT_TYPE_VESTED_REWARDS"]').first.get_attribute("value")
option_value = page.locator(
'[data-testid="transfer-form"] [name="fromAccount"] option[value^="ACCOUNT_TYPE_VESTED_REWARDS"]'
).first.get_attribute("value")
page.select_option('[data-testid="transfer-form"] [name="fromAccount"]', option_value)
page.select_option(
'[data-testid="transfer-form"] [name="fromAccount"]', option_value
)
page.locator('[data-testid=transfer-form] input[name="amount"]').fill('0.000001')
page.locator('[data-testid=transfer-form] input[name="amount"]').fill("0.000001")
page.locator('[data-testid=transfer-form] [type="submit"]').click()
expect(page.get_by_test_id('input-error-text')).to_be_visible
expect(page.get_by_test_id('input-error-text')).to_have_text("Amount below minimum requirements for partial transfer. Use max to bypass")
expect(page.get_by_test_id("input-error-text")).to_be_visible
expect(page.get_by_test_id("input-error-text")).to_have_text(
"Amount below minimum requirements for partial transfer. Use max to bypass"
)
vega.one_off_transfer(
from_key_name=PARTY_B.name,
to_key_name=PARTY_B.name,
from_account_type= vega_protos.vega.AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
to_account_type= vega_protos.vega.AccountType.ACCOUNT_TYPE_GENERAL,
asset= asset_id,
amount= 24.999999,
from_account_type=vega_protos.vega.AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
to_account_type=vega_protos.vega.AccountType.ACCOUNT_TYPE_GENERAL,
asset=asset_id,
amount=24.999999,
)
vega.forward("10s")
vega.wait_fn(10)
@@ -127,6 +152,10 @@ def test_transfer_vesting_below_minimum(continuous_market, vega: VegaService, pa
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
expected_confirmation_text = re.compile(r"Transfer completeYour transaction has been confirmedView in block explorerTransferTo .{6}….{6}0\.00001 tDAI")
actual_confirmation_text = page.get_by_test_id('toast-content').text_content()
assert expected_confirmation_text.search(actual_confirmation_text), f"Expected pattern not found in {actual_confirmation_text}"
expected_confirmation_text = re.compile(
r"Transfer completeYour transaction has been confirmedView in block explorerTransferTo .{6}….{6}0\.00001 tDAI"
)
actual_confirmation_text = page.get_by_test_id("toast-content").text_content()
assert expected_confirmation_text.search(
actual_confirmation_text
), f"Expected pattern not found in {actual_confirmation_text}"
+63 -45
View File
@@ -15,6 +15,7 @@ tif = "order-tif"
expire = "expire"
api_request_match = r"http://localhost:\d+/api/v2/requests"
@pytest.fixture(scope="module")
def vega(request):
with init_vega(request) as vega:
@@ -25,50 +26,59 @@ def vega(request):
def continuous_market(vega):
return setup_continuous_market(vega)
def handle_route_connection_lost(route: Route, request):
if request.method == "POST" and re.match(api_request_match, request.url):
route.fulfill(
status=200,
headers={"Content-Type": "application/json"},
body='{"jsonrpc": "2.0", "id": "1"}'
)
else:
route.continue_()
if request.method == "POST" and re.match(api_request_match, request.url):
route.fulfill(
status=200,
headers={"Content-Type": "application/json"},
body='{"jsonrpc": "2.0", "id": "1"}',
)
else:
route.continue_()
def handle_route_connection_rejected(route: Route, request):
if request.method == "POST" and re.match(api_request_match, request.url):
custom_response = {
"jsonrpc": "2.0",
"error": {
"code": 3001,
"data": "the user rejected the wallet connection",
"message": "User error"
},
"id": "0"
}
route.fulfill(
status=400,
headers={"Content-Type": "application/json"},
body=json.dumps(custom_response)
)
else:
route.continue_()
if request.method == "POST" and re.match(api_request_match, request.url):
custom_response = {
"jsonrpc": "2.0",
"error": {
"code": 3001,
"data": "the user rejected the wallet connection",
"message": "User error",
},
"id": "0",
}
route.fulfill(
status=400,
headers={"Content-Type": "application/json"},
body=json.dumps(custom_response),
)
else:
route.continue_()
def assert_connection_approve(route: Route, request, page:Page):
if request.method == "POST" and re.match(api_request_match, request.url):
expect(page.get_by_test_id("toast-content")).to_have_text("Please go to your Vega wallet application and approve or reject the transaction.")
else:
route.continue_()
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
def assert_connection_approve(route: Route, request, page: Page):
if request.method == "POST" and re.match(api_request_match, request.url):
expect(page.get_by_test_id("toast-content")).to_have_text(
"Please go to your Vega wallet application and approve or reject the transaction."
)
else:
route.continue_()
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_wallet_connection_error(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
page.route("**/*", handle_route_connection_lost)
page.get_by_test_id("connect-vega-wallet").click()
page.get_by_test_id("connector-jsonRpc").click()
expect(page.get_by_test_id("wallet-dialog-title")).to_have_text("Something went wrong")
expect(page.get_by_test_id("wallet-dialog-title")).to_have_text(
"Something went wrong"
)
@pytest.mark.usefixtures("page", "risk_accepted")
@pytest.mark.usefixtures("risk_accepted")
def test_wallet_connection_rejected(continuous_market, page: Page):
# 0002-WCON-002
# 0002-WCON-005
@@ -78,11 +88,13 @@ def test_wallet_connection_rejected(continuous_market, page: Page):
page.route("**/*", handle_route_connection_rejected)
page.get_by_test_id("connect-vega-wallet").click()
page.get_by_test_id("connector-jsonRpc").click()
expect(page.get_by_test_id("dialog-content").nth(1)).to_have_text("User errorthe user rejected the wallet connectionTry againAbout the Vega wallet | Supported browsers ")
expect(page.get_by_test_id("dialog-content").nth(1)).to_have_text(
"User errorthe user rejected the wallet connectionTry againAbout the Vega wallet | Supported browsers "
)
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
def test_wallet_connection_error_transaction(continuous_market, vega: VegaService, page: Page):
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_wallet_connection_error_transaction(continuous_market, page: Page):
# 0003-WTXN-009
# 0003-WTXN-011
# 0002-WCON-016
@@ -92,20 +104,26 @@ def test_wallet_connection_error_transaction(continuous_market, vega: VegaServic
page.get_by_test_id(order_price).fill("120")
page.route("**/*", handle_route_connection_lost)
page.get_by_test_id(place_order).click()
expect(page.get_by_test_id("toast-content")).to_have_text("Wallet disconnectedThe connection to your Vega Wallet has been lost.Connect vega wallet")
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
def test_wallet_transaction_rejected(continuous_market, vega: VegaService, page: Page):
expect(page.get_by_test_id("toast-content")).to_have_text(
"Wallet disconnectedThe connection to your Vega Wallet has been lost.Connect vega wallet"
)
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_wallet_transaction_rejected(continuous_market, page: Page):
# 0003-WTXN-007
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id(order_size).fill("10")
page.get_by_test_id(order_price).fill("120")
page.route("**/*", handle_route_connection_rejected)
page.get_by_test_id(place_order).click()
expect(page.get_by_test_id("toast-content")).to_have_text("Error occurredthe user rejected the wallet connection")
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
def test_wallet_connection_approve(continuous_market, vega: VegaService, page: Page):
expect(page.get_by_test_id("toast-content")).to_have_text(
"Error occurredthe user rejected the wallet connection"
)
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_wallet_connection_approve(continuous_market, page: Page):
# 0002-WCON-005
# 0002-WCON-007
# 0002-WCON-009
@@ -113,4 +131,4 @@ def test_wallet_connection_approve(continuous_market, vega: VegaService, page: P
page.get_by_test_id(order_size).fill("10")
page.get_by_test_id(order_price).fill("120")
page.route("**/*", assert_connection_approve)
page.get_by_test_id(place_order).click()
page.get_by_test_id(place_order).click()
+10 -1
View File
@@ -4,9 +4,18 @@ export default {
preset: '../../jest.preset.js',
transform: {
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest',
'^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/next/babel'] }],
'^.+\\.[tj]sx?$': [
'babel-jest',
{
presets: ['@nx/next/babel'],
// required for pennant to work in jest, due to having untranspiled exports
plugins: [['@babel/plugin-proposal-private-methods']],
},
],
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/apps/trading',
setupFilesAfterEnv: ['./setup-tests.ts'],
// dont ignore pennant from transpilation
transformIgnorePatterns: ['<rootDir>/node_modules/pennant'],
};
@@ -1,9 +1,9 @@
import { routerConfig } from '../../pages/client-router';
import { useRouterConfig } from '../../pages/client-router';
import { matchRoutes, useLocation } from 'react-router-dom';
export const useGetCurrentRouteId = () => {
const location = useLocation();
const matches = matchRoutes(routerConfig, location);
const matches = matchRoutes(useRouterConfig(), location);
const lastRoute = matches ? matches[matches.length - 1] : undefined;
if (lastRoute) {
const id = lastRoute.route.id;
@@ -0,0 +1,62 @@
import {
Intent,
useToasts,
ToastHeading,
CLOSE_AFTER,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useEffect, useMemo } from 'react';
import { useT } from '../use-t';
import { VegaWalletConnectButton } from '../../components/vega-wallet-connect-button';
const WALLET_DISCONNECTED_TOAST_ID = 'WALLET_DISCONNECTED_TOAST_ID';
export const useWalletDisconnectedToasts = () => {
const t = useT();
const [hasToast, setToast, updateToast] = useToasts((state) => [
state.hasToast,
state.setToast,
state.update,
]);
const { isAlive } = useVegaWallet();
const toast = useMemo(
() => ({
id: WALLET_DISCONNECTED_TOAST_ID,
intent: Intent.Danger,
content: (
<>
<ToastHeading>{t('Wallet connection lost')}</ToastHeading>
<p>{t('The connection to the Vega wallet has been lost.')}</p>
<p className="mt-2">
<VegaWalletConnectButton
intent={Intent.Danger}
onClick={() => {
updateToast(WALLET_DISCONNECTED_TOAST_ID, {
hidden: true,
});
}}
/>
</p>
</>
),
onClose: () => {
updateToast(WALLET_DISCONNECTED_TOAST_ID, {
hidden: true,
});
},
closeAfter: CLOSE_AFTER,
}),
[t, updateToast]
);
useEffect(() => {
if (isAlive === false) {
if (hasToast(WALLET_DISCONNECTED_TOAST_ID)) {
updateToast(WALLET_DISCONNECTED_TOAST_ID, { hidden: false });
} else {
setToast(toast);
}
}
}, [hasToast, isAlive, setToast, t, toast, updateToast]);
};
+1
View File
@@ -75,6 +75,7 @@ i18n
'positions',
'trades',
'trading',
'trading-view',
'ui-toolkit',
'utils',
'wallet',
+14 -9
View File
@@ -1,4 +1,5 @@
import { FLAGS } from '@vegaprotocol/environment';
import { useFeatureFlags } from '@vegaprotocol/environment';
import { useMemo } from 'react';
import {
JsonRpcConnector,
ViewConnector,
@@ -18,13 +19,17 @@ if (typeof window !== 'undefined') {
view = new ViewConnector();
}
export const snap = FLAGS.METAMASK_SNAPS
? new SnapConnector(DEFAULT_SNAP_ID)
: undefined;
export const snap = new SnapConnector(DEFAULT_SNAP_ID);
export const Connectors = {
injected,
jsonRpc,
view,
snap,
export const useConnectors = () => {
const featureFlags = useFeatureFlags((state) => state.flags);
return useMemo(
() => ({
injected,
jsonRpc,
view,
snap: featureFlags.METAMASK_SNAPS ? snap : undefined,
}),
[featureFlags.METAMASK_SNAPS]
);
};
+139 -136
View File
@@ -22,7 +22,7 @@ import { ApplyCodeFormContainer } from '../client-pages/referrals/apply-code-for
import { CreateCodeContainer } from '../client-pages/referrals/create-code-form';
import { NotFound as ReferralNotFound } from '../client-pages/referrals/error-boundary';
import { compact } from 'lodash';
import { FLAGS } from '@vegaprotocol/environment';
import { useFeatureFlags } from '@vegaprotocol/environment';
import { LiquidityHeader } from '../components/liquidity-header';
import { MarketHeader } from '../components/market-header';
import { PortfolioSidebar } from '../client-pages/portfolio/portfolio-sidebar';
@@ -44,144 +44,147 @@ const NotFound = () => {
);
};
export const routerConfig: RouteObject[] = compact([
{
index: true,
element: <Home />,
id: AppRoutes.HOME,
},
{
path: 'disclaimer',
element: <LayoutCentered />,
id: AppRoutes.DISCLAIMER,
children: [{ index: true, element: <Disclaimer /> }],
},
// Referrals routing (the pages should be available if the feature flag is on)
FLAGS.REFERRALS
? {
path: AppRoutes.REFERRALS,
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
children: [
{
element: (
<LayoutWithSky>
<Referrals />
</LayoutWithSky>
),
children: [
{
index: true,
element: <ReferralStatistics />,
},
{
path: AppRoutes.REFERRALS_CREATE_CODE,
element: <CreateCodeContainer />,
},
{
path: AppRoutes.REFERRALS_APPLY_CODE,
element: <ApplyCodeFormContainer />,
},
],
},
{
path: '*',
element: <ReferralNotFound />,
},
],
}
: undefined,
{
path: 'fees/*',
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
children: [
{
index: true,
element: <Fees />,
},
],
},
{
path: 'rewards/*',
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
children: [
{
index: true,
element: <Rewards />,
},
],
},
{
path: 'markets/*',
element: (
<LayoutWithSidebar
header={<MarketHeader />}
sidebar={<MarketsSidebar />}
/>
),
children: [
{
index: true,
element: <MarketsPage />,
id: AppRoutes.MARKETS,
},
{
path: 'all',
element: <Navigate to="/markets" />,
},
{
path: ':marketId',
element: <MarketPage />,
id: AppRoutes.MARKET,
},
],
},
{
path: 'portfolio/*',
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
children: [
{
index: true,
element: <Portfolio />,
id: AppRoutes.PORTFOLIO,
},
{
path: 'assets',
element: <Assets />,
id: AppRoutes.ASSETS,
children: [
{ index: true, element: <Navigate to="deposit" /> },
{ path: 'deposit', element: <Deposit />, id: AppRoutes.DEPOSIT },
{ path: 'withdraw', element: <Withdraw />, id: AppRoutes.WITHDRAW },
{ path: 'transfer', element: <Transfer />, id: AppRoutes.TRANSFER },
],
},
],
},
export const useRouterConfig = (): RouteObject[] => {
const featureFlags = useFeatureFlags((state) => state.flags);
return compact([
{
index: true,
element: <Home />,
id: AppRoutes.HOME,
},
{
path: 'disclaimer',
element: <LayoutCentered />,
id: AppRoutes.DISCLAIMER,
children: [{ index: true, element: <Disclaimer /> }],
},
// Referrals routing (the pages should be available if the feature flag is on)
featureFlags.REFERRALS
? {
path: AppRoutes.REFERRALS,
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
children: [
{
element: (
<LayoutWithSky>
<Referrals />
</LayoutWithSky>
),
children: [
{
index: true,
element: <ReferralStatistics />,
},
{
path: AppRoutes.REFERRALS_CREATE_CODE,
element: <CreateCodeContainer />,
},
{
path: AppRoutes.REFERRALS_APPLY_CODE,
element: <ApplyCodeFormContainer />,
},
],
},
{
path: '*',
element: <ReferralNotFound />,
},
],
}
: undefined,
{
path: 'fees/*',
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
children: [
{
index: true,
element: <Fees />,
},
],
},
{
path: 'rewards/*',
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
children: [
{
index: true,
element: <Rewards />,
},
],
},
{
path: 'markets/*',
element: (
<LayoutWithSidebar
header={<MarketHeader />}
sidebar={<MarketsSidebar />}
/>
),
children: [
{
index: true,
element: <MarketsPage />,
id: AppRoutes.MARKETS,
},
{
path: 'all',
element: <Navigate to="/markets" />,
},
{
path: ':marketId',
element: <MarketPage />,
id: AppRoutes.MARKET,
},
],
},
{
path: 'portfolio/*',
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
children: [
{
index: true,
element: <Portfolio />,
id: AppRoutes.PORTFOLIO,
},
{
path: 'assets',
element: <Assets />,
id: AppRoutes.ASSETS,
children: [
{ index: true, element: <Navigate to="deposit" /> },
{ path: 'deposit', element: <Deposit />, id: AppRoutes.DEPOSIT },
{ path: 'withdraw', element: <Withdraw />, id: AppRoutes.WITHDRAW },
{ path: 'transfer', element: <Transfer />, id: AppRoutes.TRANSFER },
],
},
],
},
{
path: 'liquidity/*',
element: (
<LayoutWithSidebar
header={<LiquidityHeader />}
sidebar={<LiquiditySidebar />}
/>
),
id: AppRoutes.LIQUIDITY,
children: [
{
path: ':marketId',
element: <Liquidity />,
},
],
},
{
path: '*',
element: <NotFound />,
},
]);
{
path: 'liquidity/*',
element: (
<LayoutWithSidebar
header={<LiquidityHeader />}
sidebar={<LiquiditySidebar />}
/>
),
id: AppRoutes.LIQUIDITY,
children: [
{
path: ':marketId',
element: <Liquidity />,
},
],
},
{
path: '*',
element: <NotFound />,
},
]);
};
export const ClientRouter = () => {
const routes = useRoutes(routerConfig);
const routes = useRoutes(useRouterConfig());
return (
<Suspense
fallback={
+4 -3
View File
@@ -3,7 +3,7 @@ import {
useAssetDetailsDialogStore,
} from '@vegaprotocol/assets';
import { VegaConnectDialog, ViewAsDialog } from '@vegaprotocol/wallet';
import { Connectors } from '../lib/vega-connectors';
import { useConnectors } from '../lib/vega-connectors';
import {
Web3ConnectUncontrolledDialog,
WithdrawalApprovalDialogContainer,
@@ -13,13 +13,14 @@ import { RiskMessage } from '../components/welcome-dialog';
const DialogsContainer = () => {
const { isOpen, id, trigger, setOpen } = useAssetDetailsDialogStore();
const connectors = useConnectors();
return (
<>
<VegaConnectDialog
connectors={Connectors}
connectors={connectors}
riskMessage={<RiskMessage />}
/>
<ViewAsDialog connector={Connectors.view} />
<ViewAsDialog connector={connectors.view} />
<AssetDetailsDialog
assetId={id}
trigger={trigger || null}
+4 -3
View File
@@ -7,13 +7,14 @@ import {
useVegaWallet,
} from '@vegaprotocol/wallet';
import { useGlobalStore } from '../stores';
import { Connectors } from '../lib/vega-connectors';
import { useConnectors } from '../lib/vega-connectors';
import { useTelemetryApproval } from '../lib/hooks/use-telemetry-approval';
export const MaybeConnectEagerly = () => {
const { VEGA_ENV, SENTRY_DSN } = useEnvironment();
const update = useGlobalStore((store) => store.update);
const eagerConnecting = useVegaEagerConnect(Connectors);
const connectors = useConnectors();
const eagerConnecting = useVegaEagerConnect(connectors);
const [isTelemetryApproved] = useTelemetryApproval();
useEthereumEagerConnect(
isTelemetryApproved ? { dsn: SENTRY_DSN, env: VEGA_ENV } : {}
@@ -23,7 +24,7 @@ export const MaybeConnectEagerly = () => {
const [searchParams] = useSearchParams();
const [query] = useState(searchParams.get('address'));
if (query && !pubKey) {
connect(Connectors['view']);
connect(connectors.view);
}
useEffect(() => {
update({ eagerConnecting });
+2
View File
@@ -6,6 +6,7 @@ import { useEthereumWithdrawApprovalsToasts } from '@vegaprotocol/web3';
import { useReadyToWithdrawalToasts } from '@vegaprotocol/withdraws';
import { Links } from '../lib/links';
import { useReferralToasts } from '../client-pages/referrals/hooks/use-referral-toasts';
import { useWalletDisconnectedToasts } from '../lib/hooks/use-wallet-disconnected-toasts';
export const ToastsManager = () => {
useProposalToasts();
@@ -16,6 +17,7 @@ export const ToastsManager = () => {
withdrawalsLink: Links.PORTFOLIO(),
});
useReferralToasts();
useWalletDisconnectedToasts();
const toasts = useToasts((store) => store.toasts);
return <ToastsContainer order="desc" toasts={toasts} />;
+26 -17
View File
@@ -1,43 +1,52 @@
import 'pennant/dist/style.css';
import { CandlestickChart } from 'pennant';
import {
CandlestickChart,
type Overlay,
type ChartType,
type Interval,
type Study,
} from 'pennant';
import { VegaDataSource } from './data-source';
import { useApolloClient } from '@apollo/client';
import { useMemo } from 'react';
import debounce from 'lodash/debounce';
import AutoSizer from 'react-virtualized-auto-sizer';
import { useVegaWallet } from '@vegaprotocol/wallet';
import {
STUDY_SIZE,
useCandlesChartSettings,
} from './use-candles-chart-settings';
import { useT } from './use-t';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
export type CandlesChartContainerProps = {
marketId: string;
interval: Interval;
chartType: ChartType;
overlays: Overlay[];
studies: Study[];
studySizes: number[];
defaultStudySize: number;
setStudies: (studies?: Study[]) => void;
setStudySizes: (sizes: number[]) => void;
setOverlays: (overlays?: Overlay[]) => void;
};
const CANDLES_TO_WIDTH_FACTOR = 0.2;
export const CandlesChartContainer = ({
marketId,
interval,
chartType,
overlays,
studies,
studySizes,
defaultStudySize,
setStudies,
setStudySizes,
setOverlays,
}: CandlesChartContainerProps) => {
const client = useApolloClient();
const { pubKey } = useVegaWallet();
const { theme } = useThemeSwitcher();
const t = useT();
const {
interval,
chartType,
overlays,
studies,
studySizes,
setStudies,
setStudySizes,
setOverlays,
} = useCandlesChartSettings();
const handlePaneChange = useMemo(
() =>
debounce((sizes: number[]) => {
@@ -69,7 +78,7 @@ export const CandlesChartContainer = ({
</span>
),
initialNumCandlesToDisplay: candlesCount,
studySize: STUDY_SIZE,
studySize: defaultStudySize,
studySizes,
}}
interval={interval}
@@ -1,85 +0,0 @@
import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { CandlesMenu } from './candles-menu';
import {
useCandlesChartSettingsStore,
DEFAULT_CHART_SETTINGS,
} from './use-candles-chart-settings';
import { Overlay, Study, overlayLabels, studyLabels } from 'pennant';
describe('CandlesMenu', () => {
const openDropdown = async () => {
await userEvent.click(
screen.getByRole('button', {
name: 'Indicators',
})
);
};
beforeEach(() => {
// clear store each time to avoid conditional testing of defaults
useCandlesChartSettingsStore.setState({ overlays: [], studies: [] });
});
it.each(Object.values(Overlay))('can set %s overlay', async (overlay) => {
render(<CandlesMenu />);
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(<CandlesMenu />);
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 () => {
useCandlesChartSettingsStore.setState(DEFAULT_CHART_SETTINGS);
render(<CandlesMenu />);
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'
);
});
});
+12
View File
@@ -0,0 +1,12 @@
import { Interval as PennantInterval } from 'pennant';
import { Interval } from '@vegaprotocol/types';
export const PENNANT_INTERVAL_MAP = {
[Interval.INTERVAL_BLOCK]: undefined, // TODO: handle block tick
[Interval.INTERVAL_I1M]: PennantInterval.I1M,
[Interval.INTERVAL_I5M]: PennantInterval.I5M,
[Interval.INTERVAL_I15M]: PennantInterval.I15M,
[Interval.INTERVAL_I1H]: PennantInterval.I1H,
[Interval.INTERVAL_I6H]: PennantInterval.I6H,
[Interval.INTERVAL_I1D]: PennantInterval.I1D,
} as const;
+5 -1
View File
@@ -56,6 +56,7 @@ const defaultConfig = {
*/
export class VegaDataSource implements DataSource {
client: ApolloClient<object>;
from?: Date;
marketId: string;
partyId: null | string;
_decimalPlaces = 0;
@@ -158,6 +159,7 @@ export class VegaDataSource implements DataSource {
*/
async query(interval: PennantInterval, from: string) {
try {
this.from = new Date(from);
const { data } = await this.client.query<
CandlesQuery,
CandlesQueryVariables
@@ -215,7 +217,9 @@ export class VegaDataSource implements DataSource {
this.decimalPlaces,
this.positionDecimalPlaces
);
if (!this.from || candle.date < this.from) {
return;
}
onSubscriptionData(candle);
}
});
+1 -1
View File
@@ -1,5 +1,5 @@
export * from './__generated__/Candles';
export * from './__generated__/Chart';
export * from './candles-chart';
export * from './candles-menu';
export { PENNANT_INTERVAL_MAP } from './constants';
export * from './data-source';
@@ -11,7 +11,7 @@ import {
} from '@vegaprotocol/markets';
import { AsyncRendererInline } from '@vegaprotocol/ui-toolkit';
import { DealTicket } from './deal-ticket';
import { FLAGS } from '@vegaprotocol/environment';
import { useFeatureFlags } from '@vegaprotocol/environment';
import { useT } from '../../use-t';
interface DealTicketContainerProps {
@@ -24,6 +24,7 @@ export const DealTicketContainer = ({
marketId,
...props
}: DealTicketContainerProps) => {
const featureFlags = useFeatureFlags((state) => state.flags);
const t = useT();
const showStopOrder = useDealTicketFormValues((state) =>
isStopOrderType(state.formValues[marketId]?.type)
@@ -50,7 +51,7 @@ export const DealTicketContainer = ({
reload={reload}
>
{market && marketData ? (
FLAGS.STOP_ORDERS && showStopOrder ? (
featureFlags.STOP_ORDERS && showStopOrder ? (
<StopOrder
market={market}
marketPrice={marketPrice}
@@ -11,7 +11,7 @@ import {
DealTicketType,
useDealTicketFormValues,
} from '../../hooks/use-form-values';
import type { FeatureFlags } from '@vegaprotocol/environment';
import { useFeatureFlags } from '@vegaprotocol/environment';
import { formatForInput } from '@vegaprotocol/utils';
jest.mock('zustand');
@@ -19,17 +19,6 @@ jest.mock('./deal-ticket-fee-details', () => ({
DealTicketFeeDetails: () => <div data-testid="deal-ticket-fee-details" />,
}));
jest.mock('@vegaprotocol/environment', () => {
const actual = jest.requireActual('@vegaprotocol/environment');
return {
...actual,
FLAGS: {
...actual.FLAGS,
STOP_ORDERS: true,
} as FeatureFlags,
};
});
const marketPrice = '200';
const market = generateMarket();
const submit = jest.fn();
@@ -94,6 +83,7 @@ jest.mock('@vegaprotocol/data-provider', () => ({
describe('StopOrder', () => {
beforeEach(() => {
localStorage.clear();
useFeatureFlags.setState({ flags: { STOP_ORDERS: true } });
});
afterEach(() => {
@@ -18,7 +18,7 @@ import { MarketModeValidationType } from '../../constants';
import { DealTicketType } from '../../hooks/use-form-values';
import * as RadioGroup from '@radix-ui/react-radio-group';
import classNames from 'classnames';
import { FLAGS } from '@vegaprotocol/environment';
import { useFeatureFlags } from '@vegaprotocol/environment';
import { Trans } from 'react-i18next';
import { useT, ns } from '../../use-t';
@@ -49,6 +49,7 @@ export const TypeToggle = ({
value,
onValueChange,
}: Pick<TypeSelectorProps, 'onValueChange' | 'value'>) => {
const featureFlags = useFeatureFlags((state) => state.flags);
const t = useT();
const options = useOptions();
const toggles = useToggles();
@@ -57,8 +58,8 @@ export const TypeToggle = ({
<RadioGroup.Root
name="order-type"
className={classNames('mb-2 grid h-8 leading-8 font-alpha text-xs', {
'grid-cols-3': FLAGS.STOP_ORDERS,
'grid-cols-2': !FLAGS.STOP_ORDERS,
'grid-cols-3': featureFlags.STOP_ORDERS,
'grid-cols-2': !featureFlags.STOP_ORDERS,
})}
value={value}
onValueChange={onValueChange}
@@ -80,7 +81,7 @@ export const TypeToggle = ({
</button>
</RadioGroup.Item>
))}
{FLAGS.STOP_ORDERS && (
{featureFlags.STOP_ORDERS && (
<TradingDropdown
trigger={
<TradingDropdownTrigger
+11 -10
View File
@@ -5,7 +5,7 @@ import { prepend0x } from '@vegaprotocol/smart-contracts';
import sortBy from 'lodash/sortBy';
import { useSubmitApproval } from './use-submit-approval';
import { useSubmitFaucet } from './use-submit-faucet';
import { useCallback, useState } from 'react';
import { useCallback, useEffect } from 'react';
import { useDepositBalances } from './use-deposit-balances';
import type { Asset } from '@vegaprotocol/assets';
import {
@@ -30,7 +30,7 @@ export const DepositManager = ({
const { config } = useEthereumConfig();
const [persistentDeposit, savePersistentDeposit] =
usePersistentDeposit(initialAssetId);
const [assetId, setAssetId] = useState(persistentDeposit?.assetId);
const assetId = persistentDeposit?.assetId;
const asset = assets.find((a) => a.id === assetId);
const bridgeContract = useBridgeContract();
@@ -70,18 +70,19 @@ export const DepositManager = ({
[savePersistentDeposit, persistentDeposit]
);
useEffect(() => {
// When we change asset, also clear the tracked faucet/approve transactions so
// we dont render stale UI
approve.reset();
faucet.reset();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [assetId]);
return (
<DepositForm
selectedAsset={asset}
onDisconnect={reset}
onSelectAsset={(id) => {
setAssetId(id);
savePersistentDeposit({ assetId: id });
// When we change asset, also clear the tracked faucet/approve transactions so
// we dont render stale UI
approve.reset();
faucet.reset();
}}
onSelectAsset={(assetId) => savePersistentDeposit({ assetId })}
handleAmountChange={onAmountChange}
assets={sortBy(assets, 'name')}
submitApprove={approve.perform}
+1
View File
@@ -13,3 +13,4 @@ export * from './use-get-deposit-maximum';
export * from './use-get-deposited-amount';
export * from './use-submit-approval';
export * from './use-submit-faucet';
export * from './use-persistent-deposit';
@@ -10,7 +10,7 @@ interface PersistedDeposit {
}
type PersistedDepositData = Record<string, PersistedDeposit>;
const usePersistentDepositStore = create<{
export const usePersistentDepositStore = create<{
deposits: PersistedDepositData;
saveValue: (entry: PersistedDeposit) => void;
lastVisited?: PersistedDeposit;
@@ -3,7 +3,13 @@ import { act } from 'react-dom/test-utils';
import type { ClientOptions } from '@vegaprotocol/apollo-client';
import { createClient } from '@vegaprotocol/apollo-client';
import { Networks } from '../types';
import { STORAGE_KEY, useEnvironment } from './use-environment';
import {
STORAGE_KEY,
useEnvironment,
featureFlagsLocalStorageKey,
getUserEnabledFeatureFlags,
setUserEnabledFeatureFlag,
} from './use-environment';
const noop = () => {
/* no op*/
@@ -459,3 +465,30 @@ describe('useEnvironment', () => {
expect(localStorage.getItem(STORAGE_KEY)).toBe(newUrl);
});
});
describe('getUserEnabledFeatureFlags', () => {
beforeEach(() => {
localStorage.clear();
});
it('reads enabled flags from local storage', () => {
localStorage.setItem(
featureFlagsLocalStorageKey,
'STOP_ORDERS,STOP_ORDERS,REFERRALS,BLAH'
);
const userEnabledFlags = getUserEnabledFeatureFlags(true, ['STOP_ORDERS']);
expect(userEnabledFlags).toEqual(['STOP_ORDERS']);
});
});
describe('setUserEnabledFeatureFlag', () => {
beforeEach(() => {
localStorage.clear();
});
it('saves enabled flags to local storage', () => {
setUserEnabledFeatureFlag('STOP_ORDERS', true);
expect(localStorage.getItem(featureFlagsLocalStorageKey)).toEqual(
'STOP_ORDERS'
);
expect(getUserEnabledFeatureFlags()).toEqual(['STOP_ORDERS']);
});
});
+470 -400
View File
@@ -40,12 +40,478 @@ const VERSION = 1;
export const STORAGE_KEY = `vega_url_${VERSION}`;
const SUBSCRIPTION_TIMEOUT = 3000;
export const ENV = compileEnvVars();
export const FLAGS = compileFeatureFlags();
/**
* Fetch and validate a vega node configuration
*/
const fetchConfig = async (url?: string) => {
if (!url) return [];
const res = await fetch(url);
const content = await res.text();
const parsed = tomlParse(content);
const tomlResults = tomlConfigSchema.parse(parsed);
const {
API: {
GraphQL: { Hosts },
},
} = tomlResults;
return Hosts;
};
/**
* Find a suitable node by running a test query and test
* subscription, against a list of clients, first to resolve wins
*/
const findNode = async (clients: ClientCollection): Promise<string | null> => {
const tests = Object.entries(clients).map((args) => testNode(...args));
try {
const url = await Promise.any(tests);
return url;
} catch {
// All tests rejected, no suitable node found
return null;
}
};
/**
* Test a node for suitability for connection
*/
const testNode = async (
url: string,
client: Client
): Promise<string | null> => {
const results = await Promise.all([
// these promises will only resolve with true/false
testQuery(client),
testSubscription(client),
]);
if (results[0] && results[1]) {
return url;
}
const message = `Tests failed for node: ${url}`;
console.warn(message);
// throwing here will mean this tests is ignored and a different
// node that hopefully does resolve will fulfill the Promise.any
throw new Error(message);
};
/**
* Run a test query on a client
*/
const testQuery = async (client: Client) => {
try {
const result = await client.query<NodeCheckQuery>({
query: NodeCheckDocument,
});
if (!result || result.error) {
return false;
}
return true;
} catch (err) {
return false;
}
};
/**
* Run a test subscription on a client. A subscription
* that takes longer than SUBSCRIPTION_TIMEOUT ms to respond
* is deemed a failure
*/
const testSubscription = (client: Client) => {
return new Promise((resolve) => {
const sub = client
.subscribe<NodeCheckTimeUpdateSubscription>({
query: NodeCheckTimeUpdateDocument,
errorPolicy: 'all',
})
.subscribe({
next: () => {
resolve(true);
sub.unsubscribe();
},
error: () => {
resolve(false);
sub.unsubscribe();
},
});
setTimeout(() => {
resolve(false);
sub.unsubscribe();
}, SUBSCRIPTION_TIMEOUT);
});
};
export const userControllableFeatureFlags: (keyof FeatureFlags)[] = [];
/**
* Retrieve env vars, parsing where needed some type casting is needed
* here to appease the environment store interface
*/
const compileEnvVars = () => {
const VEGA_ENV = windowOrDefault(
'VEGA_ENV',
process.env['NX_VEGA_ENV']
) as Networks;
const env: Environment = {
VEGA_URL: windowOrDefault('VEGA_URL', process.env['NX_VEGA_URL']),
VEGA_ENV,
VEGA_CONFIG_URL: windowOrDefault(
'VEGA_CONFIG_URL',
process.env['NX_VEGA_CONFIG_URL'] as string
),
VEGA_NETWORKS: parseNetworks(
windowOrDefault('VEGA_NETWORKS', process.env['NX_VEGA_NETWORKS'])
),
VEGA_WALLET_URL: windowOrDefault(
'VEGA_WALLET_URL',
process.env['NX_VEGA_WALLET_URL'] as string
),
HOSTED_WALLET_URL: windowOrDefault(
'HOSTED_WALLET_URL',
process.env['NX_HOSTED_WALLET_URL']
),
ETHERSCAN_URL: getEtherscanUrl(
VEGA_ENV,
windowOrDefault('ETHERSCAN_URL', process.env['NX_ETHERSCAN_URL'])
),
ETHEREUM_PROVIDER_URL: getEthereumProviderUrl(
VEGA_ENV,
windowOrDefault(
'ETHEREUM_PROVIDER_URL',
process.env['NX_ETHEREUM_PROVIDER_URL']
)
),
ETH_LOCAL_PROVIDER_URL: windowOrDefault(
'ETH_LOCAL_PROVIDER_URL',
process.env['NX_ETH_LOCAL_PROVIDER_URL']
),
ETH_WALLET_MNEMONIC: windowOrDefault(
'ETH_WALLET_MNEMONIC',
process.env['NX_ETH_WALLET_MNEMONIC']
),
ORACLE_PROOFS_URL: windowOrDefault(
'ORACLE_PROOFS_URL',
process.env['NX_ORACLE_PROOFS_URL']
),
VEGA_DOCS_URL: windowOrDefault(
'VEGA_DOCS_URL',
process.env['NX_VEGA_DOCS_URL']
),
VEGA_CONSOLE_URL: windowOrDefault(
'VEGA_CONSOLE_URL',
process.env['NX_VEGA_CONSOLE_URL']
),
VEGA_EXPLORER_URL: windowOrDefault(
'VEGA_EXPLORER_URL',
process.env['NX_VEGA_EXPLORER_URL']
),
VEGA_TOKEN_URL: windowOrDefault(
'VEGA_TOKEN_URL',
process.env['NX_VEGA_TOKEN_URL']
),
GITHUB_FEEDBACK_URL: windowOrDefault(
'GITHUB_FEEDBACK_URL',
process.env['NX_GITHUB_FEEDBACK_URL']
),
GIT_BRANCH: windowOrDefault(
'GIT_COMMIT_BRANCH',
process.env['GIT_COMMIT_BRANCH']
),
GIT_COMMIT_HASH: windowOrDefault(
'GIT_COMMIT_HASH',
process.env['GIT_COMMIT_HASH']
),
GIT_ORIGIN_URL: windowOrDefault(
'GIT_ORIGIN_URL',
process.env['GIT_ORIGIN_URL']
),
ANNOUNCEMENTS_CONFIG_URL: windowOrDefault(
'ANNOUNCEMENTS_CONFIG_URL',
process.env['NX_ANNOUNCEMENTS_CONFIG_URL']
),
VEGA_INCIDENT_URL: windowOrDefault(
'VEGA_INCIDENT_URL',
process.env['NX_VEGA_INCIDENT_URL']
),
APP_VERSION: windowOrDefault('APP_VERSION', process.env['NX_APP_VERSION']),
SENTRY_DSN: windowOrDefault('SENTRY_DSN', process.env['NX_SENTRY_DSN']),
TENDERMINT_URL: windowOrDefault(
'NX_TENDERMINT_URL',
process.env['NX_TENDERMINT_URL']
),
TENDERMINT_WEBSOCKET_URL: windowOrDefault(
'NX_TENDERMINT_WEBSOCKET_URL',
process.env['NX_TENDERMINT_WEBSOCKET_URL']
),
CHROME_EXTENSION_URL: windowOrDefault(
'NX_CHROME_EXTENSION_URL',
process.env['NX_CHROME_EXTENSION_URL']
),
MOZILLA_EXTENSION_URL: windowOrDefault(
'NX_MOZILLA_EXTENSION_URL',
process.env['NX_MOZILLA_EXTENSION_URL']
),
CHARTING_LIBRARY_PATH: windowOrDefault(
'NX_CHARTING_LIBRARY_PATH',
process.env['NX_CHARTING_LIBRARY_PATH']
),
CHARTING_LIBRARY_HASH: windowOrDefault(
'NX_CHARTING_LIBRARY_HASH',
process.env['NX_CHARTING_LIBRARY_HASH']
),
};
return env;
};
export const featureFlagsLocalStorageKey = 'vega_feature_flags';
let userEnabledFeatureFlags: (keyof FeatureFlags)[] | undefined = undefined;
export const setUserEnabledFeatureFlag = (
flag: keyof FeatureFlags,
enabled = false
) => {
const enabledFlags = getUserEnabledFeatureFlags();
if (enabled && !enabledFlags.includes(flag)) {
enabledFlags.push(flag);
}
if (!enabled && enabledFlags.includes(flag)) {
enabledFlags.splice(enabledFlags.indexOf(flag), 1);
}
userEnabledFeatureFlags = enabledFlags;
if (typeof window !== 'undefined') {
window.localStorage.setItem(
featureFlagsLocalStorageKey,
enabledFlags.join(',')
);
}
};
export const getUserEnabledFeatureFlags = (
refresh = false,
allowedFlags = userControllableFeatureFlags
): (keyof FeatureFlags)[] => {
if (typeof window === 'undefined') {
return [];
}
if (typeof userEnabledFeatureFlags !== 'undefined' && !refresh) {
return userEnabledFeatureFlags;
}
const enabledFlags = window.localStorage.getItem(featureFlagsLocalStorageKey);
userEnabledFeatureFlags = enabledFlags
? uniq(
(enabledFlags.split(',') as (keyof FeatureFlags)[]).filter((flag) =>
allowedFlags.includes(flag)
)
)
: [];
return userEnabledFeatureFlags;
};
const TRUTHY = ['1', 'true'];
export const compileFeatureFlags = (refresh = false): FeatureFlags => {
const COSMIC_ELEVATOR_FLAGS: CosmicElevatorFlags = {
ICEBERG_ORDERS: TRUTHY.includes(
windowOrDefault(
'NX_ICEBERG_ORDERS',
process.env['NX_ICEBERG_ORDERS']
) as string
),
STOP_ORDERS: TRUTHY.includes(
windowOrDefault('NX_STOP_ORDERS', process.env['NX_STOP_ORDERS']) as string
),
SUCCESSOR_MARKETS: TRUTHY.includes(
windowOrDefault(
'NX_SUCCESSOR_MARKETS',
process.env['NX_SUCCESSOR_MARKETS']
) as string
),
PRODUCT_PERPETUALS: TRUTHY.includes(
windowOrDefault(
'NX_PRODUCT_PERPETUALS',
process.env['NX_PRODUCT_PERPETUALS']
) as string
),
METAMASK_SNAPS: TRUTHY.includes(
windowOrDefault(
'NX_METAMASK_SNAPS',
process.env['NX_METAMASK_SNAPS']
) as string
),
REFERRALS: TRUTHY.includes(
windowOrDefault('NX_REFERRALS', process.env['NX_REFERRALS']) as string
),
DISABLE_CLOSE_POSITION: TRUTHY.includes(
windowOrDefault(
'NX_DISABLE_CLOSE_POSITION',
process.env['NX_DISABLE_CLOSE_POSITION']
) as string
),
UPDATE_MARKET_STATE: TRUTHY.includes(
windowOrDefault(
'NX_UPDATE_MARKET_STATE',
process.env['NX_UPDATE_MARKET_STATE']
) as string
),
GOVERNANCE_TRANSFERS: TRUTHY.includes(
windowOrDefault(
'NX_GOVERNANCE_TRANSFERS',
process.env['NX_GOVERNANCE_TRANSFERS']
) as string
),
VOLUME_DISCOUNTS: TRUTHY.includes(
windowOrDefault(
'NX_VOLUME_DISCOUNTS',
process.env['NX_VOLUME_DISCOUNTS']
) as string
),
};
const EXPLORER_FLAGS = {
EXPLORER_ASSETS: TRUTHY.includes(
windowOrDefault(
'NX_EXPLORER_ASSETS',
process.env['NX_EXPLORER_ASSETS']
) as string
),
EXPLORER_GENESIS: TRUTHY.includes(
windowOrDefault(
'NX_EXPLORER_GENESIS',
process.env['NX_EXPLORER_GENESIS']
) as string
),
EXPLORER_GOVERNANCE: TRUTHY.includes(
windowOrDefault(
'NX_EXPLORER_GOVERNANCE',
process.env['NX_EXPLORER_GOVERNANCE']
) as string
),
EXPLORER_MARKETS: TRUTHY.includes(
windowOrDefault(
'NX_EXPLORER_MARKETS',
process.env['NX_EXPLORER_MARKETS']
) as string
),
EXPLORER_ORACLES: TRUTHY.includes(
windowOrDefault(
'NX_EXPLORER_ORACLES',
process.env['NX_EXPLORER_ORACLES']
) as string
),
EXPLORER_TXS_LIST: TRUTHY.includes(
windowOrDefault(
'NX_EXPLORER_TXS_LIST',
process.env['NX_EXPLORER_TXS_LIST']
) as string
),
EXPLORER_NETWORK_PARAMETERS: TRUTHY.includes(
windowOrDefault(
'NX_EXPLORER_NETWORK_PARAMETERS',
process.env['NX_EXPLORER_NETWORK_PARAMETERS']
) as string
),
EXPLORER_PARTIES: TRUTHY.includes(
windowOrDefault(
'NX_EXPLORER_PARTIES',
process.env['NX_EXPLORER_PARTIES']
) as string
),
EXPLORER_VALIDATORS: TRUTHY.includes(
windowOrDefault(
'NX_EXPLORER_VALIDATORS',
process.env['NX_EXPLORER_VALIDATORS']
) as string
),
};
const GOVERNANCE_FLAGS = {
GOVERNANCE_NETWORK_DOWN: TRUTHY.includes(
windowOrDefault(
'NX_NETWORK_DOWN',
process.env['NX_NETWORK_DOWN']
) as string
),
GOVERNANCE_NETWORK_LIMITS: TRUTHY.includes(
windowOrDefault(
'NX_GOVERNANCE_NETWORK_LIMITS',
process.env['NX_GOVERNANCE_NETWORK_LIMITS']
) as string
),
};
const flags = {
...COSMIC_ELEVATOR_FLAGS,
...EXPLORER_FLAGS,
...GOVERNANCE_FLAGS,
};
getUserEnabledFeatureFlags(refresh).forEach((flag) => (flags[flag] = true));
return flags;
};
const parseNetworks = (value?: string) => {
if (value) {
try {
return JSON.parse(value);
} catch (e) {
return {};
}
}
return {};
};
/**
* Provides a fallback ethereum provider url for test purposes in some apps
*/
const getEthereumProviderUrl = (
network: Networks | undefined,
envvar: string | undefined
) => {
if (envvar) return envvar;
return network === Networks.MAINNET
? 'https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8'
: 'https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8';
};
/**
* Provide a fallback etherscan url for test purposes in some apps
*/
const getEtherscanUrl = (
network: Networks | undefined,
envvar: string | undefined
) => {
if (envvar) return envvar;
return network === Networks.MAINNET
? 'https://etherscan.io'
: 'https://sepolia.etherscan.io';
};
const windowOrDefault = (key: string, defaultValue?: string) => {
if (typeof window !== 'undefined') {
// @ts-ignore avoid conflict in env
if (window._env_ && window._env_[key]) {
// @ts-ignore presence has been check above
return window._env_[key];
}
}
return defaultValue || undefined;
};
export const useFeatureFlags = create<{
flags: FeatureFlags;
setFeatureFlag: (flag: keyof FeatureFlags, enabled: boolean) => void;
}>()((set, get) => ({
flags: compileFeatureFlags(),
setFeatureFlag: (flag: keyof FeatureFlags, enabled: boolean) => {
if (userControllableFeatureFlags.includes(flag)) {
setUserEnabledFeatureFlag(flag, enabled);
set({ flags: { ...get().flags, [flag]: enabled } });
}
},
}));
export const useEnvironment = create<EnvStore>()((set, get) => ({
...compileEnvVars(),
...compileFeatureFlags(),
nodes: [],
status: 'default',
error: null,
@@ -166,400 +632,4 @@ export const useInitializeEnv = () => {
}, [status, initialize]);
};
/**
* Fetch and validate a vega node configuration
*/
const fetchConfig = async (url?: string) => {
if (!url) return [];
const res = await fetch(url);
const content = await res.text();
const parsed = tomlParse(content);
const tomlResults = tomlConfigSchema.parse(parsed);
const {
API: {
GraphQL: { Hosts },
},
} = tomlResults;
return Hosts;
};
/**
* Find a suitable node by running a test query and test
* subscription, against a list of clients, first to resolve wins
*/
const findNode = async (clients: ClientCollection): Promise<string | null> => {
const tests = Object.entries(clients).map((args) => testNode(...args));
try {
const url = await Promise.any(tests);
return url;
} catch {
// All tests rejected, no suitable node found
return null;
}
};
/**
* Test a node for suitability for connection
*/
const testNode = async (
url: string,
client: Client
): Promise<string | null> => {
const results = await Promise.all([
// these promises will only resolve with true/false
testQuery(client),
testSubscription(client),
]);
if (results[0] && results[1]) {
return url;
}
const message = `Tests failed for node: ${url}`;
console.warn(message);
// throwing here will mean this tests is ignored and a different
// node that hopefully does resolve will fulfill the Promise.any
throw new Error(message);
};
/**
* Run a test query on a client
*/
const testQuery = async (client: Client) => {
try {
const result = await client.query<NodeCheckQuery>({
query: NodeCheckDocument,
});
if (!result || result.error) {
return false;
}
return true;
} catch (err) {
return false;
}
};
/**
* Run a test subscription on a client. A subscription
* that takes longer than SUBSCRIPTION_TIMEOUT ms to respond
* is deemed a failure
*/
const testSubscription = (client: Client) => {
return new Promise((resolve) => {
const sub = client
.subscribe<NodeCheckTimeUpdateSubscription>({
query: NodeCheckTimeUpdateDocument,
errorPolicy: 'all',
})
.subscribe({
next: () => {
resolve(true);
sub.unsubscribe();
},
error: () => {
resolve(false);
sub.unsubscribe();
},
});
setTimeout(() => {
resolve(false);
sub.unsubscribe();
}, SUBSCRIPTION_TIMEOUT);
});
};
/**
* Retrieve env vars, parsing where needed some type casting is needed
* here to appease the environment store interface
*/
function compileEnvVars() {
const VEGA_ENV = windowOrDefault(
'VEGA_ENV',
process.env['NX_VEGA_ENV']
) as Networks;
const env: Environment = {
VEGA_URL: windowOrDefault('VEGA_URL', process.env['NX_VEGA_URL']),
VEGA_ENV,
VEGA_CONFIG_URL: windowOrDefault(
'VEGA_CONFIG_URL',
process.env['NX_VEGA_CONFIG_URL'] as string
),
VEGA_NETWORKS: parseNetworks(
windowOrDefault('VEGA_NETWORKS', process.env['NX_VEGA_NETWORKS'])
),
VEGA_WALLET_URL: windowOrDefault(
'VEGA_WALLET_URL',
process.env['NX_VEGA_WALLET_URL'] as string
),
HOSTED_WALLET_URL: windowOrDefault(
'HOSTED_WALLET_URL',
process.env['NX_HOSTED_WALLET_URL']
),
ETHERSCAN_URL: getEtherscanUrl(
VEGA_ENV,
windowOrDefault('ETHERSCAN_URL', process.env['NX_ETHERSCAN_URL'])
),
ETHEREUM_PROVIDER_URL: getEthereumProviderUrl(
VEGA_ENV,
windowOrDefault(
'ETHEREUM_PROVIDER_URL',
process.env['NX_ETHEREUM_PROVIDER_URL']
)
),
ETH_LOCAL_PROVIDER_URL: windowOrDefault(
'ETH_LOCAL_PROVIDER_URL',
process.env['NX_ETH_LOCAL_PROVIDER_URL']
),
ETH_WALLET_MNEMONIC: windowOrDefault(
'ETH_WALLET_MNEMONIC',
process.env['NX_ETH_WALLET_MNEMONIC']
),
ORACLE_PROOFS_URL: windowOrDefault(
'ORACLE_PROOFS_URL',
process.env['NX_ORACLE_PROOFS_URL']
),
VEGA_DOCS_URL: windowOrDefault(
'VEGA_DOCS_URL',
process.env['NX_VEGA_DOCS_URL']
),
VEGA_CONSOLE_URL: windowOrDefault(
'VEGA_CONSOLE_URL',
process.env['NX_VEGA_CONSOLE_URL']
),
VEGA_EXPLORER_URL: windowOrDefault(
'VEGA_EXPLORER_URL',
process.env['NX_VEGA_EXPLORER_URL']
),
VEGA_TOKEN_URL: windowOrDefault(
'VEGA_TOKEN_URL',
process.env['NX_VEGA_TOKEN_URL']
),
GITHUB_FEEDBACK_URL: windowOrDefault(
'GITHUB_FEEDBACK_URL',
process.env['NX_GITHUB_FEEDBACK_URL']
),
GIT_BRANCH: windowOrDefault(
'GIT_COMMIT_BRANCH',
process.env['GIT_COMMIT_BRANCH']
),
GIT_COMMIT_HASH: windowOrDefault(
'GIT_COMMIT_HASH',
process.env['GIT_COMMIT_HASH']
),
GIT_ORIGIN_URL: windowOrDefault(
'GIT_ORIGIN_URL',
process.env['GIT_ORIGIN_URL']
),
ANNOUNCEMENTS_CONFIG_URL: windowOrDefault(
'ANNOUNCEMENTS_CONFIG_URL',
process.env['NX_ANNOUNCEMENTS_CONFIG_URL']
),
VEGA_INCIDENT_URL: windowOrDefault(
'VEGA_INCIDENT_URL',
process.env['NX_VEGA_INCIDENT_URL']
),
APP_VERSION: windowOrDefault('APP_VERSION', process.env['NX_APP_VERSION']),
SENTRY_DSN: windowOrDefault('SENTRY_DSN', process.env['NX_SENTRY_DSN']),
TENDERMINT_URL: windowOrDefault(
'NX_TENDERMINT_URL',
process.env['NX_TENDERMINT_URL']
),
TENDERMINT_WEBSOCKET_URL: windowOrDefault(
'NX_TENDERMINT_WEBSOCKET_URL',
process.env['NX_TENDERMINT_WEBSOCKET_URL']
),
CHROME_EXTENSION_URL: windowOrDefault(
'NX_CHROME_EXTENSION_URL',
process.env['NX_CHROME_EXTENSION_URL']
),
MOZILLA_EXTENSION_URL: windowOrDefault(
'NX_MOZILLA_EXTENSION_URL',
process.env['NX_MOZILLA_EXTENSION_URL']
),
};
return env;
}
function compileFeatureFlags(): FeatureFlags {
const TRUTHY = ['1', 'true'];
const COSMIC_ELEVATOR_FLAGS: CosmicElevatorFlags = {
ICEBERG_ORDERS: TRUTHY.includes(
windowOrDefault(
'NX_ICEBERG_ORDERS',
process.env['NX_ICEBERG_ORDERS']
) as string
),
STOP_ORDERS: TRUTHY.includes(
windowOrDefault('NX_STOP_ORDERS', process.env['NX_STOP_ORDERS']) as string
),
SUCCESSOR_MARKETS: TRUTHY.includes(
windowOrDefault(
'NX_SUCCESSOR_MARKETS',
process.env['NX_SUCCESSOR_MARKETS']
) as string
),
PRODUCT_PERPETUALS: TRUTHY.includes(
windowOrDefault(
'NX_PRODUCT_PERPETUALS',
process.env['NX_PRODUCT_PERPETUALS']
) as string
),
METAMASK_SNAPS: TRUTHY.includes(
windowOrDefault(
'NX_METAMASK_SNAPS',
process.env['NX_METAMASK_SNAPS']
) as string
),
REFERRALS: TRUTHY.includes(
windowOrDefault('NX_REFERRALS', process.env['NX_REFERRALS']) as string
),
DISABLE_CLOSE_POSITION: TRUTHY.includes(
windowOrDefault(
'NX_DISABLE_CLOSE_POSITION',
process.env['NX_DISABLE_CLOSE_POSITION']
) as string
),
UPDATE_MARKET_STATE: TRUTHY.includes(
windowOrDefault(
'NX_UPDATE_MARKET_STATE',
process.env['NX_UPDATE_MARKET_STATE']
) as string
),
GOVERNANCE_TRANSFERS: TRUTHY.includes(
windowOrDefault(
'NX_GOVERNANCE_TRANSFERS',
process.env['NX_GOVERNANCE_TRANSFERS']
) as string
),
VOLUME_DISCOUNTS: TRUTHY.includes(
windowOrDefault(
'NX_VOLUME_DISCOUNTS',
process.env['NX_VOLUME_DISCOUNTS']
) as string
),
};
const EXPLORER_FLAGS = {
EXPLORER_ASSETS: TRUTHY.includes(
windowOrDefault(
'NX_EXPLORER_ASSETS',
process.env['NX_EXPLORER_ASSETS']
) as string
),
EXPLORER_GENESIS: TRUTHY.includes(
windowOrDefault(
'NX_EXPLORER_GENESIS',
process.env['NX_EXPLORER_GENESIS']
) as string
),
EXPLORER_GOVERNANCE: TRUTHY.includes(
windowOrDefault(
'NX_EXPLORER_GOVERNANCE',
process.env['NX_EXPLORER_GOVERNANCE']
) as string
),
EXPLORER_MARKETS: TRUTHY.includes(
windowOrDefault(
'NX_EXPLORER_MARKETS',
process.env['NX_EXPLORER_MARKETS']
) as string
),
EXPLORER_ORACLES: TRUTHY.includes(
windowOrDefault(
'NX_EXPLORER_ORACLES',
process.env['NX_EXPLORER_ORACLES']
) as string
),
EXPLORER_TXS_LIST: TRUTHY.includes(
windowOrDefault(
'NX_EXPLORER_TXS_LIST',
process.env['NX_EXPLORER_TXS_LIST']
) as string
),
EXPLORER_NETWORK_PARAMETERS: TRUTHY.includes(
windowOrDefault(
'NX_EXPLORER_NETWORK_PARAMETERS',
process.env['NX_EXPLORER_NETWORK_PARAMETERS']
) as string
),
EXPLORER_PARTIES: TRUTHY.includes(
windowOrDefault(
'NX_EXPLORER_PARTIES',
process.env['NX_EXPLORER_PARTIES']
) as string
),
EXPLORER_VALIDATORS: TRUTHY.includes(
windowOrDefault(
'NX_EXPLORER_VALIDATORS',
process.env['NX_EXPLORER_VALIDATORS']
) as string
),
};
const GOVERNANCE_FLAGS = {
GOVERNANCE_NETWORK_DOWN: TRUTHY.includes(
windowOrDefault(
'NX_NETWORK_DOWN',
process.env['NX_NETWORK_DOWN']
) as string
),
GOVERNANCE_NETWORK_LIMITS: TRUTHY.includes(
windowOrDefault(
'NX_GOVERNANCE_NETWORK_LIMITS',
process.env['NX_GOVERNANCE_NETWORK_LIMITS']
) as string
),
};
return {
...COSMIC_ELEVATOR_FLAGS,
...EXPLORER_FLAGS,
...GOVERNANCE_FLAGS,
};
}
function parseNetworks(value?: string) {
if (value) {
try {
return JSON.parse(value);
} catch (e) {
return {};
}
}
return {};
}
/**
* Provides a fallback ethereum provider url for test purposes in some apps
*/
function getEthereumProviderUrl(
network: Networks | undefined,
envvar: string | undefined
) {
if (envvar) return envvar;
return network === Networks.MAINNET
? 'https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8'
: 'https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8';
}
/**
* Provide a fallback etherscan url for test purposes in some apps
*/
function getEtherscanUrl(
network: Networks | undefined,
envvar: string | undefined
) {
if (envvar) return envvar;
return network === Networks.MAINNET
? 'https://etherscan.io'
: 'https://sepolia.etherscan.io';
}
export function windowOrDefault(key: string, defaultValue?: string) {
if (typeof window !== 'undefined') {
// @ts-ignore avoid conflict in env
if (window._env_ && window._env_[key]) {
// @ts-ignore presence has been check above
return window._env_[key];
}
}
return defaultValue || undefined;
}
export const ENV = compileEnvVars();
@@ -60,6 +60,8 @@ export const envSchema = z
TENDERMINT_WEBSOCKET_URL: z.optional(z.string()),
CHROME_EXTENSION_URL: z.optional(z.string()),
MOZILLA_EXTENSION_URL: z.optional(z.string()),
CHARTING_LIBRARY_PATH: z.optional(z.string()),
CHARTING_LIBRARY_HASH: z.optional(z.string()),
})
.refine(
(data) => {
+2
View File
@@ -11,6 +11,7 @@ import en_fills from './locales/en/fills.json';
import en_funding_payments from './locales/en/funding-payments.json';
import en_governance from './locales/en/governance.json';
import en_trading from './locales/en/trading.json';
import en_trading_view from './locales/en/trading-view.json';
import en_markets from './locales/en/markets.json';
import en_web3 from './locales/en/web3.json';
import en_proposals from './locales/en/proposals.json';
@@ -32,6 +33,7 @@ export const locales = {
'funding-payments': en_funding_payments,
governance: en_governance,
trading: en_trading,
trading_view: en_trading_view,
markets: en_markets,
web3: en_web3,
positions: en_positions,
@@ -1,5 +1,3 @@
{
"Indicators": "Indicators",
"Interval: {{interval}}": "Interval: {{interval}}",
"No open orders": "No open orders"
}
+3 -3
View File
@@ -298,8 +298,8 @@
"liquidityOnsenIntro": "Earn rewards for providing liquidity on the",
"liquidityOnsenLinkText": "SushiSwap Onsen Menu",
"liquidityProviderVote": "Liquidity provider vote",
"liquidityProviderVotesAgainst": "LP votes against",
"liquidityProviderVotesFor": "LP votes for",
"liquidityProviderVotesAgainst": "LP share against",
"liquidityProviderVotesFor": "LP share for",
"liquidityRewardsTitle": "Active liquidity rewards",
"liquidityRewardsTitlePrevious": "Previous liquidity rewards",
"liquidityStakedBalance": "SLP token balance",
@@ -759,7 +759,7 @@
"Total stake": "Total stake",
"Total supply": "Total supply",
"totalDistributed": "Total distributed",
"totalLiquidityProviderTokensVoted": "Total LP tokens voted",
"totalLiquidityProviderTokensVoted": "Total LP share voted",
"totalPenalties": "Total penalties",
"TotalPenaltiesDescription": "Total of penalties taking into account performance (considering proportion of blocks proposed against the number of blocks the validator was expected to propose) and any overstaking.",
"totalStake": "Total stake",

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