+
+
- {helpText &&
{helpText}
}
+ {helpText &&
{helpText}
}
{children}
diff --git a/apps/trading/components/sidebar/sidebar.tsx b/apps/trading/components/sidebar/sidebar.tsx
index 303e5b429..8827e131b 100644
--- a/apps/trading/components/sidebar/sidebar.tsx
+++ b/apps/trading/components/sidebar/sidebar.tsx
@@ -6,7 +6,6 @@ import { create } from 'zustand';
import { TransferContainer } from '@vegaprotocol/accounts';
import { DealTicketContainer } from '@vegaprotocol/deal-ticket';
import { DepositContainer } from '@vegaprotocol/deposits';
-import { t } from '@vegaprotocol/i18n';
import { MarketInfoAccordionContainer } from '@vegaprotocol/markets';
import { TinyScroll, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { NodeHealthContainer } from '../node-health';
@@ -16,6 +15,7 @@ import { WithdrawContainer } from '../withdraw-container';
import { GetStarted } from '../welcome-dialog';
import { useVegaWallet, useViewAsDialog } from '@vegaprotocol/wallet';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
+import { useT } from '../../lib/use-t';
export enum ViewType {
Order = 'Order',
@@ -51,6 +51,7 @@ type SidebarView =
};
export const Sidebar = ({ options }: { options?: ReactNode }) => {
+ const t = useT();
const currentRouteId = useGetCurrentRouteId();
const navClasses = 'flex lg:flex-col items-center gap-2 lg:gap-4 p-1';
const setViewAsDialogOpen = useViewAsDialog((state) => state.setOpen);
@@ -150,6 +151,7 @@ export const SidebarDivider = () => {
};
export const SidebarContent = () => {
+ const t = useT();
const params = useParams();
const currentRouteId = useGetCurrentRouteId();
diff --git a/apps/trading/components/stop-orders-container/stop-orders-container.tsx b/apps/trading/components/stop-orders-container/stop-orders-container.tsx
index b2e45b93d..c8b8dd964 100644
--- a/apps/trading/components/stop-orders-container/stop-orders-container.tsx
+++ b/apps/trading/components/stop-orders-container/stop-orders-container.tsx
@@ -1,5 +1,4 @@
import { useDataGridEvents } from '@vegaprotocol/datagrid';
-import { t } from '@vegaprotocol/i18n';
import { StopOrdersManager } from '@vegaprotocol/orders';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
@@ -8,8 +7,10 @@ import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
+import { useT } from '../../lib/use-t';
export const StopOrdersContainer = () => {
+ const t = useT();
const { pubKey, isReadOnly } = useVegaWallet();
const onMarketClick = useMarketClickHandler(true);
diff --git a/apps/trading/components/telemetry/telemetry-approval.tsx b/apps/trading/components/telemetry/telemetry-approval.tsx
index 0c21a4cd3..852b3ec4f 100644
--- a/apps/trading/components/telemetry/telemetry-approval.tsx
+++ b/apps/trading/components/telemetry/telemetry-approval.tsx
@@ -4,7 +4,7 @@ import {
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
-import { t } from '@vegaprotocol/i18n';
+import { useT } from '../../lib/use-t';
interface Props {
telemetryValue: string;
@@ -15,6 +15,7 @@ export const TelemetryApproval = ({
telemetryValue,
setTelemetryValue,
}: Props) => {
+ const t = useT();
return (
diff --git a/apps/trading/components/telemetry/telemetry.tsx b/apps/trading/components/telemetry/telemetry.tsx
index ebe9dc103..8c556f0a5 100644
--- a/apps/trading/components/telemetry/telemetry.tsx
+++ b/apps/trading/components/telemetry/telemetry.tsx
@@ -3,12 +3,13 @@ import { Intent, useToasts } from '@vegaprotocol/ui-toolkit';
import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
import { useCallback, useEffect } from 'react';
import { TelemetryApproval } from './telemetry-approval';
-import { t } from '@vegaprotocol/i18n';
import { useOnboardingStore } from '../welcome-dialog/use-get-onboarding-step';
+import { useT } from '../../lib/use-t';
const TELEMETRY_APPROVAL_TOAST_ID = 'telemetry_toast_id';
export const Telemetry = () => {
+ const t = useT();
const onboardingDissmissed = useOnboardingStore((store) => store.dismissed);
const [telemetryValue, setTelemetryValue, isTelemetryNeeded, closeTelemetry] =
useTelemetryApproval();
@@ -63,6 +64,7 @@ export const Telemetry = () => {
hasToast,
onApprovalClose,
setTelemetryApprovalAndClose,
+ t,
]);
return null;
diff --git a/apps/trading/components/vega-wallet-connect-button/vega-wallet-connect-button.tsx b/apps/trading/components/vega-wallet-connect-button/vega-wallet-connect-button.tsx
index 1d47dc692..b905aaf56 100644
--- a/apps/trading/components/vega-wallet-connect-button/vega-wallet-connect-button.tsx
+++ b/apps/trading/components/vega-wallet-connect-button/vega-wallet-connect-button.tsx
@@ -2,7 +2,6 @@ import { useMemo, useState } from 'react';
import CopyToClipboard from 'react-copy-to-clipboard';
import { isBrowserWalletInstalled } from '@vegaprotocol/wallet';
import { truncateByChars } from '@vegaprotocol/utils';
-import { t } from '@vegaprotocol/i18n';
import {
VegaIcon,
VegaIconNames,
@@ -23,8 +22,10 @@ import { useCopyTimeout } from '@vegaprotocol/react-helpers';
import { ViewType, useSidebar } from '../sidebar';
import classNames from 'classnames';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
+import { useT } from '../../lib/use-t';
export const VegaWalletConnectButton = () => {
+ const t = useT();
const [dropdownOpen, setDropdownOpen] = useState(false);
const openVegaWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
@@ -129,6 +130,7 @@ export const VegaWalletConnectButton = () => {
};
const KeypairItem = ({ pk, active }: { pk: PubKey; active: boolean }) => {
+ const t = useT();
const [copied, setCopied] = useCopyTimeout();
return (
diff --git a/apps/trading/components/vega-wallet/vega-wallet-menu.tsx b/apps/trading/components/vega-wallet/vega-wallet-menu.tsx
index 089082e88..0adbdf0bc 100644
--- a/apps/trading/components/vega-wallet/vega-wallet-menu.tsx
+++ b/apps/trading/components/vega-wallet/vega-wallet-menu.tsx
@@ -1,4 +1,3 @@
-import { t } from '@vegaprotocol/i18n';
import { useCopyTimeout } from '@vegaprotocol/react-helpers';
import {
TradingButton as Button,
@@ -11,12 +10,14 @@ import { useCallback, useMemo } from 'react';
import CopyToClipboard from 'react-copy-to-clipboard';
import { ViewType, useSidebar } from '../sidebar';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
+import { useT } from '../../lib/use-t';
export const VegaWalletMenu = ({
setMenu,
}: {
setMenu: (open: 'nav' | 'wallet' | null) => void;
}) => {
+ const t = useT();
const { pubKey, pubKeys, selectPubKey, disconnect } = useVegaWallet();
const currentRouteId = useGetCurrentRouteId();
const setViews = useSidebar((store) => store.setViews);
@@ -76,6 +77,7 @@ const KeypairListItem = ({
isActive: boolean;
onSelectItem: (pk: string) => void;
}) => {
+ const t = useT();
const [copied, setCopied] = useCopyTimeout();
return (
diff --git a/apps/trading/components/welcome-dialog/get-started.tsx b/apps/trading/components/welcome-dialog/get-started.tsx
index 5e28d2cf0..fd0c64e84 100644
--- a/apps/trading/components/welcome-dialog/get-started.tsx
+++ b/apps/trading/components/welcome-dialog/get-started.tsx
@@ -1,5 +1,4 @@
import classNames from 'classnames';
-import { t } from '@vegaprotocol/i18n';
import {
ExternalLink,
Intent,
@@ -18,12 +17,15 @@ import {
import { Links, Routes } from '../../lib/links';
import { useGlobalStore } from '../../stores';
import { useSidebar, ViewType } from '../sidebar';
+import { useT } from '../../lib/use-t';
+import { Trans } from 'react-i18next';
interface Props {
lead?: string;
}
const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
+ const t = useT();
const dismiss = useOnboardingStore((store) => store.dismiss);
const setDialogOpen = useOnboardingStore((store) => store.setDialogOpen);
const marketId = useGlobalStore((store) => store.marketId);
@@ -78,6 +80,7 @@ const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
};
export const GetStartedCheckList = () => {
+ const t = useT();
const { pubKey } = useVegaWallet();
const currentStep = useGetOnboardingStep();
return (
@@ -104,6 +107,7 @@ export const GetStartedCheckList = () => {
};
export const GetStarted = ({ lead }: Props) => {
+ const t = useT();
const { pubKey } = useVegaWallet();
const { VEGA_ENV, VEGA_NETWORKS } = useEnvironment();
const openVegaWalletDialog = useVegaWalletDialogStore(
@@ -132,18 +136,26 @@ export const GetStarted = ({ lead }: Props) => {
{VEGA_ENV === Networks.MAINNET && (
- {t('Experiment for free with virtual assets on')}{' '}
-
- {t('Fairground Testnet')}
-
+
+ Fairground Testnet
+ ,
+ ]}
+ />
)}
{VEGA_ENV === Networks.TESTNET && (
- {t('Ready to trade with real funds?')}{' '}
-
- {t('Switch to Mainnet')}
-
+
+ Switch to Mainnet
+ ,
+ ]}
+ />
)}
@@ -154,11 +166,14 @@ export const GetStarted = ({ lead }: Props) => {
return (
- You need a{' '}
-
- Vega wallet
- {' '}
- to start trading in this market.
+
+ Vega wallet
+ ,
+ ]}
+ />
{
+ const t = useT();
const variables = useMemo(() => {
return {
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
@@ -75,6 +76,6 @@ export const ProposedMarkets = () => {
)}
),
- [newMarkets, tokenLink]
+ [newMarkets, tokenLink, t]
);
};
diff --git a/apps/trading/components/welcome-dialog/risk-message.tsx b/apps/trading/components/welcome-dialog/risk-message.tsx
index 29ed85ead..a2cf2860a 100644
--- a/apps/trading/components/welcome-dialog/risk-message.tsx
+++ b/apps/trading/components/welcome-dialog/risk-message.tsx
@@ -1,9 +1,20 @@
-import { t } from '@vegaprotocol/i18n';
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { Link } from 'react-router-dom';
import { Links } from '../../lib/links';
+import { useT } from '../../lib/use-t';
+import { Trans } from 'react-i18next';
+
+const DisclaimerLink = ({ children }: { children?: string[] }) => (
+
+
+ {children}
+
+
+
+);
export const RiskMessage = () => {
+ const t = useT();
return (
<>
@@ -24,15 +35,10 @@ export const RiskMessage = () => {
- {t(
- 'By using the Vega Console, you acknowledge that you have read and understood the'
- )}{' '}
-
-
- {t('Vega Console Disclaimer')}
-
-
-
+ ]}
+ />
>
);
diff --git a/apps/trading/components/welcome-dialog/welcome-dialog-content.tsx b/apps/trading/components/welcome-dialog/welcome-dialog-content.tsx
index b47147812..353fb0a30 100644
--- a/apps/trading/components/welcome-dialog/welcome-dialog-content.tsx
+++ b/apps/trading/components/welcome-dialog/welcome-dialog-content.tsx
@@ -1,4 +1,3 @@
-import { t } from '@vegaprotocol/i18n';
import { GetStarted } from './get-started';
import { TradingAnchorButton } from '@vegaprotocol/ui-toolkit';
import { Links } from '../../lib/links';
@@ -6,8 +5,10 @@ import { Networks, useEnvironment } from '@vegaprotocol/environment';
import type { ReactNode } from 'react';
import { useTopTradedMarkets } from '../../lib/hooks/use-top-traded-markets';
import { useOnboardingStore } from './use-get-onboarding-step';
+import { useT } from '../../lib/use-t';
export const WelcomeDialogContent = () => {
+ const t = useT();
const { VEGA_ENV } = useEnvironment();
const setOnboardingDialog = useOnboardingStore(
(store) => store.setDialogOpen
diff --git a/apps/trading/components/welcome-dialog/welcome-dialog.tsx b/apps/trading/components/welcome-dialog/welcome-dialog.tsx
index 340bd0e7f..cb1d26c6e 100644
--- a/apps/trading/components/welcome-dialog/welcome-dialog.tsx
+++ b/apps/trading/components/welcome-dialog/welcome-dialog.tsx
@@ -1,13 +1,14 @@
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
-import { t } from '@vegaprotocol/i18n';
import { useEnvironment } from '@vegaprotocol/environment';
import { WelcomeDialogContent } from './welcome-dialog-content';
import { useOnboardingStore } from './use-get-onboarding-step';
import { VegaConnectDialog } from '@vegaprotocol/wallet';
import { Connectors } from '../../lib/vega-connectors';
import { RiskMessage } from './risk-message';
+import { useT } from '../../lib/use-t';
export const WelcomeDialog = () => {
+ const t = useT();
const { VEGA_ENV } = useEnvironment();
const dismissed = useOnboardingStore((store) => store.dismissed);
const dialogOpen = useOnboardingStore((store) => store.dialogOpen);
diff --git a/apps/trading/components/withdrawals-container/withdrawals-container.tsx b/apps/trading/components/withdrawals-container/withdrawals-container.tsx
index 517a3b277..434b757df 100644
--- a/apps/trading/components/withdrawals-container/withdrawals-container.tsx
+++ b/apps/trading/components/withdrawals-container/withdrawals-container.tsx
@@ -5,10 +5,11 @@ import {
useIncompleteWithdrawals,
} from '@vegaprotocol/withdraws';
import { useVegaWallet } from '@vegaprotocol/wallet';
-import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/data-provider';
+import { useT } from '../../lib/use-t';
export const WithdrawalsContainer = () => {
+ const t = useT();
const { pubKey } = useVegaWallet();
const { data, error } = useDataProvider({
dataProvider: withdrawalProvider,
diff --git a/apps/trading/components/withdrawals-menu/withdrawals-menu.tsx b/apps/trading/components/withdrawals-menu/withdrawals-menu.tsx
index 060cc4c5f..246da4551 100644
--- a/apps/trading/components/withdrawals-menu/withdrawals-menu.tsx
+++ b/apps/trading/components/withdrawals-menu/withdrawals-menu.tsx
@@ -1,9 +1,10 @@
-import { t } from '@vegaprotocol/i18n';
import { TradingButton } from '@vegaprotocol/ui-toolkit';
import { ViewType, useSidebar } from '../sidebar';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
+import { useT } from '../../lib/use-t';
export const WithdrawalsMenu = () => {
+ const t = useT();
const setViews = useSidebar((store) => store.setViews);
const currentRouteId = useGetCurrentRouteId();
return (
diff --git a/apps/trading/e2e/.env b/apps/trading/e2e/.env
new file mode 100644
index 000000000..59d4ae60d
--- /dev/null
+++ b/apps/trading/e2e/.env
@@ -0,0 +1,2 @@
+CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest
+VEGA_VERSION=v0.73.6
diff --git a/apps/trading/e2e/.env.develop b/apps/trading/e2e/.env.develop
new file mode 100644
index 000000000..370823bd5
--- /dev/null
+++ b/apps/trading/e2e/.env.develop
@@ -0,0 +1,2 @@
+CONSOLE_IMAGE_NAME=vegaprotocol/trading:develop
+VEGA_VERSION=v0.73.6
diff --git a/apps/trading/e2e/.env.main b/apps/trading/e2e/.env.main
new file mode 100644
index 000000000..e1b5b0b79
--- /dev/null
+++ b/apps/trading/e2e/.env.main
@@ -0,0 +1,2 @@
+CONSOLE_IMAGE_NAME=vegaprotocol/trading:main
+VEGA_VERSION=v0.73.6
diff --git a/apps/trading/e2e/README.md b/apps/trading/e2e/README.md
new file mode 100644
index 000000000..78cddb358
--- /dev/null
+++ b/apps/trading/e2e/README.md
@@ -0,0 +1,136 @@
+# Trading Market-Sim End-To-End Tests
+
+This direcotry contains end-to-end tests for the trading application using vega-market-sim. This README will guide you through setting up your environment and running the tests.
+
+## Prerequisites
+
+- [Poetry](https://python-poetry.org/docs/#installing-with-the-official-installer)
+- [Docker](https://www.docker.com/)
+- [Python versions ">=3.9,<3.11"](https://www.python.org/)
+
+## Getting Started
+
+1. **Install Poetry**: Follow the instructions on the [official Poetry website](https://python-poetry.org/docs/#installing-with-the-official-installer).
+2. **Install Docker**: Follow the instructions on the [official Docker website](https://docs.docker.com/desktop/).
+3. **Install Python**: Follow the instructions on the [official Python website](https://www.python.org/)
+ **ensure you install a version between 3.9 and 3.11.**
+4. **Start up a Poetry environment**: Execute the commands below to configure the Poetry environment.
+
+### Ensure you are in the tests folder before running commands
+
+```bash
+poetry shell
+```
+
+5. **Install python dependencies**
+
+```bash
+poetry install
+```
+
+6. **Install Playwright Browsers**: Execute the command below to browsers for Playwright.
+
+```bash
+playwright install chromium
+```
+
+7. **Download necessary binaries**:
+ Use the following command within your Python environment. The `--force` flag ensures the binaries are overwritten, and the `--version` specifies the desired version. e.g. `v0.73.4`
+
+```bash
+python -m vega_sim.tools.load_binaries --force --version $VEGA_VERSION
+```
+
+8. **Pull the desired Docker image**
+
+```bash
+docker pull vegaprotocol/trading:develop
+```
+
+9. **Run tests**: Poetry/Python will serve the app from docker
+
+### Update the .env file with the correct trading image.
+
+```bash
+poetry run pytest
+```
+
+### Docker images
+
+Pull the desired image:
+
+**Testnet**
+
+```bash
+docker pull vegaprotocol/trading:develop
+```
+
+**Mainnet**
+
+```bash
+docker pull vegaprotocol/trading:main
+```
+
+Find all available images on [Docker Hub](https://hub.docker.com/r/vegaprotocol/trading/tags).
+
+#### Create a Docker Image of Your Locally Built Trading App
+
+To build your Docker image, use the following commands:
+
+```bash
+yarn nx build trading ./docker/prepare-dist.sh
+```
+
+```bash
+docker build -f docker/node-outside-docker.Dockerfile --build-arg APP=trading --build-arg ENV_NAME=stagnet1 -t vegaprotocol/trading:latest .
+```
+
+## Running Tests 🧪
+
+Before running make sure the docker daemon is runnign so that the app can be served.
+
+To run a specific test, use the `-k` option followed by the name of the test.
+
+Run all tests:
+
+```bash
+poetry run pytest
+```
+
+Run a targeted test:
+
+```bash
+poetry run pytest -k "test_name" -s --headed
+```
+
+Run from anywhere:
+
+```bash
+yarn trading:test -- "test_name" -s --headed
+```
+
+## 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.
+
+### From within the e2e folder:
+
+```bash
+poetry run pytest -s --numprocesses auto --dist loadfile
+```
+
+### From anywhere:
+
+```bash
+yarn trading:test:all
+```
+
+# Things to know
+
+If you "intellisense" isn't working follow these steps:
+
+1. ```bash
+ poetry run which python
+ ```
+
+2. Then open the command menu in vscode (cmd + shift + p) and type `select interpreter` , press enter, select enter interpreter path press enter then paste in the output from that above command you should get the right python again
diff --git a/apps/trading/e2e/actions/utils.py b/apps/trading/e2e/actions/utils.py
new file mode 100644
index 000000000..8dd831f14
--- /dev/null
+++ b/apps/trading/e2e/actions/utils.py
@@ -0,0 +1,48 @@
+from collections import namedtuple
+from playwright.sync_api import Page
+from vega_sim.null_service import VegaServiceNull
+from typing import Optional
+
+WalletConfig = namedtuple("WalletConfig", ["name", "passphrase"])
+ASSET_NAME = "tDAI"
+
+def wait_for_toast_confirmation(page: Page, timeout: int = 30000):
+ page.wait_for_function("""
+ document.querySelector('[data-testid="toast-content"]') &&
+ document.querySelector('[data-testid="toast-content"]').innerText.includes('AWAITING CONFIRMATION')
+ """, timeout=timeout)
+
+def create_and_faucet_wallet(
+ vega: VegaServiceNull,
+ wallet: WalletConfig,
+ symbol: Optional[str] = None,
+ amount: float = 1e4,
+
+):
+ asset_id = vega.find_asset_id(symbol=symbol if symbol is not None else ASSET_NAME)
+ vega.create_key(wallet.name)
+ vega.mint(wallet.name, asset_id, amount)
+
+def next_epoch(vega: VegaServiceNull):
+ forwards = 0
+ epoch_seq = vega.statistics().epoch_seq
+ while epoch_seq == vega.statistics().epoch_seq:
+ vega.wait_fn(1)
+ forwards += 1
+ if forwards > 2 * 10 * 60:
+ raise Exception(
+ "Epoch not started after forwarding the duration of two epochs."
+ )
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+def truncate_middle(market_id, start=6, end=4):
+ if len(market_id) < 11:
+ return market_id
+ return market_id[:start] + '\u2026' + market_id[-end:]
+
+def change_keys(page: Page, vega:VegaServiceNull, key_name):
+ page.get_by_test_id("manage-vega-wallet").click()
+ page.get_by_test_id("key-" + vega.wallet.public_key(key_name)).click()
+ page.click(f'data-testid=key-{vega.wallet.public_key(key_name)} >> .inline-flex')
+ page.reload()
diff --git a/apps/trading/e2e/actions/vega.py b/apps/trading/e2e/actions/vega.py
new file mode 100644
index 000000000..ff8f83a29
--- /dev/null
+++ b/apps/trading/e2e/actions/vega.py
@@ -0,0 +1,65 @@
+from typing import List, Tuple, Optional
+from vega_sim.service import VegaService, PeggedOrder
+
+def submit_order(
+ vega: VegaService,
+ wallet_name: str,
+ market_id: str,
+ side: str,
+ volume: float,
+ price: float,
+ peak_size: Optional[float] = None,
+ minimum_visible_size: Optional[float] = None,
+):
+ return vega.submit_order(
+ trading_key=wallet_name,
+ market_id=market_id,
+ time_in_force="TIME_IN_FORCE_GTC",
+ order_type="TYPE_LIMIT",
+ side=side,
+ volume=volume,
+ price=price,
+ peak_size=peak_size,
+ minimum_visible_size=minimum_visible_size,
+ )
+
+
+def submit_multiple_orders(
+ vega: VegaService,
+ wallet_name: str,
+ market_id: str,
+ side: str,
+ volume_price_pair: List[Tuple[float, float]],
+):
+ for volume, price in volume_price_pair:
+ submit_order(vega, wallet_name, market_id, side, volume, price)
+
+
+def submit_liquidity(vega: VegaService, wallet_name: str, market_id: str):
+ vega.submit_simple_liquidity(
+ key_name=wallet_name,
+ market_id=market_id,
+ commitment_amount=10000,
+ fee=0.000,
+ is_amendment=False,
+ )
+ vega.submit_order(
+ market_id=market_id,
+ trading_key=wallet_name,
+ side="SIDE_BUY",
+ order_type="TYPE_LIMIT",
+ pegged_order=PeggedOrder(reference="PEGGED_REFERENCE_MID", offset=1),
+ wait=False,
+ time_in_force="TIME_IN_FORCE_GTC",
+ volume=99,
+ )
+ vega.submit_order(
+ market_id=market_id,
+ trading_key=wallet_name,
+ side="SIDE_SELL",
+ order_type="TYPE_LIMIT",
+ pegged_order=PeggedOrder(reference="PEGGED_REFERENCE_MID", offset=1),
+ wait=False,
+ time_in_force="TIME_IN_FORCE_GTC",
+ volume=99,
+ )
\ No newline at end of file
diff --git a/apps/trading/e2e/config.py b/apps/trading/e2e/config.py
new file mode 100644
index 000000000..73cda9276
--- /dev/null
+++ b/apps/trading/e2e/config.py
@@ -0,0 +1,9 @@
+import os
+from dotenv import load_dotenv
+
+load_dotenv()
+
+console_image_name = os.getenv(
+ "CONSOLE_IMAGE_NAME", default="vegaprotocol/trading:latest"
+)
+vega_version = os.getenv("VEGA_VERSION", default="latest")
diff --git a/apps/trading/e2e/conftest.py b/apps/trading/e2e/conftest.py
new file mode 100644
index 000000000..dec6bc5dd
--- /dev/null
+++ b/apps/trading/e2e/conftest.py
@@ -0,0 +1,253 @@
+import logging
+import pytest
+import os
+import json
+import requests
+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
+from config import console_image_name, vega_version
+from fixtures.market import (
+ setup_simple_market,
+ setup_opening_auction_market,
+ setup_continuous_market,
+ setup_perps_market,
+)
+
+import sys
+
+# Workaround for current xdist issue with displaying live logs from multiple workers
+# https://github.com/pytest-dev/pytest-xdist/issues/402
+sys.stdout = sys.stderr
+
+docker_client = docker.from_env()
+logger = logging.getLogger()
+
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_runtest_makereport(item, call):
+ outcome = "passed" if call.excinfo is None else "failed"
+ item.config.cache.set(item.nodeid, outcome)
+
+
+def pytest_configure(config):
+ worker_id = os.environ.get("PYTEST_XDIST_WORKER")
+ if worker_id is not None:
+ log_dir = os.path.join(os.getcwd(), "logs")
+ log_name = f"tests_{worker_id}.log"
+ if not os.path.exists(log_dir):
+ os.makedirs(log_dir)
+ logging.basicConfig(
+ format=config.getini("log_file_format"),
+ datefmt=config.getini("log_file_date_format"),
+ filename=os.path.join(log_dir, log_name),
+ level=config.getini("log_file_level"),
+ )
+
+class CustomHttpRequestHandler(http.server.SimpleHTTPRequestHandler):
+ def do_GET(self):
+ # Set the path to your website's directory here
+ if self.path == '/':
+ self.path = 'dist/apps/trading/exported/index.html'
+ return http.server.SimpleHTTPRequestHandler.do_GET(self)
+
+# Start VegaServiceNull
+@contextmanager
+def init_vega(request=None):
+ default_seconds = 1
+ seconds_per_block = default_seconds
+ if request and hasattr(request, "param"):
+ seconds_per_block = request.param
+
+ logger.info(
+ "Starting VegaServiceNull",
+ extra={"worker_id": os.environ.get("PYTEST_XDIST_WORKER")},
+ )
+ logger.info(f"Using console image: {console_image_name}")
+ logger.info(f"Using vega version: {vega_version}")
+ with VegaServiceNull(
+ run_with_console=False,
+ launch_graphql=False,
+ retain_log_files=True,
+ use_full_vega_wallet=True,
+ store_transactions=True,
+ transactions_per_block=1000,
+ seconds_per_block=seconds_per_block,
+ ) as vega:
+ try:
+ container = docker_client.containers.run(
+ console_image_name, detach=True, ports={"80/tcp": vega.console_port}
+ )
+ # docker setup
+ logger.info(
+ f"Container {container.id} started",
+ extra={"worker_id": os.environ.get("PYTEST_XDIST_WORKER")},
+ )
+ yield vega
+ except docker.errors.APIError as e:
+ logger.info(f"Container creation failed.")
+ logger.info(e)
+ raise e
+ finally:
+ logger.info(f"Stopping container {container.id}")
+ container.stop()
+ # Remove the container
+ logger.info(f"Removing container {container.id}")
+ container.remove()
+
+@contextmanager
+def init_page(vega: VegaServiceNull, browser: Browser, request: pytest.FixtureRequest):
+ with browser.new_context(
+ viewport={"width": 1920, "height": 1080},
+ base_url=f"http://localhost:{vega.console_port}",
+ ) as context, context.new_page() as page:
+ context.tracing.start(screenshots=True, snapshots=True, sources=True)
+ try:
+ # Wait for the console to be up and running before any tests are run
+ attempts = 0
+ while attempts < 100:
+ try:
+ code = requests.get(
+ f"http://localhost:{vega.console_port}/"
+ ).status_code
+ if code == 200:
+ break
+ except requests.exceptions.ConnectionError as e:
+ attempts += 1
+ if attempts < 100:
+ time.sleep(0.1)
+ continue
+ else:
+ raise e
+
+ # Set window._env_ so built app uses datanode from vega market sim
+ env = json.dumps(
+ {
+ "VEGA_URL": f"http://localhost:{vega.data_node_rest_port}/graphql",
+ "VEGA_WALLET_URL": f"http://localhost:{vega.wallet_port}",
+ }
+ )
+ window_env = f"window._env_ = Object.assign({{}}, window._env_, {env})"
+ page.add_init_script(script=window_env)
+ yield page
+ finally:
+ try:
+ if not os.path.exists("apps/trading/e2e/traces"):
+ os.makedirs("apps/trading/e2e/traces")
+ except OSError as e:
+ print(f"Failed to create directory '{'apps/trading/e2e/traces'}': {e}")
+
+ # Check whether this test failed or passed
+ outcome = request.config.cache.get(request.node.nodeid, None)
+ if outcome != "passed":
+ try:
+ trace_path = os.path.join("traces", request.node.name + "trace.zip")
+ context.tracing.stop(path=trace_path)
+ except Exception as e:
+ logger.error(f"Failed to save trace: {e}")
+
+
+@pytest.fixture
+def vega(request):
+ with init_vega(request) as vega:
+ yield vega
+
+
+@pytest.fixture
+def page(vega, browser, request):
+ with init_page(vega, browser, request) as page_instance:
+ yield page_instance
+
+
+# Set auth token so eager connection for MarketSim wallet is successful
+def auth_setup(vega: VegaServiceNull, page: Page):
+ DEFAULT_WALLET_NAME = "MarketSim" # This is the default wallet name within VegaServiceNull and CANNOT be changed
+
+ # Calling get_keypairs will internally call _load_tokens for the given wallet
+ keypairs = vega.wallet.get_keypairs(DEFAULT_WALLET_NAME)
+ wallet_api_token = vega.wallet.login_tokens[DEFAULT_WALLET_NAME]
+
+ # Set token to localStorage so eager connect hook picks it up and immediately connects
+ wallet_config = json.dumps(
+ {
+ "token": f"VWT {wallet_api_token}",
+ "connector": "jsonRpc",
+ "url": f"http://localhost:{vega.wallet_port}",
+ }
+ )
+
+ storage_javascript = [
+ # Store wallet config so eager connection is initiated
+ f"localStorage.setItem('vega_wallet_config', '{wallet_config}');",
+ # Ensure wallet ris dialog doesnt show, otherwise eager connect wont work
+ "localStorage.setItem('vega_wallet_risk_accepted', 'true');",
+ # Ensure initial risk dialog doesnt show
+ "localStorage.setItem('vega_risk_accepted', 'true');",
+ ]
+ script = "".join(storage_javascript)
+ page.add_init_script(script)
+
+ return {
+ "wallet": DEFAULT_WALLET_NAME,
+ "wallet_api_token": wallet_api_token,
+ "public_key": keypairs["Key 1"],
+ }
+
+
+@pytest.fixture(scope="function")
+def auth(vega: VegaServiceNull, page: Page):
+ return auth_setup(vega, page)
+
+
+# Set 'risk accepted' flag, so that the risk dialog doesn't show up
+def risk_accepted_setup(page: Page):
+ onboarding_config = json.dumps({"state": {"dismissed": True}, "version": 0})
+ storage_javascript = [
+ "localStorage.setItem('vega_risk_accepted', 'true');",
+ f"localStorage.setItem('vega_onboarding', '{onboarding_config}');",
+ "localStorage.setItem('vega_telemetry_approval', 'false');",
+ "localStorage.setItem('vega_telemetry_viewed', 'true');",
+ ]
+ script = "".join(storage_javascript)
+ page.add_init_script(script)
+
+
+@pytest.fixture(scope="function")
+def risk_accepted(page: Page):
+ risk_accepted_setup(page)
+
+
+@pytest.fixture(scope="function")
+def simple_market(vega, request):
+ kwargs = {}
+ if hasattr(request, "param"):
+ kwargs.update(request.param)
+ return setup_simple_market(vega, **kwargs)
+
+
+@pytest.fixture(scope="function")
+def opening_auction_market(vega):
+ return setup_opening_auction_market(vega)
+
+
+@pytest.fixture(scope="function")
+def continuous_market(vega):
+ return setup_continuous_market(vega)
+
+
+@pytest.fixture(scope="function")
+def proposed_market(vega):
+ return setup_simple_market(vega, approve_proposal=False)
+
+
+@pytest.fixture(scope="function")
+def perps_market(vega, request):
+ kwargs = {}
+ if hasattr(request, "param"):
+ kwargs.update(request.param)
+ return setup_perps_market(vega, **kwargs)
diff --git a/apps/trading/e2e/fixtures/market.py b/apps/trading/e2e/fixtures/market.py
new file mode 100644
index 000000000..244787250
--- /dev/null
+++ b/apps/trading/e2e/fixtures/market.py
@@ -0,0 +1,228 @@
+from vega_sim.service import VegaService
+from actions.vega import submit_multiple_orders, submit_order, submit_liquidity
+from wallet_config import MM_WALLET, MM_WALLET2, TERMINATE_WALLET, wallets
+import logging
+
+logger = logging.getLogger()
+
+mint_amount: float = 10e5
+market_name = "BTC:DAI_2023"
+
+def setup_simple_market(
+ vega: VegaService,
+ approve_proposal=True,
+ custom_market_name=market_name,
+ custom_asset_name="tDAI",
+ custom_asset_symbol="tDAI",
+):
+ for wallet in wallets:
+ vega.create_key(wallet.name)
+
+ vega.mint(
+ MM_WALLET.name,
+ asset="VOTE",
+ amount=mint_amount,
+ )
+
+ vega.update_network_parameter(
+ MM_WALLET.name, parameter="market.fee.factors.makerFee", new_value="0.1"
+ )
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ vega.create_asset(
+ MM_WALLET.name,
+ name=custom_asset_name,
+ symbol=custom_asset_symbol,
+ decimals=5,
+ max_faucet_amount=1e10,
+ )
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ tdai_id = vega.find_asset_id(symbol=custom_asset_symbol)
+ logger.info(f"Created asset: {custom_asset_symbol}")
+
+ vega.mint(
+ "Key 1",
+ asset=tdai_id,
+ amount=mint_amount,
+ )
+
+ vega.mint(
+ MM_WALLET.name,
+ asset=tdai_id,
+ amount=mint_amount,
+ )
+
+ vega.mint(
+ MM_WALLET2.name,
+ asset=tdai_id,
+ amount=mint_amount,
+ )
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ market_id = vega.create_simple_market(
+ custom_market_name,
+ proposal_key=MM_WALLET.name,
+ settlement_asset_id=tdai_id,
+ termination_key=TERMINATE_WALLET.name,
+ market_decimals=5,
+ approve_proposal=approve_proposal,
+ forward_time_to_enactment=approve_proposal,
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ return market_id
+
+
+def setup_simple_successor_market(
+ vega: VegaService, parent_market_id, tdai_id, market_name, approve_proposal=True
+):
+ market_id = vega.create_simple_market(
+ market_name,
+ proposal_key=MM_WALLET.name,
+ settlement_asset_id=tdai_id,
+ termination_key=MM_WALLET2.name,
+ market_decimals=5,
+ approve_proposal=approve_proposal,
+ forward_time_to_enactment=approve_proposal,
+ parent_market_id=parent_market_id,
+ parent_market_insurance_pool_fraction=0.5,
+ )
+ submit_liquidity(vega, MM_WALLET.name, market_id)
+ submit_multiple_orders(
+ vega, MM_WALLET.name, market_id, "SIDE_SELL", [[1, 110], [1, 105]]
+ )
+ submit_multiple_orders(
+ vega, MM_WALLET2.name, market_id, "SIDE_BUY", [[1, 90], [1, 95]]
+ )
+
+ submit_order(vega, "Key 1", market_id, "SIDE_BUY", 1, 110)
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ return market_id
+
+
+def setup_opening_auction_market(vega: VegaService, market_id: str = None, **kwargs):
+ if market_id is None or market_id not in vega.all_markets():
+ market_id = setup_simple_market(vega, **kwargs)
+
+ submit_liquidity(vega, MM_WALLET.name, market_id)
+ submit_multiple_orders(
+ vega, MM_WALLET.name, market_id, "SIDE_SELL", [[1, 110], [1, 105]]
+ )
+ submit_multiple_orders(
+ vega, MM_WALLET2.name, market_id, "SIDE_BUY", [[1, 90], [1, 95]]
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ return market_id
+
+
+def setup_continuous_market(vega: VegaService, market_id: str = None, **kwargs):
+ if market_id is None or market_id not in vega.all_markets():
+ market_id = setup_opening_auction_market(vega, **kwargs)
+
+ submit_order(vega, "Key 1", market_id, "SIDE_BUY", 1, 110)
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ return market_id
+
+def setup_perps_market(
+ vega: VegaService,
+ custom_asset_name="tDAI",
+ custom_asset_symbol="tDAI",
+):
+ for wallet in wallets:
+ vega.create_key(wallet.name)
+
+ vega.mint(
+ MM_WALLET.name,
+ asset="VOTE",
+ amount=mint_amount,
+ )
+
+ vega.update_network_parameter(
+ MM_WALLET.name, parameter="market.fee.factors.makerFee", new_value="0.1"
+ )
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ vega.create_asset(
+ MM_WALLET.name,
+ name=custom_asset_name,
+ symbol=custom_asset_symbol,
+ decimals=5,
+ max_faucet_amount=1e10,
+ )
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ tdai_id = vega.find_asset_id(symbol=custom_asset_symbol)
+ logger.info(f"Created asset: {custom_asset_symbol}")
+
+ vega.mint(
+ "Key 1",
+ asset=tdai_id,
+ amount=mint_amount,
+ )
+
+ vega.mint(
+ MM_WALLET.name,
+ asset=tdai_id,
+ amount=mint_amount,
+ )
+
+ vega.mint(
+ MM_WALLET2.name,
+ asset=tdai_id,
+ amount=mint_amount,
+ )
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ vega.update_network_parameter(
+ proposal_key=MM_WALLET.name,
+ parameter="limits.markets.proposePerpetualEnabled",
+ new_value="1",
+ )
+
+ vega.wait_for_total_catchup()
+
+ market_id = vega.create_simple_perps_market(
+ market_name="BTC:DAI_Perpetual",
+ proposal_key=MM_WALLET.name,
+ settlement_asset_id=tdai_id,
+ settlement_data_key=TERMINATE_WALLET.name,
+ funding_payment_frequency_in_seconds=10,
+ market_decimals=5,
+ )
+ vega.wait_for_total_catchup()
+
+ submit_liquidity(vega, MM_WALLET.name, market_id)
+ submit_multiple_orders(
+ vega, MM_WALLET.name, market_id, "SIDE_SELL", [[1, 110], [1, 105]]
+ )
+ submit_multiple_orders(
+ vega, MM_WALLET2.name, market_id, "SIDE_BUY", [[1, 90], [1, 95]]
+ )
+ submit_order(vega, "Key 1", market_id, "SIDE_BUY", 1, 110)
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ return market_id
\ No newline at end of file
diff --git a/apps/trading/e2e/poetry.lock b/apps/trading/e2e/poetry.lock
new file mode 100644
index 000000000..e64569e73
--- /dev/null
+++ b/apps/trading/e2e/poetry.lock
@@ -0,0 +1,1345 @@
+# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
+
+[[package]]
+name = "certifi"
+version = "2023.7.22"
+description = "Python package for providing Mozilla's CA Bundle."
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"},
+ {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"},
+]
+
+[[package]]
+name = "cffi"
+version = "1.16.0"
+description = "Foreign Function Interface for Python calling C code."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"},
+ {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"},
+ {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"},
+ {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"},
+ {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"},
+ {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"},
+ {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"},
+ {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"},
+ {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"},
+ {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"},
+ {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"},
+ {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"},
+ {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"},
+ {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"},
+ {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"},
+ {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"},
+ {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"},
+ {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"},
+ {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"},
+ {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"},
+ {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"},
+ {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"},
+ {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"},
+ {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"},
+ {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"},
+ {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"},
+ {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"},
+]
+
+[package.dependencies]
+pycparser = "*"
+
+[[package]]
+name = "charset-normalizer"
+version = "3.3.2"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+optional = false
+python-versions = ">=3.7.0"
+files = [
+ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"},
+ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "deprecated"
+version = "1.2.14"
+description = "Python @deprecated decorator to deprecate old python classes, functions or methods."
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+ {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"},
+ {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"},
+]
+
+[package.dependencies]
+wrapt = ">=1.10,<2"
+
+[package.extras]
+dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"]
+
+[[package]]
+name = "docker"
+version = "6.1.3"
+description = "A Python library for the Docker Engine API."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "docker-6.1.3-py3-none-any.whl", hash = "sha256:aecd2277b8bf8e506e484f6ab7aec39abe0038e29fa4a6d3ba86c3fe01844ed9"},
+ {file = "docker-6.1.3.tar.gz", hash = "sha256:aa6d17830045ba5ef0168d5eaa34d37beeb113948c413affe1d5991fc11f9a20"},
+]
+
+[package.dependencies]
+packaging = ">=14.0"
+pywin32 = {version = ">=304", markers = "sys_platform == \"win32\""}
+requests = ">=2.26.0"
+urllib3 = ">=1.26.0"
+websocket-client = ">=0.32.0"
+
+[package.extras]
+ssh = ["paramiko (>=2.4.3)"]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.1.3"
+description = "Backport of PEP 654 (exception groups)"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"},
+ {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"},
+]
+
+[package.extras]
+test = ["pytest (>=6)"]
+
+[[package]]
+name = "execnet"
+version = "2.0.2"
+description = "execnet: rapid multi-Python deployment"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41"},
+ {file = "execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af"},
+]
+
+[package.extras]
+testing = ["hatch", "pre-commit", "pytest", "tox"]
+
+[[package]]
+name = "googleapis-common-protos"
+version = "1.61.0"
+description = "Common protobufs used in Google APIs"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "googleapis-common-protos-1.61.0.tar.gz", hash = "sha256:8a64866a97f6304a7179873a465d6eee97b7a24ec6cfd78e0f575e96b821240b"},
+ {file = "googleapis_common_protos-1.61.0-py2.py3-none-any.whl", hash = "sha256:22f1915393bb3245343f6efe87f6fe868532efc12aa26b391b15132e1279f1c0"},
+]
+
+[package.dependencies]
+protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0"
+
+[package.extras]
+grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"]
+
+[[package]]
+name = "greenlet"
+version = "3.0.0"
+description = "Lightweight in-process concurrent programming"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "greenlet-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e09dea87cc91aea5500262993cbd484b41edf8af74f976719dd83fe724644cd6"},
+ {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47932c434a3c8d3c86d865443fadc1fbf574e9b11d6650b656e602b1797908a"},
+ {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdfaeecf8cc705d35d8e6de324bf58427d7eafb55f67050d8f28053a3d57118c"},
+ {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a68d670c8f89ff65c82b936275369e532772eebc027c3be68c6b87ad05ca695"},
+ {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ad562a104cd41e9d4644f46ea37167b93190c6d5e4048fcc4b80d34ecb278f"},
+ {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02a807b2a58d5cdebb07050efe3d7deaf915468d112dfcf5e426d0564aa3aa4a"},
+ {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b1660a15a446206c8545edc292ab5c48b91ff732f91b3d3b30d9a915d5ec4779"},
+ {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:813720bd57e193391dfe26f4871186cf460848b83df7e23e6bef698a7624b4c9"},
+ {file = "greenlet-3.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:aa15a2ec737cb609ed48902b45c5e4ff6044feb5dcdfcf6fa8482379190330d7"},
+ {file = "greenlet-3.0.0-cp310-universal2-macosx_11_0_x86_64.whl", hash = "sha256:7709fd7bb02b31908dc8fd35bfd0a29fc24681d5cc9ac1d64ad07f8d2b7db62f"},
+ {file = "greenlet-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:211ef8d174601b80e01436f4e6905aca341b15a566f35a10dd8d1e93f5dbb3b7"},
+ {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6512592cc49b2c6d9b19fbaa0312124cd4c4c8a90d28473f86f92685cc5fef8e"},
+ {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:871b0a8835f9e9d461b7fdaa1b57e3492dd45398e87324c047469ce2fc9f516c"},
+ {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b505fcfc26f4148551826a96f7317e02c400665fa0883fe505d4fcaab1dabfdd"},
+ {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123910c58234a8d40eaab595bc56a5ae49bdd90122dde5bdc012c20595a94c14"},
+ {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96d9ea57292f636ec851a9bb961a5cc0f9976900e16e5d5647f19aa36ba6366b"},
+ {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b72b802496cccbd9b31acea72b6f87e7771ccfd7f7927437d592e5c92ed703c"},
+ {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:527cd90ba3d8d7ae7dceb06fda619895768a46a1b4e423bdb24c1969823b8362"},
+ {file = "greenlet-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:37f60b3a42d8b5499be910d1267b24355c495064f271cfe74bf28b17b099133c"},
+ {file = "greenlet-3.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1482fba7fbed96ea7842b5a7fc11d61727e8be75a077e603e8ab49d24e234383"},
+ {file = "greenlet-3.0.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:be557119bf467d37a8099d91fbf11b2de5eb1fd5fc5b91598407574848dc910f"},
+ {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b2f1922a39d5d59cc0e597987300df3396b148a9bd10b76a058a2f2772fc04"},
+ {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1e22c22f7826096ad503e9bb681b05b8c1f5a8138469b255eb91f26a76634f2"},
+ {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d363666acc21d2c204dd8705c0e0457d7b2ee7a76cb16ffc099d6799744ac99"},
+ {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:334ef6ed8337bd0b58bb0ae4f7f2dcc84c9f116e474bb4ec250a8bb9bd797a66"},
+ {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6672fdde0fd1a60b44fb1751a7779c6db487e42b0cc65e7caa6aa686874e79fb"},
+ {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:952256c2bc5b4ee8df8dfc54fc4de330970bf5d79253c863fb5e6761f00dda35"},
+ {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:269d06fa0f9624455ce08ae0179430eea61085e3cf6457f05982b37fd2cefe17"},
+ {file = "greenlet-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9adbd8ecf097e34ada8efde9b6fec4dd2a903b1e98037adf72d12993a1c80b51"},
+ {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6b5ce7f40f0e2f8b88c28e6691ca6806814157ff05e794cdd161be928550f4c"},
+ {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf94aa539e97a8411b5ea52fc6ccd8371be9550c4041011a091eb8b3ca1d810"},
+ {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80dcd3c938cbcac986c5c92779db8e8ce51a89a849c135172c88ecbdc8c056b7"},
+ {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e52a712c38e5fb4fd68e00dc3caf00b60cb65634d50e32281a9d6431b33b4af1"},
+ {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5539f6da3418c3dc002739cb2bb8d169056aa66e0c83f6bacae0cd3ac26b423"},
+ {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:343675e0da2f3c69d3fb1e894ba0a1acf58f481f3b9372ce1eb465ef93cf6fed"},
+ {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:abe1ef3d780de56defd0c77c5ba95e152f4e4c4e12d7e11dd8447d338b85a625"},
+ {file = "greenlet-3.0.0-cp37-cp37m-win32.whl", hash = "sha256:e693e759e172fa1c2c90d35dea4acbdd1d609b6936115d3739148d5e4cd11947"},
+ {file = "greenlet-3.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bdd696947cd695924aecb3870660b7545a19851f93b9d327ef8236bfc49be705"},
+ {file = "greenlet-3.0.0-cp37-universal2-macosx_11_0_x86_64.whl", hash = "sha256:cc3e2679ea13b4de79bdc44b25a0c4fcd5e94e21b8f290791744ac42d34a0353"},
+ {file = "greenlet-3.0.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:63acdc34c9cde42a6534518e32ce55c30f932b473c62c235a466469a710bfbf9"},
+ {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a1a6244ff96343e9994e37e5b4839f09a0207d35ef6134dce5c20d260d0302c"},
+ {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b822fab253ac0f330ee807e7485769e3ac85d5eef827ca224feaaefa462dc0d0"},
+ {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8060b32d8586e912a7b7dac2d15b28dbbd63a174ab32f5bc6d107a1c4143f40b"},
+ {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:621fcb346141ae08cb95424ebfc5b014361621b8132c48e538e34c3c93ac7365"},
+ {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb36985f606a7c49916eff74ab99399cdfd09241c375d5a820bb855dfb4af9f"},
+ {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10b5582744abd9858947d163843d323d0b67be9432db50f8bf83031032bc218d"},
+ {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f351479a6914fd81a55c8e68963609f792d9b067fb8a60a042c585a621e0de4f"},
+ {file = "greenlet-3.0.0-cp38-cp38-win32.whl", hash = "sha256:9de687479faec7db5b198cc365bc34addd256b0028956501f4d4d5e9ca2e240a"},
+ {file = "greenlet-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:3fd2b18432e7298fcbec3d39e1a0aa91ae9ea1c93356ec089421fabc3651572b"},
+ {file = "greenlet-3.0.0-cp38-universal2-macosx_11_0_x86_64.whl", hash = "sha256:3c0d36f5adc6e6100aedbc976d7428a9f7194ea79911aa4bf471f44ee13a9464"},
+ {file = "greenlet-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4cd83fb8d8e17633ad534d9ac93719ef8937568d730ef07ac3a98cb520fd93e4"},
+ {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a5b2d4cdaf1c71057ff823a19d850ed5c6c2d3686cb71f73ae4d6382aaa7a06"},
+ {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e7dcdfad252f2ca83c685b0fa9fba00e4d8f243b73839229d56ee3d9d219314"},
+ {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c94e4e924d09b5a3e37b853fe5924a95eac058cb6f6fb437ebb588b7eda79870"},
+ {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad6fb737e46b8bd63156b8f59ba6cdef46fe2b7db0c5804388a2d0519b8ddb99"},
+ {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d55db1db455c59b46f794346efce896e754b8942817f46a1bada2d29446e305a"},
+ {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:56867a3b3cf26dc8a0beecdb4459c59f4c47cdd5424618c08515f682e1d46692"},
+ {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a812224a5fb17a538207e8cf8e86f517df2080c8ee0f8c1ed2bdaccd18f38f4"},
+ {file = "greenlet-3.0.0-cp39-cp39-win32.whl", hash = "sha256:0d3f83ffb18dc57243e0151331e3c383b05e5b6c5029ac29f754745c800f8ed9"},
+ {file = "greenlet-3.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:831d6f35037cf18ca5e80a737a27d822d87cd922521d18ed3dbc8a6967be50ce"},
+ {file = "greenlet-3.0.0-cp39-universal2-macosx_11_0_x86_64.whl", hash = "sha256:a048293392d4e058298710a54dfaefcefdf49d287cd33fb1f7d63d55426e4355"},
+ {file = "greenlet-3.0.0.tar.gz", hash = "sha256:19834e3f91f485442adc1ee440171ec5d9a4840a1f7bd5ed97833544719ce10b"},
+]
+
+[package.extras]
+docs = ["Sphinx"]
+test = ["objgraph", "psutil"]
+
+[[package]]
+name = "grpcio"
+version = "1.59.2"
+description = "HTTP/2-based RPC framework"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "grpcio-1.59.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:d2fa68a96a30dd240be80bbad838a0ac81a61770611ff7952b889485970c4c71"},
+ {file = "grpcio-1.59.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:cf0dead5a2c5a3347af2cfec7131d4f2a2e03c934af28989c9078f8241a491fa"},
+ {file = "grpcio-1.59.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:e420ced29b5904cdf9ee5545e23f9406189d8acb6750916c2db4793dada065c6"},
+ {file = "grpcio-1.59.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b230028a008ae1d0f430acb227d323ff8a619017415cf334c38b457f814119f"},
+ {file = "grpcio-1.59.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a4a3833c0e067f3558538727235cd8a49709bff1003200bbdefa2f09334e4b1"},
+ {file = "grpcio-1.59.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6b25ed37c27e652db01be341af93fbcea03d296c024d8a0e680017a268eb85dd"},
+ {file = "grpcio-1.59.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73abb8584b0cf74d37f5ef61c10722adc7275502ab71789a8fe3cb7ef04cf6e2"},
+ {file = "grpcio-1.59.2-cp310-cp310-win32.whl", hash = "sha256:d6f70406695e3220f09cd7a2f879333279d91aa4a8a1d34303b56d61a8180137"},
+ {file = "grpcio-1.59.2-cp310-cp310-win_amd64.whl", hash = "sha256:3c61d641d4f409c5ae46bfdd89ea42ce5ea233dcf69e74ce9ba32b503c727e29"},
+ {file = "grpcio-1.59.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:3059668df17627f0e0fa680e9ef8c995c946c792612e9518f5cc1503be14e90b"},
+ {file = "grpcio-1.59.2-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:72ca2399097c0b758198f2ff30f7178d680de8a5cfcf3d9b73a63cf87455532e"},
+ {file = "grpcio-1.59.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c978f864b35f2261e0819f5cd88b9830b04dc51bcf055aac3c601e525a10d2ba"},
+ {file = "grpcio-1.59.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9411e24328a2302e279e70cae6e479f1fddde79629fcb14e03e6d94b3956eabf"},
+ {file = "grpcio-1.59.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb7e0fe6ad73b7f06d7e2b689c19a71cf5cc48f0c2bf8608469e51ffe0bd2867"},
+ {file = "grpcio-1.59.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c2504eed520958a5b77cc99458297cb7906308cb92327f35fb7fbbad4e9b2188"},
+ {file = "grpcio-1.59.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2171c39f355ba5b551c5d5928d65aa6c69807fae195b86ef4a7d125bcdb860a9"},
+ {file = "grpcio-1.59.2-cp311-cp311-win32.whl", hash = "sha256:d2794f0e68b3085d99b4f6ff9c089f6fdd02b32b9d3efdfbb55beac1bf22d516"},
+ {file = "grpcio-1.59.2-cp311-cp311-win_amd64.whl", hash = "sha256:2067274c88bc6de89c278a672a652b4247d088811ece781a4858b09bdf8448e3"},
+ {file = "grpcio-1.59.2-cp312-cp312-linux_armv7l.whl", hash = "sha256:535561990e075fa6bd4b16c4c3c1096b9581b7bb35d96fac4650f1181e428268"},
+ {file = "grpcio-1.59.2-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:a213acfbf186b9f35803b52e4ca9addb153fc0b67f82a48f961be7000ecf6721"},
+ {file = "grpcio-1.59.2-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:6959fb07e8351e20501ffb8cc4074c39a0b7ef123e1c850a7f8f3afdc3a3da01"},
+ {file = "grpcio-1.59.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e82c5cf1495244adf5252f925ac5932e5fd288b3e5ab6b70bec5593074b7236c"},
+ {file = "grpcio-1.59.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:023088764012411affe7db183d1ada3ad9daf2e23ddc719ff46d7061de661340"},
+ {file = "grpcio-1.59.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:da2d94c15f88cd40d7e67f7919d4f60110d2b9d5b1e08cf354c2be773ab13479"},
+ {file = "grpcio-1.59.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6009386a2df66159f64ac9f20425ae25229b29b9dd0e1d3dd60043f037e2ad7e"},
+ {file = "grpcio-1.59.2-cp312-cp312-win32.whl", hash = "sha256:75c6ecb70e809cf1504465174343113f51f24bc61e22a80ae1c859f3f7034c6d"},
+ {file = "grpcio-1.59.2-cp312-cp312-win_amd64.whl", hash = "sha256:cbe946b3e6e60a7b4618f091e62a029cb082b109a9d6b53962dd305087c6e4fd"},
+ {file = "grpcio-1.59.2-cp37-cp37m-linux_armv7l.whl", hash = "sha256:f8753a6c88d1d0ba64302309eecf20f70d2770f65ca02d83c2452279085bfcd3"},
+ {file = "grpcio-1.59.2-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:f1ef0d39bc1feb420caf549b3c657c871cad4ebbcf0580c4d03816b0590de0cf"},
+ {file = "grpcio-1.59.2-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:4c93f4abbb54321ee6471e04a00139c80c754eda51064187963ddf98f5cf36a4"},
+ {file = "grpcio-1.59.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:08d77e682f2bf730a4961eea330e56d2f423c6a9b91ca222e5b1eb24a357b19f"},
+ {file = "grpcio-1.59.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ff16d68bf453275466a9a46739061a63584d92f18a0f5b33d19fc97eb69867c"},
+ {file = "grpcio-1.59.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4abb717e320e74959517dc8e84a9f48fbe90e9abe19c248541e9418b1ce60acd"},
+ {file = "grpcio-1.59.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:36f53c2b3449c015880e7d55a89c992c357f176327b0d2873cdaaf9628a37c69"},
+ {file = "grpcio-1.59.2-cp37-cp37m-win_amd64.whl", hash = "sha256:cc3e4cd087f07758b16bef8f31d88dbb1b5da5671d2f03685ab52dece3d7a16e"},
+ {file = "grpcio-1.59.2-cp38-cp38-linux_armv7l.whl", hash = "sha256:27f879ae604a7fcf371e59fba6f3ff4635a4c2a64768bd83ff0cac503142fef4"},
+ {file = "grpcio-1.59.2-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:7cf05053242f61ba94014dd3a986e11a083400a32664058f80bf4cf817c0b3a1"},
+ {file = "grpcio-1.59.2-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:e1727c1c0e394096bb9af185c6923e8ea55a5095b8af44f06903bcc0e06800a2"},
+ {file = "grpcio-1.59.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d573e70a6fe77555fb6143c12d3a7d3fa306632a3034b4e7c59ca09721546f8"},
+ {file = "grpcio-1.59.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31176aa88f36020055ace9adff2405a33c8bdbfa72a9c4980e25d91b2f196873"},
+ {file = "grpcio-1.59.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:11168ef43e4a43ff1b1a65859f3e0ef1a173e277349e7fb16923ff108160a8cd"},
+ {file = "grpcio-1.59.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:53c9aa5ddd6857c0a1cd0287225a2a25873a8e09727c2e95c4aebb1be83a766a"},
+ {file = "grpcio-1.59.2-cp38-cp38-win32.whl", hash = "sha256:3b4368b33908f683a363f376dfb747d40af3463a6e5044afee07cf9436addf96"},
+ {file = "grpcio-1.59.2-cp38-cp38-win_amd64.whl", hash = "sha256:0a754aff9e3af63bdc4c75c234b86b9d14e14a28a30c4e324aed1a9b873d755f"},
+ {file = "grpcio-1.59.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:1f9524d1d701e399462d2c90ba7c193e49d1711cf429c0d3d97c966856e03d00"},
+ {file = "grpcio-1.59.2-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:f93dbf58f03146164048be5426ffde298b237a5e059144847e4940f5b80172c3"},
+ {file = "grpcio-1.59.2-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:6da6dea3a1bacf99b3c2187e296db9a83029ed9c38fd4c52b7c9b7326d13c828"},
+ {file = "grpcio-1.59.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5f09cffa619adfb44799fa4a81c2a1ad77c887187613fb0a8f201ab38d89ba1"},
+ {file = "grpcio-1.59.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c35aa9657f5d5116d23b934568e0956bd50c615127810fffe3ac356a914c176a"},
+ {file = "grpcio-1.59.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:74100fecaec8a535e380cf5f2fb556ff84957d481c13e54051c52e5baac70541"},
+ {file = "grpcio-1.59.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:128e20f57c5f27cb0157e73756d1586b83c1b513ebecc83ea0ac37e4b0e4e758"},
+ {file = "grpcio-1.59.2-cp39-cp39-win32.whl", hash = "sha256:686e975a5d16602dc0982c7c703948d17184bd1397e16c8ee03511ecb8c4cdda"},
+ {file = "grpcio-1.59.2-cp39-cp39-win_amd64.whl", hash = "sha256:242adc47725b9a499ee77c6a2e36688fa6c96484611f33b1be4c57ab075a92dd"},
+ {file = "grpcio-1.59.2.tar.gz", hash = "sha256:d8f9cd4ad1be90b0cf350a2f04a38a36e44a026cac1e036ac593dc48efe91d52"},
+]
+
+[package.extras]
+protobuf = ["grpcio-tools (>=1.59.2)"]
+
+[[package]]
+name = "grpcio-tools"
+version = "1.59.2"
+description = "Protobuf code generator for gRPC"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "grpcio-tools-1.59.2.tar.gz", hash = "sha256:75905266cf90f1866b322575c2edcd4b36532c33fc512bb1b380dc58d84b1030"},
+ {file = "grpcio_tools-1.59.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:9b2885c0e2c9a97bde33497a919032afbd8b5c6dc2f8d4dd4198e77226e0de05"},
+ {file = "grpcio_tools-1.59.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:2f410375830a9bb7140a07da4d75bf380e0958377bed50d77d1dae302de4314e"},
+ {file = "grpcio_tools-1.59.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:e21fc172522d2dda815223a359b2aca9bc317a1b5e5dea5a58cd5079333af133"},
+ {file = "grpcio_tools-1.59.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:072a7ce979ea4f7579c3c99fcbde3d1882c3d1942a3b51d159f67af83b714cd8"},
+ {file = "grpcio_tools-1.59.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b38f8edb2909702c2478b52f6213982c21e4f66f739ac953b91f97863ba2c06a"},
+ {file = "grpcio_tools-1.59.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:12fdee2de80d83eadb1294e0f8a0cb6cefcd2e4988ed680038ab09cd04361ee4"},
+ {file = "grpcio_tools-1.59.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a3cb707da722a0b6c4021fc2cc1c005a8d4037d8ad0252f93df318b9b8a6b4f3"},
+ {file = "grpcio_tools-1.59.2-cp310-cp310-win32.whl", hash = "sha256:ec2fbb02ebb9f2ae1b1c69cccf913dee8c41f5acad94014d3ce11b53720376e3"},
+ {file = "grpcio_tools-1.59.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0dc271a200dbab6547b2c73fcbdb7efe94c31cb633aa20d073f7cf4493493e1"},
+ {file = "grpcio_tools-1.59.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:d634b65cc8ee769edccf1647d8a16861a27e0d8cbd787c711168d2c5e9bddbd1"},
+ {file = "grpcio_tools-1.59.2-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:b0b712acec00a9cbc2204c271d638062a2cb8ce74f25d158b023ff6e93182659"},
+ {file = "grpcio_tools-1.59.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:dd5c78f8e7c6e721b9009c92481a0e3b30a9926ef721120723a03b8a34a34fb9"},
+ {file = "grpcio_tools-1.59.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:724f4f0eecc17fa66216eebfff145631070f04ed7fb4ddf7a7d1c4f954ecc2a1"},
+ {file = "grpcio_tools-1.59.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77ec33ddee691e60511e2a7c793aad4cf172ae20e08d95c786cbba395f6203a7"},
+ {file = "grpcio_tools-1.59.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fa1b9dee7811fad081816e884d063c4dd4946dba61aa54243b4c76c311090c48"},
+ {file = "grpcio_tools-1.59.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ba8dba19e7b2b6f7369004533866f222ba483b9e14d2d152ecf9339c0df1283a"},
+ {file = "grpcio_tools-1.59.2-cp311-cp311-win32.whl", hash = "sha256:df35d145bc2f6e5f57b74cb69f66526675a5f2dcf7d54617ce0deff0c82cca0a"},
+ {file = "grpcio_tools-1.59.2-cp311-cp311-win_amd64.whl", hash = "sha256:99ddc0f5304071a355c261ae49ea5d29b9e9b6dcf422dfc55ada70a243e27e8f"},
+ {file = "grpcio_tools-1.59.2-cp312-cp312-linux_armv7l.whl", hash = "sha256:670f5889853215999eb3511a623dd7dff01b1ce1a64610d13366e0fd337f8c79"},
+ {file = "grpcio_tools-1.59.2-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:1e949e66d4555ce319fd7acef90df625138078d8729c4dc6f6a9f05925034433"},
+ {file = "grpcio_tools-1.59.2-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:09d809ca88999b2578119683f9f0f6a9b42de95ea21550852114a1540b6a642c"},
+ {file = "grpcio_tools-1.59.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db0925545180223fabd6da9b34513efac83aa16673ef8b1cb0cc678e8cf0923c"},
+ {file = "grpcio_tools-1.59.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2ccb59dfbf2ebd668a5a7c4b7bb2b859859641d2b199114b557cd045aac6102"},
+ {file = "grpcio_tools-1.59.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:12cc7698fad48866f68fdef831685cb31ef5814ac605d248c4e5fc964a6fb3f6"},
+ {file = "grpcio_tools-1.59.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:55c401599d5093c4cfa83b8f0ee9757b4d6d3029b10bd67be2cffeada7a44961"},
+ {file = "grpcio_tools-1.59.2-cp312-cp312-win32.whl", hash = "sha256:896f5cdf58f658025a4f7e4ea96c81183b4b6a4b1b4d92ae66d112ac91f062f1"},
+ {file = "grpcio_tools-1.59.2-cp312-cp312-win_amd64.whl", hash = "sha256:b53db1523015a3acda75722357df6c94afae37f6023800c608e09a5c05393804"},
+ {file = "grpcio_tools-1.59.2-cp37-cp37m-linux_armv7l.whl", hash = "sha256:d08b398509ea4d544bcecddd9a21f59dc556396916c3915904cac206af2db72b"},
+ {file = "grpcio_tools-1.59.2-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:09749e832e06493841000275248b031f7154665900d1e1b0e42fc17a64bf904d"},
+ {file = "grpcio_tools-1.59.2-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:e972746000aa192521715f776fab617a3437bed29e90fe0e0fd0d0d6f498d7d4"},
+ {file = "grpcio_tools-1.59.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cbeeb3d8ec4cb25c92e17bfbdcef3c3669e85c5ee787a6e581cb942bc0ae2b88"},
+ {file = "grpcio_tools-1.59.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed8e6632d8d839456332d97b96db10bd2dbf3078e728d063394ac2d54597ad80"},
+ {file = "grpcio_tools-1.59.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:531f87c8e884c6a2e58f040039dfbfe997a4e33baa58f7c7d9993db37b1f5ad0"},
+ {file = "grpcio_tools-1.59.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:feca316e17cfead823af6eae0fc20c0d5299a94d71cfb7531a0e92d050a5fb2f"},
+ {file = "grpcio_tools-1.59.2-cp37-cp37m-win_amd64.whl", hash = "sha256:41b5dd6a06c2563ac3b3adda6d875b15e63eb7b1629e85fc9af608c3a76c4c82"},
+ {file = "grpcio_tools-1.59.2-cp38-cp38-linux_armv7l.whl", hash = "sha256:7ec536cdae870a74080c665cfb1dca8d0784a931aa3c26376ef971a3a51b59d4"},
+ {file = "grpcio_tools-1.59.2-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:9c106ebbed0db446f59f0efe5c3fce33a0a21bf75b392966585e4b5934891b92"},
+ {file = "grpcio_tools-1.59.2-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:32141ef309543a446337e934f0b7a2565a6fca890ff4e543630a09ef72c8d00b"},
+ {file = "grpcio_tools-1.59.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f2ce5ecd63c492949b03af73b1dd6d502c567cc2f9c2057137e518b0c702a01"},
+ {file = "grpcio_tools-1.59.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9ce2a209871ed1c5ae2229e6f4f5a3ea96d83b7871df5d9773d72a72545683"},
+ {file = "grpcio_tools-1.59.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7f0e26af7c07bfa906c91ca9f5932514928a7f032f5f20aecad6b5541037de7e"},
+ {file = "grpcio_tools-1.59.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:48782727c5cff8b8c96e028a8a58614ff6a37eadc0db85866516210c7aafe9ae"},
+ {file = "grpcio_tools-1.59.2-cp38-cp38-win32.whl", hash = "sha256:4a1810bc5de51cc162a19ed3c11da8ddc64d8cfcba049ef337c20fcb397f048b"},
+ {file = "grpcio_tools-1.59.2-cp38-cp38-win_amd64.whl", hash = "sha256:3cf9949a2aadcece3c1e0dd59249aea53dbfc8cc94f7d707797acd67cf6cf931"},
+ {file = "grpcio_tools-1.59.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:f52e0ce8f2dcf1f160c847304016c446075a83ab925d98933d4681bfa8af2962"},
+ {file = "grpcio_tools-1.59.2-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:eb597d6bf9f5bfa54d00546e828f0d4e2c69250d1bc17c27903c0c7b66372135"},
+ {file = "grpcio_tools-1.59.2-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:17ef468836d7cf0b2419f4d5c7ac84ec2d598a1ae410773585313edacf7c393e"},
+ {file = "grpcio_tools-1.59.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dee5f7e7a56177234e61a483c70ca2ae34e73128372c801bb7039993870889f1"},
+ {file = "grpcio_tools-1.59.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f50ff312b88918c5a6461e45c5e03869749a066b1c24a7327e8e13e117efe4fc"},
+ {file = "grpcio_tools-1.59.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a85da4200295ee17e3c1ae068189a43844420ed7e9d531a042440f52de486dfb"},
+ {file = "grpcio_tools-1.59.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f518f22a3082de00f0d7a216e96366a87e6973111085ba1603c3bfa7dba2e728"},
+ {file = "grpcio_tools-1.59.2-cp39-cp39-win32.whl", hash = "sha256:6e735a26e8ea8bb89dc69343d1d00ea607449c6d81e21f339ee118562f3d1931"},
+ {file = "grpcio_tools-1.59.2-cp39-cp39-win_amd64.whl", hash = "sha256:3491cb69c909d586c23d7e6d0ac87844ca22f496f505ce429c0d3301234f2cf3"},
+]
+
+[package.dependencies]
+grpcio = ">=1.59.2"
+protobuf = ">=4.21.6,<5.0dev"
+setuptools = "*"
+
+[[package]]
+name = "idna"
+version = "3.4"
+description = "Internationalized Domain Names in Applications (IDNA)"
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
+ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
+]
+
+[[package]]
+name = "inflection"
+version = "0.5.1"
+description = "A port of Ruby on Rails inflector to Python"
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"},
+ {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"},
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.0.0"
+description = "brain-dead simple config-ini parsing"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
+ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
+]
+
+[[package]]
+name = "numpy"
+version = "1.26.2"
+description = "Fundamental package for array computing in Python"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "numpy-1.26.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3703fc9258a4a122d17043e57b35e5ef1c5a5837c3db8be396c82e04c1cf9b0f"},
+ {file = "numpy-1.26.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cc392fdcbd21d4be6ae1bb4475a03ce3b025cd49a9be5345d76d7585aea69440"},
+ {file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36340109af8da8805d8851ef1d74761b3b88e81a9bd80b290bbfed61bd2b4f75"},
+ {file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcc008217145b3d77abd3e4d5ef586e3bdfba8fe17940769f8aa09b99e856c00"},
+ {file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ced40d4e9e18242f70dd02d739e44698df3dcb010d31f495ff00a31ef6014fe"},
+ {file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b272d4cecc32c9e19911891446b72e986157e6a1809b7b56518b4f3755267523"},
+ {file = "numpy-1.26.2-cp310-cp310-win32.whl", hash = "sha256:22f8fc02fdbc829e7a8c578dd8d2e15a9074b630d4da29cda483337e300e3ee9"},
+ {file = "numpy-1.26.2-cp310-cp310-win_amd64.whl", hash = "sha256:26c9d33f8e8b846d5a65dd068c14e04018d05533b348d9eaeef6c1bd787f9919"},
+ {file = "numpy-1.26.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b96e7b9c624ef3ae2ae0e04fa9b460f6b9f17ad8b4bec6d7756510f1f6c0c841"},
+ {file = "numpy-1.26.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aa18428111fb9a591d7a9cc1b48150097ba6a7e8299fb56bdf574df650e7d1f1"},
+ {file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06fa1ed84aa60ea6ef9f91ba57b5ed963c3729534e6e54055fc151fad0423f0a"},
+ {file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96ca5482c3dbdd051bcd1fce8034603d6ebfc125a7bd59f55b40d8f5d246832b"},
+ {file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:854ab91a2906ef29dc3925a064fcd365c7b4da743f84b123002f6139bcb3f8a7"},
+ {file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f43740ab089277d403aa07567be138fc2a89d4d9892d113b76153e0e412409f8"},
+ {file = "numpy-1.26.2-cp311-cp311-win32.whl", hash = "sha256:a2bbc29fcb1771cd7b7425f98b05307776a6baf43035d3b80c4b0f29e9545186"},
+ {file = "numpy-1.26.2-cp311-cp311-win_amd64.whl", hash = "sha256:2b3fca8a5b00184828d12b073af4d0fc5fdd94b1632c2477526f6bd7842d700d"},
+ {file = "numpy-1.26.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a4cd6ed4a339c21f1d1b0fdf13426cb3b284555c27ac2f156dfdaaa7e16bfab0"},
+ {file = "numpy-1.26.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d5244aabd6ed7f312268b9247be47343a654ebea52a60f002dc70c769048e75"},
+ {file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a3cdb4d9c70e6b8c0814239ead47da00934666f668426fc6e94cce869e13fd7"},
+ {file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa317b2325f7aa0a9471663e6093c210cb2ae9c0ad824732b307d2c51983d5b6"},
+ {file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:174a8880739c16c925799c018f3f55b8130c1f7c8e75ab0a6fa9d41cab092fd6"},
+ {file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f79b231bf5c16b1f39c7f4875e1ded36abee1591e98742b05d8a0fb55d8a3eec"},
+ {file = "numpy-1.26.2-cp312-cp312-win32.whl", hash = "sha256:4a06263321dfd3598cacb252f51e521a8cb4b6df471bb12a7ee5cbab20ea9167"},
+ {file = "numpy-1.26.2-cp312-cp312-win_amd64.whl", hash = "sha256:b04f5dc6b3efdaab541f7857351aac359e6ae3c126e2edb376929bd3b7f92d7e"},
+ {file = "numpy-1.26.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4eb8df4bf8d3d90d091e0146f6c28492b0be84da3e409ebef54349f71ed271ef"},
+ {file = "numpy-1.26.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1a13860fdcd95de7cf58bd6f8bc5a5ef81c0b0625eb2c9a783948847abbef2c2"},
+ {file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64308ebc366a8ed63fd0bf426b6a9468060962f1a4339ab1074c228fa6ade8e3"},
+ {file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baf8aab04a2c0e859da118f0b38617e5ee65d75b83795055fb66c0d5e9e9b818"},
+ {file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d73a3abcac238250091b11caef9ad12413dab01669511779bc9b29261dd50210"},
+ {file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b361d369fc7e5e1714cf827b731ca32bff8d411212fccd29ad98ad622449cc36"},
+ {file = "numpy-1.26.2-cp39-cp39-win32.whl", hash = "sha256:bd3f0091e845164a20bd5a326860c840fe2af79fa12e0469a12768a3ec578d80"},
+ {file = "numpy-1.26.2-cp39-cp39-win_amd64.whl", hash = "sha256:2beef57fb031dcc0dc8fa4fe297a742027b954949cabb52a2a376c144e5e6060"},
+ {file = "numpy-1.26.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1cc3d5029a30fb5f06704ad6b23b35e11309491c999838c31f124fee32107c79"},
+ {file = "numpy-1.26.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94cc3c222bb9fb5a12e334d0479b97bb2df446fbe622b470928f5284ffca3f8d"},
+ {file = "numpy-1.26.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe6b44fb8fcdf7eda4ef4461b97b3f63c466b27ab151bec2366db8b197387841"},
+ {file = "numpy-1.26.2.tar.gz", hash = "sha256:f65738447676ab5777f11e6bbbdb8ce11b785e105f690bc45966574816b6d3ea"},
+]
+
+[[package]]
+name = "packaging"
+version = "23.2"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
+ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
+]
+
+[[package]]
+name = "pandas"
+version = "2.1.3"
+description = "Powerful data structures for data analysis, time series, and statistics"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "pandas-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:acf08a73b5022b479c1be155d4988b72f3020f308f7a87c527702c5f8966d34f"},
+ {file = "pandas-2.1.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3cc4469ff0cf9aa3a005870cb49ab8969942b7156e0a46cc3f5abd6b11051dfb"},
+ {file = "pandas-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35172bff95f598cc5866c047f43c7f4df2c893acd8e10e6653a4b792ed7f19bb"},
+ {file = "pandas-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59dfe0e65a2f3988e940224e2a70932edc964df79f3356e5f2997c7d63e758b4"},
+ {file = "pandas-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0296a66200dee556850d99b24c54c7dfa53a3264b1ca6f440e42bad424caea03"},
+ {file = "pandas-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:465571472267a2d6e00657900afadbe6097c8e1dc43746917db4dfc862e8863e"},
+ {file = "pandas-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04d4c58e1f112a74689da707be31cf689db086949c71828ef5da86727cfe3f82"},
+ {file = "pandas-2.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7fa2ad4ff196768ae63a33f8062e6838efed3a319cf938fdf8b95e956c813042"},
+ {file = "pandas-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4441ac94a2a2613e3982e502ccec3bdedefe871e8cea54b8775992485c5660ef"},
+ {file = "pandas-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5ded6ff28abbf0ea7689f251754d3789e1edb0c4d0d91028f0b980598418a58"},
+ {file = "pandas-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca5680368a5139d4920ae3dc993eb5106d49f814ff24018b64d8850a52c6ed2"},
+ {file = "pandas-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:de21e12bf1511190fc1e9ebc067f14ca09fccfb189a813b38d63211d54832f5f"},
+ {file = "pandas-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a5d53c725832e5f1645e7674989f4c106e4b7249c1d57549023ed5462d73b140"},
+ {file = "pandas-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7cf4cf26042476e39394f1f86868d25b265ff787c9b2f0d367280f11afbdee6d"},
+ {file = "pandas-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72c84ec1b1d8e5efcbff5312abe92bfb9d5b558f11e0cf077f5496c4f4a3c99e"},
+ {file = "pandas-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f539e113739a3e0cc15176bf1231a553db0239bfa47a2c870283fd93ba4f683"},
+ {file = "pandas-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc77309da3b55732059e484a1efc0897f6149183c522390772d3561f9bf96c00"},
+ {file = "pandas-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:08637041279b8981a062899da0ef47828df52a1838204d2b3761fbd3e9fcb549"},
+ {file = "pandas-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b99c4e51ef2ed98f69099c72c75ec904dd610eb41a32847c4fcbc1a975f2d2b8"},
+ {file = "pandas-2.1.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f7ea8ae8004de0381a2376662c0505bb0a4f679f4c61fbfd122aa3d1b0e5f09d"},
+ {file = "pandas-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcd76d67ca2d48f56e2db45833cf9d58f548f97f61eecd3fdc74268417632b8a"},
+ {file = "pandas-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1329dbe93a880a3d7893149979caa82d6ba64a25e471682637f846d9dbc10dd2"},
+ {file = "pandas-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:321ecdb117bf0f16c339cc6d5c9a06063854f12d4d9bc422a84bb2ed3207380a"},
+ {file = "pandas-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:11a771450f36cebf2a4c9dbd3a19dfa8c46c4b905a3ea09dc8e556626060fe71"},
+ {file = "pandas-2.1.3.tar.gz", hash = "sha256:22929f84bca106921917eb73c1521317ddd0a4c71b395bcf767a106e3494209f"},
+]
+
+[package.dependencies]
+numpy = {version = ">=1.22.4,<2", markers = "python_version < \"3.11\""}
+python-dateutil = ">=2.8.2"
+pytz = ">=2020.1"
+tzdata = ">=2022.1"
+
+[package.extras]
+all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"]
+aws = ["s3fs (>=2022.05.0)"]
+clipboard = ["PyQt5 (>=5.15.6)", "qtpy (>=2.2.0)"]
+compression = ["zstandard (>=0.17.0)"]
+computation = ["scipy (>=1.8.1)", "xarray (>=2022.03.0)"]
+consortium-standard = ["dataframe-api-compat (>=0.1.7)"]
+excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pyxlsb (>=1.0.9)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)"]
+feather = ["pyarrow (>=7.0.0)"]
+fss = ["fsspec (>=2022.05.0)"]
+gcp = ["gcsfs (>=2022.05.0)", "pandas-gbq (>=0.17.5)"]
+hdf5 = ["tables (>=3.7.0)"]
+html = ["beautifulsoup4 (>=4.11.1)", "html5lib (>=1.1)", "lxml (>=4.8.0)"]
+mysql = ["SQLAlchemy (>=1.4.36)", "pymysql (>=1.0.2)"]
+output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.8.10)"]
+parquet = ["pyarrow (>=7.0.0)"]
+performance = ["bottleneck (>=1.3.4)", "numba (>=0.55.2)", "numexpr (>=2.8.0)"]
+plot = ["matplotlib (>=3.6.1)"]
+postgresql = ["SQLAlchemy (>=1.4.36)", "psycopg2 (>=2.9.3)"]
+spss = ["pyreadstat (>=1.1.5)"]
+sql-other = ["SQLAlchemy (>=1.4.36)"]
+test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"]
+xml = ["lxml (>=4.8.0)"]
+
+[[package]]
+name = "playwright"
+version = "1.39.0"
+description = "A high-level API to automate web browsers"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "playwright-1.39.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:384e195a6d09343f319031cf552e9cd601ede78fe9c082b9fa197537c5cbfe7a"},
+ {file = "playwright-1.39.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d2c3634411828d9273196ed6f69f2fa7645c89732b3c982dcf09ab03ed4c5d2b"},
+ {file = "playwright-1.39.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:d2fd90f370599cf9a2c6a041bd79a5eeec62baf0e943c7c5c2079b29be476d2a"},
+ {file = "playwright-1.39.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:699a8e707ca5f3567aa28223ee1be7e42d2bf25eda7d3d86babda71e36e5f16f"},
+ {file = "playwright-1.39.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:654bb3ae0dc3c69ffddc0c38c127c3b8e93032d8cf3928e2c4f21890cb39514b"},
+ {file = "playwright-1.39.0-py3-none-win32.whl", hash = "sha256:40ed7f2546c64f1bb3d22b2295b4d43ed5a2f0b7ea7599d93a72f723a1883e1e"},
+ {file = "playwright-1.39.0-py3-none-win_amd64.whl", hash = "sha256:a420d814e21b05e1156747e2a9fae6c3cca2b46bb4a0226fb26ee65538ce09c9"},
+]
+
+[package.dependencies]
+greenlet = "3.0.0"
+pyee = "11.0.1"
+
+[[package]]
+name = "plotly"
+version = "5.18.0"
+description = "An open-source, interactive data visualization library for Python"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "plotly-5.18.0-py3-none-any.whl", hash = "sha256:23aa8ea2f4fb364a20d34ad38235524bd9d691bf5299e800bca608c31e8db8de"},
+ {file = "plotly-5.18.0.tar.gz", hash = "sha256:360a31e6fbb49d12b007036eb6929521343d6bee2236f8459915821baefa2cbb"},
+]
+
+[package.dependencies]
+packaging = "*"
+tenacity = ">=6.2.0"
+
+[[package]]
+name = "pluggy"
+version = "1.3.0"
+description = "plugin and hook calling mechanisms for python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"},
+ {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"},
+]
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+testing = ["pytest", "pytest-benchmark"]
+
+[[package]]
+name = "protobuf"
+version = "4.25.0"
+description = ""
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "protobuf-4.25.0-cp310-abi3-win32.whl", hash = "sha256:5c1203ac9f50e4853b0a0bfffd32c67118ef552a33942982eeab543f5c634395"},
+ {file = "protobuf-4.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:c40ff8f00aa737938c5378d461637d15c442a12275a81019cc2fef06d81c9419"},
+ {file = "protobuf-4.25.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:cf21faba64cd2c9a3ed92b7a67f226296b10159dbb8fbc5e854fc90657d908e4"},
+ {file = "protobuf-4.25.0-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:32ac2100b0e23412413d948c03060184d34a7c50b3e5d7524ee96ac2b10acf51"},
+ {file = "protobuf-4.25.0-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:683dc44c61f2620b32ce4927de2108f3ebe8ccf2fd716e1e684e5a50da154054"},
+ {file = "protobuf-4.25.0-cp38-cp38-win32.whl", hash = "sha256:1a3ba712877e6d37013cdc3476040ea1e313a6c2e1580836a94f76b3c176d575"},
+ {file = "protobuf-4.25.0-cp38-cp38-win_amd64.whl", hash = "sha256:b2cf8b5d381f9378afe84618288b239e75665fe58d0f3fd5db400959274296e9"},
+ {file = "protobuf-4.25.0-cp39-cp39-win32.whl", hash = "sha256:63714e79b761a37048c9701a37438aa29945cd2417a97076048232c1df07b701"},
+ {file = "protobuf-4.25.0-cp39-cp39-win_amd64.whl", hash = "sha256:d94a33db8b7ddbd0af7c467475fb9fde0c705fb315a8433c0e2020942b863a1f"},
+ {file = "protobuf-4.25.0-py3-none-any.whl", hash = "sha256:1a53d6f64b00eecf53b65ff4a8c23dc95df1fa1e97bb06b8122e5a64f49fc90a"},
+ {file = "protobuf-4.25.0.tar.gz", hash = "sha256:68f7caf0d4f012fd194a301420cf6aa258366144d814f358c5b32558228afa7c"},
+]
+
+[[package]]
+name = "protoc-gen-openapiv2"
+version = "0.0.1"
+description = "Provides the missing pieces for gRPC Gateway."
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "protoc-gen-openapiv2-0.0.1.tar.gz", hash = "sha256:6f79188d842c13177c9c0558845442c340b43011bf67dfef1dfc3bc067506409"},
+ {file = "protoc_gen_openapiv2-0.0.1-py3-none-any.whl", hash = "sha256:18090c8be3877c438e7da0f7eb7cace45a9a210306bca4707708dbad367857be"},
+]
+
+[package.dependencies]
+googleapis-common-protos = "*"
+protobuf = ">=4.21.0"
+
+[[package]]
+name = "psutil"
+version = "5.9.6"
+description = "Cross-platform lib for process and system monitoring in Python."
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+files = [
+ {file = "psutil-5.9.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:fb8a697f11b0f5994550555fcfe3e69799e5b060c8ecf9e2f75c69302cc35c0d"},
+ {file = "psutil-5.9.6-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:91ecd2d9c00db9817a4b4192107cf6954addb5d9d67a969a4f436dbc9200f88c"},
+ {file = "psutil-5.9.6-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:10e8c17b4f898d64b121149afb136c53ea8b68c7531155147867b7b1ac9e7e28"},
+ {file = "psutil-5.9.6-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:18cd22c5db486f33998f37e2bb054cc62fd06646995285e02a51b1e08da97017"},
+ {file = "psutil-5.9.6-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:ca2780f5e038379e520281e4c032dddd086906ddff9ef0d1b9dcf00710e5071c"},
+ {file = "psutil-5.9.6-cp27-none-win32.whl", hash = "sha256:70cb3beb98bc3fd5ac9ac617a327af7e7f826373ee64c80efd4eb2856e5051e9"},
+ {file = "psutil-5.9.6-cp27-none-win_amd64.whl", hash = "sha256:51dc3d54607c73148f63732c727856f5febec1c7c336f8f41fcbd6315cce76ac"},
+ {file = "psutil-5.9.6-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c69596f9fc2f8acd574a12d5f8b7b1ba3765a641ea5d60fb4736bf3c08a8214a"},
+ {file = "psutil-5.9.6-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92e0cc43c524834af53e9d3369245e6cc3b130e78e26100d1f63cdb0abeb3d3c"},
+ {file = "psutil-5.9.6-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:748c9dd2583ed86347ed65d0035f45fa8c851e8d90354c122ab72319b5f366f4"},
+ {file = "psutil-5.9.6-cp36-cp36m-win32.whl", hash = "sha256:3ebf2158c16cc69db777e3c7decb3c0f43a7af94a60d72e87b2823aebac3d602"},
+ {file = "psutil-5.9.6-cp36-cp36m-win_amd64.whl", hash = "sha256:ff18b8d1a784b810df0b0fff3bcb50ab941c3b8e2c8de5726f9c71c601c611aa"},
+ {file = "psutil-5.9.6-cp37-abi3-win32.whl", hash = "sha256:a6f01f03bf1843280f4ad16f4bde26b817847b4c1a0db59bf6419807bc5ce05c"},
+ {file = "psutil-5.9.6-cp37-abi3-win_amd64.whl", hash = "sha256:6e5fb8dc711a514da83098bc5234264e551ad980cec5f85dabf4d38ed6f15e9a"},
+ {file = "psutil-5.9.6-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:daecbcbd29b289aac14ece28eca6a3e60aa361754cf6da3dfb20d4d32b6c7f57"},
+ {file = "psutil-5.9.6.tar.gz", hash = "sha256:e4b92ddcd7dd4cdd3f900180ea1e104932c7bce234fb88976e2a3b296441225a"},
+]
+
+[package.extras]
+test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
+
+[[package]]
+name = "pycparser"
+version = "2.21"
+description = "C parser in Python"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
+ {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
+]
+
+[[package]]
+name = "pyee"
+version = "11.0.1"
+description = "A rough port of Node.js's EventEmitter to Python with a few tricks of its own"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pyee-11.0.1-py3-none-any.whl", hash = "sha256:9bcc9647822234f42c228d88de63d0f9ffa881e87a87f9d36ddf5211f6ac977d"},
+ {file = "pyee-11.0.1.tar.gz", hash = "sha256:a642c51e3885a33ead087286e35212783a4e9b8d6514a10a5db4e57ac57b2b29"},
+]
+
+[package.dependencies]
+typing-extensions = "*"
+
+[package.extras]
+dev = ["black", "flake8", "flake8-black", "isort", "jupyter-console", "mkdocs", "mkdocs-include-markdown-plugin", "mkdocstrings[python]", "pytest", "pytest-asyncio", "pytest-trio", "toml", "tox", "trio", "trio", "trio-typing", "twine", "twisted", "validate-pyproject[all]"]
+
+[[package]]
+name = "pynacl"
+version = "1.5.0"
+description = "Python binding to the Networking and Cryptography (NaCl) library"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"},
+ {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"},
+]
+
+[package.dependencies]
+cffi = ">=1.4.1"
+
+[package.extras]
+docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"]
+tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"]
+
+[[package]]
+name = "pytest"
+version = "7.4.3"
+description = "pytest: simple powerful testing with Python"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"},
+ {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
+iniconfig = "*"
+packaging = "*"
+pluggy = ">=0.12,<2.0"
+tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
+
+[package.extras]
+testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
+
+[[package]]
+name = "pytest-base-url"
+version = "2.0.0"
+description = "pytest plugin for URL based testing"
+optional = false
+python-versions = ">=3.7,<4.0"
+files = [
+ {file = "pytest-base-url-2.0.0.tar.gz", hash = "sha256:e1e88a4fd221941572ccdcf3bf6c051392d2f8b6cef3e0bc7da95abec4b5346e"},
+ {file = "pytest_base_url-2.0.0-py3-none-any.whl", hash = "sha256:ed36fd632c32af9f1c08f2c2835dcf42ca8fcd097d6ed44a09f253d365ad8297"},
+]
+
+[package.dependencies]
+pytest = ">=3.0.0,<8.0.0"
+requests = ">=2.9"
+
+[[package]]
+name = "pytest-playwright"
+version = "0.4.3"
+description = "A pytest wrapper with fixtures for Playwright to automate web browsers"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pytest-playwright-0.4.3.tar.gz", hash = "sha256:699e2c47fbb1e6a57895454693eba278cf55d04d44c15017709b00e1de1d9ccd"},
+ {file = "pytest_playwright-0.4.3-py3-none-any.whl", hash = "sha256:c9ff6e7ebfd967b562f5c3d67f1ae6b45a061d6ea51ad304fdd95aca9db20774"},
+]
+
+[package.dependencies]
+playwright = ">=1.18"
+pytest = ">=6.2.4,<8.0.0"
+pytest-base-url = ">=1.0.0,<3.0.0"
+python-slugify = ">=6.0.0,<9.0.0"
+
+[[package]]
+name = "pytest-xdist"
+version = "3.4.0"
+description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "pytest-xdist-3.4.0.tar.gz", hash = "sha256:3a94a931dd9e268e0b871a877d09fe2efb6175c2c23d60d56a6001359002b832"},
+ {file = "pytest_xdist-3.4.0-py3-none-any.whl", hash = "sha256:e513118bf787677a427e025606f55e95937565e06dfaac8d87f55301e57ae607"},
+]
+
+[package.dependencies]
+execnet = ">=1.1"
+pytest = ">=6.2.0"
+
+[package.extras]
+psutil = ["psutil (>=3.0)"]
+setproctitle = ["setproctitle"]
+testing = ["filelock"]
+
+[[package]]
+name = "python-dateutil"
+version = "2.8.2"
+description = "Extensions to the standard Python datetime module"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+files = [
+ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
+ {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
+]
+
+[package.dependencies]
+six = ">=1.5"
+
+[[package]]
+name = "python-dotenv"
+version = "1.0.0"
+description = "Read key-value pairs from a .env file and set them as environment variables"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"},
+ {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"},
+]
+
+[package.extras]
+cli = ["click (>=5.0)"]
+
+[[package]]
+name = "python-slugify"
+version = "8.0.1"
+description = "A Python slugify application that also handles Unicode"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "python-slugify-8.0.1.tar.gz", hash = "sha256:ce0d46ddb668b3be82f4ed5e503dbc33dd815d83e2eb6824211310d3fb172a27"},
+ {file = "python_slugify-8.0.1-py2.py3-none-any.whl", hash = "sha256:70ca6ea68fe63ecc8fa4fcf00ae651fc8a5d02d93dcd12ae6d4fc7ca46c4d395"},
+]
+
+[package.dependencies]
+text-unidecode = ">=1.3"
+
+[package.extras]
+unidecode = ["Unidecode (>=1.1.1)"]
+
+[[package]]
+name = "pytz"
+version = "2023.3.post1"
+description = "World timezone definitions, modern and historical"
+optional = false
+python-versions = "*"
+files = [
+ {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"},
+ {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"},
+]
+
+[[package]]
+name = "pywin32"
+version = "306"
+description = "Python for Window Extensions"
+optional = false
+python-versions = "*"
+files = [
+ {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"},
+ {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"},
+ {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"},
+ {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"},
+ {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"},
+ {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"},
+ {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"},
+ {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"},
+ {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"},
+ {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"},
+ {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"},
+ {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"},
+ {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"},
+ {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"},
+]
+
+[[package]]
+name = "requests"
+version = "2.31.0"
+description = "Python HTTP for Humans."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
+ {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
+]
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset-normalizer = ">=2,<4"
+idna = ">=2.5,<4"
+urllib3 = ">=1.21.1,<3"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)"]
+use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
+
+[[package]]
+name = "scipy"
+version = "1.11.3"
+description = "Fundamental algorithms for scientific computing in Python"
+optional = false
+python-versions = "<3.13,>=3.9"
+files = [
+ {file = "scipy-1.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:370f569c57e1d888304052c18e58f4a927338eafdaef78613c685ca2ea0d1fa0"},
+ {file = "scipy-1.11.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:9885e3e4f13b2bd44aaf2a1a6390a11add9f48d5295f7a592393ceb8991577a3"},
+ {file = "scipy-1.11.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e04aa19acc324a1a076abb4035dabe9b64badb19f76ad9c798bde39d41025cdc"},
+ {file = "scipy-1.11.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e1a8a4657673bfae1e05e1e1d6e94b0cabe5ed0c7c144c8aa7b7dbb774ce5c1"},
+ {file = "scipy-1.11.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7abda0e62ef00cde826d441485e2e32fe737bdddee3324e35c0e01dee65e2a88"},
+ {file = "scipy-1.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:033c3fd95d55012dd1148b201b72ae854d5086d25e7c316ec9850de4fe776929"},
+ {file = "scipy-1.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:925c6f09d0053b1c0f90b2d92d03b261e889b20d1c9b08a3a51f61afc5f58165"},
+ {file = "scipy-1.11.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5664e364f90be8219283eeb844323ff8cd79d7acbd64e15eb9c46b9bc7f6a42a"},
+ {file = "scipy-1.11.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00f325434b6424952fbb636506f0567898dca7b0f7654d48f1c382ea338ce9a3"},
+ {file = "scipy-1.11.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f290cf561a4b4edfe8d1001ee4be6da60c1c4ea712985b58bf6bc62badee221"},
+ {file = "scipy-1.11.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:91770cb3b1e81ae19463b3c235bf1e0e330767dca9eb4cd73ba3ded6c4151e4d"},
+ {file = "scipy-1.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:e1f97cd89c0fe1a0685f8f89d85fa305deb3067d0668151571ba50913e445820"},
+ {file = "scipy-1.11.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dfcc1552add7cb7c13fb70efcb2389d0624d571aaf2c80b04117e2755a0c5d15"},
+ {file = "scipy-1.11.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0d3a136ae1ff0883fffbb1b05b0b2fea251cb1046a5077d0b435a1839b3e52b7"},
+ {file = "scipy-1.11.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bae66a2d7d5768eaa33008fa5a974389f167183c87bf39160d3fefe6664f8ddc"},
+ {file = "scipy-1.11.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2f6dee6cbb0e263b8142ed587bc93e3ed5e777f1f75448d24fb923d9fd4dce6"},
+ {file = "scipy-1.11.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:74e89dc5e00201e71dd94f5f382ab1c6a9f3ff806c7d24e4e90928bb1aafb280"},
+ {file = "scipy-1.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:90271dbde4be191522b3903fc97334e3956d7cfb9cce3f0718d0ab4fd7d8bfd6"},
+ {file = "scipy-1.11.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a63d1ec9cadecce838467ce0631c17c15c7197ae61e49429434ba01d618caa83"},
+ {file = "scipy-1.11.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:5305792c7110e32ff155aed0df46aa60a60fc6e52cd4ee02cdeb67eaccd5356e"},
+ {file = "scipy-1.11.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ea7f579182d83d00fed0e5c11a4aa5ffe01460444219dedc448a36adf0c3917"},
+ {file = "scipy-1.11.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c77da50c9a91e23beb63c2a711ef9e9ca9a2060442757dffee34ea41847d8156"},
+ {file = "scipy-1.11.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:15f237e890c24aef6891c7d008f9ff7e758c6ef39a2b5df264650eb7900403c0"},
+ {file = "scipy-1.11.3-cp39-cp39-win_amd64.whl", hash = "sha256:4b4bb134c7aa457e26cc6ea482b016fef45db71417d55cc6d8f43d799cdf9ef2"},
+ {file = "scipy-1.11.3.tar.gz", hash = "sha256:bba4d955f54edd61899776bad459bf7326e14b9fa1c552181f0479cc60a568cd"},
+]
+
+[package.dependencies]
+numpy = ">=1.21.6,<1.28.0"
+
+[package.extras]
+dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"]
+doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"]
+test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"]
+
+[[package]]
+name = "setuptools"
+version = "68.2.2"
+description = "Easily download, build, install, upgrade, and uninstall Python packages"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"},
+ {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"},
+]
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
+testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
+testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
+
+[[package]]
+name = "six"
+version = "1.16.0"
+description = "Python 2 and 3 compatibility utilities"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+files = [
+ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+]
+
+[[package]]
+name = "tenacity"
+version = "8.2.3"
+description = "Retry code until it succeeds"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "tenacity-8.2.3-py3-none-any.whl", hash = "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c"},
+ {file = "tenacity-8.2.3.tar.gz", hash = "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a"},
+]
+
+[package.extras]
+doc = ["reno", "sphinx", "tornado (>=4.5)"]
+
+[[package]]
+name = "text-unidecode"
+version = "1.3"
+description = "The most basic Text::Unidecode port"
+optional = false
+python-versions = "*"
+files = [
+ {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"},
+ {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"},
+]
+
+[[package]]
+name = "toml"
+version = "0.10.2"
+description = "Python Library for Tom's Obvious, Minimal Language"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+files = [
+ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
+ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
+]
+
+[[package]]
+name = "tomli"
+version = "2.0.1"
+description = "A lil' TOML parser"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
+ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.8.0"
+description = "Backported and Experimental Type Hints for Python 3.8+"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"},
+ {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"},
+]
+
+[[package]]
+name = "tzdata"
+version = "2023.3"
+description = "Provider of IANA time zone data"
+optional = false
+python-versions = ">=2"
+files = [
+ {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"},
+ {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"},
+]
+
+[[package]]
+name = "urllib3"
+version = "2.1.0"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"},
+ {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"},
+]
+
+[package.extras]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
+socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
+zstd = ["zstandard (>=0.18.0)"]
+
+[[package]]
+name = "vega-sim"
+version = "1.2.1"
+description = "Simulator for running self-contained Vega chain on local PC"
+optional = false
+python-versions = "^3.9,<3.12"
+files = []
+develop = false
+
+[package.dependencies]
+deprecated = "*"
+docker = "*"
+grpcio-tools = "*"
+inflection = "*"
+numpy = "*"
+pandas = "*"
+plotly = "*"
+protoc-gen-openapiv2 = "*"
+psutil = "*"
+PyNaCl = "*"
+python-dotenv = "*"
+requests = "*"
+scipy = "*"
+toml = "*"
+websockets = "*"
+
+[package.extras]
+agents = ["TA-Lib"]
+jupyter = ["ipywidgets", "jupyter", "jupyterlab", "matplotlib"]
+learning = ["gymnasium", "matplotlib", "numba (>=0.57.1,<0.58.0)", "pettingzoo", "stable-baselines3", "tensorboard", "tianshou", "torch", "tqdm"]
+profile = ["pytest-profiling", "snakeviz"]
+
+[package.source]
+type = "git"
+url = "https://github.com/vegaprotocol/vega-market-sim.git"
+reference = "HEAD"
+resolved_reference = "e93f7dfa8463c59cfd0e299362b845511cebeef6"
+
+[[package]]
+name = "websocket-client"
+version = "1.6.4"
+description = "WebSocket client for Python with low level API options"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "websocket-client-1.6.4.tar.gz", hash = "sha256:b3324019b3c28572086c4a319f91d1dcd44e6e11cd340232978c684a7650d0df"},
+ {file = "websocket_client-1.6.4-py3-none-any.whl", hash = "sha256:084072e0a7f5f347ef2ac3d8698a5e0b4ffbfcab607628cadabc650fc9a83a24"},
+]
+
+[package.extras]
+docs = ["Sphinx (>=6.0)", "sphinx-rtd-theme (>=1.1.0)"]
+optional = ["python-socks", "wsaccel"]
+test = ["websockets"]
+
+[[package]]
+name = "websockets"
+version = "12.0"
+description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"},
+ {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"},
+ {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"},
+ {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"},
+ {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"},
+ {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"},
+ {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"},
+ {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"},
+ {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"},
+ {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"},
+ {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"},
+ {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"},
+ {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"},
+ {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"},
+ {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"},
+ {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"},
+ {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"},
+ {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"},
+ {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"},
+ {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"},
+ {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"},
+ {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"},
+ {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"},
+ {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"},
+ {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"},
+ {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"},
+ {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"},
+ {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"},
+ {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"},
+ {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"},
+ {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"},
+ {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"},
+ {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"},
+ {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"},
+ {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"},
+ {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"},
+ {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"},
+ {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"},
+ {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"},
+ {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"},
+ {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"},
+ {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"},
+ {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"},
+ {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"},
+ {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"},
+ {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"},
+ {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"},
+ {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"},
+ {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"},
+ {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"},
+ {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"},
+ {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"},
+ {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"},
+ {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"},
+ {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"},
+ {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"},
+ {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"},
+ {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"},
+ {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"},
+ {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"},
+ {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"},
+ {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"},
+ {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"},
+ {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"},
+ {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"},
+ {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"},
+ {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"},
+ {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"},
+ {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"},
+ {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"},
+ {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"},
+ {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"},
+]
+
+[[package]]
+name = "wrapt"
+version = "1.16.0"
+description = "Module for decorators, wrappers and monkey patching."
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"},
+ {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"},
+ {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"},
+ {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"},
+ {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"},
+ {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"},
+ {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"},
+ {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"},
+ {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"},
+ {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"},
+ {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"},
+ {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"},
+ {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"},
+ {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"},
+ {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"},
+ {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"},
+ {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"},
+ {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"},
+ {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"},
+ {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"},
+ {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"},
+ {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"},
+ {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"},
+ {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"},
+ {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"},
+ {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"},
+ {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"},
+ {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"},
+ {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"},
+ {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"},
+ {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"},
+ {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"},
+ {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"},
+ {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"},
+ {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"},
+ {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"},
+ {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"},
+ {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"},
+ {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"},
+ {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"},
+ {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"},
+ {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"},
+ {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"},
+ {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"},
+ {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"},
+ {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"},
+ {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"},
+ {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"},
+ {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"},
+ {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"},
+ {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"},
+ {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"},
+ {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"},
+ {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"},
+ {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"},
+ {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"},
+ {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"},
+ {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"},
+ {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"},
+ {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"},
+ {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"},
+ {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"},
+ {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"},
+ {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"},
+ {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"},
+ {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"},
+ {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"},
+ {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"},
+ {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"},
+ {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"},
+]
+
+[metadata]
+lock-version = "2.0"
+python-versions = ">=3.9,<3.11"
+content-hash = "d1231fe591b774e34b8f94a54cd02e4d7dae924c57785263841c3b0b0feed505"
diff --git a/apps/trading/e2e/pyproject.toml b/apps/trading/e2e/pyproject.toml
new file mode 100644
index 000000000..af048431a
--- /dev/null
+++ b/apps/trading/e2e/pyproject.toml
@@ -0,0 +1,29 @@
+[tool.poetry]
+name = "trading market-sim e2e"
+version = "0.1.0"
+description = ""
+authors = ["Matthew Russell
"]
+readme = "README.md"
+packages = [{include = "trading market-sim e2e"}]
+
+[tool.poetry.dependencies]
+python = ">=3.9,<3.11"
+psutil = "^5.9.5"
+vega-sim = {git = "https://github.com/vegaprotocol/vega-market-sim.git"}
+pytest-playwright = "^0.4.2"
+docker = "^6.1.3"
+pytest-xdist = "^3.3.1"
+python-dotenv = "^1.0.0"
+
+[build-system]
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
+
+[tool.pytest.ini_options]
+log_cli = true
+log_cli_format = "%(asctime)s - %(name)s - %(levelname)s: %(message)s"
+log_cli_date_format = "%Y-%m-%d %H:%M:%S"
+log_cli_level = "INFO"
+log_file_format = "%(asctime)s - %(name)s - %(levelname)s: %(message)s"
+log_file_date_format = "%Y-%m-%d %H:%M:%S"
+log_file_level = "INFO"
\ No newline at end of file
diff --git a/apps/trading/e2e/tests/assets/test_assets.py b/apps/trading/e2e/tests/assets/test_assets.py
new file mode 100644
index 000000000..8643d6fe5
--- /dev/null
+++ b/apps/trading/e2e/tests/assets/test_assets.py
@@ -0,0 +1,95 @@
+import pytest
+import re
+from playwright.sync_api import expect, Page
+
+label_value_tooltip_pairs = [
+ {
+ "label": "ID",
+ "value": "asset-id",
+ },
+ {
+ "label": "Type",
+ "value": "Builtin asset",
+ "valueToolTip": "A Vega builtin asset",
+ },
+ {
+ "label": "Name",
+ "value": "tDAI",
+ },
+ {
+ "label": "Symbol",
+ "value": "tDAI",
+ },
+ {
+ "label": "Decimals",
+ "value": "5",
+ "labelTooltip": "Number of decimal / precision handled by this asset",
+ },
+ {
+ "label": "Quantum",
+ "value": "0.00001",
+ "labelTooltip": "The minimum economically meaningful amount of the asset",
+ },
+ {
+ "label": "Status",
+ "value": "Enabled",
+ "labelTooltip": "The status of the asset in the Vega network",
+ "valueToolTip": "Asset can be used on the Vega network",
+ },
+ {
+ "label": "Max faucet amount",
+ "value": "10,000,000,000.00",
+ "labelTooltip": "Maximum amount that can be requested by a party through the built-in asset faucet at a time",
+ },
+ {
+ "label": "Infrastructure fee account balance",
+ "value": "0.00",
+ "labelTooltip": "The infrastructure fee account in this asset",
+ },
+ {
+ "label": "Global reward pool account balance",
+ "value": "0.00",
+ "labelTooltip": "The global rewards acquired in this asset",
+ },
+]
+
+
+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)
+ page.get_by_test_id("dialog-title").click()
+
+
+@pytest.mark.usefixtures("page", "continuous_market", "auth", "risk_accepted")
+def test_asset_details(page: Page):
+ page.goto("/#/portfolio")
+ page.locator('[data-testid="tab-collateral"] >> text=tDAI').click()
+
+ for index, pair in enumerate(label_value_tooltip_pairs):
+ if index in [7, 8, 9]: # Skip indices 7, 8, and 9.
+ continue
+
+ label = pair.get("label", "")
+ 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()
+ 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}"
+
+ 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)
+
+ if label_tooltip:
+ tooltip(page, index, "label", label_tooltip)
+
+ if value_tooltip:
+ tooltip(page, index, "value", value_tooltip)
+
+ page.get_by_test_id("dialog-close").click()
+ assert not page.query_selector("dialog-content")
diff --git a/apps/trading/e2e/tests/deal_ticket/test_basic_submit.py b/apps/trading/e2e/tests/deal_ticket/test_basic_submit.py
new file mode 100644
index 000000000..fd66559b7
--- /dev/null
+++ b/apps/trading/e2e/tests/deal_ticket/test_basic_submit.py
@@ -0,0 +1,143 @@
+import pytest
+from playwright.sync_api import Page, expect
+from vega_sim.service import VegaService
+from datetime import datetime, timedelta
+from conftest import init_vega
+from fixtures.market import setup_continuous_market
+from actions.utils import wait_for_toast_confirmation
+
+order_size = "order-size"
+order_price = "order-price"
+place_order = "place-order"
+order_side_sell = "order-side-SIDE_SELL"
+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")
+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)")
+ page.get_by_test_id(order_size).fill("10")
+ page.get_by_test_id(order_price).fill("120")
+ expires_at = datetime.now() + timedelta(days=1)
+ expires_at_input_value = expires_at.strftime("%Y-%m-%dT%H:%M:%S")
+ page.get_by_test_id("date-picker-field").clear()
+ page.get_by_test_id("date-picker-field").fill(expires_at_input_value)
+ # 7002-SORD-011
+ expect(page.get_by_test_id("place-order").locator("span").first).to_have_text(
+ "Place limit order"
+ )
+ expect(page.get_by_test_id("place-order").locator("span").last).to_have_text(
+ "10 BTC @ 120.00 BTC"
+ )
+ page.get_by_test_id(place_order).click()
+ wait_for_toast_confirmation(page)
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ page.get_by_test_id("All").click()
+ # 7002-SORD-017
+ expect(page.get_by_role("row").nth(2)).to_contain_text(
+ "BTC:DAI_2023Futr10+10LimitFilled120.00GTT:"
+ )
+
+
+@pytest.mark.usefixtures("page", "auth", "risk_accepted")
+def test_limit_buy_order(continuous_market, vega: VegaService, page: Page):
+ 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.get_by_test_id(place_order).click()
+ wait_for_toast_confirmation(page)
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ page.get_by_test_id("All").click()
+ # 7002-SORD-017
+ expect(page.get_by_role("row").nth(2)).to_contain_text(
+ "BTC:DAI_2023Futr10+10LimitFilled120.00GTC"
+ )
+
+
+@pytest.mark.usefixtures("page", "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")
+ page.get_by_test_id(order_price).fill("100")
+ page.get_by_test_id(order_side_sell).click()
+ page.get_by_test_id(tif).select_option("Good for Normal (GFN)")
+ # 7002-SORD-011
+ expect(page.get_by_test_id("place-order").locator("span").first).to_have_text(
+ "Place limit order"
+ )
+ expect(page.get_by_test_id("place-order").locator("span").last).to_have_text(
+ "10 BTC @ 100.00 BTC"
+ )
+ page.get_by_test_id(place_order).click()
+ wait_for_toast_confirmation(page)
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ page.get_by_test_id("All").click()
+ expect(page.get_by_role("row").nth(2)).to_contain_text(
+ "BTC:DAI_2023Futr10-10LimitFilled100.00GFN"
+ )
+
+
+@pytest.mark.usefixtures("page", "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()
+ page.get_by_test_id(order_size).fill("10")
+ page.get_by_test_id(order_side_sell).click()
+ # 7002-SORD-011
+ expect(page.get_by_test_id("place-order").locator("span").first).to_have_text(
+ "Place market order"
+ )
+ expect(page.get_by_test_id("place-order").locator("span").last).to_have_text(
+ "10 BTC @ market"
+ )
+ page.get_by_test_id(place_order).click()
+ wait_for_toast_confirmation(page)
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ page.get_by_test_id("All").click()
+ expect(page.get_by_role("row").nth(2)).to_contain_text(
+ "BTC:DAI_2023Futr10-10MarketFilled-IOC"
+ )
+
+
+@pytest.mark.usefixtures("page", "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()
+ page.get_by_test_id(order_size).fill("10")
+ page.get_by_test_id(tif).select_option("Fill or Kill (FOK)")
+ page.get_by_test_id(place_order).click()
+ wait_for_toast_confirmation(page)
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ page.get_by_test_id("All").click()
+ # 7002-SORD-010
+ # 0003-WTXN-012
+ # 0003-WTXN-003
+ expect(page.get_by_role("row").nth(2)).to_contain_text(
+ "BTC:DAI_2023Futr10+10MarketFilled-FOK"
+ )
diff --git a/apps/trading/e2e/tests/deal_ticket/test_deal_ticket_basics.py b/apps/trading/e2e/tests/deal_ticket/test_deal_ticket_basics.py
new file mode 100644
index 000000000..1b6d7a340
--- /dev/null
+++ b/apps/trading/e2e/tests/deal_ticket/test_deal_ticket_basics.py
@@ -0,0 +1,35 @@
+import pytest
+from playwright.sync_api import Page, expect
+from conftest import init_vega
+from fixtures.market import setup_continuous_market
+
+@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.skip("We currently can't approve wallet connection through Sim")
+@pytest.mark.usefixtures("page", "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")
+ page.get_by_test_id("order-connect-wallet").click()
+ expect(page.locator('[role="dialog"]')).to_be_visible()
+ page.get_by_test_id("connector-jsonRpc").click()
+ expect(page.get_by_test_id("wallet-dialog-title")).to_be_visible()
+ # TODO: accept wallet connection and assert wallet is connected.
+ 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")
+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()
+ page.get_by_test_id("Order").click()
+ expect(page.get_by_test_id("deal-ticket-form")).not_to_be_visible()
+ page.reload()
+ expect(page.get_by_test_id("deal-ticket-form")).to_be_visible()
diff --git a/apps/trading/e2e/tests/deal_ticket/test_fees_margin_estimations.py b/apps/trading/e2e/tests/deal_ticket/test_fees_margin_estimations.py
new file mode 100644
index 000000000..37b1e5d1b
--- /dev/null
+++ b/apps/trading/e2e/tests/deal_ticket/test_fees_margin_estimations.py
@@ -0,0 +1,89 @@
+import pytest
+from playwright.sync_api import Page, expect
+from vega_sim.service import VegaService
+from actions.vega import submit_order
+from actions.utils import wait_for_toast_confirmation
+
+notional = "deal-ticket-fee-notional"
+fees = "deal-ticket-fee-fees"
+margin_required = "deal-ticket-fee-margin-required"
+item_value = "item-value"
+market_trading_mode = "market-trading-mode"
+
+
+@pytest.mark.skip("tbd")
+@pytest.mark.usefixtures("page", "vega", "continuous_market", "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
+ page.goto(f"/#/markets/{market_id}")
+
+ # submit order from UI and verify fees and margin
+ expect(page.get_by_test_id(notional)).to_have_text("Notional- BTC")
+ expect(page.get_by_test_id(fees)).to_have_text("Fees- tDAI")
+ expect(page.get_by_test_id(margin_required)).to_have_text(
+ "Margin required0.00 tDAI"
+ )
+ page.get_by_test_id("order-size").type("200")
+ page.get_by_test_id("order-price").type("20")
+
+ expect(page.get_by_test_id(notional)).to_have_text("Notional4,000.00 BTC")
+ expect(page.get_by_test_id(fees)).to_have_text("Fees~402.00 tDAI")
+ expect(page.get_by_test_id(margin_required)).to_have_text(
+ "Margin required1,661.88832 tDAI"
+ )
+
+ page.get_by_test_id("place-order").click()
+ wait_for_toast_confirmation(page)
+ vega.forward("10s")
+ vega.wait_fn(10)
+ vega.wait_for_total_catchup()
+ expect(page.get_by_test_id(margin_required)).to_have_text(
+ "Margin required1,661.88832 tDAI "
+ )
+ page.get_by_test_id("toast-close").click()
+
+ # submit order by sim function
+ order = submit_order(vega, "Key 1", market_id, "SIDE_BUY", 400, 38329483272398.838)
+ vega.forward("20s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ expect(page.get_by_test_id(margin_required)).to_have_text(
+ "Margin required897,716,007,278,798.50 tDAI "
+ )
+ expect(page.get_by_test_id("deal-ticket-warning-margin")).to_contain_text(
+ "You may not have enough margin available to open this position."
+ )
+
+ # cancel order and verify that warning margin disappeared
+ vega.cancel_order("Key 1", market_id, order)
+ vega.forward("20s")
+ vega.wait_fn(10)
+ vega.wait_for_total_catchup()
+ expect(page.get_by_test_id("deal-ticket-warning-auction")).to_contain_text(
+ "Any orders placed now will not trade until the auction ends"
+ )
+
+ # add order at the current price so that it is possible to change the status to price monitoring
+ submit_order(vega, "Key 1", market_id, "SIDE_SELL", 1, 110)
+ vega.forward("20s")
+ vega.wait_fn(10)
+ vega.wait_for_total_catchup()
+ page.reload()
+ expect(page.get_by_test_id(margin_required)).to_have_text(
+ "Margin required1,700.53688 tDAI"
+ )
+ expect(
+ page.get_by_test_id(market_trading_mode).get_by_test_id(item_value)
+ ).to_have_text("Continuous")
+
+ # verify if we can submit order after reverted margin
+ page.get_by_test_id("place-order").click()
+ wait_for_toast_confirmation(page)
+ vega.forward("10s")
+ vega.wait_fn(10)
+ vega.wait_for_total_catchup()
+ # skip temporary
+ # expect(page.get_by_test_id("toast-content")).to_contain_text(
+ # "Your transaction has been confirmed"
+ # )
diff --git a/apps/trading/e2e/tests/deal_ticket/test_stop_order.py b/apps/trading/e2e/tests/deal_ticket/test_stop_order.py
new file mode 100644
index 000000000..028e6ecb5
--- /dev/null
+++ b/apps/trading/e2e/tests/deal_ticket/test_stop_order.py
@@ -0,0 +1,376 @@
+import pytest
+from playwright.sync_api import Page, expect
+from vega_sim.service import VegaService
+from actions.vega import submit_order
+from datetime import datetime, timedelta
+from conftest import init_vega
+from fixtures.market import setup_continuous_market
+
+stop_order_btn = "order-type-Stop"
+stop_limit_order_btn = "order-type-StopLimit"
+stop_market_order_btn = "order-type-StopMarket"
+order_side_sell = "order-side-SIDE_SELL"
+trigger_above = "triggerDirection-risesAbove"
+trigger_below = "triggerDirection-fallsBelow"
+trigger_price = "triggerPrice"
+trigger_type_price = "triggerType-price"
+trigger_type_trailing_percent_offset = "triggerType-trailingPercentOffset"
+order_size = "order-size"
+order_price = "order-price"
+order_tif = "order-tif"
+expire = "expire"
+expiry_strategy = '[for="expiryStrategy"]'
+expiry_strategy_submit = "expiryStrategy-submit"
+expiry_strategy_cancel = "expiryStrategy-cancel"
+date_picker_field = "date-picker-field"
+submit_stop_order = "place-order"
+stop_orders_tab = "Stop orders"
+row_table = "row"
+cancel = "cancel"
+market_name_col = '[col-id="market.tradableInstrument.instrument.code"]'
+trigger_col = '[col-id="trigger"]'
+expiresAt_col = '[col-id="expiresAt"]'
+size_col = '[col-id="submission.size"]'
+submission_type = '[col-id="submission.type"]'
+status_col = '[col-id="status"]'
+price_col = '[col-id="submission.price"]'
+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)
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup
+
+@pytest.mark.usefixtures("page", "continuous_market", "auth", "risk_accepted")
+def test_stop_order_form_error_validation(continuous_market, page: Page):
+ # 7002-SORD-032
+ page.goto(f"/#/markets/{continuous_market}")
+ page.get_by_test_id(stop_order_btn).click()
+ page.get_by_test_id(stop_limit_order_btn).is_visible()
+ page.get_by_test_id(stop_limit_order_btn).click()
+ page.get_by_test_id(order_side_sell).click()
+ page.get_by_test_id(submit_stop_order).click()
+ expect(page.get_by_test_id("stop-order-error-message-trigger-price")).to_have_text(
+ "You need provide a price"
+ )
+ expect(page.get_by_test_id("stop-order-error-message-size")).to_have_text(
+ "Size cannot be lower than 1"
+ )
+
+ page.get_by_test_id(order_size).fill("1")
+ page.get_by_test_id(order_price).fill("0.0000001")
+ expect(page.get_by_test_id("stop-order-error-message-price")).to_have_text(
+ "Price cannot be lower than 0.00001"
+ )
+
+@pytest.mark.skip("core issue")
+@pytest.mark.usefixtures("page", "vega", "continuous_market", "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()
+ page.get_by_test_id(stop_order_btn).click()
+ page.get_by_test_id(stop_market_order_btn).is_visible()
+ page.get_by_test_id(stop_market_order_btn).click()
+ page.get_by_test_id(trigger_price).fill("103")
+ page.get_by_test_id(order_size).fill("3")
+ page.get_by_test_id(submit_stop_order).click()
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ page.get_by_test_id(close_toast).click()
+ page.get_by_role(row_table).locator(market_name_col).nth(1).is_visible()
+ expect((page.get_by_role(row_table).locator(market_name_col)).nth(1)).to_have_text(
+ "BTC:DAI_2023Futr"
+ )
+ expect((page.get_by_role(row_table).locator(trigger_col)).nth(1)).to_have_text(
+ "Mark > 103.00"
+ )
+ expect((page.get_by_role(row_table).locator(expiresAt_col)).nth(1)).to_have_text("")
+ expect((page.get_by_role(row_table).locator(size_col)).nth(1)).to_have_text("+3")
+ expect((page.get_by_role(row_table).locator(submission_type)).nth(1)).to_have_text(
+ "Market"
+ )
+ expect((page.get_by_role(row_table).locator(status_col)).nth(1)).to_have_text(
+ "Rejected"
+ )
+ expect((page.get_by_role(row_table).locator(price_col)).nth(1)).to_have_text("-")
+ expect((page.get_by_role(row_table).locator(timeInForce_col)).nth(1)).to_have_text(
+ "FOK"
+ )
+ expect(
+ (page.get_by_role(row_table).locator(updatedAt_col)).nth(1)
+ ).not_to_be_empty()
+
+@pytest.mark.skip("core issue")
+@pytest.mark.usefixtures("page", "vega", "continuous_market", "auth", "risk_accepted")
+def test_submit_stop_market_order_triggered(
+ continuous_market, vega: VegaService, page: Page
+):
+ # 7002-SORD-071
+ # 7002-SORD-074
+ # 7002-SORD-075
+ # 7002-SORD-067
+ # 7002-SORD-068
+ page.goto(f"/#/markets/{continuous_market}")
+ page.get_by_test_id(stop_orders_tab).click()
+ # create a position because stop order is reduce only type
+ create_position(vega, continuous_market)
+
+ page.get_by_test_id(stop_order_btn).click()
+ page.get_by_test_id(stop_market_order_btn).is_visible()
+ page.get_by_test_id(stop_market_order_btn).click()
+ page.get_by_test_id(order_side_sell).click()
+ page.get_by_test_id(trigger_price).fill("103")
+ page.get_by_test_id(order_size).fill("1")
+ page.get_by_test_id(expire).click()
+ expires_at = datetime.now() + timedelta(days=1)
+ expires_at_input_value = expires_at.strftime("%Y-%m-%dT%H:%M:%S")
+ page.get_by_test_id("date-picker-field").fill(expires_at_input_value)
+ page.get_by_test_id(expiry_strategy_cancel).click()
+ page.get_by_test_id(submit_stop_order).click()
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ page.wait_for_selector('[data-testid="toast-close"]', state="visible")
+ page.get_by_test_id(close_toast).click()
+
+ page.get_by_role(row_table).locator(market_name_col).nth(1).is_visible()
+ expect((page.get_by_role(row_table).locator(market_name_col)).nth(1)).to_have_text(
+ "BTC:DAI_2023Futr"
+ )
+ expect((page.get_by_role(row_table).locator(trigger_col)).nth(1)).to_have_text(
+ "Mark > 103.00"
+ )
+ expect((page.get_by_role(row_table).locator(expiresAt_col)).nth(1)).to_contain_text(
+ "Cancels"
+ )
+ expect((page.get_by_role(row_table).locator(size_col)).nth(1)).to_have_text("-1")
+ expect((page.get_by_role(row_table).locator(submission_type)).nth(1)).to_have_text(
+ "Market"
+ )
+ expect((page.get_by_role(row_table).locator(status_col)).nth(1)).to_have_text(
+ "Triggered"
+ )
+ expect((page.get_by_role(row_table).locator(price_col)).nth(1)).to_have_text("-")
+ expect((page.get_by_role(row_table).locator(timeInForce_col)).nth(1)).to_have_text(
+ "FOK"
+ )
+ expect(
+ (page.get_by_role(row_table).locator(updatedAt_col)).nth(1)
+ ).not_to_be_empty()
+
+@pytest.mark.skip("core issue")
+@pytest.mark.usefixtures("continuous_market", "auth", "risk_accepted")
+def test_submit_stop_limit_order_pending(
+ continuous_market, vega: VegaService, page: Page
+):
+ # 7002-SORD-071
+ # 7002-SORD-074
+ # 7002-SORD-075
+ # 7002-SORD-069
+ page.goto(f"/#/markets/{continuous_market}")
+ page.get_by_test_id(stop_orders_tab).click()
+ # create a position because stop order is reduce only type
+ create_position(vega, continuous_market)
+
+ page.get_by_test_id(stop_order_btn).click()
+ page.get_by_test_id(stop_limit_order_btn).is_visible()
+ page.get_by_test_id(stop_limit_order_btn).click()
+ page.get_by_test_id(order_side_sell).click()
+ page.get_by_test_id(trigger_below).click()
+ page.get_by_test_id(trigger_price).fill("102")
+ page.get_by_test_id(order_price).fill("99")
+ page.get_by_test_id(order_size).fill("1")
+ page.get_by_test_id("order-tif").select_option("TIME_IN_FORCE_IOC")
+ page.get_by_test_id(expire).click()
+ expires_at = datetime.now() + timedelta(days=1)
+ expires_at_input_value = expires_at.strftime("%Y-%m-%dT%H:%M:%S")
+ page.get_by_test_id("date-picker-field").fill(expires_at_input_value)
+ page.get_by_test_id(submit_stop_order).click()
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ page.wait_for_selector('[data-testid="toast-close"]', state="visible")
+ page.get_by_test_id(close_toast).click()
+ page.get_by_role(row_table).locator(market_name_col).nth(1).is_visible()
+ expect((page.get_by_role(row_table).locator(market_name_col)).nth(1)).to_have_text(
+ "BTC:DAI_2023Futr"
+ )
+ expect((page.get_by_role(row_table).locator(trigger_col)).nth(1)).to_have_text(
+ "Mark < 102.00"
+ )
+ expect((page.get_by_role(row_table).locator(expiresAt_col)).nth(1)).to_contain_text(
+ "Submit"
+ )
+ expect((page.get_by_role(row_table).locator(size_col)).nth(1)).to_have_text("-1")
+ expect((page.get_by_role(row_table).locator(submission_type)).nth(1)).to_have_text(
+ "Limit"
+ )
+ expect((page.get_by_role(row_table).locator(status_col)).nth(1)).to_have_text(
+ "Pending"
+ )
+ expect((page.get_by_role(row_table).locator(price_col)).nth(1)).to_have_text(
+ "99.00"
+ )
+ expect((page.get_by_role(row_table).locator(timeInForce_col)).nth(1)).to_have_text(
+ "IOC"
+ )
+ expect(
+ (page.get_by_role(row_table).locator(updatedAt_col)).nth(1)
+ ).not_to_be_empty()
+
+@pytest.mark.skip("core issue")
+@pytest.mark.usefixtures("continuous_market", "auth", "risk_accepted")
+def test_submit_stop_limit_order_cancel(
+ continuous_market, vega: VegaService, page: Page
+):
+ page.goto(f"/#/markets/{continuous_market}")
+ page.get_by_test_id(stop_orders_tab).click()
+ # create a position because stop order is reduce only type
+ create_position(vega, continuous_market)
+
+ page.get_by_test_id(stop_order_btn).click()
+ page.get_by_test_id(stop_limit_order_btn).is_visible()
+ page.get_by_test_id(stop_limit_order_btn).click()
+ page.get_by_test_id(order_side_sell).click()
+ page.get_by_test_id(trigger_below).click()
+ page.get_by_test_id(trigger_price).fill("102")
+ page.get_by_test_id(order_price).fill("99")
+ page.get_by_test_id(order_size).fill("1")
+ page.get_by_test_id(submit_stop_order).click()
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ page.get_by_test_id(close_toast).first.click()
+ page.get_by_test_id(cancel).click()
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ page.get_by_test_id(close_toast).first.click()
+
+ expect(
+ (page.get_by_role(row_table).locator('[col-id="status"]')).nth(1)
+ ).to_have_text("Cancelled")
+
+
+class TestStopOcoValidation:
+ @pytest.fixture(scope="class")
+ def vega(self, request):
+ with init_vega(request) as vega:
+ yield vega
+
+ @pytest.fixture(scope="class")
+ def continuous_market(self, vega):
+ return setup_continuous_market(vega)
+
+ @pytest.mark.usefixtures("page", "auth", "risk_accepted")
+ def test_stop_market_order_form_validation(self, continuous_market, page: Page):
+ # 7002-SORD-052
+ # 7002-SORD-055
+ # 7002-SORD-056
+ # 7002-SORD-057
+ # 7002-SORD-058
+ # 7002-SORD-064
+ # 7002-SORD-065
+ page.goto(f"/#/markets/{continuous_market}")
+ page.get_by_test_id(stop_order_btn).click()
+ page.get_by_test_id(stop_market_order_btn).is_visible()
+ page.get_by_test_id(stop_market_order_btn).click()
+ expect(
+ page.get_by_test_id("sidebar-content").get_by_text("Trigger").first
+ ).to_be_visible()
+ expect(page.locator('[for="triggerDirection-risesAbove"]')).to_have_text(
+ "Rises above"
+ )
+ expect(page.locator('[for="triggerDirection-fallsBelow"]')).to_have_text(
+ "Falls below"
+ )
+ page.get_by_test_id(trigger_price).click()
+ expect(page.get_by_test_id(trigger_price)).to_be_empty
+ expect(page.locator('[for="triggerType-price"]')).to_have_text("Price")
+ expect(page.locator('[for="triggerType-trailingPercentOffset"]')).to_have_text(
+ "Trailing Percent Offset"
+ )
+ expect(page.locator('[for="order-size"]')).to_have_text("Size")
+ page.get_by_test_id(order_size).click()
+ 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")
+ def test_stop_limit_order_form_validation(self, continuous_market, page: Page):
+ # 7002-SORD-020
+ # 7002-SORD-021
+ # 7002-SORD-022
+ # 7002-SORD-033
+ # 7002-SORD-034
+ # 7002-SORD-035
+ # 7002-SORD-036
+ # 7002-SORD-037
+ # 7002-SORD-038
+ # 7002-SORD-049
+ # 7002-SORD-050
+ # 7002-SORD-051
+
+ page.goto(f"/#/markets/{continuous_market}")
+ page.get_by_test_id(stop_order_btn).click()
+ page.get_by_test_id(stop_limit_order_btn).is_visible()
+ page.get_by_test_id(stop_limit_order_btn).click()
+ expect(
+ page.get_by_test_id("sidebar-content").get_by_text("Trigger").first
+ ).to_be_visible()
+ expect(page.locator('[for="triggerDirection-risesAbove"]')).to_have_text(
+ "Rises above"
+ )
+ expect(page.locator('[for="triggerDirection-risesAbove"]')).to_be_checked
+ expect(page.locator('[for="triggerDirection-fallsBelow"]')).to_have_text(
+ "Falls below"
+ )
+ page.get_by_test_id(trigger_price).click()
+ expect(page.get_by_test_id(trigger_price)).to_be_empty
+ expect(page.locator('[for="triggerType-price"]')).to_have_text("Price")
+ expect(page.locator('[for="triggerType-price"]')).to_be_checked
+ expect(page.locator('[for="triggerType-trailingPercentOffset"]')).to_have_text(
+ "Trailing Percent Offset"
+ )
+ expect(page.locator('[for="order-size"]').first).to_have_text("Size")
+ expect(page.locator('[for="order-price"]').last).to_have_text("Price")
+ page.get_by_test_id(order_size).click()
+ expect(page.get_by_test_id(order_size)).to_be_empty
+ page.get_by_test_id(order_price).click()
+ expect(page.get_by_test_id(order_price)).to_be_empty()
+
+ @pytest.mark.skip("core issue")
+ @pytest.mark.usefixtures("page", "auth", "risk_accepted")
+ def test_maximum_number_of_active_stop_orders(
+ self, continuous_market, vega: VegaService, page: Page
+ ):
+ page.goto(f"/#/markets/{continuous_market}")
+ page.get_by_test_id(stop_orders_tab).click()
+ # create a position because stop order is reduce only type
+ create_position(vega, continuous_market)
+ for i in range(4):
+ page.get_by_test_id(stop_order_btn).click()
+ page.get_by_test_id(stop_limit_order_btn).is_visible()
+ page.get_by_test_id(stop_limit_order_btn).click()
+ page.get_by_test_id(order_side_sell).click()
+ page.get_by_test_id(trigger_below).click()
+ page.get_by_test_id(trigger_price).fill("102")
+ page.get_by_test_id(order_price).fill("99")
+ page.get_by_test_id(order_size).fill("1")
+ page.get_by_test_id(submit_stop_order).click()
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ if page.get_by_test_id(close_toast).is_visible():
+ page.get_by_test_id(close_toast).click()
+ # 7002-SORD-011
+ expect(page.get_by_test_id("stop-order-warning-limit")).to_have_text(
+ "There is a limit of 4 active stop orders per market. Orders submitted above the limit will be immediately rejected."
+ )
diff --git a/apps/trading/e2e/tests/deal_ticket/test_stop_order_oco.py b/apps/trading/e2e/tests/deal_ticket/test_stop_order_oco.py
new file mode 100644
index 000000000..f31d4c3f2
--- /dev/null
+++ b/apps/trading/e2e/tests/deal_ticket/test_stop_order_oco.py
@@ -0,0 +1,329 @@
+import pytest
+from playwright.sync_api import Page, expect
+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"
+order_side_sell = "order-side-SIDE_SELL"
+trigger_above = "triggerDirection-risesAbove"
+trigger_below = "triggerDirection-fallsBelow"
+trigger_price = "triggerPrice"
+trigger_type_price = "triggerType-price"
+trigger_type_trailing_percent_offset = "triggerType-trailingPercentOffset"
+order_size = "order-size"
+order_price = "order-price"
+order_tif = "order-tif"
+expire = "expire"
+expiry_strategy = '[for="expiryStrategy"]'
+expiry_strategy_submit = "expiryStrategy-submit"
+expiry_strategy_cancel = "expiryStrategy-cancel"
+date_picker_field = "date-picker-field"
+submit_stop_order = "place-order"
+stop_orders_tab = "Stop orders"
+row_table = "row"
+cancel = "cancel"
+market_name_col = '[col-id="market.tradableInstrument.instrument.code"]'
+trigger_col = '[col-id="trigger"]'
+expiresAt_col = '[col-id="expiresAt"]'
+size_col = '[col-id="submission.size"]'
+submission_type = '[col-id="submission.type"]'
+status_col = '[col-id="status"]'
+price_col = '[col-id="submission.price"]'
+timeInForce_col = '[col-id="submission.timeInForce"]'
+updatedAt_col = '[col-id="updatedAt"]'
+close_toast = "toast-close"
+trigger_direction_fallsBelow_oco = "triggerDirection-fallsBelow-oco"
+trigger_direction_fallsAbove_oco = "triggerDirection-fallsAbove-oco"
+oco = "oco"
+trigger_price_oco = "triggerPrice-oco"
+order_size_oco = "order-size-oco"
+order_limit_price_oco = "order-price-oco"
+
+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)
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup
+
+
+@pytest.mark.usefixtures("page", "vega", "continuous_market", "auth", "risk_accepted")
+def test_submit_stop_order_market_oco_rejected(
+ continuous_market, vega: VegaService, page: Page
+):
+ page.goto(f"/#/markets/{continuous_market}")
+ page.get_by_test_id(stop_orders_tab).click()
+ page.get_by_test_id(stop_order_btn).click()
+ page.get_by_test_id(stop_market_order_btn).is_visible()
+ page.get_by_test_id(stop_market_order_btn).click()
+ page.get_by_test_id(trigger_price).fill("103")
+ page.get_by_test_id(order_size).fill("3")
+ # 7002-SORD-098
+ expect(
+ page.get_by_test_id("stop-order-warning-message-trigger-price")
+ ).to_have_text("Stop order will be triggered immediately")
+
+ # 7002-SORD-082
+ page.get_by_test_id(oco).click()
+ # 7002-SORD-085
+ expect(page.get_by_test_id(trigger_direction_fallsBelow_oco)).to_be_checked
+ # 7002-SORD-086
+ page.get_by_test_id(trigger_price_oco).fill("102")
+ page.get_by_test_id(order_size_oco).fill("3")
+ page.get_by_test_id(submit_stop_order).click()
+ wait_for_toast_confirmation(page)
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ page.get_by_role(row_table).locator(market_name_col).nth(1).is_visible()
+
+ expect((page.get_by_role(row_table).locator(market_name_col)).nth(1)).to_have_text(
+ "BTC:DAI_2023Futr"
+ )
+
+ expect((page.get_by_role(row_table).locator(expiresAt_col)).nth(1)).to_have_text("")
+ expect((page.get_by_role(row_table).locator(size_col)).nth(1)).to_have_text("+3")
+ # 7002-SORD-083
+ expect((page.get_by_role(row_table).locator(submission_type)).nth(1)).to_have_text(
+ "Market"
+ )
+ expect((page.get_by_role(row_table).locator(status_col)).nth(1)).to_have_text(
+ "RejectedOCO"
+ )
+ expect((page.get_by_role(row_table).locator(price_col)).nth(1)).to_have_text("-")
+ expect((page.get_by_role(row_table).locator(timeInForce_col)).nth(1)).to_have_text(
+ "FOK"
+ )
+ expect(
+ (page.get_by_role(row_table).locator(updatedAt_col)).nth(1)
+ ).not_to_be_empty()
+
+ expect((page.get_by_role(row_table).locator(market_name_col)).nth(2)).to_have_text(
+ "BTC:DAI_2023Futr"
+ )
+
+ expect((page.get_by_role(row_table).locator(expiresAt_col)).nth(2)).to_have_text("")
+ expect((page.get_by_role(row_table).locator(size_col)).nth(2)).to_have_text("+3")
+ expect((page.get_by_role(row_table).locator(submission_type)).nth(2)).to_have_text(
+ "Market"
+ )
+ expect((page.get_by_role(row_table).locator(status_col)).nth(2)).to_have_text(
+ "RejectedOCO"
+ )
+ expect((page.get_by_role(row_table).locator(price_col)).nth(2)).to_have_text("-")
+ expect((page.get_by_role(row_table).locator(timeInForce_col)).nth(2)).to_have_text(
+ "FOK"
+ )
+ expect(
+ (page.get_by_role(row_table).locator(updatedAt_col)).nth(2)
+ ).not_to_be_empty()
+ # 7002-SORD-084
+ trigger_price_list = (
+ page.locator(".ag-center-cols-container").locator(trigger_col).all_inner_texts()
+ )
+ trigger_value_list = ["Mark < 102.00", "Mark > 103.00"]
+ assert trigger_price_list.sort() == trigger_value_list.sort()
+
+
+@pytest.mark.usefixtures("page", "vega", "continuous_market", "auth", "risk_accepted")
+def test_submit_stop_oco_market_order_triggered(
+ continuous_market, vega: VegaService, page: Page
+):
+ create_position(vega, continuous_market)
+ page.goto(f"/#/markets/{continuous_market}")
+ page.get_by_test_id(stop_orders_tab).click()
+
+ page.get_by_test_id(stop_order_btn).click()
+ page.get_by_test_id(stop_market_order_btn).is_visible()
+ page.get_by_test_id(stop_market_order_btn).click()
+ page.get_by_test_id(trigger_price).fill("103")
+ page.get_by_test_id(order_size).fill("3")
+
+ expect(
+ page.get_by_test_id("stop-order-warning-message-trigger-price")
+ ).to_have_text("Stop order will be triggered immediately")
+
+ page.get_by_test_id(oco).click()
+ expect(page.get_by_test_id(trigger_direction_fallsBelow_oco)).to_be_checked
+
+ page.get_by_test_id(trigger_price_oco).fill("102")
+ page.get_by_test_id(order_size_oco).fill("3")
+ page.get_by_test_id(submit_stop_order).click()
+ wait_for_toast_confirmation(page)
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ page.get_by_role(row_table).locator(market_name_col).nth(1).is_visible()
+
+ expect((page.get_by_role(row_table).locator(market_name_col)).nth(1)).to_have_text(
+ "BTC:DAI_2023Futr"
+ )
+
+ expect((page.get_by_role(row_table).locator(expiresAt_col)).nth(1)).to_have_text("")
+ expect((page.get_by_role(row_table).locator(size_col)).nth(1)).to_have_text("+3")
+ expect((page.get_by_role(row_table).locator(submission_type)).nth(1)).to_have_text(
+ "Market"
+ )
+
+ expect((page.get_by_role(row_table).locator(price_col)).nth(1)).to_have_text("-")
+ expect((page.get_by_role(row_table).locator(timeInForce_col)).nth(1)).to_have_text(
+ "FOK"
+ )
+ expect(
+ (page.get_by_role(row_table).locator(updatedAt_col)).nth(1)
+ ).not_to_be_empty()
+
+ expect((page.get_by_role(row_table).locator(market_name_col)).nth(2)).to_have_text(
+ "BTC:DAI_2023Futr"
+ )
+ expect((page.get_by_role(row_table).locator(expiresAt_col)).nth(2)).to_have_text("")
+ expect((page.get_by_role(row_table).locator(size_col)).nth(2)).to_have_text("+3")
+ expect((page.get_by_role(row_table).locator(submission_type)).nth(2)).to_have_text(
+ "Market"
+ )
+
+ expect((page.get_by_role(row_table).locator(price_col)).nth(2)).to_have_text("-")
+ expect((page.get_by_role(row_table).locator(timeInForce_col)).nth(2)).to_have_text(
+ "FOK"
+ )
+ expect(
+ (page.get_by_role(row_table).locator(updatedAt_col)).nth(2)
+ ).not_to_be_empty()
+
+ status = (
+ page.locator(".ag-center-cols-container").locator(status_col).all_inner_texts()
+ )
+ value = ["StoppedOCO", "TriggeredOCO"]
+ assert status.sort() == value.sort()
+
+ trigger_price_list = (
+ page.locator(".ag-center-cols-container").locator(trigger_col).all_inner_texts()
+ )
+ trigger_value_list = ["Mark < 102.00", "Mark > 103.00"]
+ assert trigger_price_list.sort() == trigger_value_list.sort()
+
+
+@pytest.mark.usefixtures("page", "vega", "continuous_market", "auth", "risk_accepted")
+def test_submit_stop_oco_market_order_pending(
+ continuous_market, vega: VegaService, page: Page
+):
+ create_position(vega, continuous_market)
+ page.goto(f"/#/markets/{continuous_market}")
+ page.get_by_test_id(stop_orders_tab).click()
+ page.get_by_test_id(stop_order_btn).click()
+ page.get_by_test_id(stop_market_order_btn).is_visible()
+ page.get_by_test_id(stop_market_order_btn).click()
+ page.get_by_test_id(order_side_sell).click()
+ page.locator("label").filter(has_text="Falls below").click()
+ page.get_by_test_id(trigger_price).fill("99")
+ page.get_by_test_id(order_size).fill("3")
+ page.get_by_test_id(oco).click()
+ expect(page.get_by_test_id(trigger_direction_fallsAbove_oco)).to_be_checked
+ page.get_by_test_id(trigger_price_oco).fill("120")
+ page.get_by_test_id(order_size_oco).fill("2")
+ page.get_by_test_id(submit_stop_order).click()
+ wait_for_toast_confirmation(page)
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ page.get_by_test_id(close_toast).click()
+ page.get_by_role(row_table).locator(market_name_col).nth(1).is_visible()
+
+ expect((page.get_by_role(row_table).locator(status_col)).nth(1)).to_have_text(
+ "PendingOCO"
+ )
+ expect((page.get_by_role(row_table).locator(status_col)).nth(2)).to_have_text(
+ "PendingOCO"
+ )
+
+@pytest.mark.usefixtures("page", "vega", "continuous_market", "auth", "risk_accepted")
+def test_submit_stop_oco_limit_order_pending(
+ continuous_market, vega: VegaService, page: Page
+):
+ create_position(vega, continuous_market)
+ page.goto(f"/#/markets/{continuous_market}")
+ page.get_by_test_id(stop_orders_tab).click()
+ page.get_by_test_id(stop_order_btn).click()
+ page.get_by_test_id(stop_limit_order_btn).is_visible()
+ page.get_by_test_id(stop_limit_order_btn).click()
+ page.get_by_test_id(order_side_sell).click()
+ page.locator("label").filter(has_text="Falls below").click()
+ page.get_by_test_id(trigger_price).fill("102")
+ page.get_by_test_id(order_size).fill("3")
+ page.get_by_test_id(order_price).fill("103")
+ page.get_by_test_id(oco).click()
+ # 7002-SORD-090
+ expect(page.get_by_test_id(trigger_direction_fallsAbove_oco)).to_be_checked
+ page.get_by_test_id(trigger_price_oco).fill("120")
+ page.get_by_test_id(order_size_oco).fill("2")
+ # 7002-SORD-089
+ page.get_by_test_id(order_limit_price_oco).fill("99")
+ page.get_by_test_id(submit_stop_order).click()
+ wait_for_toast_confirmation(page)
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ page.get_by_test_id(close_toast).click()
+ page.get_by_role(row_table).locator(market_name_col).nth(1).is_visible()
+
+ expect((page.get_by_role(row_table).locator(submission_type)).nth(1)).to_have_text(
+ "Limit"
+ )
+ expect((page.get_by_role(row_table).locator(submission_type)).nth(2)).to_have_text(
+ "Limit"
+ )
+
+ price = (
+ page.locator(".ag-center-cols-container").locator(price_col).all_inner_texts()
+ )
+ prices = ["103.00", "99.00"]
+ assert price.sort() == prices.sort()
+
+ # 7002-SORD-091
+ trigger_price_list = (
+ page.locator(".ag-center-cols-container").locator(trigger_col).all_inner_texts()
+ )
+ trigger_value_list = ["Limit < 102.00", "Limit > 103.00"]
+ assert trigger_price_list.sort() == trigger_value_list.sort()
+
+
+@pytest.mark.usefixtures("page", "vega", "continuous_market", "auth", "risk_accepted")
+def test_submit_stop_oco_limit_order_cancel(
+ continuous_market, vega: VegaService, page: Page
+):
+ create_position(vega, continuous_market)
+ page.goto(f"/#/markets/{continuous_market}")
+ page.get_by_test_id(stop_order_btn).click()
+ page.get_by_test_id(stop_limit_order_btn).is_visible()
+ page.get_by_test_id(stop_limit_order_btn).click()
+ page.get_by_test_id(order_side_sell).click()
+ page.locator("label").filter(has_text="Falls below").click()
+ page.get_by_test_id(trigger_price).fill("102")
+ page.get_by_test_id(order_size).fill("3")
+ page.get_by_test_id(order_price).fill("103")
+ page.get_by_test_id(oco).click()
+ # 7002-SORD-092
+ expect(page.get_by_test_id(trigger_direction_fallsAbove_oco)).to_be_checked
+ # 7002-SORD-094
+ page.get_by_test_id(trigger_price_oco).fill("120")
+ page.get_by_test_id(order_size_oco).fill("2")
+ # 7002-SORD-093
+ page.get_by_test_id(order_limit_price_oco).fill("99")
+ page.get_by_test_id(submit_stop_order).click()
+ wait_for_toast_confirmation(page)
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ page.get_by_test_id(close_toast).click()
+ page.get_by_test_id(stop_orders_tab).click()
+ page.get_by_test_id(cancel).first.click()
+ wait_for_toast_confirmation(page)
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ expect(
+ page.locator(".ag-center-cols-container").locator('[col-id="status"]').first
+ ).to_have_text("CancelledOCO")
+ expect(
+ page.locator(".ag-center-cols-container").locator('[col-id="status"]').last
+ ).to_have_text("CancelledOCO")
+
+
diff --git a/apps/trading/e2e/tests/deal_ticket/test_trading_deal_ticket_submit_account.py b/apps/trading/e2e/tests/deal_ticket/test_trading_deal_ticket_submit_account.py
new file mode 100644
index 000000000..7d99a6631
--- /dev/null
+++ b/apps/trading/e2e/tests/deal_ticket/test_trading_deal_ticket_submit_account.py
@@ -0,0 +1,49 @@
+import pytest
+from playwright.sync_api import Page, expect
+from vega_sim.service import VegaService
+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"
+deal_ticket_warning_margin = "deal-ticket-warning-margin"
+deal_ticket_deposit_dialog_button = "deal-ticket-deposit-dialog-button"
+
+@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")
+def test_should_display_info_and_button_for_deposit(continuous_market, vega: VegaService, 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")
+ # 7002-SORD-060
+ expect(page.get_by_test_id(deal_ticket_warning_margin)).to_have_text("You may not have enough margin available to open this position.")
+ page.get_by_test_id(deal_ticket_warning_margin).hover()
+ expect(page.get_by_test_id("tooltip-content").nth(0)).to_have_text("1,661,896.6317 tDAI is currently required.You have only 1,000,000.00.Deposit tDAI")
+ 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")
+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")
+ change_keys(page, vega, "key_empty")
+ page.get_by_test_id(order_size).fill("200")
+ page.get_by_test_id(order_price).fill("20")
+ # 7002-SORD-060
+ expect(page.get_by_test_id(place_order)).to_be_enabled()
+ # 7002-SORD-003
+ expect(page.get_by_test_id("deal-ticket-error-message-zero-balance")).to_have_text("You need tDAI in your wallet to trade in this market.Make a deposit")
+ expect(page.get_by_test_id(deal_ticket_deposit_dialog_button)).to_be_visible()
diff --git a/apps/trading/e2e/tests/get_started/test_get_started.py b/apps/trading/e2e/tests/get_started/test_get_started.py
new file mode 100644
index 000000000..4b675f344
--- /dev/null
+++ b/apps/trading/e2e/tests/get_started/test_get_started.py
@@ -0,0 +1,195 @@
+import pytest
+from playwright.sync_api import expect, Page
+import json
+from vega_sim.service import VegaService
+from fixtures.market import setup_simple_market
+from conftest import init_vega
+from actions.vega import submit_order
+from wallet_config import MM_WALLET, TERMINATE_WALLET, wallets
+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
+ expect(page.get_by_test_id("order-connect-wallet")).to_be_visible
+ expect(page.get_by_test_id("order-connect-wallet")).to_be_enabled
+ # 0007-FUGS-006
+ # 0007-FUGS-002
+ expect(page.locator(".list-none")).to_contain_text(
+ "1.Connect2.Deposit funds3.Open a position"
+ )
+ DEFAULT_WALLET_NAME = "MarketSim" # This is the default wallet name within VegaServiceNull and CANNOT be changed
+
+ # Calling get_keypairs will internally call _load_tokens for the given wallet
+ keypairs = vega.wallet.get_keypairs(DEFAULT_WALLET_NAME)
+ wallet_api_token = vega.wallet.login_tokens[DEFAULT_WALLET_NAME]
+
+ # Set token to localStorage so eager connect hook picks it up and immediately connects
+ wallet_config = json.dumps(
+ {
+ "token": f"VWT {wallet_api_token}",
+ "connector": "jsonRpc",
+ "url": f"http://localhost:{vega.wallet_port}",
+ }
+ )
+
+ storage_javascript = [
+ # Store wallet config so eager connection is initiated
+ f"localStorage.setItem('vega_wallet_config', '{wallet_config}');",
+ # Ensure wallet ris dialog doesnt show, otherwise eager connect wont work
+ "localStorage.setItem('vega_wallet_risk_accepted', 'true');",
+ # Ensure initial risk dialog doesnt show
+ "localStorage.setItem('vega_risk_accepted', 'true');",
+ ]
+ script = "".join(storage_javascript)
+ page.add_init_script(script)
+ page.reload()
+
+ # Assert step 1 complete
+ expect(page.get_by_test_id("icon-tick")).to_have_count(1)
+ env = json.dumps(
+ {
+ "VEGA_URL": f"http://localhost:{vega.data_node_rest_port}/graphql",
+ "VEGA_WALLET_URL": f"http://localhost:{vega.wallet_port}",
+ }
+ )
+ window_env = f"window._env_ = Object.assign({{}}, window._env_, {env})"
+ page.add_init_script(script=window_env)
+
+ page.reload()
+
+ mint_amount: float = 10e5
+
+ for wallet in wallets:
+ vega.create_key(wallet.name)
+
+ vega.mint(
+ MM_WALLET.name,
+ asset="VOTE",
+ amount=mint_amount,
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ vega.create_asset(
+ MM_WALLET.name,
+ name="tDAI",
+ symbol="tDAI",
+ decimals=5,
+ max_faucet_amount=1e10,
+ )
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ tdai_id = vega.find_asset_id(symbol="tDAI")
+ logger.info(f"tDAI: {tdai_id}")
+
+ vega.mint(
+ "Key 1",
+ asset=tdai_id,
+ amount=10,
+ )
+
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ # Assert step 2 complete
+ expect(page.get_by_test_id("icon-tick")).to_have_count(2)
+
+ market_id = vega.create_simple_market(
+ "tDAI",
+ proposal_key=MM_WALLET.name,
+ settlement_asset_id=tdai_id,
+ termination_key=TERMINATE_WALLET.name,
+ market_decimals=5,
+ approve_proposal=True,
+ forward_time_to_enactment=True,
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ submit_order(vega, "Key 1", market_id, "SIDE_BUY", 1, 110)
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ page.get_by_test_id("get-started-button").click()
+ # Assert dialog isn't visible
+ expect(page.get_by_test_id("welcome-dialog")).not_to_be_visible()
+
+
+ @pytest.mark.usefixtures("page", "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")
+ page.wait_for_selector('[data-testid="connect-vega-wallet"]', state="attached")
+ expect(get_started_locator).to_be_enabled
+ expect(get_started_locator).to_be_visible
+ # 0007-FUGS-015
+ expect(get_started_locator).to_have_text("Get started")
+ get_started_locator.click()
+ # 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}")
+ locator = page.get_by_test_id("connect-vega-wallet")
+ page.wait_for_selector('[data-testid="connect-vega-wallet"]', state="attached")
+ expect(locator).to_be_enabled
+ expect(locator).to_be_visible
+ expect(locator).to_have_text("Connect")
+
+
+ @pytest.mark.usefixtures("page", "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")
+ def test_browser_wallet_installed_deal_ticket(simple_market, page: Page):
+ page.add_init_script("window.vega = {}")
+ page.goto(f"/#/markets/{simple_market}")
+ # 0007-FUGS-013
+ 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
+ expect(page).to_have_url(
+ f"http://localhost:{vega.console_port}/#/markets/{continuous_market}"
+ )
+ page.get_by_test_id("icon-cross").click()
+ # 0007-FUGS-018
+ 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)
+ page.get_by_test_id("browse-markets-button").click()
+ # 0007-FUGS-005
+ expect(page).to_have_url(f"http://localhost:{vega.console_port}/#/markets/{simple_market}")
\ No newline at end of file
diff --git a/apps/trading/e2e/tests/iceberg_orders/test_iceberg_orders.py b/apps/trading/e2e/tests/iceberg_orders/test_iceberg_orders.py
new file mode 100644
index 000000000..0c518a58c
--- /dev/null
+++ b/apps/trading/e2e/tests/iceberg_orders/test_iceberg_orders.py
@@ -0,0 +1,128 @@
+import pytest
+from playwright.sync_api import expect, Page
+from vega_sim.service import VegaService
+from actions.vega import submit_order
+from conftest import init_vega
+from fixtures.market import setup_continuous_market
+from wallet_config import MM_WALLET2
+
+def hover_and_assert_tooltip(page: Page, element_text):
+ element = page.get_by_text(element_text)
+ element.hover()
+ expect(page.get_by_role("tooltip")).to_be_visible()
+
+
+class TestIcebergOrdersValidations:
+ @pytest.fixture(scope="class")
+ def vega(self, request):
+ with init_vega(request) as vega:
+ yield vega
+
+ @pytest.fixture(scope="class")
+ def continuous_market(self, vega):
+ return setup_continuous_market(vega)
+
+ @pytest.mark.usefixtures("page", "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()
+ page.get_by_test_id("order-peak-size").type("2")
+ page.get_by_test_id("order-minimum-size").type("1")
+ page.get_by_test_id("order-size").type("3")
+ page.get_by_test_id("order-price").type("107")
+ page.get_by_test_id("place-order").click()
+
+ expect(page.get_by_test_id("toast-content")).to_have_text(
+ "Awaiting confirmationPlease wait for your transaction to be confirmedView in block explorer"
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ expect(page.get_by_test_id("toast-content")).to_have_text(
+ "Order filledYour transaction has been confirmedView in block explorerSubmit order - filledBTC:DAI_2023+3 @ 107.00 tDAI"
+ )
+ page.get_by_test_id("All").click()
+ expect(
+ (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")
+def test_iceberg_open_order(continuous_market, vega: VegaService, page: Page):
+ page.goto(f"/#/markets/{continuous_market}")
+
+ submit_order(vega, "Key 1", continuous_market, "SIDE_SELL", 102, 101, 2, 1)
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ page.wait_for_selector(".ag-center-cols-container .ag-row")
+ expect(
+ page.locator(
+ ".ag-center-cols-container .ag-row [col-id='openVolume'] [data-testid='stack-cell-primary']"
+ )
+ ).to_have_text("-98")
+ page.get_by_test_id("Open").click()
+ page.wait_for_selector(".ag-center-cols-container .ag-row")
+
+ expect(
+ page.locator(".ag-center-cols-container .ag-row [col-id='remaining']")
+ ).to_have_text("99")
+ expect(
+ page.locator(".ag-center-cols-container .ag-row [col-id='size']")
+ ).to_have_text("-102")
+ expect(
+ page.locator(".ag-center-cols-container .ag-row [col-id='type'] ")
+ ).to_have_text("Limit (Iceberg)")
+ expect(
+ page.locator(".ag-center-cols-container .ag-row [col-id='status']")
+ ).to_have_text("Active")
+ expect(page.get_by_test_id("price-10100000")).to_be_visible
+ expect(page.get_by_test_id("ask-vol-10100000")).to_have_text("3")
+ page.get_by_test_id("Trades").click()
+ expect(page.locator('[id^="cell-price-"]').first).to_have_text("101.50")
+ expect(page.locator('[id^="cell-size-"]').first).to_have_text("99")
+
+ submit_order(vega, MM_WALLET2.name, continuous_market, "SIDE_BUY", 103, 101)
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ expect(
+ page.locator(
+ '[data-testid="tab-open-orders"] .ag-center-cols-container .ag-row'
+ )
+ ).not_to_be_visible
+ page.get_by_test_id("Closed").click()
+ expect(
+ page.locator(".ag-center-cols-container .ag-row [col-id='remaining']").first
+ ).to_have_text("102")
+ expect(
+ page.locator(
+ "[data-testid=\"tab-closed-orders\"] .ag-center-cols-container .ag-row [col-id='size']"
+ ).first
+ ).to_have_text("-102")
+ expect(
+ page.locator(
+ "[data-testid=\"tab-closed-orders\"] .ag-center-cols-container .ag-row [col-id='type']"
+ ).first
+ ).to_have_text("Limit (Iceberg)")
+ expect(
+ page.locator(
+ "[data-testid=\"tab-closed-orders\"] .ag-center-cols-container .ag-row [col-id='status']"
+ ).first
+ ).to_have_text("Filled")
+ expect(page.locator('[id^="cell-price-"]').nth(2)).to_have_text("101.00")
+ expect(page.locator('[id^="cell-size-"]').nth(2)).to_have_text("3")
+
+
+def verify_order_label(page: Page, test_id: str, expected_text: str):
+ element = page.get_by_test_id(test_id)
+ expect(element).to_be_visible()
+ expect(element).to_have_text(expected_text)
+
+
+def verify_order_value(page: Page, test_id: str, expected_text: str):
+ element = page.get_by_test_id(test_id)
+ expect(element).to_be_visible()
+ expect(element).to_have_text(expected_text)
diff --git a/apps/trading/e2e/tests/liquidity_provision/test_liquidity_provision.py b/apps/trading/e2e/tests/liquidity_provision/test_liquidity_provision.py
new file mode 100644
index 000000000..64d47d9f7
--- /dev/null
+++ b/apps/trading/e2e/tests/liquidity_provision/test_liquidity_provision.py
@@ -0,0 +1,85 @@
+import pytest
+from playwright.sync_api import Page, expect
+from vega_sim.service import VegaService
+from conftest import init_vega
+from fixtures.market import setup_continuous_market
+from actions.utils import next_epoch, truncate_middle, change_keys
+
+
+@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")
+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
+ expect(row).to_contain_text(
+ "Active"
+ )
+ # 5002-LIQP-006
+ 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")
+ # 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("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")
+ # 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")
+
+ vega.submit_simple_liquidity(
+ key_name="market_maker",
+ market_id=continuous_market,
+ commitment_amount=1,
+ fee=0.001,
+ is_amendment=True,
+ )
+
+ 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
+ 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(row).to_contain_text(
+ "Active"
+ )
+
+@pytest.mark.skip("Waiting for the ability to cancel LP")
+@pytest.mark.usefixtures("page", "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
+ expect(row).to_contain_text(
+ "Active"
+ )
+ vega.submit_simple_liquidity(
+ key_name="market_maker",
+ market_id=continuous_market,
+ commitment_amount=0,
+ fee=0,
+ is_amendment=False,
+ )
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
\ No newline at end of file
diff --git a/apps/trading/e2e/tests/market/test_closed_markets.py b/apps/trading/e2e/tests/market/test_closed_markets.py
new file mode 100644
index 000000000..b4c3c8aa2
--- /dev/null
+++ b/apps/trading/e2e/tests/market/test_closed_markets.py
@@ -0,0 +1,135 @@
+import pytest
+import re
+import vega_sim.api.governance as governance
+from vega_sim.service import VegaService
+from playwright.sync_api import Page, expect
+from fixtures.market import setup_continuous_market
+from conftest import init_vega
+
+
+@pytest.fixture(scope="class")
+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)
+ vega.submit_termination_and_settlement_data(
+ settlement_key="FJMKnwfZdd48C8NqvYrG",
+ settlement_price=110,
+ market_id=market_id,
+ )
+ vega.forward("10s")
+ vega.wait_fn(10)
+ vega.wait_for_total_catchup()
+
+
+class TestSettledMarket:
+ @pytest.mark.usefixtures("risk_accepted", "auth")
+ def test_settled_header(self, page: Page, create_settled_market):
+ page.goto(f"/#/markets/all")
+ page.get_by_test_id("Closed markets").click()
+ headers = [
+ "Market",
+ "Status",
+ "Settlement date",
+ "Best bid",
+ "Best offer",
+ "Mark price",
+ "Settlement price",
+ "Settlement asset",
+ "",
+ ]
+
+ page.wait_for_selector('[data-testid="tab-closed-markets"]', state="visible")
+ page_headers = (
+ page.get_by_test_id("tab-closed-markets")
+ .locator(".ag-header-cell-text")
+ .all()
+ )
+ for i, header in enumerate(headers):
+ expect(page_headers[i]).to_have_text(header)
+
+ @pytest.mark.usefixtures(
+ "risk_accepted",
+ "auth",
+ )
+ def test_settled_rows(self, page: Page, create_settled_market):
+ page.goto(f"/#/markets/all")
+ page.get_by_test_id("Closed markets").click()
+
+ row_selector = page.locator(
+ '[data-testid="tab-closed-markets"] .ag-center-cols-container .ag-row'
+ ).first
+
+ # 6001-MARK-001
+ expect(row_selector.locator('[col-id="code"]')).to_have_text("BTC:DAI_2023Futr")
+ # 6001-MARK-003
+ expect(row_selector.locator('[col-id="state"]')).to_have_text("Settled")
+ # 6001-MARK-004
+ # 6001-MARK-005
+ # 6001-MARK-009
+ # 6001-MARK-008
+ # 6001-MARK-010
+ pattern = r"(\d+)\s+months\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}"
+
+
+ expected_pattern = re.compile(r"https://.*?/oracles/[a-f0-9]{64}")
+ actual_href = row_selector.locator(
+ '[col-id="settlementDate"] [data-testid="link"]'
+ ).get_attribute("href")
+ assert expected_pattern.match(
+ actual_href
+ ), f"Expected href to match {expected_pattern.pattern}, but got {actual_href}"
+ # 6001-MARK-011
+ 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
+ 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
+ # expect(row_selector.locator('[col-id="settlementDataOracleId"]')).to_have_text(
+ # "110.00"
+ # )
+ expected_pattern = re.compile(r"https://.*?/oracles/[a-f0-9]{64}")
+ actual_href = row_selector.locator(
+ '[col-id="settlementDataOracleId"] [data-testid="link"]'
+ ).get_attribute("href")
+ assert expected_pattern.match(
+ actual_href
+ ), f"Expected href to match {expected_pattern.pattern}, but got {actual_href}"
+
+ # 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}"
+
+
+@pytest.mark.usefixtures("risk_accepted", "auth")
+def test_terminated_market_no_settlement_date(page: Page, vega: VegaService):
+ setup_continuous_market(vega)
+ print("I have started test_terminated_market_no_settlement_date")
+ governance.submit_oracle_data(
+ wallet=vega.wallet,
+ payload={"trading.terminated": "true"},
+ key_name="FJMKnwfZdd48C8NqvYrG",
+ )
+ vega.forward("60s")
+ vega.wait_fn(10)
+ vega.wait_for_total_catchup()
+ page.goto(f"/#/markets/all")
+ page.get_by_test_id("Closed markets").click()
+ row_selector = page.locator(
+ '[data-testid="tab-closed-markets"] .ag-center-cols-container .ag-row'
+ ).first
+ expect(row_selector.locator('[col-id="state"]')).to_have_text("Trading Terminated")
+ expect(row_selector.locator('[col-id="settlementDate"]')).to_have_text("Unknown")
+
+ # TODO Create test for terminated market with settlement date in future
+ # TODO Create test for terminated market with settlement date in past
diff --git a/apps/trading/e2e/tests/market/test_market.py b/apps/trading/e2e/tests/market/test_market.py
new file mode 100644
index 000000000..e072cbc4f
--- /dev/null
+++ b/apps/trading/e2e/tests/market/test_market.py
@@ -0,0 +1,198 @@
+import pytest
+from playwright.sync_api import Page, expect
+from vega_sim.service import VegaService
+from actions.vega import submit_order
+from wallet_config import MM_WALLET, MM_WALLET2
+import logging
+
+logger = logging.getLogger()
+
+table_row_selector = (
+ '[data-testid="tab-open-markets"] .ag-center-cols-container .ag-row'
+)
+trading_mode_col = '[col-id="tradingMode"]'
+state_col = '[col-id="state"]'
+item_value = "item-value"
+price_monitoring_bounds_row = "key-value-table-row"
+market_trading_mode = "market-trading-mode"
+market_state = "market-state"
+liquidity_supplied = "liquidity-supplied"
+item_value = "item-value"
+price_monitoring_bounds_row = "key-value-table-row"
+market_trading_mode = "market-trading-mode"
+market_state = "market-state"
+liquidity_supplied = "liquidity-supplied"
+
+initial_commitment: float = 100
+initial_price: float = 1
+initial_volume: float = 1
+initial_spread: float = 0.1
+market_name = "BTC:DAI_2023"
+
+
+@pytest.mark.usefixtures("vega", "page", "simple_market", "risk_accepted")
+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(
+ "Opening auction"
+ )
+ expect(page.locator(table_row_selector).locator('[col-id="state"]')).to_have_text(
+ "Pending"
+ )
+ result = page.get_by_text(market_name)
+ result.first.click()
+ page.get_by_test_id(market_trading_mode).get_by_text("Opening auction").hover()
+ expect(page.get_by_test_id("opening-auction-sub-status").first).to_have_text(
+ "Opening auction: Not enough liquidity to open"
+ )
+ logger.info(page.get_by_test_id("opening-auction-sub-status").inner_text)
+ vega.submit_liquidity(
+ key_name=MM_WALLET.name,
+ market_id=simple_market,
+ commitment_amount=initial_commitment,
+ fee=0.002,
+ is_amendment=False,
+ )
+
+ vega.submit_order(
+ market_id=simple_market,
+ trading_key=MM_WALLET.name,
+ side="SIDE_BUY",
+ order_type="TYPE_LIMIT",
+ price=initial_price - 0.0005,
+ wait=False,
+ time_in_force="TIME_IN_FORCE_GTC",
+ volume=99,
+ )
+ vega.submit_order(
+ market_id=simple_market,
+ trading_key=MM_WALLET.name,
+ side="SIDE_SELL",
+ order_type="TYPE_LIMIT",
+ price=initial_price + 0.0005,
+ wait=False,
+ time_in_force="TIME_IN_FORCE_GTC",
+ volume=99,
+ )
+ #6002-MDET-009
+ expect(
+ page.get_by_test_id(liquidity_supplied).get_by_test_id(item_value)
+ ).to_have_text("0.00 (0.00%)")
+
+ # add orders to provide liquidity
+ submit_order(
+ vega, MM_WALLET.name, simple_market, "SIDE_BUY", initial_volume, initial_price
+ )
+ submit_order(
+ vega, MM_WALLET.name, simple_market, "SIDE_SELL", initial_volume, initial_price
+ )
+ submit_order(
+ vega,
+ MM_WALLET.name,
+ simple_market,
+ "SIDE_BUY",
+ initial_volume,
+ initial_price + initial_spread / 2,
+ )
+ submit_order(
+ vega,
+ MM_WALLET.name,
+ simple_market,
+ "SIDE_SELL",
+ initial_volume,
+ initial_price + initial_spread / 2,
+ )
+ submit_order(
+ vega, MM_WALLET2.name, simple_market, "SIDE_SELL", initial_volume, initial_price
+ )
+ expect(
+ page.get_by_test_id(liquidity_supplied).get_by_test_id(item_value)
+ ).to_have_text("100.00 (>100%)")
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ expect(
+ page.get_by_test_id(liquidity_supplied).get_by_test_id(item_value)
+ ).to_have_text("50.00 (>100%)")
+
+ page.goto(f"/#/markets/all")
+ # temporary skip
+ # expect(page.locator(table_row_selector).locator(trading_mode_col)).to_have_text(
+ # "Continuous"
+ # )
+
+ # commented out because we have an issue #4233
+ # expect(page.locator(row_selector).locator(state_col)
+ # ).to_have_text("Pending")
+
+ page.goto(f"/#/markets/all")
+ result = page.get_by_text(market_name)
+ result.first.click()
+
+ page.get_by_test_id("Info").click()
+ page.get_by_test_id("accordion-title").get_by_text(
+ "Price monitoring bounds 1"
+ ).click()
+ expect(
+ page.get_by_test_id(price_monitoring_bounds_row).first.get_by_text(
+ "1.32217 BTC"
+ )
+ ).to_be_visible()
+ expect(
+ page.get_by_test_id(price_monitoring_bounds_row).last.get_by_text("0.79245 BTC")
+ ).to_be_visible()
+
+ # add orders that change the price so that it goes beyond the limits of price monitoring
+ submit_order(vega, MM_WALLET.name, simple_market, "SIDE_SELL", 100, 110)
+ submit_order(vega, MM_WALLET2.name, simple_market, "SIDE_BUY", 100, 90)
+ submit_order(vega, MM_WALLET.name, simple_market, "SIDE_SELL", 100, 105)
+ submit_order(vega, MM_WALLET2.name, simple_market, "SIDE_BUY", 100, 95)
+
+ # add order at the current price so that it is possible to change the status to price monitoring
+ to_cancel = submit_order(vega, MM_WALLET2.name, simple_market, "SIDE_BUY", 1, 105)
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ expect(
+ page.get_by_test_id(price_monitoring_bounds_row).first.get_by_text(
+ "135.44204 BTC"
+ )
+ ).to_be_visible()
+ expect(
+ page.get_by_test_id(price_monitoring_bounds_row).last.get_by_text(
+ "81.17758 BTC"
+ )
+ ).to_be_visible()
+ expect(
+ page.get_by_test_id(market_trading_mode).get_by_test_id(item_value)
+ ).to_have_text("Monitoring auction - price")
+ expect(page.get_by_test_id(market_state).get_by_test_id(item_value)).to_have_text(
+ "Suspended"
+ )
+ expect(
+ page.get_by_test_id(liquidity_supplied).get_by_test_id(item_value)
+ ).to_have_text("50.00 (8.78%)")
+
+ # cancel order to increase liquidity
+ vega.cancel_order(MM_WALLET2.name, simple_market, to_cancel)
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ expect(page.get_by_text(market_name).first).to_be_attached()
+ expect(
+ page.get_by_test_id(market_trading_mode).get_by_test_id(item_value)
+ ).to_have_text("Continuous")
+ expect(page.get_by_test_id(market_state).get_by_test_id(item_value)).to_have_text(
+ "Active"
+ )
+ # commented out because we have an issue #4233
+ # expect(page.get_by_text("Opening auction")).to_be_hidden()
+
+ #6002-MDET-009
+ expect(
+ page.get_by_test_id(liquidity_supplied).get_by_test_id(item_value)
+ ).to_have_text("50.00 (>100%)")
diff --git a/apps/trading/e2e/tests/market/test_market_info.py b/apps/trading/e2e/tests/market/test_market_info.py
new file mode 100644
index 000000000..2dbe9c5e4
--- /dev/null
+++ b/apps/trading/e2e/tests/market/test_market_info.py
@@ -0,0 +1,294 @@
+import re
+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"
+
+
+@pytest.fixture(scope="module")
+def vega():
+ with init_vega() as 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:
+ setup_continuous_market(vega)
+ risk_accepted_setup(page)
+ page.goto("/")
+ page.get_by_test_id("Info").click()
+ yield page
+
+
+@pytest.fixture(autouse=True)
+def after_each(page: Page):
+ yield
+ opened_element = page.locator('h3[data-state="open"]')
+ if opened_element.all() and opened_element.get_by_role("button").is_visible():
+ opened_element.get_by_role("button").click()
+
+
+def validate_info_section(page: Page, fields: [[str, str]]):
+ for rowNumber, field in enumerate(fields):
+ name, value = field
+ expect(
+ page.get_by_test_id("key-value-table-row").nth(rowNumber).locator("dt")
+ ).to_contain_text(name)
+ expect(
+ page.get_by_test_id("key-value-table-row").nth(rowNumber).locator("dd")
+ ).to_contain_text(value)
+
+
+def test_market_info_current_fees(page: Page):
+ # 6002-MDET-101
+ page.get_by_test_id(market_title_test_id).get_by_text("Current fees").click()
+ fields = [
+ ["Maker Fee", "10%"],
+ ["Infrastructure Fee", "0.05%"],
+ ["Liquidity Fee", "0%"],
+ ["Total Fees", "10.05%"],
+ ]
+ validate_info_section(page, fields)
+
+
+def test_market_info_market_price(page: Page):
+ # 6002-MDET-102
+ page.get_by_test_id(market_title_test_id).get_by_text("Market price").click()
+ fields = [
+ ["Mark Price", "107.50"],
+ ["Best Bid Price", "101.50"],
+ ["Best Offer Price", "103.50"],
+ ["Quote Unit", "BTC"],
+ ]
+ validate_info_section(page, fields)
+
+
+def test_market_info_market_volume(page: Page):
+ # 6002-MDET-103
+ page.get_by_test_id(market_title_test_id).get_by_text("Market volume").click()
+ fields = [
+ ["24 Hour Volume", "-"],
+ ["Open Interest", "1"],
+ ["Best Bid Volume", "99"],
+ ["Best Offer Volume", "99"],
+ ["Best Static Bid Volume", "1"],
+ ["Best Static Offer Volume", "1"],
+ ]
+ validate_info_section(page, fields)
+
+
+def test_market_info_insurance_pool(page: Page):
+ # 6002-MDET-104
+ page.get_by_test_id(market_title_test_id).get_by_text("Insurance pool").click()
+ fields = [["Balance", "0.00 tDAI"]]
+ validate_info_section(page, fields)
+
+
+def test_market_info_key_details(page: Page, vega: VegaService):
+ # 6002-MDET-201
+ page.get_by_test_id(market_title_test_id).get_by_text("Key details").click()
+ market_id = vega.find_market_id("BTC:DAI_2023")
+ short_market_id = market_id[:6] + "…" + market_id[-4:]
+ fields = [
+ ["Market ID", short_market_id],
+ ["Name", "BTC:DAI_2023"],
+ ["Parent Market ID", "-"],
+ ["Insurance Pool Fraction", "-"],
+ ["Status", "Active"],
+ ["Trading Mode", "Continuous"],
+ ["Market Decimal Places", "5"],
+ ["Position Decimal Places", "0"],
+ ["Settlement Asset Decimal Places", "5"],
+ ]
+ validate_info_section(page, fields)
+
+
+def test_market_info_instrument(page: Page):
+ # 6002-MDET-202
+ page.get_by_test_id(market_title_test_id).get_by_text("Instrument").click()
+ fields = [
+ ["Market Name", "BTC:DAI_2023"],
+ ["Code", "BTC:DAI_2023"],
+ ["Product Type", "Future"],
+ ["Quote Name", "BTC"],
+ ]
+ validate_info_section(page, fields)
+
+
+# @pytest.mark.skip("oracle test to be fixed")
+def test_market_info_oracle(page: Page, vega: VegaService):
+ # 6002-MDET-203
+ page.get_by_test_id(market_title_test_id).get_by_text("Oracle").click()
+ expect(
+ page.locator('[data-state="open"]').get_by_test_id("accordion-content")
+ ).to_contain_text("No oracle proof for settlement data")
+ expect(page.get_by_test_id("oracle-spec-links")).to_have_text(
+ "View settlement data specification"
+ )
+ # expect(page.get_by_test_id("oracle-spec-links")).to_have_attribute(
+ # "href", re.compile(rf'(\/oracles\/{vega.find_market_id("BTC:DAI_2023")})')
+ # )
+
+
+def test_market_info_settlement_asset(page: Page, vega: VegaService):
+ # 6002-MDET-206
+ page.get_by_test_id(market_title_test_id).get_by_text("Settlement asset").click()
+ tdai_id = vega.find_asset_id("tDAI")
+ tdai_id_short = tdai_id[:6] + "…" + tdai_id[-4:]
+ fields = [
+ ["ID", tdai_id_short],
+ ["Type", "Builtin asset"],
+ ["Name", "tDAI"],
+ ["Symbol", "tDAI"],
+ ["Decimals", "5"],
+ ["Quantum", "0.00001"],
+ ["Status", "Enabled"],
+ ["Max faucet amount", "10,000,000,000.00"],
+ ["Infrastructure fee account balance", "0.00"],
+ ["Global reward pool account balance", "0.00"],
+ ]
+ validate_info_section(page, fields)
+
+
+def test_market_info_metadata(page: Page):
+ # 6002-MDET-207
+ page.get_by_test_id(market_title_test_id).get_by_text("Metadata").click()
+ fields = [
+ ["Base", "BTC"],
+ ]
+ validate_info_section(page, fields)
+
+
+def test_market_info_risk_model(page: Page):
+ # 6002-MDET-208
+ page.get_by_test_id(market_title_test_id).get_by_text("Risk model").click()
+ fields = [
+ ["Tau", "0.00011407711613050422"],
+ ["Risk Aversion Parameter", "0.000001"],
+ ["Sigma", "1"],
+ ]
+ validate_info_section(page, fields)
+
+
+def test_market_info_margin_scaling_factors(page: Page):
+ # 6002-MDET-209
+ page.get_by_test_id(market_title_test_id).get_by_text(
+ "Margin scaling factors"
+ ).click()
+ fields = [
+ ["Linear Slippage Factor", "0.001"],
+ ["Quadratic Slippage Factor", "0"],
+ ["Search Level", "1.1"],
+ ["Initial Margin", "1.5"],
+ ["Collateral Release", "1.7"],
+ ]
+ validate_info_section(page, fields)
+
+
+def test_market_info_risk_factors(page: Page):
+ # 6002-MDET-210
+ page.get_by_test_id(market_title_test_id).get_by_text("Risk factors").click()
+ fields = [
+ ["Long", "0.05153"],
+ ["Short", "0.05422"],
+ ["Max Leverage Long", "19.036"],
+ ["Max Leverage Short", "18.111"],
+ ["Max Initial Leverage Long", "12.691"],
+ ["Max Initial Leverage Short", "12.074"],
+ ]
+ validate_info_section(page, fields)
+
+
+def test_market_info_price_monitoring_bounds(page: Page):
+ # 6002-MDET-211
+ page.get_by_test_id(market_title_test_id).get_by_text(
+ "Price monitoring bounds 1"
+ ).click()
+ expect(page.locator("p.col-span-1").nth(0)).to_contain_text(
+ "99.9999% probability price bounds"
+ )
+ expect(page.locator("p.col-span-1").nth(1)).to_contain_text("Within 86,400 seconds")
+ fields = [
+ ["Highest Price", "138.66685 BTC"],
+ ["Lowest Price", "83.11038 BTC"],
+ ]
+ validate_info_section(page, fields)
+
+
+def test_market_info_liquidity_monitoring_parameters(page: Page):
+ # 6002-MDET-212
+ page.get_by_test_id(market_title_test_id).get_by_text(
+ "Liquidity monitoring parameters"
+ ).click()
+ fields = [
+ ["Triggering Ratio", "0.7"],
+ ["Time Window", "3,600"],
+ ["Scaling Factor", "1"],
+ ]
+ validate_info_section(page, fields)
+
+
+# Liquidity resolves to 3 results
+def test_market_info_liquidit(page: Page):
+ # 6002-MDET-213
+ page.get_by_test_id(market_title_test_id).get_by_text(
+ "Liquidity", exact=True
+ ).click()
+ fields = [
+ ["Target Stake", "5.82757 tDAI"],
+ ["Supplied Stake", "10,000.00 tDAI"],
+ ]
+ validate_info_section(page, fields)
+
+
+def test_market_info_liquidity_price_range(page: Page):
+ # 6002-MDET-214
+ page.get_by_test_id(market_title_test_id).get_by_text(
+ "Liquidity price range"
+ ).click()
+ fields = [
+ ["Liquidity Price Range", "100% of mid price"],
+ ["Lowest Price", "0.00 BTC"],
+ ["Highest Price", "205.00 BTC"],
+ ]
+ validate_info_section(page, fields)
+
+
+def test_market_info_proposal(page: Page, vega: VegaService):
+ # 6002-MDET-301
+ page.get_by_test_id(market_title_test_id).get_by_text("Proposal").click()
+ first_link = (
+ page.get_by_test_id("accordion-content").get_by_test_id("external-link").first
+ )
+ second_link = (
+ page.get_by_test_id("accordion-content").get_by_test_id("external-link").nth(1)
+ )
+ expect(first_link).to_have_text("View governance proposal")
+ expect(first_link).to_have_attribute(
+ "href", re.compile(rf'(\/proposals\/{vega.find_market_id("BTC:DAI_2023")})')
+ )
+ expect(second_link).to_have_text("Propose a change to market")
+
+ # create regular expression that matches "/proposals/propose/update-market" string
+ expect(second_link).to_have_attribute(
+ "href", re.compile(r"(\/proposals\/propose\/update-market)")
+ )
+
+
+def test_market_info_succession_line(page: Page, vega: VegaService):
+ page.get_by_test_id(market_title_test_id).get_by_text("Succession line").click()
+ market_id = vega.find_market_id("BTC:DAI_2023")
+ succession_line = page.get_by_test_id("succession-line-item")
+ expect(succession_line.get_by_test_id("external-link")).to_have_text("BTC:DAI_2023")
+ expect(succession_line.get_by_test_id("external-link")).to_have_attribute(
+ "href", re.compile(rf"(\/proposals\/{market_id})")
+ )
+ expect(page.get_by_test_id("succession-line-item-market-id")).to_have_text(
+ market_id
+ )
diff --git a/apps/trading/e2e/tests/market/test_market_selector.py b/apps/trading/e2e/tests/market/test_market_selector.py
new file mode 100644
index 000000000..21602c2de
--- /dev/null
+++ b/apps/trading/e2e/tests/market/test_market_selector.py
@@ -0,0 +1,88 @@
+import pytest
+from playwright.sync_api import expect, Page
+
+
+@pytest.mark.usefixtures("page", "continuous_market", "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()
+ page.get_by_test_id("header-title").click()
+ # 6001-MARK-066
+ expect(page.get_by_test_id("market-selector")).to_be_visible()
+
+ # 6001-MARK-021
+ # 6001-MARK-022
+ # 6001-MARK-024
+ # 6001-MARK-025
+ btc_market = page.locator('[data-testid="market-selector-list"] a')
+ expect(btc_market.locator("h3")).to_have_text("BTC:DAI_2023Futr")
+ expect(btc_market.locator('[data-testid="market-selector-volume"]')).to_have_text(
+ "0.00"
+ )
+ expect(btc_market.locator('[data-testid="market-selector-price"]')).to_have_text(
+ "107.50 tDAI"
+ )
+ expect(btc_market.locator("span.rounded-md.leading-none")).to_be_visible()
+ expect(btc_market.locator("span.rounded-md.leading-none")).to_have_text("Futr")
+ 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.parametrize(
+ "simple_market",
+ [
+ {
+ "custom_market_name": "APPL.MF21",
+ "custom_asset_name": "tUSDC",
+ "custom_asset_symbol": "tUSDC",
+ }
+ ],
+ indirect=True,
+)
+def test_market_selector_filter(continuous_market, page: Page):
+ page.goto(f"/#/markets/{continuous_market}")
+ page.get_by_test_id("header-title").click()
+ # 6001-MARK-027
+
+ page.get_by_test_id("product-Spot").click()
+ expect(page.get_by_test_id("market-selector-list")).to_contain_text(
+ "Spot markets coming soon."
+ )
+ page.get_by_test_id("product-Perpetual").click()
+ expect(page.get_by_test_id("market-selector-list")).to_contain_text(
+ "No perpetual markets."
+ )
+ page.get_by_test_id("product-Future").click()
+ expect(page.locator('[data-testid="market-selector-list"] a')).to_have_count(2)
+
+ # 6001-MARK-029
+ page.get_by_test_id("search-term").fill("btc")
+ expect(page.locator('[data-testid="market-selector-list"] a')).to_have_count(1)
+ expect(page.locator('[data-testid="market-selector-list"] a').nth(0)).to_have_text(
+ "BTC:DAI_2023107.50 tDAI0.00"
+ )
+
+ page.get_by_test_id("search-term").clear()
+ expect(page.locator('[data-testid="market-selector-list"] a')).to_have_count(2)
+
+ # 6001-MARK-030
+ # 6001-MARK-031
+ # 6001-MARK-032
+ # 6001-MARK-033
+ page.get_by_test_id("sort-trigger").click()
+
+ expect(page.get_by_test_id("sort-item-Gained")).to_have_text("Top gaining")
+ expect(page.get_by_test_id("sort-item-Gained")).to_be_visible()
+ expect(page.get_by_test_id("sort-item-Lost")).to_have_text("Top losing")
+ expect(page.get_by_test_id("sort-item-Lost")).to_be_visible()
+ expect(page.get_by_test_id("sort-item-New")).to_have_text("New markets")
+ expect(page.get_by_test_id("sort-item-New")).to_be_visible()
+
+ # 6001-MARK-028
+ page.get_by_test_id("sort-trigger").click(force=True)
+ page.get_by_test_id("asset-trigger").click()
+ page.get_by_role("menuitemcheckbox").nth(0).get_by_text("tDAI").click()
+ expect(page.locator('[data-testid="market-selector-list"] a')).to_have_count(1)
+ expect(page.locator('[data-testid="market-selector-list"] a').nth(0)).to_have_text(
+ "BTC:DAI_2023107.50 tDAI0.00"
+ )
diff --git a/apps/trading/e2e/tests/market/test_markets_all.py b/apps/trading/e2e/tests/market/test_markets_all.py
new file mode 100644
index 000000000..fee574f60
--- /dev/null
+++ b/apps/trading/e2e/tests/market/test_markets_all.py
@@ -0,0 +1,160 @@
+import pytest
+from playwright.sync_api import Page, expect
+from fixtures.market import setup_continuous_market
+
+from conftest import init_vega
+
+market_names = ["ETHBTC.QM21", "BTCUSD.MF21", "SOLUSD", "AAPL.MF21"]
+
+
+@pytest.fixture(scope="module")
+def vega():
+ with init_vega() as vega:
+ yield vega
+
+
+@pytest.fixture(scope="module")
+def create_markets(vega):
+ for market_name in market_names:
+ setup_continuous_market(vega, custom_market_name=market_name)
+
+
+@pytest.mark.usefixtures("risk_accepted")
+def test_table_headers(page: Page, create_markets):
+ page.goto(f"/#/markets/all")
+ headers = [
+ "Market",
+ "Description",
+ "Settlement asset",
+ "Trading mode",
+ "Status",
+ "Mark price",
+ "24h volume",
+ "Open Interest",
+ "Spread",
+ "",
+ ]
+ page.wait_for_selector('[data-testid="tab-open-markets"]', state="visible")
+ page_headers = (
+ page.get_by_test_id("tab-open-markets").locator(".ag-header-cell-text").all()
+ )
+ for i, header in enumerate(headers):
+ expect(page_headers[i]).to_have_text(header)
+
+
+@pytest.mark.usefixtures("risk_accepted")
+def test_markets_tab(page: Page, create_markets):
+ page.goto(f"/#/markets/all")
+ expect(page.get_by_test_id("Open markets")).to_have_attribute(
+ "data-state", "active"
+ )
+ expect(page.get_by_test_id("Proposed markets")).to_have_attribute(
+ "data-state", "inactive"
+ )
+ expect(page.get_by_test_id("Closed markets")).to_have_attribute(
+ "data-state", "inactive"
+ )
+
+
+@pytest.mark.usefixtures("risk_accepted")
+def test_markets_content(page: Page, create_markets):
+ page.goto(f"/#/markets/all")
+ row_selector = page.locator(
+ '[data-testid="tab-open-markets"] .ag-center-cols-container .ag-row'
+ ).first
+ instrument_code_locator = '[col-id="tradableInstrument.instrument.code"] [data-testid="stack-cell-primary"]'
+ # 6001-MARK-035
+ expect(row_selector.locator(instrument_code_locator)).to_have_text("ETHBTC.QM21")
+
+ # 6001-MARK-073
+ expect(row_selector.locator('[title="Future"]')).to_have_text("Futr")
+
+ # 6001-MARK-036
+ expect(
+ row_selector.locator('[col-id="tradableInstrument.instrument.name"]')
+ ).to_have_text("ETHBTC.QM21")
+
+ # 6001-MARK-037
+ expect(row_selector.locator('[col-id="tradingMode"]')).to_have_text("Continuous")
+
+ # 6001-MARK-038
+ expect(row_selector.locator('[col-id="state"]')).to_have_text("Active")
+
+ # 6001-MARK-039
+ expect(row_selector.locator('[col-id="data.markPrice"]')).to_have_text("107.50")
+
+ # 6001-MARK-040
+ expect(row_selector.locator('[col-id="data.candles"]')).to_have_text("0.00")
+
+ # 6001-MARK-042
+ expect(
+ row_selector.locator(
+ '[col-id="tradableInstrument.instrument.product.settlementAsset.symbol"]'
+ )
+ ).to_have_text("tDAI")
+
+ expect(row_selector.locator('[col-id="data.bestBidPrice"]')).to_have_text("2")
+
+ # 6001-MARK-043
+ row_selector.locator(
+ '[col-id="tradableInstrument.instrument.product.settlementAsset.symbol"] button'
+ ).click()
+ expect(page.get_by_test_id("dialog-title")).to_have_text("Asset details - tDAI")
+ # 6001-MARK-019
+ page.get_by_test_id("close-asset-details-dialog").click()
+
+
+@pytest.mark.usefixtures("risk_accepted")
+def test_market_actions(page: Page, create_markets):
+ # 6001-MARK-044
+ # 6001-MARK-045
+ # 6001-MARK-046
+ # 6001-MARK-047
+ page.goto(f"/#/markets/all")
+ page.locator(
+ '.ag-pinned-right-cols-container [col-id="market-actions"]'
+ ).first.locator("button").click()
+
+ actions = [
+ "Copy Market ID",
+ "View on Explorer",
+ "View settlement asset details",
+ ]
+ action_elements = (
+ page.get_by_test_id("market-actions-content").get_by_role("menuitem").all()
+ )
+
+ for i, action in enumerate(actions):
+ expect(action_elements[i]).to_have_text(action)
+
+
+@pytest.mark.usefixtures("risk_accepted")
+def test_sort_markets(page: Page, create_markets):
+ # 6001-MARK-064
+
+ page.goto(f"/#/markets/all")
+ sorted_market_names = [
+ "AAPL.MF21",
+ "BTCUSD.MF21",
+ "ETHBTC.QM21",
+ "SOLUSD",
+ ]
+ page.locator('.ag-header-row [col-id="tradableInstrument.instrument.code"]').click()
+ for i, market_name in enumerate(sorted_market_names):
+ expect(
+ page.locator(
+ f'[row-index="{i}"] [col-id="tradableInstrument.instrument.name"]'
+ )
+ ).to_have_text(market_name)
+
+
+@pytest.mark.usefixtures("risk_accepted")
+def test_drag_and_drop_column(page: Page, create_markets):
+ # 6001-MARK-065
+ page.goto(f"/#/markets/all")
+ col_instrument_code = '.ag-header-row [col-id="tradableInstrument.instrument.code"]'
+
+ page.locator(col_instrument_code).drag_to(
+ page.locator('.ag-header-row [col-id="data.bestBidPrice"]')
+ )
+ expect(page.locator(col_instrument_code)).to_have_attribute("aria-colindex", "9")
diff --git a/apps/trading/e2e/tests/market/test_markets_no_markets.py b/apps/trading/e2e/tests/market/test_markets_no_markets.py
new file mode 100644
index 000000000..fd2e2a6f0
--- /dev/null
+++ b/apps/trading/e2e/tests/market/test_markets_no_markets.py
@@ -0,0 +1,36 @@
+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
+
+
+@pytest.fixture(scope="module")
+def vega(request):
+ with init_vega(request) as vega:
+ yield vega
+
+
+@pytest.fixture(scope="module")
+def page(vega, browser, request):
+ with init_page(vega, browser, request) as page:
+ risk_accepted_setup(page)
+ page.goto("/#/markets/all")
+ yield page
+
+
+def test_no_open_markets(page: Page):
+ # 6001-MARK-034
+ page.get_by_test_id("Open markets").click()
+ expect(page.locator(".ag-overlay-wrapper")).to_have_text("No markets")
+
+
+def test_no_closed_markets(page: Page):
+ page.get_by_test_id("Closed markets").click()
+ expect(page.locator(".ag-overlay-wrapper")).to_have_text("No markets")
+
+
+def test_no_proposed_markets(page: Page):
+ # 6001-MARK-061
+ page.get_by_test_id("Proposed markets").click()
+ expect(page.locator(".ag-overlay-wrapper")).to_have_text("No proposed markets")
diff --git a/apps/trading/e2e/tests/market/test_markets_proposed.py b/apps/trading/e2e/tests/market/test_markets_proposed.py
new file mode 100644
index 000000000..a2f9a659c
--- /dev/null
+++ b/apps/trading/e2e/tests/market/test_markets_proposed.py
@@ -0,0 +1,113 @@
+import pytest
+import vega_sim.api.governance as governance
+import re
+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
+
+row_selector = '[data-testid="tab-proposed-markets"] .ag-center-cols-container .ag-row'
+col_market_id = '[col-id="market"] [data-testid="stack-cell-primary"]'
+
+
+@pytest.fixture(scope="module")
+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
+ market_id = setup_simple_market(vega, approve_proposal=False)
+ # approve market
+ governance.approve_proposal(
+ key_name=MM_WALLET.name,
+ proposal_id=market_id,
+ wallet=vega.wallet,
+ )
+ return market_id
+
+
+@pytest.mark.usefixtures("risk_accepted")
+def test_can_see_table_headers(proposed_market, page: Page):
+ page.goto("/#/markets/all")
+ page.click('[data-testid="Proposed markets"]')
+
+ # Test that you can see table headers
+ headers = [
+ "Market",
+ "Settlement asset",
+ "State",
+ "Parent market",
+ "Closing date",
+ "Enactment date",
+ "",
+ ]
+
+ header_elements = page.locator(".ag-header-cell-text")
+ for i, header in enumerate(headers):
+ assert header_elements.nth(i).inner_text() == header
+
+
+@pytest.mark.usefixtures("risk_accepted")
+def test_renders_markets_correctly(proposed_market, page: Page):
+ page.goto(f"/#/markets/all")
+ page.click('[data-testid="Proposed markets"]')
+ row = page.locator(row_selector)
+ # 6001-MARK-049
+ expect(row.locator(col_market_id)).to_have_text("BTC:DAI_2023")
+
+ # 6001-MARK-051
+ expect(row.locator('[col-id="asset"]')).to_have_text("tDAI")
+
+ # 6001-MARK-052
+ # 6001-MARK-053
+ expect(row.locator('[col-id="state"]')).to_have_text("Open")
+ expect(
+ row.locator('[col-id="terms.change.successorConfiguration.parentMarketId"]')
+ ).to_have_text("-")
+
+ # 6001-MARK-056
+ expect(row.locator('[col-id="closing-date"]')).not_to_be_empty()
+
+ # 6001-MARK-057
+ expect(row.locator('[col-id="enactment-date"]')).not_to_be_empty
+
+ # 6001-MARK-058
+ page.get_by_test_id("dropdown-menu").click()
+ dropdown_content = '[data-testid="proposal-actions-content"]'
+ first_item_link = (
+ page.locator(f"{dropdown_content} [role='menuitem']").nth(0).locator("a")
+ )
+
+ # 6001-MARK-059
+ expect(first_item_link).to_contain_text("View proposal")
+ expect(first_item_link).to_have_attribute(
+ "href",
+ re.compile(r"\/proposals\/[a-f0-9]{64}$"),
+ )
+
+ # temporary skip
+ # 6001-MARK-060
+ # proposed_markets_tab = page.get_by_test_id("tab-proposed-markets")
+ # external_links = proposed_markets_tab.locator("font-alpha")
+ # last_link = external_links.last
+ # assert last_link.inner_text() == 'Propose a new market'
+
+ # expected_href = f"https://governance.stagnet1.vega.rocks/proposals/propose/new-market"
+ # assert last_link.get_attribute('href') == expected_href
+
+
+@pytest.mark.usefixtures("risk_accepted")
+def test_can_drag_and_drop_columns(proposed_market, page: Page):
+ # 6001-MARK-063
+ page.goto("/#/markets/all")
+ page.click('[data-testid="Proposed markets"]')
+ col_market = page.locator('[col-id="market"]').first
+ col_state = page.locator('[col-id="state"]').first
+ col_market.drag_to(col_state)
+
+ # Check the attribute of the dragged element
+ attribute_value = col_market.get_attribute("aria-colindex")
+ assert attribute_value != "1"
diff --git a/apps/trading/e2e/tests/market/test_monitoring_auction_price_volatility_market.py b/apps/trading/e2e/tests/market/test_monitoring_auction_price_volatility_market.py
new file mode 100644
index 000000000..d5694e1cd
--- /dev/null
+++ b/apps/trading/e2e/tests/market/test_monitoring_auction_price_volatility_market.py
@@ -0,0 +1,119 @@
+import pytest
+from playwright.sync_api import Page, expect
+from vega_sim.service import VegaService
+from actions.vega import submit_order
+from fixtures.market import setup_simple_market
+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:
+ yield vega
+
+
+@pytest.fixture(scope="module")
+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(
+ key_name=MM_WALLET.name,
+ market_id=simple_market,
+ commitment_amount=100,
+ fee=0.002,
+ is_amendment=False,
+ )
+
+ vega.submit_order(
+ market_id=simple_market,
+ trading_key=MM_WALLET.name,
+ side="SIDE_BUY",
+ order_type="TYPE_LIMIT",
+ price=1 - 0.0005,
+ wait=False,
+ time_in_force="TIME_IN_FORCE_GTC",
+ volume=99,
+ )
+ vega.submit_order(
+ market_id=simple_market,
+ trading_key=MM_WALLET.name,
+ side="SIDE_SELL",
+ order_type="TYPE_LIMIT",
+ price=1 + 0.0005,
+ wait=False,
+ time_in_force="TIME_IN_FORCE_GTC",
+ 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_WALLET2.name, simple_market, "SIDE_SELL", 1, 1)
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ # add orders that change the price so that it goes beyond the limits of price monitoring
+ submit_order(vega, MM_WALLET.name, simple_market, "SIDE_SELL", 100, 110)
+ submit_order(vega, MM_WALLET2.name, simple_market, "SIDE_BUY", 100, 90)
+ submit_order(vega, MM_WALLET.name, simple_market, "SIDE_SELL", 100, 105)
+ submit_order(vega, MM_WALLET2.name, simple_market, "SIDE_BUY", 100, 95)
+ submit_order(vega, MM_WALLET2.name, simple_market, "SIDE_BUY", 1, 105)
+
+ vega.forward("10s")
+ 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):
+
+ page.goto(f"/#/markets/{simple_market}")
+ page.get_by_test_id("order-size").clear()
+ page.get_by_test_id("order-size").type("1")
+ page.get_by_test_id("order-price").clear()
+ page.get_by_test_id("order-price").type("110")
+ 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_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_be_visible()
+
+ page.get_by_test_id("order-tif").select_option("Good 'til Cancelled (GTC)")
+
+ expect(page.get_by_test_id("deal-ticket-error-message-tif")).not_to_be_visible()
+
+ page.get_by_test_id("place-order").click()
+
+ wait_for_toast_confirmation(page)
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ page.get_by_test_id("All").click()
+ expect(page.get_by_role("row").nth(2)).to_contain_text(
+ "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):
+ page.goto(f"/#/markets/{simple_market}")
+ page.get_by_test_id("order-type-Market").click()
+ page.get_by_test_id("order-size").clear()
+ page.get_by_test_id("order-size").type("1")
+ # 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_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_be_visible()
diff --git a/apps/trading/e2e/tests/market_lifecycle/test_market_lifecycle.py b/apps/trading/e2e/tests/market_lifecycle/test_market_lifecycle.py
new file mode 100644
index 000000000..4449f9abd
--- /dev/null
+++ b/apps/trading/e2e/tests/market_lifecycle/test_market_lifecycle.py
@@ -0,0 +1,184 @@
+import pytest
+import re
+import vega_sim.api.governance as governance
+from playwright.sync_api import Page, expect
+from vega_sim.service import VegaService, PeggedOrder
+import vega_sim.api.governance as governance
+from actions.vega import submit_order
+from wallet_config import MM_WALLET, MM_WALLET2, GOVERNANCE_WALLET
+
+
+
+@pytest.mark.usefixtures("vega", "page", "proposed_market", "risk_accepted")
+def test_market_lifecycle(proposed_market, vega: VegaService, page: Page):
+ # 7002-SORD-001
+ # 7002-SORD-002
+ trading_mode = page.get_by_test_id("market-trading-mode").get_by_test_id(
+ "item-value"
+ )
+ market_state = page.get_by_test_id("market-state").get_by_test_id("item-value")
+
+ # setup market in proposed step, without liquidity provided
+ market_id = proposed_market
+ page.goto(f"/#/markets/{market_id}")
+ # 6002-MDET-001
+ expect(page.get_by_test_id("header-title")).to_have_text("BTC:DAI_2023Futr")
+ # 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('.*'))
+ # 6002-MDET-003
+ expect(page.get_by_test_id("market-price")).to_have_text("Mark Price0.00")
+ # 6002-MDET-004
+ expect(page.get_by_test_id("market-change")).to_have_text("Change (24h)0.00%0.00")
+ # 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%)")
+ 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")
+ # 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(market_state).to_have_text("Proposed")
+
+ # approve market
+ governance.approve_proposal(
+ key_name=MM_WALLET.name,
+ proposal_id=market_id,
+ wallet=vega.wallet,
+ )
+
+ # "wait" for market to be approved and enacted
+ vega.forward("60s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ # check that market is in pending state
+ expect(trading_mode).to_have_text("Opening auction")
+ expect(market_state).to_have_text("Pending")
+
+ # Add liquidity and place some orders. Orders should match to produce the uncrossing price. A market can only move from opening auction to continuous trading when the enactment date has passed, there is sufficient liquidity and an uncrossing price is produced.
+ vega.submit_simple_liquidity(
+ key_name=MM_WALLET.name,
+ market_id=market_id,
+ commitment_amount=10000,
+ fee=0.000,
+ is_amendment=False,
+ )
+
+ vega.submit_order(
+ market_id=market_id,
+ trading_key=MM_WALLET.name,
+ side="SIDE_BUY",
+ order_type="TYPE_LIMIT",
+ pegged_order=PeggedOrder(reference="PEGGED_REFERENCE_MID", offset=1),
+ wait=False,
+ time_in_force="TIME_IN_FORCE_GTC",
+ volume=99,
+ )
+ vega.submit_order(
+ market_id=market_id,
+ trading_key=MM_WALLET.name,
+ side="SIDE_SELL",
+ order_type="TYPE_LIMIT",
+ pegged_order=PeggedOrder(reference="PEGGED_REFERENCE_MID", offset=1),
+ wait=False,
+ time_in_force="TIME_IN_FORCE_GTC",
+ volume=99,
+ )
+
+ submit_order(vega, MM_WALLET.name, market_id, "SIDE_SELL", 1, 110)
+ submit_order(vega, MM_WALLET2.name, market_id, "SIDE_BUY", 1, 90)
+ submit_order(vega, MM_WALLET.name, market_id, "SIDE_SELL", 1, 105)
+ submit_order(vega, MM_WALLET2.name, market_id, "SIDE_BUY", 1, 95)
+ submit_order(vega, MM_WALLET.name, market_id, "SIDE_SELL", 1, 100)
+ submit_order(vega, MM_WALLET2.name, market_id, "SIDE_BUY", 1, 100)
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ # check market state is now active and trading mode is continuous
+ expect(trading_mode).to_have_text("Continuous")
+ expect(market_state).to_have_text("Active")
+
+ # put invalid oracle to trigger market termination
+ governance.submit_oracle_data(
+ wallet=vega.wallet,
+ payload={"trading.terminated": "true"},
+ key_name=GOVERNANCE_WALLET.name,
+ )
+ vega.forward("60s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ # market state should be changed to "Trading Terminated" because of the invalid oracle
+ expect(trading_mode).to_have_text("No trading")
+ expect(market_state).to_have_text("Trading Terminated")
+
+ # settle market
+ vega.submit_termination_and_settlement_data(
+ settlement_key=GOVERNANCE_WALLET.name,
+ settlement_price=100,
+ market_id=market_id,
+ )
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ # check market state is now settled
+ expect(trading_mode).to_have_text("No trading")
+ expect(market_state).to_have_text("Settled")
+
+
+""" @pytest.mark.usefixtures("page", "risk_accepted", "continuous_market")
+def test_market_closing_banners(page: Page, continuous_market, vega: VegaService):
+ market_id = continuous_market
+ page.goto(f"/#/markets/{market_id}")
+ proposalID = vega.update_market_state(
+ continuous_market,
+ "market_maker",
+ MarketStateUpdateType.Terminate,
+ approve_proposal=False,
+ vote_enactment_time = datetime.now() + timedelta(weeks=1),
+ forward_time_to_enactment = False,
+ price=107,
+ )
+ may_close_warning_pattern = r"TRADING ON MARKET BTC:DAI_2023 MAY STOP ON \d+ \w+\.\s*THERE IS OPEN PROPOSAL TO CLOSE THIS MARKET\.\nProposed final price is 107\.00 BTC\.\nView proposal"
+ match_result = re.fullmatch(may_close_warning_pattern, page.locator(".grow").inner_text())
+ assert match_result is not None
+
+ vega.update_market_state(
+ continuous_market,
+ "market_maker",
+ MarketStateUpdateType.Terminate,
+ approve_proposal=False,
+ vote_enactment_time = datetime.now() + timedelta(weeks=1),
+ forward_time_to_enactment = False,
+ price=110,
+ )
+
+ expect(page.locator(".grow")).to_have_text("Trading on Market BTC:DAI_2023 may stop. There are open proposals to close this marketView proposals")
+
+ governance.approve_proposal(
+ proposal_id=proposalID,
+ wallet=vega.wallet,
+ key_name="market_maker"
+
+ )
+ vega.forward("60s")
+ vega.wait_fn(10)
+ vega.wait_for_total_catchup()
+
+ 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
+ """
\ No newline at end of file
diff --git a/apps/trading/e2e/tests/navigation/test_navigation.py b/apps/trading/e2e/tests/navigation/test_navigation.py
new file mode 100644
index 000000000..a4bfa8614
--- /dev/null
+++ b/apps/trading/e2e/tests/navigation/test_navigation.py
@@ -0,0 +1,111 @@
+import pytest
+from playwright.sync_api import Page, expect, Locator
+
+from conftest import init_page, init_vega
+
+
+@pytest.fixture(scope="module")
+def vega():
+ with init_vega() as vega:
+ yield 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:
+ yield page
+
+
+@pytest.mark.usefixtures("risk_accepted")
+def test_network_switcher(page: Page):
+ page.goto("/#/disclaimer")
+ navbar = page.locator('nav[aria-label="Main"]')
+ assert_network_switcher(navbar)
+
+
+@pytest.mark.usefixtures("risk_accepted")
+def test_navbar_pages(page: Page):
+ page.goto("/#/disclaimer")
+ navbar = page.locator('nav[aria-label="Main"]')
+ assert_links(navbar)
+
+
+@pytest.mark.usefixtures("risk_accepted")
+def test_navigation_mobile(page: Page):
+ page.goto("/#/disclaimer")
+ page.set_viewport_size({"width": 800, "height": 1040})
+ navbar = page.locator('nav[aria-label="Main"]')
+
+ # region navigation
+ burger = navbar.get_by_test_id("navbar-mobile-burger")
+ expect(burger).to_be_visible()
+ burger.click()
+ menu = navbar.get_by_test_id("navbar-menu-content")
+ expect(menu).to_be_visible()
+ assert_links(menu)
+ assert_network_switcher(menu)
+ menu.get_by_role("button", name="Close menu").click()
+ # endregion
+
+ # region wallet
+ wallet_button = navbar.get_by_test_id("navbar-mobile-wallet")
+ expect(wallet_button).to_be_visible()
+ wallet_button.click()
+ dialog = page.get_by_test_id("dialog-content")
+ expect(dialog.get_by_test_id("wallet-dialog-title")).to_be_visible()
+ # endregion
+
+
+def assert_links(container: Locator):
+ pages = [
+ {"name": "Markets", "href": "#/markets"},
+ {"name": "Trading", "href": "#/markets/"},
+ {"name": "Portfolio", "href": "#/portfolio"},
+ ]
+
+ for page in pages:
+ link = container.get_by_role("link", name=page["name"])
+ expect(link).to_be_visible()
+ expect(link).to_have_attribute("href", page["href"])
+
+ # False indicates external link configured by env var
+ resource_pages = [
+ {"name": "Docs", "href": False},
+ {"name": "Give Feedback", "href": False},
+ {"name": "Disclaimer", "href": "#/disclaimer"},
+ ]
+
+ container.get_by_role("button", name="Resources").click()
+
+ dropdown = container.get_by_test_id("navbar-content-resources")
+
+ for resource_page in resource_pages:
+ page_name = resource_page["name"]
+ page_href = resource_page["href"]
+ link = dropdown.get_by_role("link", name=page_name)
+ expect(link).to_be_visible()
+ if not page_href:
+ href = link.get_attribute("href")
+ expect(link).to_have_attribute("target", "_blank")
+ assert len(href) >= 0, f"href for {page_name} is empty"
+ else:
+ expect(link).to_have_attribute("href", page_href)
+
+
+def assert_network_switcher(container: Locator):
+ network_switcher_trigger = container.get_by_test_id(
+ "navbar-network-switcher-trigger"
+ )
+ # 0006-NETW-002
+ expect(network_switcher_trigger).to_have_text = "Fairground testnet"
+ network_switcher_trigger.click()
+ dropdown = container.get_by_test_id("navbar-content-network-switcher")
+ expect(dropdown).to_be_visible()
+ links = dropdown.get_by_role("link")
+ expect(links).to_have_count(2)
+ mainnet_link = container.get_by_role("link", name="Mainnet")
+ expect(mainnet_link).to_be_visible()
+ # 0006-NETW-003
+ expect(mainnet_link).to_have_attribute("href", "https://console.vega.xyz")
+ expect(container.get_by_role("link", name="Fairground testnet")).to_be_visible()
diff --git a/apps/trading/e2e/tests/order/test_order_details.py b/apps/trading/e2e/tests/order/test_order_details.py
new file mode 100644
index 000000000..016b79b64
--- /dev/null
+++ b/apps/trading/e2e/tests/order/test_order_details.py
@@ -0,0 +1,64 @@
+import pytest
+import re
+from playwright.sync_api import expect, Page
+from vega_sim.service import VegaService
+from actions.vega import submit_order
+
+order_details = [
+ ("order-market-label", "Market", "order-market-value", "BTC:DAI_2023"),
+ ("order-side-label", "Side", "order-side-value", "Short"),
+ ("order-type-label", "Type", "order-type-value", "Limit"),
+ ("order-price-label", "Price", "order-price-value", "101.00"),
+ ("order-size-label", "Size", "order-size-value", "-102"),
+ ("order-remaining-label", "Remaining", "order-remaining-value", "-2"),
+ ("order-status-label", "Status", "order-status-value", "Active"),
+ ("order-id-label", "Order ID", "order-id-value", r"^.{10}\u2026.+Copy$", True),
+ (
+ "order-created-label",
+ "Created",
+ "order-created-value",
+ r"^\d{1,2}/\d{1,2}/\d{4}, \d{1,2}:\d{2}:\d{2}$",
+ True,
+ ),
+ (
+ "order-time-in-force-label",
+ "Time in force",
+ "order-time-in-force-value",
+ "Good 'til Cancelled (GTC)",
+ ),
+]
+
+
+def verify_order_label(page: Page, test_id: str, expected_text: str):
+ element = page.get_by_test_id(test_id)
+ expect(element).to_be_visible()
+ expect(element).to_have_text(expected_text)
+
+
+def verify_order_value(
+ page: Page, test_id: str, expected_text: str, is_regex: bool = False
+):
+ element = page.get_by_test_id(test_id)
+ expect(element).to_be_visible()
+ if is_regex:
+ actual_text = element.text_content()
+ assert re.match(
+ expected_text, actual_text
+ ), f"Expected {expected_text}, but got {actual_text}"
+ else:
+ expect(element).to_have_text(expected_text)
+
+@pytest.mark.skip("tbd")
+@pytest.mark.usefixtures("vega", "page", "continuous_market", "auth", "risk_accepted")
+def test_order_details_are_correctly_displayed(
+ continuous_market, vega: VegaService, page: Page
+):
+ page.goto(f"/#/markets/{continuous_market}")
+ submit_order(vega, "Key 1", vega.all_markets()[0].id, "SIDE_SELL", 102, 101, 2, 1)
+ page.get_by_test_id("Open").click()
+ page.get_by_test_id("icon-kebab").click()
+ page.get_by_test_id("view-order").click()
+ for detail in order_details:
+ label_id, label_text, value_id, value_text, is_regex = (*detail, False)[:5]
+ verify_order_label(page, label_id, label_text)
+ verify_order_value(page, value_id, value_text, is_regex)
diff --git a/apps/trading/e2e/tests/order/test_order_match.py b/apps/trading/e2e/tests/order/test_order_match.py
new file mode 100644
index 000000000..b22313e13
--- /dev/null
+++ b/apps/trading/e2e/tests/order/test_order_match.py
@@ -0,0 +1,159 @@
+import pytest
+import re
+import logging
+from playwright.sync_api import expect, Page
+from vega_sim.service import VegaService
+from playwright.sync_api import expect
+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()
+ # Required so that we can get liquidation price
+ expect(
+ page.locator(
+ f'[data-testid^="tab-{data_test_id.lower()}"] >> .ag-center-cols-container .ag-row-first'
+ )
+ ).to_be_visible()
+ actual_text = page.locator(
+ f'[data-testid^="tab-{data_test_id.lower()}"] >> .ag-center-cols-container .ag-row-first'
+ ).text_content()
+ lines = actual_text.strip().split("\n")
+ for expected, actual in zip(expected_pattern, lines):
+ # We are using regex so that we can run tests in different timezones.
+ if re.match(r"^\\d", expected): # check if it's a regex
+ if re.search(expected, actual):
+ logger.info(f"Matched: {expected} == {actual}")
+ else:
+ logger.info(f"Not Matched: {expected} != {actual}")
+ raise AssertionError(f"Pattern does not match: {expected} != {actual}")
+ else: # it's not a regex, so we escape it
+ if re.search(re.escape(expected), actual):
+ logger.info(f"Matched: {expected} == {actual}")
+ else:
+ logger.info(f"Not Matched: {expected} != {actual}")
+ raise AssertionError(f"Pattern does not match: {expected} != {actual}")
+
+
+def submit_order(vega: VegaService, wallet_name, market_id, side, volume, price):
+ vega.submit_order(
+ trading_key=wallet_name,
+ market_id=market_id,
+ time_in_force="TIME_IN_FORCE_GTC",
+ order_type="TYPE_LIMIT",
+ side=side,
+ volume=volume,
+ price=price,
+ )
+
+
+@pytest.mark.usefixtures(
+ "vega", "page", "opening_auction_market", "auth", "risk_accepted"
+)
+def test_limit_order_trade_open_order(
+ opening_auction_market, vega: VegaService, page: Page
+):
+ market_id = opening_auction_market
+ submit_order(vega, "Key 1", market_id, "SIDE_BUY", 1, 110)
+
+ page.goto(f"/#/markets/{market_id}")
+ # Assert that the user order is displayed on the orderbook
+ orderbook_trade = page.get_by_test_id("price-11000000").nth(1)
+ # 6003-ORDB-001
+ # 6003-ORDB-002
+ expect(orderbook_trade).to_be_visible()
+
+ expected_open_order = [
+ "BTC:DAI_2023",
+ "+1",
+ "Limit",
+ "Active",
+ "0/1",
+ "110.00",
+ "Good 'til Cancelled (GTC)",
+ r"\d{1,2}/\d{1,2}/\d{4},\s*\d{1,2}:\d{2}:\d{2}\s*(?:AM|PM)",
+ "-",
+ ]
+ logger.info("Assert Open orders:")
+ verify_data_grid(page, "Open", expected_open_order)
+
+
+@pytest.mark.usefixtures("vega", "page", "continuous_market", "auth", "risk_accepted")
+def test_limit_order_trade_open_position(continuous_market, page: Page):
+ page.goto(f"/#/markets/{continuous_market}")
+
+ primary_id = "stack-cell-primary"
+ secondary_id = "stack-cell-secondary"
+
+ position = {
+ "market_code": "BTC:DAI_2023",
+ "settlement_asset": "tDAI",
+ "product_type": "Futr",
+ "size": "+1",
+ "notional": "107.50",
+ "average_entry_price": "107.50",
+ "mark_price": "107.50",
+ "margin": "8.50269",
+ "leverage": "1.0x",
+ "liquidation": "0.00",
+ "realised_pnl": "0.00",
+ "unrealised_pnl": "0.00",
+ }
+
+ tab = page.get_by_test_id("tab-positions")
+ table = tab.locator(".ag-center-cols-container")
+
+ # 7004-POSI-001
+ # 7004-POSI-002
+
+ market = table.locator("[col-id='marketCode']")
+ expect(market.get_by_test_id(primary_id)).to_have_text(position["market_code"])
+ expect(market.get_by_test_id(secondary_id)).to_have_text(
+ position["settlement_asset"] + position["product_type"]
+ )
+
+ size_and_notional = table.locator("[col-id='openVolume']")
+ expect(size_and_notional.get_by_test_id(primary_id)).to_have_text(position["size"])
+ expect(size_and_notional.get_by_test_id(secondary_id)).to_have_text(
+ position["notional"]
+ )
+
+ entry_and_mark = table.locator("[col-id='markPrice']")
+ expect(entry_and_mark.get_by_test_id(primary_id)).to_have_text(
+ position["average_entry_price"]
+ )
+ expect(entry_and_mark.get_by_test_id(secondary_id)).to_have_text(
+ position["mark_price"]
+ )
+
+ margin_and_leverage = table.locator("[col-id='margin']")
+ expect(margin_and_leverage.get_by_test_id(primary_id)).to_have_text(
+ position["margin"]
+ )
+ expect(margin_and_leverage.get_by_test_id(secondary_id)).to_have_text(
+ position["leverage"]
+ )
+
+ liquidation = table.locator("[col-id='liquidationPrice']")
+ expect(liquidation.get_by_test_id("liquidation-price")).to_have_text(
+ position["liquidation"]
+ )
+
+ realisedPNL = table.locator("[col-id='realisedPNL']")
+ expect(realisedPNL).to_have_text(position["realised_pnl"])
+
+ unrealisedPNL = table.locator("[col-id='unrealisedPNL']")
+ expect(unrealisedPNL).to_have_text(position["unrealised_pnl"])
+
+
+@pytest.mark.usefixtures("vega", "page", "continuous_market", "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
+ page.get_by_test_id("Orderbook").click()
+ price_element = page.get_by_test_id("price-11000000").nth(1)
+ # 6003-ORDB-010
+ print(price_element)
+ expect(price_element).to_be_hidden()
diff --git a/apps/trading/e2e/tests/order/test_order_status.py b/apps/trading/e2e/tests/order/test_order_status.py
new file mode 100644
index 000000000..2487c5fe4
--- /dev/null
+++ b/apps/trading/e2e/tests/order/test_order_status.py
@@ -0,0 +1,414 @@
+import pytest
+from playwright.sync_api import Page, expect
+from vega_sim.service import VegaService, PeggedOrder
+from conftest import auth_setup, init_page, init_vega, risk_accepted_setup
+from fixtures.market import setup_continuous_market, setup_simple_market
+from actions.utils import wait_for_toast_confirmation
+
+order_tab = "tab-orders"
+
+
+@pytest.fixture(scope="module")
+def vega(request):
+ with init_vega(request) as vega:
+ yield vega
+
+
+@pytest.fixture(scope="module", autouse=True)
+def markets(vega: VegaService):
+ market_1 = setup_continuous_market(
+ vega,
+ custom_market_name="market-1",
+ )
+ market_2 = setup_continuous_market(
+ vega,
+ custom_market_name="market-2",
+ )
+ market_3 = setup_continuous_market(
+ vega,
+ custom_market_name="market-3",
+ )
+ market_4 = setup_continuous_market(
+ vega,
+ custom_market_name="market-4",
+ )
+ market_5 = setup_simple_market(
+ vega,
+ custom_market_name="market-5",
+ )
+
+ vega.submit_order(
+ trading_key="Key 1",
+ market_id=market_1,
+ time_in_force="TIME_IN_FORCE_IOC",
+ order_type="TYPE_LIMIT",
+ side="SIDE_SELL",
+ volume=100,
+ price=130,
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ vega.submit_order(
+ trading_key="Key 1",
+ market_id=market_1,
+ time_in_force="TIME_IN_FORCE_GTC",
+ order_type="TYPE_LIMIT",
+ side="SIDE_SELL",
+ volume=100,
+ price=88,
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ vega.submit_order(
+ trading_key="Key 1",
+ market_id=market_1,
+ time_in_force="TIME_IN_FORCE_IOC",
+ order_type="TYPE_LIMIT",
+ side="SIDE_SELL",
+ volume=100,
+ price=88,
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ vega.submit_order(
+ trading_key="Key 1",
+ market_id=market_1,
+ time_in_force="TIME_IN_FORCE_GTC",
+ order_type="TYPE_LIMIT",
+ side="SIDE_SELL",
+ volume=1e10,
+ price=130,
+ wait=False,
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ vega.submit_order(
+ trading_key="Key 1",
+ market_id=market_2,
+ time_in_force="TIME_IN_FORCE_IOC",
+ order_type="TYPE_LIMIT",
+ side="SIDE_BUY",
+ volume=100,
+ price=104,
+ )
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ vega.submit_order(
+ trading_key="Key 1",
+ market_id=market_3,
+ time_in_force="TIME_IN_FORCE_GTT",
+ order_type="TYPE_LIMIT",
+ side="SIDE_SELL",
+ volume=10,
+ price=120,
+ expires_at=vega.get_blockchain_time() + 5 * 1e9,
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ vega.submit_order(
+ market_id=market_4,
+ trading_key="Key 1",
+ side="SIDE_BUY",
+ order_type="TYPE_LIMIT",
+ pegged_order=PeggedOrder(reference="PEGGED_REFERENCE_MID", offset=5),
+ time_in_force="TIME_IN_FORCE_GTC",
+ volume=20,
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ vega.submit_order(
+ market_id=market_4,
+ trading_key="Key 1",
+ side="SIDE_BUY",
+ order_type="TYPE_LIMIT",
+ pegged_order=PeggedOrder(reference="PEGGED_REFERENCE_BEST_BID", offset=10),
+ time_in_force="TIME_IN_FORCE_GTC",
+ volume=40,
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ vega.submit_order(
+ market_id=market_4,
+ trading_key="Key 1",
+ side="SIDE_SELL",
+ order_type="TYPE_LIMIT",
+ pegged_order=PeggedOrder(reference="PEGGED_REFERENCE_BEST_ASK", offset=15),
+ time_in_force="TIME_IN_FORCE_GTC",
+ volume=60,
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ vega.submit_order(
+ market_id=market_5,
+ trading_key="Key 1",
+ side="SIDE_SELL",
+ order_type="TYPE_LIMIT",
+ pegged_order=PeggedOrder(reference="PEGGED_REFERENCE_BEST_ASK", offset=15),
+ wait=False,
+ time_in_force="TIME_IN_FORCE_GTC",
+ volume=60,
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ vega.submit_order(
+ trading_key="Key 1",
+ market_id=market_2,
+ time_in_force="TIME_IN_FORCE_GTC",
+ order_type="TYPE_LIMIT",
+ side="SIDE_SELL",
+ volume=10,
+ price=150,
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ vega.submit_order(
+ trading_key="Key 1",
+ market_id=market_2,
+ time_in_force="TIME_IN_FORCE_GTC",
+ order_type="TYPE_LIMIT",
+ side="SIDE_SELL",
+ volume=10,
+ price=160,
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ vega.submit_order(
+ trading_key="Key 1",
+ market_id=market_3,
+ time_in_force="TIME_IN_FORCE_GTC",
+ order_type="TYPE_LIMIT",
+ side="SIDE_BUY",
+ volume=10,
+ price=60,
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+
+@pytest.fixture(scope="module")
+def page(vega, browser, request):
+ with init_page(vega, browser, request) as page:
+ risk_accepted_setup(page)
+ auth_setup(vega, page)
+ page.goto("/")
+ page.get_by_test_id("All").click()
+ yield page
+
+
+# close toast that is still opened after test
+@pytest.fixture(autouse=True)
+def after_each(page: Page):
+ yield
+ if page.get_by_test_id("toast-close").is_visible():
+ page.get_by_test_id("toast-close").click()
+
+
+# 7002-SORD-040 (as all the tests are about status)
+
+
+def test_order_sorted(page: Page):
+ # 7003-MORD-002
+ orders_update_date = page.locator(
+ '.ag-center-cols-container [col-id="updatedAt"]'
+ ).all_text_contents()
+
+ orders_update_date_sorted = sorted(orders_update_date, reverse=True)
+
+ assert all([a == b for a, b in zip(orders_update_date, orders_update_date_sorted)])
+
+
+def test_order_status_active(page: Page):
+ # 7002-SORD-041
+ expect(page.get_by_test_id(order_tab)).to_contain_text(
+ "market-2Futr" + "0" + "-10" + "Limit" + "Active" + "150.00" + "GTC"
+ )
+
+
+def test_status_expired(page: Page):
+ # 7002-SORD-042
+ expect(page.get_by_test_id(order_tab)).to_contain_text(
+ "market-3Futr" + "0" + "-10" + "Limit" + "Expired" + "120.00" + "GTT:"
+ )
+
+
+def test_order_status_Stopped(page: Page):
+ # 7002-SORD-044
+ expect(page.get_by_test_id(order_tab)).to_contain_text(
+ "market-1Futr" + "0" + "-100" + "Limit" + "Stopped" + "130.00" + "IOC"
+ )
+
+
+def test_order_status_partially_filled(page: Page):
+ # 7002-SORD-045
+ expect(page.get_by_test_id(order_tab)).to_contain_text(
+ "market-2Futr" + "99" + "+100" + "Limit" + "Partially Filled" + "104.00" + "IOC"
+ )
+
+
+def test_order_status_filled(page: Page):
+ # 7002-SORD-046
+ # 7003-MORD-020
+ expect(page.get_by_test_id(order_tab)).to_contain_text(
+ "market-1Futr" + "100" + "-100" + "Limit" + "Filled" + "88.00" + "GTC"
+ )
+
+
+def test_order_status_rejected(page: Page):
+ # 7002-SORD-047
+ # 7003-MORD-018
+ expect(page.get_by_test_id(order_tab)).to_contain_text(
+ "market-1Futr"
+ + "0"
+ + "-10,000,000,000"
+ + "Limit"
+ + "Rejected: Margin check failed"
+ + "130.00"
+ + "GTC"
+ )
+
+
+def test_order_status_parked(page: Page):
+ # 7002-SORD-048
+ # 7003-MORD-016
+ expect(page.get_by_test_id(order_tab)).to_contain_text(
+ "market-5Futr"
+ + "0"
+ + "-60"
+ + "Ask + 15.00 Peg limit"
+ + "Parked"
+ + "0.00"
+ + "GTC"
+ )
+
+
+def test_order_status_pegged_ask(page: Page):
+ # 7003-MORD-016
+ expect(page.get_by_test_id(order_tab)).to_contain_text(
+ "market-4Futr"
+ + "0"
+ + "-60"
+ + "Ask + 15.00 Peg limit"
+ + "Active"
+ + "125.00"
+ + "GTC"
+ )
+
+
+def test_order_status_pegged_bid(page: Page):
+ # 7003-MORD-016
+ expect(page.get_by_test_id(order_tab)).to_contain_text(
+ "market-4Futr"
+ + "0"
+ + "+40"
+ + "Bid - 10.00 Peg limit"
+ + "Active"
+ + "85.00"
+ + "GTC"
+ )
+
+
+def test_order_status_pegged_mid(page: Page):
+ # 7003-MORD-016
+ expect(page.get_by_test_id(order_tab)).to_contain_text(
+ "market-4Futr"
+ + "0"
+ + "+20"
+ + "Mid - 5.00 Peg limit"
+ + "Active"
+ + "97.50"
+ + "GTC"
+ )
+
+
+def test_order_amend_order(vega: VegaService, page: Page):
+ # 7002-SORD-053
+ # 7003-MORD-012
+ # 7003-MORD-014
+ # 7003-MORD-015
+ page.get_by_test_id("edit").nth(1).click()
+ page.locator("#limitPrice").fill("170")
+ page.locator("#size").fill("15")
+ page.get_by_role("button", name="Update").click()
+
+ wait_for_toast_confirmation(page, timeout=5000)
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ expect(page.get_by_test_id(order_tab)).to_contain_text(
+ "market-2Futr" + "0" + "-15" + "Limit" + "Active" + "170.00" + "GTC"
+ )
+
+
+def test_order_cancel_single_order(vega: VegaService, page: Page):
+ # 7003-MORD-009
+ # 7003-MORD-010
+ # 7003-MORD-011
+ # 7002-SORD-043
+ page.get_by_test_id("cancel").first.click()
+
+ wait_for_toast_confirmation(page, timeout=5000)
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ expect(page.get_by_test_id(order_tab)).to_contain_text(
+ "market-3Futr" + "0" + "+10" + "Limit" + "Cancelled" + "60.00" + "GTC"
+ )
+
+
+def test_order_cancel_all_orders(vega: VegaService, page: Page):
+ # 7003-MORD-009
+ # 7003-MORD-010
+ # 7003-MORD-011
+ # 7002-SORD-043
+
+ page.get_by_test_id("cancelAll").click()
+
+ wait_for_toast_confirmation(page, timeout=5000)
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ expect(page.get_by_test_id("cancelAll")).not_to_be_visible()
+ expect(page.get_by_test_id("cancel")).not_to_be_visible()
+ expect(
+ page.locator('.ag-cell[col-id="status"]', has_text="Cancelled")
+ ).to_have_count(7)
diff --git a/apps/trading/e2e/tests/orderbook/test_orderbook.py b/apps/trading/e2e/tests/orderbook/test_orderbook.py
new file mode 100644
index 000000000..bde6d21e3
--- /dev/null
+++ b/apps/trading/e2e/tests/orderbook/test_orderbook.py
@@ -0,0 +1,263 @@
+import pytest
+from playwright.sync_api import Page, expect
+from typing import List
+from actions.vega import submit_order, submit_liquidity, submit_multiple_orders
+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:
+ yield vega
+
+
+@pytest.fixture(scope="module")
+def setup_market(vega):
+ market_id = setup_simple_market(vega)
+ submit_liquidity(vega, MM_WALLET.name, market_id)
+ submit_multiple_orders(
+ vega,
+ MM_WALLET.name,
+ market_id,
+ "SIDE_SELL",
+ [[10, 130.005], [3, 130], [7, 120], [5, 110], [2, 105]],
+ )
+ submit_multiple_orders(
+ vega,
+ MM_WALLET2.name,
+ market_id,
+ "SIDE_BUY",
+ [[10, 69.995], [5, 70], [5, 85], [3, 90], [3, 95]],
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ return [
+ vega,
+ market_id,
+ ]
+
+
+# these values don't align with the multiple orders above as
+# creating a trade triggers the liquidity provision
+orderbook_content = [
+ [130.00500, 10, 94],
+ [130.00000, 3, 84],
+ [120.00000, 7, 81],
+ [110.00000, 5, 74],
+ [105.00000, 2, 69],
+ [101.00000, 67, 67],
+ # mid
+ [99.00000, 102, 102],
+ [95.00000, 3, 105],
+ [90.00000, 3, 108],
+ [85.00000, 5, 113],
+ [70.00000, 5, 118],
+ [69.99500, 10, 128],
+]
+
+
+def verify_orderbook_grid(
+ page: Page, content: List[List[float]], last_trade_price: float = False
+):
+ rows = page.locator("[data-testid$=-rows-container]").all()
+ for row_index, content_row in enumerate(content):
+ cells = rows[row_index].locator("button").all()
+ for cell_index, content_cell in enumerate(content_row):
+ assert float(cells[cell_index].text_content()) == content_cell
+
+
+def verify_prices_descending(page: Page):
+ prices_locator = page.get_by_test_id("tab-orderbook").locator(
+ '[data-testid^="price-"]'
+ )
+ prices_locator.first.wait_for(state="visible")
+ 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")
+def test_orderbook_grid_content(setup_market, page: Page):
+ vega = setup_market[0]
+ market_id = setup_market[1]
+
+ # Create a so that lastTradePrice is shown in the mid section
+ # of the book
+ matching_order = [1, 100]
+ submit_order(
+ vega,
+ MM_WALLET.name,
+ market_id,
+ "SIDE_SELL",
+ matching_order[0],
+ matching_order[1],
+ )
+ submit_order(
+ vega,
+ MM_WALLET2.name,
+ market_id,
+ "SIDE_BUY",
+ matching_order[0],
+ matching_order[1],
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ # 6003-ORDB-001
+ # 6003-ORDB-002
+ # 6003-ORDB-003
+ # 6003-ORDB-004
+ # 6003-ORDB-005
+ # 6003-ORDB-006
+ # 6003-ORDB-007
+ page.goto(f"/#/markets/{market_id}")
+
+ page.locator("[data-testid=Orderbook]").click()
+
+ # 6003-ORDB-013
+ assert (
+ float(page.locator("[data-testid*=last-traded]").text_content())
+ == matching_order[1]
+ )
+
+ # 6003-ORDB-011
+ # get the spread text trimming off the parentheses on either end
+ spread_text = page.locator("[data-testid=spread]").text_content()[1:-1]
+ assert (
+ # TODO: figure out how to not have hardcoded value
+ spread_text
+ == "2.00"
+ )
+
+ verify_orderbook_grid(page, orderbook_content)
+ verify_prices_descending(page)
+
+
+@pytest.mark.usefixtures("page", "risk_accepted")
+def test_orderbook_resolution_change(setup_market, page: Page):
+ market_id = setup_market[1]
+ # 6003-ORDB-008
+ orderbook_content_0_00 = [
+ [130.01, 10, 94],
+ [130.00, 3, 84],
+ [120.00, 7, 81],
+ [110.00, 5, 74],
+ [105.00, 2, 69],
+ [101.00, 67, 67],
+ # mid
+ [99.00, 102, 102],
+ [95.00, 3, 105],
+ [90.00, 3, 108],
+ [85.00, 5, 113],
+ [70.00, 15, 128],
+ ]
+
+ orderbook_content_10 = [
+ [130, 13, 94],
+ [120, 7, 81],
+ [110, 7, 74],
+ [100, 67, 67],
+ # mid
+ [100, 105, 105],
+ [90, 8, 113],
+ [70, 15, 128],
+ ]
+
+ orderbook_content_100 = [
+ [100, 94, 94],
+ # mid
+ [100, 128, 128],
+ ]
+
+ resolutions = [
+ ["0.00", orderbook_content_0_00],
+ ["10", orderbook_content_10],
+ ["100", orderbook_content_100],
+ ]
+
+ page.goto(f"/#/markets/{market_id}")
+ # temporary skip
+ # for resolution in resolutions:
+ # page.get_by_test_id("resolution").click()
+ # page.get_by_role("menu").get_by_text(resolution[0], exact=True).click()
+ # verify_orderbook_grid(page, resolution[1])
+
+
+@pytest.mark.usefixtures("page", "risk_accepted")
+def test_orderbook_price_size_copy(setup_market, page: Page):
+ market_id = setup_market[1]
+ # 6003-ORDB-009
+ prices = page.get_by_test_id("tab-orderbook").locator('[data-testid^="price-"]')
+ volumes = page.get_by_test_id("tab-orderbook").locator('[data-testid*="-vol-"]')
+
+ page.goto(f"/#/markets/{market_id}")
+ prices.first.wait_for(state="visible")
+
+ for price in prices.all():
+ price.click()
+ expect(page.get_by_test_id("order-price")).to_have_value(price.text_content())
+
+ for volume in volumes.all():
+ 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")
+def test_orderbook_price_movement(setup_market, page: Page):
+ vega = setup_market[0]
+ market_id = setup_market[1]
+
+ page.goto(f"/#/markets/{market_id}")
+ page.locator("[data-testid=Orderbook]").click()
+
+ book_el = page.locator("[data-testid=orderbook-grid-element]")
+
+ # no arrow shown on load
+ expect(book_el.locator("[data-testid^=icon-arrow]")).not_to_be_attached()
+
+ matching_order_1 = [1, 101]
+ submit_order(
+ vega,
+ MM_WALLET2.name,
+ market_id,
+ "SIDE_BUY",
+ matching_order_1[0],
+ matching_order_1[1],
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ # 6003-ORDB-013
+ expect(book_el.locator("[data-testid=icon-arrow-up]")).to_be_attached()
+ assert (
+ float(page.locator("[data-testid*=last-traded]").text_content())
+ == matching_order_1[1]
+ )
+
+ matching_order_2 = [1, 99]
+ submit_order(
+ vega,
+ MM_WALLET2.name,
+ market_id,
+ "SIDE_SELL",
+ matching_order_2[0],
+ matching_order_2[1],
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ expect(book_el.locator("[data-testid=icon-arrow-down]")).to_be_attached()
+
+ assert (
+ float(page.locator("[data-testid*=last-traded]").text_content())
+ == matching_order_2[1]
+ )
diff --git a/apps/trading/e2e/tests/pnl/test_pnl.py b/apps/trading/e2e/tests/pnl/test_pnl.py
new file mode 100644
index 000000000..909ea4422
--- /dev/null
+++ b/apps/trading/e2e/tests/pnl/test_pnl.py
@@ -0,0 +1,110 @@
+import pytest
+from playwright.sync_api import Page
+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")
+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)
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ page.goto(f"/#/markets/{continuous_market}")
+ # Loss Trading unrealised
+ row = (
+ page.get_by_test_id("tab-positions")
+ .locator(".ag-center-cols-container .ag-row")
+ .nth(0)
+ )
+ realised_pnl = row.locator("[col-id='realisedPNL']")
+ unrealised_pnl = row.locator("[col-id='unrealisedPNL']")
+
+ check_pnl_color_value(realised_pnl, "rgb(0, 0, 0)", "0.00")
+ check_pnl_color_value(unrealised_pnl, "rgb(236, 0, 60)", "-4.00")
+
+ # profit Trading unrealised
+ change_keys(page, vega, "market_maker")
+ check_pnl_color_value(realised_pnl, "rgb(0, 0, 0)", "0.00")
+ check_pnl_color_value(unrealised_pnl, "rgb(1, 145, 75)", "4.00")
+
+ # neutral Trading unrealised
+ change_keys(page, vega, "market_maker_2")
+ check_pnl_color_value(realised_pnl, "rgb(0, 0, 0)", "0.00")
+ check_pnl_color_value(unrealised_pnl, "rgb(0, 0, 0)", "0.00")
+
+ # Portfolio Unrealised
+ page.get_by_role("link", name="Portfolio").click()
+ page.get_by_test_id("Positions").click()
+ page.wait_for_selector(
+ '[data-testid="tab-positions"] .ag-center-cols-container .ag-row',
+ state="visible",
+ )
+
+ key_1 = page.query_selector(
+ '//div[@role="row" and .//div[@col-id="partyId"]/div/span[text()="Key 1"]]'
+ )
+ key_mm = page.query_selector(
+ '//div[@role="row" and .//div[@col-id="partyId"]/div/span[text()="market_maker"]]'
+ )
+ key_mm2 = page.query_selector(
+ '//div[@role="row" and .//div[@col-id="partyId"]/div/span[text()="market_maker_2"]]'
+ )
+
+ 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_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_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")
+
+ check_pnl_color_value(key_mm_realised_pnl, "rgb(0, 0, 0)", "0.00")
+ check_pnl_color_value(key_mm_unrealised_pnl, "rgb(1, 145, 75)", "4.00")
+
+ check_pnl_color_value(key_mm2_realised_pnl, "rgb(0, 0, 0)", "0.00")
+ check_pnl_color_value(key_mm2_unrealised_pnl, "rgb(0, 0, 0)", "0.00")
+
+ submit_order(vega, "Key 1", continuous_market, "SIDE_SELL", 2, 101.50000)
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ check_pnl_color_value(key_1_realised_pnl, "rgb(236, 0, 60)", "-8.00")
+ check_pnl_color_value(key_1_unrealised_pnl, "rgb(0, 0, 0)", "0.00")
+
+ check_pnl_color_value(key_mm_realised_pnl, "rgb(1, 145, 75)", "8.00")
+ check_pnl_color_value(key_mm_unrealised_pnl, "rgb(0, 0, 0)", "0.00")
+
+ check_pnl_color_value(key_mm2_realised_pnl, "rgb(0, 0, 0)", "0.00")
+ check_pnl_color_value(key_mm2_unrealised_pnl, "rgb(0, 0, 0)", "0.00")
+
+ page.get_by_role("link", name="Trading").click()
+
+ row = (
+ page.get_by_test_id("tab-positions")
+ .locator(".ag-center-cols-container .ag-row")
+ .nth(0)
+ )
+ realised_pnl = row.locator("[col-id='realisedPNL']")
+ unrealised_pnl = row.locator("[col-id='unrealisedPNL']")
+
+ # neutral trading realised
+ check_pnl_color_value(realised_pnl, "rgb(0, 0, 0)", "0.00")
+ check_pnl_color_value(unrealised_pnl, "rgb(0, 0, 0)", "0.00")
+
+ # profit trading realised
+ change_keys(page, vega, "market_maker")
+ check_pnl_color_value(realised_pnl, "rgb(1, 145, 75)", "8.00")
+ check_pnl_color_value(unrealised_pnl, "rgb(0, 0, 0)", "0.00")
+
+ # loss trading realised
+ change_keys(page, vega, "Key 1")
+ check_pnl_color_value(realised_pnl, "rgb(236, 0, 60)", "-8.00")
+ check_pnl_color_value(unrealised_pnl, "rgb(0, 0, 0)", "0.00")
diff --git a/apps/trading/e2e/tests/portfolio/test_ledger_entries.py b/apps/trading/e2e/tests/portfolio/test_ledger_entries.py
new file mode 100644
index 000000000..b6237f50c
--- /dev/null
+++ b/apps/trading/e2e/tests/portfolio/test_ledger_entries.py
@@ -0,0 +1,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")
+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
+ 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
+ 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}"
+
diff --git a/apps/trading/e2e/tests/positions/test_collateral.py b/apps/trading/e2e/tests/positions/test_collateral.py
new file mode 100644
index 000000000..3a6ff19c4
--- /dev/null
+++ b/apps/trading/e2e/tests/positions/test_collateral.py
@@ -0,0 +1,51 @@
+import pytest
+from playwright.sync_api import Page, expect
+from vega_sim.service import VegaService
+from conftest import init_vega
+from fixtures.market import setup_continuous_market
+
+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')
+
+ # Verify headers
+ 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(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%)")
+
+ # 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")]
+
+ 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()
diff --git a/apps/trading/e2e/tests/positions/test_positions.py b/apps/trading/e2e/tests/positions/test_positions.py
new file mode 100644
index 000000000..039b7d1b3
--- /dev/null
+++ b/apps/trading/e2e/tests/positions/test_positions.py
@@ -0,0 +1,29 @@
+import pytest
+from playwright.sync_api import Page, expect
+from vega_sim.service import VegaService
+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)
+
+ vega.submit_termination_and_settlement_data(
+ settlement_key="FJMKnwfZdd48C8NqvYrG",
+ settlement_price=110,
+ market_id=market_id,
+ )
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ page.goto(f"/#/markets/{market_id}")
+ expect(page.locator(".ag-overlay-panel")).to_have_text("No positions")
+ page.get_by_test_id("open-transfer").click()
+ tab = page.get_by_test_id("tab-positions")
+ table = tab.locator(".ag-center-cols-container")
+ market = table.locator("[col-id='marketCode']")
+ 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")
+
\ No newline at end of file
diff --git a/apps/trading/e2e/tests/settings/test_settings.py b/apps/trading/e2e/tests/settings/test_settings.py
new file mode 100644
index 000000000..79adc82cc
--- /dev/null
+++ b/apps/trading/e2e/tests/settings/test_settings.py
@@ -0,0 +1,61 @@
+import pytest
+from playwright.sync_api import expect, Page
+from conftest import init_vega
+
+
+@pytest.fixture(scope="module")
+def vega():
+ with init_vega() as vega:
+ yield vega
+
+
+@pytest.mark.usefixtures("page", "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")
+
+ telemetry_switch.click()
+ expect(telemetry_switch).to_have_attribute("data-state", "checked")
+ page.reload()
+ page.get_by_test_id("Settings").click()
+ expect(telemetry_switch).to_have_attribute("data-state", "unchecked")
+
+ telemetry_switch.click()
+ expect(telemetry_switch).to_have_attribute("data-state", "checked")
+ page.reload()
+ page.get_by_test_id("Settings").click()
+ expect(telemetry_switch).to_have_attribute("data-state", "unchecked")
+
+
+# Define a mapping of icon selectors to toast selectors
+ICON_TO_TOAST = {
+ 'aria-label="arrow-top-left icon"': 'class="relative flex-1 overflow-auto p-4 pr-[40px] [&>p]:mb-[2.5px]"',
+ 'aria-label="arrow-up icon"': 'class="relative flex-1 overflow-auto p-4 pr-[40px] [&>p]:mb-[2.5px]"',
+ 'aria-label="arrow-top-right icon"': 'class="relative flex-1 overflow-auto p-4 pr-[40px] [&>p]:mb-[2.5px]"',
+ 'aria-label="arrow-bottom-left icon"': 'class="relative flex-1 overflow-auto p-4 pr-[40px] [&>p]:mb-[2.5px]"',
+ 'aria-label="arrow-down icon"': 'class="relative flex-1 overflow-auto p-4 pr-[40px] [&>p]:mb-[2.5px]"',
+ 'aria-label="arrow-bottom-right icon"': 'class="relative flex-1 overflow-auto p-4 pr-[40px] [&>p]:mb-[2.5px]"',
+}
+
+
+@pytest.mark.usefixtures("page", "risk_accepted")
+def test_toast_positions(page: Page):
+ page.goto("/")
+ page.get_by_test_id("Settings").click()
+ for icon_selector, toast_selector in ICON_TO_TOAST.items():
+ # Click the icon
+ page.click(f"[{icon_selector}]")
+ # Expect that the toast is displayed
+ expect(page.locator(f"[{toast_selector}]")).to_be_visible()
+
+
+@pytest.mark.usefixtures("page", "risk_accepted")
+def test_dark_mode(page: Page):
+ page.goto("/")
+ page.get_by_test_id("Settings").click()
+ expect(page.locator("html")).not_to_have_attribute("class", "dark")
+ page.locator("#switch-settings-theme-switch").click()
+ expect(page.locator("html")).to_have_attribute("class", "dark")
diff --git a/apps/trading/e2e/tests/successor_market/test_succession_line.py b/apps/trading/e2e/tests/successor_market/test_succession_line.py
new file mode 100644
index 000000000..7dc27cc20
--- /dev/null
+++ b/apps/trading/e2e/tests/successor_market/test_succession_line.py
@@ -0,0 +1,47 @@
+import pytest
+from playwright.sync_api import Page, expect
+from vega_sim.service import VegaService
+from fixtures.market import setup_continuous_market, setup_simple_successor_market
+
+
+@pytest.fixture
+@pytest.mark.usefixtures("vega")
+def successor_market(vega: VegaService):
+ parent_market_id = setup_continuous_market(vega)
+ tdai_id = vega.find_asset_id(symbol="tDAI")
+ successor_market_id = setup_simple_successor_market(
+ vega, parent_market_id, tdai_id, "successor_market"
+ )
+ vega.submit_termination_and_settlement_data(
+ settlement_key="FJMKnwfZdd48C8NqvYrG",
+ settlement_price=110,
+ market_id=parent_market_id,
+ )
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ return successor_market_id
+
+
+
+@pytest.mark.usefixtures("page", "risk_accepted")
+def test_succession_line(page: Page, successor_market):
+ page.goto(f"/#/markets/{successor_market}")
+ page.get_by_test_id("Info").click()
+ page.get_by_text("Succession line").click()
+
+ expect(page.get_by_test_id("succession-line-item").first).to_contain_text(
+ "BTC:DAI_2023BTC:DAI_2023"
+ )
+ expect(
+ page.get_by_test_id("succession-line-item").first.get_by_role("link")
+ ).to_be_attached
+ expect(page.get_by_test_id("succession-line-item").last).to_contain_text(
+ "successor_marketsuccessor_market"
+ )
+ expect(
+ page.get_by_test_id("succession-line-item").last.get_by_role("link")
+ ).to_be_attached
+ expect(
+ page.get_by_test_id("succession-line-item").last.get_by_test_id("icon-bullet")
+ ).to_be_visible
diff --git a/apps/trading/e2e/tests/trade_history/test_trade_history.py b/apps/trading/e2e/tests/trade_history/test_trade_history.py
new file mode 100644
index 000000000..e1e566afb
--- /dev/null
+++ b/apps/trading/e2e/tests/trade_history/test_trade_history.py
@@ -0,0 +1,76 @@
+import pytest
+import re
+import logging
+from playwright.sync_api import expect
+from actions.vega import submit_order
+from conftest import init_vega
+from playwright.sync_api import Page
+from vega_sim.null_service import VegaService
+
+logger = logging.getLogger()
+
+
+@pytest.fixture(scope="module")
+def vega():
+ with init_vega() as vega:
+ yield vega
+
+
+# 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()
+ # Required so that we can get liquidation price
+ expect(
+ page.locator(
+ f'[data-testid^="tab-{data_test_id.lower()}"] >> .ag-center-cols-container .ag-row-first'
+ )
+ ).to_be_visible()
+ actual_text = page.locator(
+ f'[data-testid^="tab-{data_test_id.lower()}"] >> .ag-center-cols-container'
+ ).text_content()
+ lines = actual_text.strip().split("\n")
+ for expected, actual in zip(expected_pattern, lines):
+ # We are using regex so that we can run tests in different timezones.
+ if re.match(r"^\\d", expected): # check if it's a regex
+ if re.search(expected, actual):
+ logger.info(f"Matched: {expected} == {actual}")
+ else:
+ logger.info(f"Not Matched: {expected} != {actual}")
+ raise AssertionError(f"Pattern does not match: {expected} != {actual}")
+ else: # it's not a regex, so we escape it
+ if re.search(re.escape(expected), actual):
+ logger.info(f"Matched: {expected} == {actual}")
+ else:
+ logger.info(f"Not Matched: {expected} != {actual}")
+ 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):
+ submit_order(vega, "Key 1", continuous_market, "SIDE_BUY", 1, 110)
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+ page.goto(f"/#/markets/{continuous_market}")
+ expected_trade = [
+ "103.50",
+ "1",
+ r"\d{1,2}/\d{1,2}/\d{4},\s*\d{1,2}:\d{2}:\d{2}\s*(?:AM|PM)" "107.50",
+ "1",
+ r"\d{1,2}/\d{1,2}/\d{4},\s*\d{1,2}:\d{2}:\d{2}\s*(?:AM|PM)",
+ ]
+ # 6005-THIS-001
+ # 6005-THIS-002
+ # 6005-THIS-003
+ # 6005-THIS-004
+ # 6005-THIS-005
+ # 6005-THIS-006
+ verify_data_grid(page, "Trades", expected_trade)
+
+
+@pytest.mark.usefixtures("page", "continuous_market", "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()
+ page.locator("[col-id=price]").last.click()
+ # 6005-THIS-007
+ expect(page.get_by_test_id("order-price")).to_have_value("107.50000")
diff --git a/apps/trading/e2e/tests/trade_match/test_trade_match.py b/apps/trading/e2e/tests/trade_match/test_trade_match.py
new file mode 100644
index 000000000..9df8b251b
--- /dev/null
+++ b/apps/trading/e2e/tests/trade_match/test_trade_match.py
@@ -0,0 +1,214 @@
+import pytest
+from playwright.sync_api import expect, Page
+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"
+)
+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}")
+
+ # sending order to be rejected, wait=False to avoid returning error from market-sim
+ vega.submit_order(
+ trading_key="Key 1",
+ market_id=opening_auction_market,
+ time_in_force="TIME_IN_FORCE_GTC",
+ order_type="TYPE_LIMIT",
+ side="SIDE_BUY",
+ volume=1,
+ price=10e15,
+ wait=False,
+ )
+
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ submit_multiple_orders(
+ vega,
+ "Key 1",
+ opening_auction_market,
+ "SIDE_BUY",
+ [[5, 110], [5, 105], [1, 50]],
+ )
+ vega.forward("10s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ submit_multiple_orders(
+ vega,
+ "Key 1",
+ opening_auction_market,
+ "SIDE_SELL",
+ [[5, 90], [5, 95], [1, 150]],
+ )
+ vega.forward("60s")
+ vega.wait_fn(1)
+ vega.wait_for_total_catchup()
+
+ # Positions
+ position = {
+ "market_code": "BTC:DAI_2023",
+ "settlement_asset": "tDAI",
+ "product_type": "Futr",
+ "size": "+2",
+ "notional": "220.00",
+ "average_entry_price": "110.00",
+ "mark_price": "110.00",
+ "margin": "93.52953",
+ "leverage": "1.0x",
+ "liquidation": "0.00",
+ "realised_pnl": "0.00",
+ "unrealised_pnl": "0.00",
+ }
+ page.goto(f"/#/markets/{opening_auction_market}")
+ # 7004-POSI-001
+ # 7004-POSI-002
+ primary_id = "stack-cell-primary"
+ secondary_id = "stack-cell-secondary"
+
+ tab = page.get_by_test_id("tab-positions")
+ table = tab.locator(".ag-center-cols-container")
+
+ market = table.locator("[col-id='marketCode']")
+ expect(market.get_by_test_id(primary_id)).to_have_text(position["market_code"])
+ expect(market.get_by_test_id(secondary_id)).to_have_text(
+ position["settlement_asset"] + position["product_type"]
+ )
+ size_and_notional = table.locator("[col-id='openVolume']")
+ expect(size_and_notional.get_by_test_id(primary_id)).to_have_text(position["size"])
+ expect(size_and_notional.get_by_test_id(secondary_id)).to_have_text(
+ position["notional"]
+ )
+
+ entry_and_mark = table.locator("[col-id='markPrice']")
+ expect(entry_and_mark.get_by_test_id(primary_id)).to_have_text(
+ position["average_entry_price"]
+ )
+ expect(entry_and_mark.get_by_test_id(secondary_id)).to_have_text(
+ position["mark_price"]
+ )
+
+ margin_and_leverage = table.locator("[col-id='margin']")
+ expect(margin_and_leverage.get_by_test_id(primary_id)).to_have_text(
+ position["margin"]
+ )
+ expect(margin_and_leverage.get_by_test_id(secondary_id)).to_have_text(
+ position["leverage"]
+ )
+ liquidation = table.locator("[col-id='liquidationPrice']")
+ expect(liquidation.get_by_test_id("liquidation-price")).to_have_text(
+ position["liquidation"]
+ )
+
+ realisedPNL = table.locator("[col-id='realisedPNL']")
+ expect(realisedPNL).to_have_text(position["realised_pnl"])
+
+ unrealisedPNL = table.locator("[col-id='unrealisedPNL']")
+ expect(unrealisedPNL).to_have_text(position["unrealised_pnl"])
+
+ # Open
+ page.get_by_test_id("Open").click()
+ rows = page.get_by_test_id("tab-open-orders").locator(row_locator).all()
+ expect(rows[0]).to_contain_text(
+ "BTC:DAI_2023Futr" + "0" + "-1" + "Limit" + "Active" + "150.00" + "GTC"
+ )
+ expect(rows[1]).to_contain_text(
+ "BTC:DAI_2023Futr" + "0" + "+1" + "Limit" + "Active" + "50.00" + "GTC"
+ )
+ expect(rows[2]).to_contain_text(
+ "BTC:DAI_2023Futr" + "0" + "+5" + "Limit" + "Active" + "105.00" + "GTC"
+ )
+
+ # Closed
+ page.get_by_test_id("Closed").click()
+ rows = page.get_by_test_id("tab-closed-orders").locator(row_locator).all()
+ expect(rows[0]).to_contain_text(
+ "BTC:DAI_2023Futr" + "0" + "-5" + "Limit" + "Filled" + "95.00" + "GTC"
+ )
+ expect(rows[1]).to_contain_text(
+ "BTC:DAI_2023Futr" + "5" + "-5" + "Limit" + "Filled" + "90.00" + "GTC"
+ )
+ expect(rows[2]).to_contain_text(
+ "BTC:DAI_2023Futr" + "5" + "+5" + "Limit" + "Filled" + "110.00" + "GTC"
+ )
+
+ # Rejected
+ page.get_by_test_id("Rejected").click()
+ expect(
+ page.get_by_test_id("tab-rejected-orders").locator(row_locator)
+ ).to_contain_text(
+ "BTC:DAI_2023Futr"
+ + "0"
+ + "+1"
+ + "Limit"
+ + "Rejected: Margin check failed"
+ + "10,000,000,000,000,000.00"
+ + "GTC"
+ )
+
+ # All
+ page.get_by_test_id("All").click()
+ rows = page.get_by_test_id("tab-orders").locator(row_locator).all()
+ expect(rows[0]).to_contain_text(
+ "BTC:DAI_2023Futr" + "0" + "-1" + "Limit" + "Active" + "150.00" + "GTC"
+ )
+ expect(rows[1]).to_contain_text(
+ "BTC:DAI_2023Futr" + "5" + "-5" + "Limit" + "Filled" + "95.00" + "GTC"
+ )
+ expect(rows[2]).to_contain_text(
+ "BTC:DAI_2023Futr" + "5" + "-5" + "Limit" + "Filled" + "90.00" + "GTC"
+ )
+ expect(rows[3]).to_contain_text(
+ "BTC:DAI_2023Futr"
+ + "0"
+ + "+1"
+ + "Limit"
+ + "Rejected: Margin check failed"
+ + "10,000,000,000,000,000.00"
+ + "GTC"
+ )
+ expect(rows[4]).to_contain_text(
+ "BTC:DAI_2023Futr" + "0" + "+1" + "Limit" + "Active" + "50.00" + "GTC"
+ )
+ expect(rows[5]).to_contain_text(
+ "BTC:DAI_2023Futr" + "1" + "+5" + "Limit" + "Active" + "105.00" + "GTC"
+ )
+ expect(rows[6]).to_contain_text(
+ "BTC:DAI_2023Futr" + "5" + "+5" + "Limit" + "Filled" + "110.00" + "GTC"
+ )
+
+ # Stop Orders
+ page.get_by_test_id("Stop orders").click()
+ expect(page.get_by_test_id("tab-stop-orders")).to_be_visible()
+ expect(page.get_by_test_id("tab-stop-orders").locator(row_locator)).to_be_visible(
+ visible=False
+ )
+
+ # Fills
+ page.get_by_test_id("Fills").click()
+ rows = page.get_by_test_id("tab-fills").locator(row_locator).all()
+ expect(rows[0]).to_contain_text(
+ "BTC:DAI_2023Futr"
+ + "-5"
+ + "106.50 tDAI"
+ + "532.50 tDAI"
+ + "Taker"
+ + "53.51625 tDAI"
+ )
+ expect(rows[1]).to_contain_text(
+ "BTC:DAI_2023Futr" + "+1" + "105.00 tDAI" + "105.00 tDAI" + "-" + "0.00 tDAI"
+ )
+ expect(rows[2]).to_contain_text(
+ "BTC:DAI_2023Futr" + "+5" + "105.00 tDAI" + "525.00 tDAI" + "-" + "0.00 tDAI"
+ )
+
+ # Collateral
+ page.get_by_test_id("Collateral").click()
+ expect(
+ page.get_by_test_id("tab-accounts").locator(".ag-floating-top-viewport .ag-row")
+ ).to_contain_text("tDAI" + "43.94338" + "0.00%" + "999,904.04037" + "999,947.98375")
diff --git a/apps/trading/e2e/tests/trading_chart/test_trading_chart.py b/apps/trading/e2e/tests/trading_chart/test_trading_chart.py
new file mode 100644
index 000000000..60c00a16c
--- /dev/null
+++ b/apps/trading/e2e/tests/trading_chart/test_trading_chart.py
@@ -0,0 +1,139 @@
+# import pytest
+# import re
+# from collections import namedtuple
+# from playwright.sync_api import Page
+# from vega_sim.service import VegaService
+# from actions.vega import submit_order
+
+# import logging
+
+# logger = logging.getLogger()
+
+# InfoItem = namedtuple('InfoItem', ['name', 'infoText'])
+
+# @pytest.mark.skip("temporary skip")
+# @pytest.mark.parametrize("vega", [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}")
+# vega.forward("24h")
+# vega.wait_for_total_catchup()
+# submit_order(vega, "market_maker", continuous_market, "SIDE_SELL", 1, 101.50000)
+# submit_order(vega, "market_maker_2", continuous_market, "SIDE_SELL", 1, 101.50000)
+# vega.forward("10s")
+# vega.wait_for_total_catchup()
+
+
+# page.click("button[aria-haspopup='menu']:has-text('Interval:')")
+# page.click(f"div[role='menuitemradio']:text-is('15m')")
+# page.wait_for_selector(".indicator-info-wrapper:visible")
+# # Check chart views and select
+# chart = "[aria-label$='chart icon']"
+# valid_chart_views = ['Mountain', 'Candlestick', 'Line', 'OHLC']
+# #6004-CHAR-002
+# #6004-CHAR-003
+# check_menu_items(page, chart, valid_chart_views, 'Candlestick')
+
+# # Check study info
+# study_info = [
+# InfoItem("Eldar-ray","Eldar-ray: Bull -5.14286Bear -5.14286"),
+# InfoItem("Force index", "Force index: -0.85714"),
+# InfoItem("MACD", "MACD: S -0.09573D -0.38291MACD -0.47863"),
+# InfoItem("RSI", "RSI: 0.00000"),
+# InfoItem("Volume", "Volume: 1")
+# ]
+# """Preparation steps to check study info on the page."""
+# element = page.locator(".plot-area-interaction").nth(1)
+# element.hover()
+
+# page.click(".pane__close-button-wrapper")
+
+# info_items = page.query_selector_all(".plot-area")
+
+# assert (len(info_items)) == 1
+# #6004-CHAR-005
+# #6004-CHAR-006
+# #6004-CHAR-007
+# #6004-CHAR-042
+# #6004-CHAR-045
+# #6004-CHAR-047
+# #6004-CHAR-049
+# #6004-CHAR-051
+# page.mouse.wheel(0, 10)
+# check_menu_item_checkbox(page, "Studies", study_info)
+# page.get_by_text("Studies").click(force=True)
+
+
+# # Check overlay info
+# overlay_info = [
+# InfoItem("Bollinger bands", "Bollinger: Upper 110.69473Lower 103.10527"),
+# InfoItem("Envelope", "Envelope: Upper 111.65000Lower 91.35000"),
+# InfoItem("EMA", "EMA: 106.30000"),
+# InfoItem("Moving average", "Moving average: 106.90000"),
+# InfoItem("Price monitoring bounds", "Price Monitoring Bounds 1: Min 83.11038Max 138.66685Reference 107.50000")
+# ]
+# #6004-CHAR-004
+# #6004-CHAR-008
+# #6004-CHAR-009
+# #6004-CHAR-034
+# #6004-CHAR-037
+# #6004-CHAR-039
+# #6004-CHAR-041
+# check_menu_item_checkbox(page, "Overlays", overlay_info)
+
+# # Check chart info
+# # 6004-CHAR-010
+# expected_date_regex = r"^\d{2}:\d{2} \d{2} [A-Za-z]{3} \d{4}$"
+# expected_ohlc = "O 101.50000H 101.50000L 101.50000C 101.50000Change −6.00000(−5.58%)"
+# indicator_info_locator = page.locator(".indicator-info-wrapper").nth(0)
+# texts = indicator_info_locator.all_text_contents()
+# combined_text = ''.join(texts)
+# actual_date = combined_text[:-67]
+# actual_ohlc = combined_text[-67:]
+# logger.info(actual_date)
+# logger.info(actual_ohlc)
+# assert re.match(expected_date_regex, actual_date)
+# assert actual_ohlc == expected_ohlc
+# # Check interval options and select '15m'
+# interval = "button[aria-haspopup='menu']:has-text('Interval:')"
+# valid_intervals = ['1m', '5m', '15m', '1H', '6H', '1D']
+# #6004-CHAR-001
+# page.click("button[aria-haspopup='menu']:has-text('Interval:')", force=True)
+# check_menu_items(page, interval, valid_intervals, '1m')
+
+
+# def check_menu_items(page, trigger_selector, valid_texts, click_item=None):
+# page.click(trigger_selector, force=True)
+# items = page.locator("div[role='menuitemradio']").all()
+# assert len(items) == len(valid_texts), f"Expected {len(valid_texts)} items but found {len(items)} items."
+
+# for i, el in enumerate(items):
+# text = el.text_content().strip()
+# assert text == valid_texts[i], f"Expected text '{valid_texts[i]}' but found '{text}'."
+# if click_item:
+# page.click(f"div[role='menuitemradio']:text-is('{click_item}')")
+# page.click(trigger_selector)
+# checked_item_text = page.text_content("div[role='menuitemradio'][data-state='checked']").strip()
+# assert checked_item_text == click_item, f"Expected checked item text '{click_item}' but found '{checked_item_text}'."
+# page.click(trigger_selector, force=True)
+
+# def check_menu_item_checkbox(page, button_text, items):
+# button_selector = f"button:has-text('{button_text}')"
+
+# for item in items:
+# page.click(button_selector)
+# page.click(f"div[role='menuitemcheckbox']:has-text('{item.name}')")
+
+# page.click(button_selector)
+# checkbox_items = page.query_selector_all("div[role='menuitemcheckbox']")
+
+# assert len(checkbox_items) == len(items), f"Expected {len(items)} checkboxes but found {len(checkbox_items)}."
+
+# for i, el in enumerate(checkbox_items):
+# text = el.text_content().strip()
+# assert text == items[i].name, f"Expected checkbox text '{items[i].name}' but found '{text}'."
+
+# for i, item in enumerate(items[0:]):
+# info_locator = page.locator(".indicator-info-wrapper").nth(i+1)
+# info_text = info_locator.text_content().strip()
+# assert info_text == item.infoText, f"Expected info text '{item.infoText}' but found '{info_text}'."
diff --git a/apps/trading/e2e/tests/transfer/test_transfer_key_to_key.py b/apps/trading/e2e/tests/transfer/test_transfer_key_to_key.py
new file mode 100644
index 000000000..c2b3e3826
--- /dev/null
+++ b/apps/trading/e2e/tests/transfer/test_transfer_key_to_key.py
@@ -0,0 +1,132 @@
+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
+import vega_sim.proto.vega as vega_protos
+
+LIQ = WalletConfig("liq", "liq")
+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")
+def test_transfer_submit(continuous_market, vega: VegaService, page: Page):
+ # 1003-TRAN-001
+ # 1003-TRAN-006
+ # 1003-TRAN-007
+ # 1003-TRAN-008
+ # 1003-TRAN-009
+ # 1003-TRAN-010
+ # 1003-TRAN-023
+ 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)
+
+ 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()
+
+ 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] [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}"
+
+
+@pytest.mark.usefixtures("page", "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"
+ )
+ vega.wait_for_total_catchup()
+
+ create_and_faucet_wallet(vega=vega, wallet=PARTY_A, amount=1e3)
+ create_and_faucet_wallet(vega=vega, wallet=PARTY_B, amount=1e5)
+ create_and_faucet_wallet(vega=vega, wallet=PARTY_C, amount=1e5)
+ vega.wait_for_total_catchup()
+
+ asset_id = vega.find_asset_id(symbol="tDAI")
+ next_epoch(vega=vega)
+
+ vega.recurring_transfer(
+ from_key_name=PARTY_A.name,
+ from_account_type=vega_protos.vega.ACCOUNT_TYPE_GENERAL,
+ to_account_type=vega_protos.vega.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
+ asset=asset_id,
+ asset_for_metric=asset_id,
+ metric=vega_protos.vega.DISPATCH_METRIC_MAKER_FEES_PAID,
+ amount=100,
+ factor=1.0,
+ )
+ # Generate trades for non-zero metrics
+ vega.submit_order(
+ trading_key=PARTY_B.name,
+ market_id=continuous_market,
+ order_type="TYPE_LIMIT",
+ time_in_force="TIME_IN_FORCE_GTC",
+ side="SIDE_SELL",
+ price=0.30,
+ volume=100,
+ )
+ vega.submit_order(
+ trading_key=PARTY_C.name,
+ market_id=continuous_market,
+ order_type="TYPE_LIMIT",
+ time_in_force="TIME_IN_FORCE_GTC",
+ side="SIDE_BUY",
+ price=0.30,
+ volume=100,
+ )
+ 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
+
+ change_keys(page, vega, "party_b")
+ 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")
+
+ 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] [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")
+ 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,
+ )
+ vega.forward("10s")
+ vega.wait_fn(10)
+ vega.wait_for_total_catchup()
+
+ page.get_by_text("Use max").first.click()
+ 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}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}"
diff --git a/apps/trading/e2e/tests/wallet/test_wallet.py b/apps/trading/e2e/tests/wallet/test_wallet.py
new file mode 100644
index 000000000..2f33742f7
--- /dev/null
+++ b/apps/trading/e2e/tests/wallet/test_wallet.py
@@ -0,0 +1,116 @@
+import pytest
+import re
+import json
+from playwright.sync_api import Page, expect, Route
+from vega_sim.service import VegaService
+from conftest import init_vega
+from fixtures.market import setup_continuous_market
+
+order_size = "order-size"
+order_price = "order-price"
+place_order = "place-order"
+order_side_sell = "order-side-SIDE_SELL"
+market_order = "order-type-Market"
+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:
+ yield vega
+
+
+@pytest.fixture(scope="module")
+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_()
+
+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_()
+
+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 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")
+
+@pytest.mark.usefixtures("page", "risk_accepted")
+def test_wallet_connection_rejected(continuous_market, page: Page):
+ # 0002-WCON-002
+ # 0002-WCON-005
+ # 0002-WCON-007
+ # 0002-WCON-015
+ page.goto(f"/#/markets/{continuous_market}")
+ 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 ")
+
+
+@pytest.mark.usefixtures("page", "auth", "risk_accepted")
+def test_wallet_connection_error_transaction(continuous_market, vega: VegaService, page: Page):
+ # 0003-WTXN-009
+ # 0003-WTXN-011
+ # 0002-WCON-016
+ # 0003-WTXN-008
+ 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_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):
+ # 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):
+ # 0002-WCON-005
+ # 0002-WCON-007
+ # 0002-WCON-009
+ 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("**/*", assert_connection_approve)
+ page.get_by_test_id(place_order).click()
\ No newline at end of file
diff --git a/apps/trading/e2e/wallet_config.py b/apps/trading/e2e/wallet_config.py
new file mode 100644
index 000000000..f85dd8374
--- /dev/null
+++ b/apps/trading/e2e/wallet_config.py
@@ -0,0 +1,12 @@
+from collections import namedtuple
+
+# Defined namedtuples
+WalletConfig = namedtuple("WalletConfig", ["name", "passphrase"])
+
+# Wallet Configurations
+MM_WALLET = WalletConfig("market_maker", "pin")
+MM_WALLET2 = WalletConfig("market_maker_2", "pin2")
+TERMINATE_WALLET = WalletConfig("FJMKnwfZdd48C8NqvYrG", "bY3DxwtsCstMIIZdNpKs")
+GOVERNANCE_WALLET = WalletConfig("FJMKnwfZdd48C8NqvYrG", "bY3DxwtsCstMIIZdNpKs")
+
+wallets = [MM_WALLET, MM_WALLET2, TERMINATE_WALLET, GOVERNANCE_WALLET]
diff --git a/apps/trading/lib/i18n/index.ts b/apps/trading/lib/i18n/index.ts
new file mode 100644
index 000000000..b637151b9
--- /dev/null
+++ b/apps/trading/lib/i18n/index.ts
@@ -0,0 +1,94 @@
+import type { Module } from 'i18next';
+import i18n from 'i18next';
+import HttpBackend from 'i18next-http-backend';
+import LocizeBackend from 'i18next-locize-backend';
+import type { HttpBackendOptions, RequestCallback } from 'i18next-http-backend';
+import LanguageDetector from 'i18next-browser-languagedetector';
+import { initReactI18next } from 'react-i18next';
+
+export const supportedLngs = ['en'];
+
+const isInDev = process.env.NODE_ENV === 'development';
+const useLocize = isInDev && !!process.env.NX_USE_LOCIZE;
+
+const backend = useLocize
+ ? {
+ projectId: '96ac1231-4bdd-455a-b9d7-f5322a2e7430',
+ apiKey: process.env.NX_LOCIZE_API_KEY,
+ referenceLng: 'en',
+ }
+ : {
+ loadPath: '/locales/{{lng}}/{{ns}}.json',
+ request: (
+ options: HttpBackendOptions,
+ url: string,
+ payload: string,
+ callback: RequestCallback
+ ) => {
+ if (typeof window === 'undefined') {
+ callback(false, { status: 200, data: {} });
+ return;
+ }
+ fetch(url).then((response) => {
+ if (!response.ok) {
+ return callback(response.statusText || 'Error', {
+ status: response.status,
+ data: {},
+ });
+ }
+ response
+ .text()
+ .then((data) => {
+ callback(null, { status: response.status, data });
+ })
+ .catch((error) => callback(error, { status: 200, data: {} }));
+ });
+ },
+ };
+
+const Backend: Module = useLocize ? LocizeBackend : HttpBackend;
+
+i18n
+ .use(Backend)
+ .use(LanguageDetector)
+ .use(initReactI18next)
+ .init({
+ fallbackLng: 'en',
+ supportedLngs,
+ load: 'languageOnly',
+ // have a common namespace used around the full app
+ ns: [
+ 'accounts',
+ 'assets',
+ 'candles-chart',
+ 'datagrid',
+ 'deal-ticket',
+ 'deposits',
+ 'environment',
+ 'fills',
+ 'funding-payments',
+ 'ledger',
+ 'liquidity',
+ 'market-depth',
+ 'markets',
+ 'orders',
+ 'positions',
+ 'trades',
+ 'trading',
+ 'ui-toolkit',
+ 'utils',
+ 'wallet',
+ 'web3',
+ ],
+ defaultNS: 'trading',
+ nsSeparator: false,
+ keySeparator: false, // we use content as keys
+ backend,
+ debug: isInDev,
+ saveMissing: useLocize && !!process.env.NX_LOCIZE_API_KEY,
+ interpolation: {
+ escapeValue: false,
+ },
+ });
+
+export default i18n;
diff --git a/apps/trading/lib/links.ts b/apps/trading/lib/links.ts
index f48128ec3..16f05519e 100644
--- a/apps/trading/lib/links.ts
+++ b/apps/trading/lib/links.ts
@@ -18,6 +18,7 @@ export const Routes = {
REFERRALS_CREATE_CODE: '/referrals/create-code',
TEAMS: '/teams',
FEES: '/fees',
+ REWARDS: '/rewards',
} as const;
type ConsoleLinks = {
@@ -42,4 +43,5 @@ export const Links: ConsoleLinks = {
REFERRALS_CREATE_CODE: () => Routes.REFERRALS_CREATE_CODE,
TEAMS: () => Routes.TEAMS,
FEES: () => Routes.FEES,
+ REWARDS: () => Routes.REWARDS,
};
diff --git a/apps/trading/lib/use-t.ts b/apps/trading/lib/use-t.ts
new file mode 100644
index 000000000..b9fdf6ce0
--- /dev/null
+++ b/apps/trading/lib/use-t.ts
@@ -0,0 +1,4 @@
+import { useTranslation } from 'react-i18next';
+export const ns = 'trading';
+export const useT = () => useTranslation('trading').t;
+export const useI18n = () => useTranslation('trading').i18n;
diff --git a/apps/trading/netlify.toml b/apps/trading/netlify.toml
deleted file mode 100644
index a92bfc034..000000000
--- a/apps/trading/netlify.toml
+++ /dev/null
@@ -1,2 +0,0 @@
-[functions]
- included_files = ["!node_modules/@swc/**/*"]
diff --git a/apps/trading/next.config.js b/apps/trading/next.config.js
index 4c6a4b4a4..dd4796f56 100644
--- a/apps/trading/next.config.js
+++ b/apps/trading/next.config.js
@@ -1,3 +1,4 @@
+const childProcess = require('child_process');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const withNx = require('@nx/next/plugins/with-nx');
const { withSentryConfig } = require('@sentry/nextjs');
@@ -10,6 +11,20 @@ const sentryWebpackOptions = {
token: SENTRY_AUTH_TOKEN,
};
+const commitHash = childProcess
+ .execSync('git rev-parse HEAD')
+ .toString()
+ .trim();
+
+// Get the tag of the last commit
+const commitLog = childProcess
+ .execSync('git log --decorate --oneline -1')
+ .toString()
+ .trim();
+
+const tagMatch = commitLog.match(/tag: ([^,)]+)/);
+const tag = tagMatch ? tagMatch[1] : '';
+
/**
* @type {import('@nx/next/plugins/with-nx').WithNxOptions}
**/
@@ -20,6 +35,10 @@ const nextConfig = {
svgr: false,
},
pageExtensions: ['page.tsx', 'page.jsx'],
+ env: {
+ GIT_COMMIT: commitHash,
+ GIT_TAG: tag,
+ },
};
module.exports = SENTRY_AUTH_TOKEN
diff --git a/apps/trading/pages/_app.page.tsx b/apps/trading/pages/_app.page.tsx
index b79919ed9..e2273fc90 100644
--- a/apps/trading/pages/_app.page.tsx
+++ b/apps/trading/pages/_app.page.tsx
@@ -1,14 +1,14 @@
-import { useMemo } from 'react';
+import { useMemo, Suspense } from 'react';
import Head from 'next/head';
import type { AppProps } from 'next/app';
-import { t } from '@vegaprotocol/i18n';
import {
- envTriggerMapping,
+ useEnvTriggerMapping,
Networks,
NodeSwitcherDialog,
useEnvironment,
useInitializeEnv,
useNodeSwitcherStore,
+ AppLoader,
} from '@vegaprotocol/environment';
import './styles.css';
import { usePageTitleStore } from '../stores';
@@ -32,14 +32,15 @@ import { SSRLoader } from './ssr-loader';
import { PartyActiveOrdersHandler } from './party-active-orders-handler';
import { MaybeConnectEagerly } from './maybe-connect-eagerly';
import { TransactionHandlers } from './transaction-handlers';
-
-const DEFAULT_TITLE = t('Welcome to Vega trading!');
+import { useT } from '../lib/use-t';
const Title = () => {
+ const t = useT();
+ const DEFAULT_TITLE = t('Welcome to Vega trading!');
const { pageTitle } = usePageTitleStore((store) => ({
pageTitle: store.pageTitle,
}));
-
+ const envTriggerMapping = useEnvTriggerMapping();
const { VEGA_ENV } = useEnvironment();
const networkName = envTriggerMapping[VEGA_ENV];
@@ -47,7 +48,7 @@ const Title = () => {
if (!pageTitle) return DEFAULT_TITLE;
if (networkName) return `${pageTitle} [${networkName}]`;
return pageTitle;
- }, [pageTitle, networkName]);
+ }, [pageTitle, networkName, DEFAULT_TITLE]);
return (
@@ -60,7 +61,7 @@ function AppBody({ Component }: AppProps) {
const location = useLocation();
const { VEGA_ENV } = useEnvironment();
const gridClasses = classNames(
- 'h-full relative z-0 grid',
+ 'grid relative h-full z-0',
'grid-rows-[repeat(3,min-content),minmax(0,1fr)]'
);
return (
@@ -123,12 +124,14 @@ function VegaTradingApp(props: AppProps) {
}
return (
-
-
-
-
-
-
+ }>
+
+
+
+
+
+
+
);
}
diff --git a/apps/trading/pages/client-router.tsx b/apps/trading/pages/client-router.tsx
index 52254d5d2..fe12ad27c 100644
--- a/apps/trading/pages/client-router.tsx
+++ b/apps/trading/pages/client-router.tsx
@@ -1,7 +1,6 @@
import type { RouteObject } from 'react-router-dom';
import { Navigate, useRoutes } from 'react-router-dom';
import { lazy, Suspense } from 'react';
-import { t } from '@vegaprotocol/i18n';
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
import { LayoutWithSidebar } from '../components/layouts';
import { LayoutCentered } from '../components/layouts/layout-centered';
@@ -14,11 +13,12 @@ import { Deposit } from '../client-pages/deposit';
import { Withdraw } from '../client-pages/withdraw';
import { Transfer } from '../client-pages/transfer';
import { Fees } from '../client-pages/fees';
+import { Rewards } from '../client-pages/rewards';
import { Routes as AppRoutes } from '../lib/links';
import { LayoutWithSky } from '../client-pages/referrals/layout';
import { Referrals } from '../client-pages/referrals/referrals';
import { ReferralStatistics } from '../client-pages/referrals/referral-statistics';
-import { ApplyCodeForm } from '../client-pages/referrals/apply-code-form';
+import { ApplyCodeFormContainer } from '../client-pages/referrals/apply-code-form';
import { CreateCodeContainer } from '../client-pages/referrals/create-code-form';
import { NotFound as ReferralNotFound } from '../client-pages/referrals/error-boundary';
import { compact } from 'lodash';
@@ -28,17 +28,21 @@ import { MarketHeader } from '../components/market-header';
import { PortfolioSidebar } from '../client-pages/portfolio/portfolio-sidebar';
import { LiquiditySidebar } from '../client-pages/liquidity/liquidity-sidebar';
import { MarketsSidebar } from '../client-pages/markets/markets-sidebar';
+import { useT } from '../lib/use-t';
// These must remain dynamically imported as pennant cannot be compiled by nextjs due to ESM
// Using dynamic imports is a workaround for this until pennant is published as ESM
const MarketPage = lazy(() => import('../client-pages/market'));
const Portfolio = lazy(() => import('../client-pages/portfolio'));
-const NotFound = () => (
-
- {t('Page not found')}
-
-);
+const NotFound = () => {
+ const t = useT();
+ return (
+
+ {t('Page not found')}
+
+ );
+};
export const routerConfig: RouteObject[] = compact([
{
@@ -75,7 +79,7 @@ export const routerConfig: RouteObject[] = compact([
},
{
path: AppRoutes.REFERRALS_APPLY_CODE,
- element: ,
+ element: ,
},
],
},
@@ -96,6 +100,16 @@ export const routerConfig: RouteObject[] = compact([
},
],
},
+ {
+ path: 'rewards/*',
+ element: } />,
+ children: [
+ {
+ index: true,
+ element: ,
+ },
+ ],
+ },
{
path: 'markets/*',
element: (
diff --git a/apps/trading/pages/toasts-manager.tsx b/apps/trading/pages/toasts-manager.tsx
index 5e424a121..25ad557cc 100644
--- a/apps/trading/pages/toasts-manager.tsx
+++ b/apps/trading/pages/toasts-manager.tsx
@@ -5,6 +5,7 @@ import { useEthereumTransactionToasts } from '@vegaprotocol/web3';
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';
export const ToastsManager = () => {
useProposalToasts();
@@ -14,6 +15,7 @@ export const ToastsManager = () => {
useReadyToWithdrawalToasts({
withdrawalsLink: Links.PORTFOLIO(),
});
+ useReferralToasts();
const toasts = useToasts((store) => store.toasts);
return ;
diff --git a/apps/trading/project.json b/apps/trading/project.json
index 094bbda24..671b013b9 100644
--- a/apps/trading/project.json
+++ b/apps/trading/project.json
@@ -41,32 +41,16 @@
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/apps/trading"],
"options": {
- "jestConfig": "apps/trading/jest.config.ts",
- "passWithNoTests": true
- },
- "configurations": {
- "ci": {
- "ci": true,
- "codeCoverage": true
- }
+ "jestConfig": "apps/trading/jest.config.ts"
}
},
"lint": {
- "executor": "@nx/linter:eslint",
+ "executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/trading/**/*.{ts,tsx,js,jsx}"]
}
},
- "build-netlify": {
- "executor": "nx:run-commands",
- "options": {
- "commands": [
- "cp apps/trading/netlify.toml netlify.toml",
- "nx build trading"
- ]
- }
- },
"build-spec": {
"executor": "nx:run-commands",
"outputs": [],
diff --git a/apps/trading/public/locales b/apps/trading/public/locales
new file mode 120000
index 000000000..b10372509
--- /dev/null
+++ b/apps/trading/public/locales
@@ -0,0 +1 @@
+../../../libs/i18n/src/locales
\ No newline at end of file
diff --git a/apps/trading/setup-tests.ts b/apps/trading/setup-tests.ts
index ebaab45f9..fd44664c8 100644
--- a/apps/trading/setup-tests.ts
+++ b/apps/trading/setup-tests.ts
@@ -2,6 +2,19 @@ import '@testing-library/jest-dom';
import 'jest-canvas-mock';
import ResizeObserver from 'resize-observer-polyfill';
import { defaultFallbackInView } from 'react-intersection-observer';
+import { locales } from '@vegaprotocol/i18n';
+import i18n from 'i18next';
+import { initReactI18next } from 'react-i18next';
+
+// Set up i18n instance so that components have the correct default
+// en translations
+i18n.use(initReactI18next).init({
+ // we init with resources
+ resources: locales,
+ fallbackLng: 'en',
+ ns: ['trading'],
+ defaultNS: 'trading',
+});
defaultFallbackInView(true);
global.ResizeObserver = ResizeObserver;
diff --git a/docker/prepare-dist.sh b/docker/prepare-dist.sh
index 9742b7a93..d311e962c 100755
--- a/docker/prepare-dist.sh
+++ b/docker/prepare-dist.sh
@@ -1,13 +1,19 @@
#!/bin/bash -e
yarn --pure-lockfile
app=${1:-trading}
-envCmd="envCmd="yarn -f ./apps/${app}/.env.${2:-mainnet}"
+
+envCmd="yarn -f ./apps/${app}/.env.${2:-mainnet}"
+
yarn install
+
if [ "${app}" = "trading" ]; then
- $envCmd yarn nx export trading
+ # Execute the command stored in envCmd and then run the nx export command
+ $envCmd && yarn nx export trading
DIST_LOCATION=dist/apps/trading/exported/
else
- $envCmd yarn nx build ${app}
+ # Execute the command stored in envCmd and then run the nx build command
+ $envCmd && yarn nx build ${app}
DIST_LOCATION=dist/apps/${app}
fi
+
cp -r $DIST_LOCATION dist-result
diff --git a/jest.preset.js b/jest.preset.js
index f078ddcec..46fdf3241 100644
--- a/jest.preset.js
+++ b/jest.preset.js
@@ -1,3 +1,10 @@
const nxPreset = require('@nx/jest/preset').default;
-module.exports = { ...nxPreset };
+module.exports = {
+ ...nxPreset,
+ moduleNameMapper: {
+ ...nxPreset.moduleNameMapper,
+ // this mapping fixes jest breaking if anything tries to import d3 due to esm exports
+ '^d3-(.*)$': 'd3-$1/dist/d3-$1',
+ },
+};
diff --git a/libs/accounts/.storybook/tsconfig.json b/libs/accounts/.storybook/tsconfig.json
deleted file mode 100644
index 9cb59597a..000000000
--- a/libs/accounts/.storybook/tsconfig.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "extends": "../tsconfig.json",
- "compilerOptions": {
- "emitDecoratorMetadata": true,
- "outDir": ""
- },
- "files": [
- "../../../node_modules/@nx/react/typings/styled-jsx.d.ts",
- "../../../node_modules/@nx/react/typings/cssmodule.d.ts",
- "../../../node_modules/@nx/react/typings/image.d.ts"
- ],
- "exclude": [
- "../**/*.spec.ts",
- "../**/*.spec.js",
- "../**/*.spec.tsx",
- "../**/*.spec.jsx",
- "jest.config.ts"
- ],
- "include": ["../src/**/*", "*.js"]
-}
diff --git a/libs/accounts/project.json b/libs/accounts/project.json
index 4a10cdc36..b2bb553ac 100644
--- a/libs/accounts/project.json
+++ b/libs/accounts/project.json
@@ -6,7 +6,7 @@
"tags": [],
"targets": {
"lint": {
- "executor": "@nx/linter:eslint",
+ "executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/accounts/**/*.{ts,tsx,js,jsx}"]
@@ -16,14 +16,7 @@
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/libs/accounts"],
"options": {
- "jestConfig": "libs/accounts/jest.config.ts",
- "passWithNoTests": true
- },
- "configurations": {
- "ci": {
- "ci": true,
- "codeCoverage": true
- }
+ "jestConfig": "libs/accounts/jest.config.ts"
}
},
"storybook": {
diff --git a/libs/accounts/src/lib/accounts-actions-dropdown.tsx b/libs/accounts/src/lib/accounts-actions-dropdown.tsx
index 9c0eb5d3a..82919ccee 100644
--- a/libs/accounts/src/lib/accounts-actions-dropdown.tsx
+++ b/libs/accounts/src/lib/accounts-actions-dropdown.tsx
@@ -1,5 +1,5 @@
import { ETHERSCAN_ADDRESS, useEtherscanLink } from '@vegaprotocol/environment';
-import { t } from '@vegaprotocol/i18n';
+import { useT } from './use-t';
import {
ActionsDropdown,
TradingDropdownCopyItem,
@@ -27,7 +27,7 @@ export const AccountsActionsDropdown = ({
}) => {
const etherscanLink = useEtherscanLink();
const openAssetDialog = useAssetDetailsDialogStore((store) => store.open);
-
+ const t = useT();
return (
account.asset.id === assetId
) || null
);
+
+export const useAccounts = (partyId: string | null) => {
+ return useDataProvider({
+ dataProvider: accountsDataProvider,
+ variables: {
+ partyId: partyId || '',
+ },
+ skip: !partyId,
+ });
+};
diff --git a/libs/accounts/src/lib/accounts-manager.tsx b/libs/accounts/src/lib/accounts-manager.tsx
index a05a74094..7de06c6d0 100644
--- a/libs/accounts/src/lib/accounts-manager.tsx
+++ b/libs/accounts/src/lib/accounts-manager.tsx
@@ -1,17 +1,17 @@
import { useRef, memo, useState, useCallback } from 'react';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
-import { t } from '@vegaprotocol/i18n';
+import { useT } from './use-t';
import { useDataProvider } from '@vegaprotocol/data-provider';
-import type { AgGridReact } from 'ag-grid-react';
+import { type AgGridReact } from 'ag-grid-react';
import {
aggregatedAccountsDataProvider,
aggregatedAccountDataProvider,
} from './accounts-data-provider';
-import type { PinnedAsset } from './accounts-table';
+import { type PinnedAsset } from './accounts-table';
import { AccountTable } from './accounts-table';
import { Dialog } from '@vegaprotocol/ui-toolkit';
import BreakdownTable from './breakdown-table';
-import type { useDataGridEvents } from '@vegaprotocol/datagrid';
+import { type useDataGridEvents } from '@vegaprotocol/datagrid';
const AccountBreakdown = ({
assetId,
@@ -22,6 +22,7 @@ const AccountBreakdown = ({
partyId: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
}) => {
+ const t = useT();
const gridRef = useRef(null);
const { data } = useDataProvider({
dataProvider: aggregatedAccountDataProvider,
@@ -37,18 +38,18 @@ const AccountBreakdown = ({
return (
-
+
{data?.asset?.symbol} {t('usage breakdown')}
{data && (
- {t('You have %s %s in total.', [
- addDecimalsFormatNumber(data.total, data.asset.decimals),
- data.asset.symbol,
- ])}
+ {t('You have {{value}} {{symbol}} in total.', {
+ value: addDecimalsFormatNumber(data.total, data.asset.decimals),
+ symbol: data.asset.symbol,
+ })}
)}
{
+ const t = useT();
const [breakdownAssetId, setBreakdownAssetId] = useState();
const { data, error } = useDataProvider({
dataProvider: aggregatedAccountsDataProvider,
diff --git a/libs/accounts/src/lib/accounts-table.tsx b/libs/accounts/src/lib/accounts-table.tsx
index e28f2aeea..b7a2583bb 100644
--- a/libs/accounts/src/lib/accounts-table.tsx
+++ b/libs/accounts/src/lib/accounts-table.tsx
@@ -5,7 +5,7 @@ import {
isNumeric,
toBigNum,
} from '@vegaprotocol/utils';
-import { t } from '@vegaprotocol/i18n';
+import { useT } from './use-t';
import type {
VegaICellRendererParams,
VegaValueFormatterParams,
@@ -96,6 +96,7 @@ export const AccountTable = ({
pinnedAsset,
...props
}: AccountTableProps) => {
+ const t = useT();
const pinnedRow = useMemo(() => {
if (!pinnedAsset) {
return;
@@ -191,7 +192,7 @@ export const AccountTable = ({
<>
{valueFormatted}
- {t('0.00%')}
+ {(0).toFixed(2)}%
>
);
@@ -310,6 +311,7 @@ export const AccountTable = ({
onClickTransfer,
isReadOnly,
showDepositButton,
+ t,
]);
const data = rowData?.filter((data) => data.asset.id !== pinnedAsset?.id);
diff --git a/libs/accounts/src/lib/breakdown-table.tsx b/libs/accounts/src/lib/breakdown-table.tsx
index cd7df26bf..24a62b12e 100644
--- a/libs/accounts/src/lib/breakdown-table.tsx
+++ b/libs/accounts/src/lib/breakdown-table.tsx
@@ -3,14 +3,14 @@ import {
addDecimalsFormatNumber,
addDecimalsFormatNumberQuantum,
} from '@vegaprotocol/utils';
-import { t } from '@vegaprotocol/i18n';
+import { useT } from './use-t';
import { Intent, TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
-import type { AgGridReact, AgGridReactProps } from 'ag-grid-react';
-import type { AccountFields } from './accounts-data-provider';
+import { type AgGridReact, type AgGridReactProps } from 'ag-grid-react';
+import { type AccountFields } from './accounts-data-provider';
import { AccountTypeMapping } from '@vegaprotocol/types';
-import type {
- VegaValueFormatterParams,
- VegaICellRendererParams,
+import {
+ type VegaValueFormatterParams,
+ type VegaICellRendererParams,
} from '@vegaprotocol/datagrid';
import { ProgressBarCell } from '@vegaprotocol/datagrid';
import { AgGrid, PriceCell } from '@vegaprotocol/datagrid';
@@ -31,6 +31,7 @@ interface BreakdownTableProps extends AgGridReactProps {
const BreakdownTable = forwardRef(
({ data }, ref) => {
+ const t = useT();
const coldefs = useMemo(() => {
const defs: ColDef[] = [
{
@@ -53,7 +54,7 @@ const BreakdownTable = forwardRef(
}
/>
) : (
- 'None'
+ t('None')
);
},
},
@@ -126,7 +127,7 @@ const BreakdownTable = forwardRef(
},
];
return defs;
- }, []);
+ }, [t]);
return (
{
+ const t = useT();
const tooltipContent = [
- {addDecimalsFormatNumber(
- (BigInt(marginAccountBalance) - BigInt(maintenanceLevel)).toString(),
- decimals
- )}{' '}
- {t('above')}{' '}
-
- {t('maintenance level')}
-
+
+ maintenance level
+ ,
+ ]}
+ values={{
+ balance: addDecimalsFormatNumber(
+ (
+ BigInt(marginAccountBalance) - BigInt(maintenanceLevel)
+ ).toString(),
+ decimals
+ ),
+ }}
+ ns={ns}
+ />
{
+ const t = useT();
const { pubKey, pubKeys } = useVegaWallet();
const { params } = useNetworkParams([
NetworkParams.transfer_fee_factor,
@@ -50,16 +52,20 @@ export const TransferContainer = ({ assetId }: { assetId?: string }) => {
return (
<>
- {t('Transfer funds to another Vega key')}
- {pubKey && (
- <>
- {t(' from ')}
-
- {truncateByChars(pubKey || '')}
-
- >
+ {pubKey ? (
+ pubKey]}
+ values={{ pubKey: truncateByChars(pubKey || '') }}
+ />
+ ) : (
+ t('TRANSFER_FUNDS_TO_ANOTHER_VEGA_KEY', {
+ defaultValue:
+ 'Transfer funds to another Vega key. If you are at all unsure, stop and seek advice.',
+ })
)}
- {t('. If you are at all unsure, stop and seek advice.')}
{
minQuantumMultiple: '1',
};
- it('form tooltips correctly displayed', async () => {
+ it.each([
+ {
+ targetText: 'Include transfer fee',
+ tooltipText:
+ 'The fee will be taken from the amount you are transferring.',
+ },
+ {
+ targetText: 'Transfer fee',
+ tooltipText: /transfer\.fee\.factor/,
+ },
+ {
+ targetText: 'Amount to be transferred',
+ tooltipText: /without the fee/,
+ },
+ {
+ targetText: 'Total amount (with fee)',
+ tooltipText: /total amount taken from your account/,
+ },
+ ])('Tooltip for "$targetText" shows', async (o) => {
// 1003-TRAN-015
// 1003-TRAN-016
// 1003-TRAN-017
@@ -94,32 +112,9 @@ describe('TransferForm', () => {
await userEvent.type(amountInput, amount);
expect(amountInput).toHaveValue(amount);
- const includeTransferLabel = screen.getByText('Include transfer fee');
- await userEvent.hover(includeTransferLabel);
- expect(await screen.findByRole('tooltip')).toHaveTextContent(
- 'The fee will be taken from the amount you are transferring.'
- );
- await userEvent.unhover(screen.getByText('Include transfer fee'));
-
- const transferFee = screen.getByText('Transfer fee');
- await userEvent.hover(transferFee);
- expect(await screen.findByRole('tooltip')).toHaveTextContent(
- /transfer.fee.factor/
- );
- await userEvent.unhover(transferFee);
-
- const amountToBeTransferred = screen.getByText('Amount to be transferred');
- await userEvent.hover(amountToBeTransferred);
- expect(await screen.findByRole('tooltip')).toHaveTextContent(
- /without the fee/
- );
- await userEvent.unhover(amountToBeTransferred);
-
- const totalAmountWithFee = screen.getByText('Total amount (with fee)');
- await userEvent.hover(totalAmountWithFee);
- expect(await screen.findByRole('tooltip')).toHaveTextContent(
- /total amount taken from your account/
- );
+ const label = screen.getByText(o.targetText);
+ await userEvent.hover(label);
+ expect(await screen.findByRole('tooltip')).toHaveTextContent(o.tooltipText);
});
it('validates a manually entered address', async () => {
diff --git a/libs/accounts/src/lib/transfer-form.tsx b/libs/accounts/src/lib/transfer-form.tsx
index b38c12946..861364c1b 100644
--- a/libs/accounts/src/lib/transfer-form.tsx
+++ b/libs/accounts/src/lib/transfer-form.tsx
@@ -1,13 +1,13 @@
import sortBy from 'lodash/sortBy';
import {
- maxSafe,
- required,
- vegaPublicKey,
+ useMaxSafe,
+ useRequired,
+ useVegaPublicKey,
addDecimal,
formatNumber,
toBigNum,
} from '@vegaprotocol/utils';
-import { t } from '@vegaprotocol/i18n';
+import { useT } from './use-t';
import {
TradingFormGroup,
TradingInput,
@@ -65,6 +65,10 @@ export const TransferForm = ({
accounts,
minQuantumMultiple,
}: TransferFormProps) => {
+ const t = useT();
+ const maxSafe = useMaxSafe();
+ const required = useRequired();
+ const vegaPublicKey = useVegaPublicKey();
const {
control,
register,
@@ -294,7 +298,7 @@ export const TransferForm = ({
)}
-
+
{
setValue('toVegaKey', '');
@@ -311,7 +315,10 @@ export const TransferForm = ({
{t('Please select')}
{pubKeys?.map((pk) => {
- const text = pk === pubKey ? t('Current key: ') + pk : pk;
+ const text =
+ pk === pubKey
+ ? t('Current key: {{pubKey}}', { pubKey: pk }) + pk
+ : pk;
return (
-
+
setValue('amount', accountBalance, {
shouldValidate: true,
@@ -467,6 +474,7 @@ export const TransferFee = ({
fee?: string;
decimals?: number;
}) => {
+ const t = useT();
if (!feeFactor || !amount || !transferAmount || !fee) return null;
if (
isNaN(Number(feeFactor)) ||
@@ -480,12 +488,12 @@ export const TransferFee = ({
const totalValue = new BigNumber(transferAmount).plus(fee).toString();
return (
-
+
{t('Transfer fee')}
@@ -540,6 +548,7 @@ export const AddressField = ({
mode,
onChange,
}: AddressInputProps) => {
+ const t = useT();
const isInput = mode === 'input';
return (
<>
@@ -548,7 +557,7 @@ export const AddressField = ({
diff --git a/libs/accounts/src/lib/use-t.ts b/libs/accounts/src/lib/use-t.ts
new file mode 100644
index 000000000..06402875e
--- /dev/null
+++ b/libs/accounts/src/lib/use-t.ts
@@ -0,0 +1,3 @@
+import { useTranslation } from 'react-i18next';
+export const ns = 'accounts';
+export const useT = () => useTranslation(ns).t;
diff --git a/libs/accounts/src/setup-tests.ts b/libs/accounts/src/setup-tests.ts
index 880268538..d7d384eda 100644
--- a/libs/accounts/src/setup-tests.ts
+++ b/libs/accounts/src/setup-tests.ts
@@ -1,6 +1,21 @@
import '@testing-library/jest-dom';
import ResizeObserver from 'resize-observer-polyfill';
import { defaultFallbackInView } from 'react-intersection-observer';
+import { locales } from '@vegaprotocol/i18n';
+import i18n from 'i18next';
+import { initReactI18next } from 'react-i18next';
defaultFallbackInView(true);
global.ResizeObserver = ResizeObserver;
+
+// Set up i18n instance so that components have the correct default
+// en translations
+i18n.use(initReactI18next).init({
+ // we init with resources
+ resources: locales,
+ fallbackLng: 'en',
+ ns: ['accounts'],
+ defaultNS: 'accounts',
+});
+
+global.ResizeObserver = ResizeObserver;
diff --git a/libs/accounts/tsconfig.json b/libs/accounts/tsconfig.json
index 36b41cfd5..3ff210f76 100644
--- a/libs/accounts/tsconfig.json
+++ b/libs/accounts/tsconfig.json
@@ -21,7 +21,7 @@
"path": "./tsconfig.spec.json"
},
{
- "path": "./.storybook/tsconfig.json"
+ "path": "./tsconfig.storybook.json"
}
]
}
diff --git a/libs/accounts/tsconfig.storybook.json b/libs/accounts/tsconfig.storybook.json
new file mode 100644
index 000000000..928423a22
--- /dev/null
+++ b/libs/accounts/tsconfig.storybook.json
@@ -0,0 +1,20 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "emitDecoratorMetadata": true,
+ "outDir": ""
+ },
+ "files": [
+ "../../node_modules/@nx/react/typings/styled-jsx.d.ts",
+ "../../node_modules/@nx/react/typings/cssmodule.d.ts",
+ "../../node_modules/@nx/react/typings/image.d.ts"
+ ],
+ "exclude": [
+ "src/**/*.spec.ts",
+ "src/**/*.spec.js",
+ "src/**/*.spec.tsx",
+ "src/**/*.spec.jsx",
+ "jest.config.ts"
+ ],
+ "include": ["src/**/*", ".storybook/*.js"]
+}
diff --git a/libs/announcements/.babelrc b/libs/announcements/.babelrc
index eaafa58dc..6a6b0e302 100644
--- a/libs/announcements/.babelrc
+++ b/libs/announcements/.babelrc
@@ -10,4 +10,4 @@
]
],
"plugins": []
-}
\ No newline at end of file
+}
diff --git a/libs/announcements/.storybook/tsconfig.json b/libs/announcements/.storybook/tsconfig.json
deleted file mode 100644
index 9cb59597a..000000000
--- a/libs/announcements/.storybook/tsconfig.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "extends": "../tsconfig.json",
- "compilerOptions": {
- "emitDecoratorMetadata": true,
- "outDir": ""
- },
- "files": [
- "../../../node_modules/@nx/react/typings/styled-jsx.d.ts",
- "../../../node_modules/@nx/react/typings/cssmodule.d.ts",
- "../../../node_modules/@nx/react/typings/image.d.ts"
- ],
- "exclude": [
- "../**/*.spec.ts",
- "../**/*.spec.js",
- "../**/*.spec.tsx",
- "../**/*.spec.jsx",
- "jest.config.ts"
- ],
- "include": ["../src/**/*", "*.js"]
-}
diff --git a/libs/announcements/project.json b/libs/announcements/project.json
index d3ca632f4..b573c7606 100644
--- a/libs/announcements/project.json
+++ b/libs/announcements/project.json
@@ -32,7 +32,7 @@
}
},
"lint": {
- "executor": "@nx/linter:eslint",
+ "executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/announcements/**/*.{ts,tsx,js,jsx}"]
@@ -42,14 +42,7 @@
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/libs/announcements"],
"options": {
- "jestConfig": "libs/announcements/jest.config.ts",
- "passWithNoTests": true
- },
- "configurations": {
- "ci": {
- "ci": true,
- "codeCoverage": true
- }
+ "jestConfig": "libs/announcements/jest.config.ts"
}
},
"storybook": {
diff --git a/libs/announcements/tsconfig.json b/libs/announcements/tsconfig.json
index 36b41cfd5..3ff210f76 100644
--- a/libs/announcements/tsconfig.json
+++ b/libs/announcements/tsconfig.json
@@ -21,7 +21,7 @@
"path": "./tsconfig.spec.json"
},
{
- "path": "./.storybook/tsconfig.json"
+ "path": "./tsconfig.storybook.json"
}
]
}
diff --git a/libs/announcements/tsconfig.storybook.json b/libs/announcements/tsconfig.storybook.json
new file mode 100644
index 000000000..928423a22
--- /dev/null
+++ b/libs/announcements/tsconfig.storybook.json
@@ -0,0 +1,20 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "emitDecoratorMetadata": true,
+ "outDir": ""
+ },
+ "files": [
+ "../../node_modules/@nx/react/typings/styled-jsx.d.ts",
+ "../../node_modules/@nx/react/typings/cssmodule.d.ts",
+ "../../node_modules/@nx/react/typings/image.d.ts"
+ ],
+ "exclude": [
+ "src/**/*.spec.ts",
+ "src/**/*.spec.js",
+ "src/**/*.spec.tsx",
+ "src/**/*.spec.jsx",
+ "jest.config.ts"
+ ],
+ "include": ["src/**/*", ".storybook/*.js"]
+}
diff --git a/libs/apollo-client/project.json b/libs/apollo-client/project.json
index 5335d677f..acaf81f43 100644
--- a/libs/apollo-client/project.json
+++ b/libs/apollo-client/project.json
@@ -5,7 +5,7 @@
"projectType": "library",
"targets": {
"lint": {
- "executor": "@nx/linter:eslint",
+ "executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/apollo-client/**/*.ts"]
@@ -15,14 +15,7 @@
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/libs/apollo-client"],
"options": {
- "jestConfig": "libs/apollo-client/jest.config.ts",
- "passWithNoTests": true
- },
- "configurations": {
- "ci": {
- "ci": true,
- "codeCoverage": true
- }
+ "jestConfig": "libs/apollo-client/jest.config.ts"
}
}
},
diff --git a/libs/assets/project.json b/libs/assets/project.json
index e34c5d955..1026004e0 100644
--- a/libs/assets/project.json
+++ b/libs/assets/project.json
@@ -6,7 +6,7 @@
"tags": [],
"targets": {
"lint": {
- "executor": "@nx/linter:eslint",
+ "executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/assets/**/*.{ts,tsx,js,jsx}"]
@@ -16,14 +16,7 @@
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/libs/assets"],
"options": {
- "jestConfig": "libs/assets/jest.config.ts",
- "passWithNoTests": true
- },
- "configurations": {
- "ci": {
- "ci": true,
- "codeCoverage": true
- }
+ "jestConfig": "libs/assets/jest.config.ts"
}
},
"build-spec": {
diff --git a/libs/assets/src/lib/asset-data-provider.ts b/libs/assets/src/lib/asset-data-provider.ts
index 9c9018860..1bc29ade4 100644
--- a/libs/assets/src/lib/asset-data-provider.ts
+++ b/libs/assets/src/lib/asset-data-provider.ts
@@ -1,9 +1,9 @@
import { makeDataProvider, useDataProvider } from '@vegaprotocol/data-provider';
-import type {
- AssetQuery,
- AssetFieldsFragment,
- AssetQueryVariables,
+import {
+ type AssetQuery,
+ type AssetQueryVariables,
+ type AssetFieldsFragment,
} from './__generated__/Asset';
import { AssetDocument } from './__generated__/Asset';
diff --git a/libs/assets/src/lib/asset-details-dialog.tsx b/libs/assets/src/lib/asset-details-dialog.tsx
index 9f2fa7503..076d3b411 100644
--- a/libs/assets/src/lib/asset-details-dialog.tsx
+++ b/libs/assets/src/lib/asset-details-dialog.tsx
@@ -1,4 +1,4 @@
-import { t } from '@vegaprotocol/i18n';
+import { useT } from './use-t';
import {
Button,
Dialog,
@@ -56,6 +56,7 @@ export const AssetDetailsDialog = ({
onChange,
asJson = false,
}: AssetDetailsDialogProps) => {
+ const t = useT();
const { data: asset } = useAssetDataProvider(assetId);
const assetSymbol = asset?.symbol || '';
@@ -77,7 +78,7 @@ export const AssetDetailsDialog = ({
);
const title = asset
- ? t(`Asset details - ${asset.symbol}`)
+ ? t('Asset details - {{symbol}}', asset)
: t('Asset not found');
return (
@@ -100,8 +101,8 @@ export const AssetDetailsDialog = ({
{content}
{t(
- 'There is 1 unit of the settlement asset (%s) to every 1 quote unit.',
- [assetSymbol]
+ 'There is 1 unit of the settlement asset ({{assetSymbol}}) to every 1 quote unit.',
+ { assetSymbol }
)}
diff --git a/libs/assets/src/lib/asset-details-table.spec.tsx b/libs/assets/src/lib/asset-details-table.spec.tsx
index 358cb23db..1e3138917 100644
--- a/libs/assets/src/lib/asset-details-table.spec.tsx
+++ b/libs/assets/src/lib/asset-details-table.spec.tsx
@@ -1,10 +1,10 @@
-import { render, screen } from '@testing-library/react';
+import { render, screen, renderHook } from '@testing-library/react';
import * as Schema from '@vegaprotocol/types';
import type { Asset } from './asset-data-provider';
import {
AssetDetail,
AssetDetailsTable,
- rows,
+ useRows,
testId,
} from './asset-details-table';
import { generateBuiltinAsset, generateERC20Asset } from './test-helpers';
@@ -67,6 +67,8 @@ describe('AssetDetailsTable', () => {
it.each(cases)(
"displays the available asset's data of %p with correct labels",
async (_type, asset, details) => {
+ const { result } = renderHook(() => useRows());
+ const rows = result.current;
render(
);
for (const detail of details) {
expect(
diff --git a/libs/assets/src/lib/asset-details-table.tsx b/libs/assets/src/lib/asset-details-table.tsx
index 8758c384d..1d30dbf32 100644
--- a/libs/assets/src/lib/asset-details-table.tsx
+++ b/libs/assets/src/lib/asset-details-table.tsx
@@ -1,6 +1,6 @@
import { EtherscanLink } from '@vegaprotocol/environment';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
-import { t } from '@vegaprotocol/i18n';
+import { useT } from './use-t';
import type * as Schema from '@vegaprotocol/types';
import type { KeyValueTableRowProps } from '@vegaprotocol/ui-toolkit';
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
@@ -10,7 +10,7 @@ import {
KeyValueTableRow,
Tooltip,
} from '@vegaprotocol/ui-toolkit';
-import type { ReactNode } from 'react';
+import { useMemo, type ReactNode } from 'react';
import type { Asset } from './asset-data-provider';
import { WITHDRAW_THRESHOLD_TOOLTIP_TEXT } from './constants';
@@ -52,183 +52,208 @@ const num = (asset: Asset, n: string | undefined | null) => {
return addDecimalsFormatNumber(n, asset.decimals);
};
-export const rows: Rows = [
- {
- key: AssetDetail.ID,
- label: t('ID'),
- tooltip: '',
- value: (asset) => (
- <>
- {truncateMiddle(asset.id)}{' '}
-
-
-
- >
- ),
- },
- {
- key: AssetDetail.TYPE,
- label: t('Type'),
- tooltip: '',
- value: (asset) => AssetTypeMapping[asset.source.__typename].value,
- valueTooltip: (asset) => AssetTypeMapping[asset.source.__typename].tooltip,
- },
- {
- key: AssetDetail.NAME,
- label: t('Name'),
- tooltip: '',
- value: (asset) => asset.name,
- },
- {
- key: AssetDetail.SYMBOL,
- label: t('Symbol'),
- tooltip: '',
- value: (asset) => asset.symbol,
- },
- {
- key: AssetDetail.DECIMALS,
- label: t('Decimals'),
- tooltip: t('Number of decimal / precision handled by this asset'),
- value: (asset) => asset.decimals.toString(),
- },
- {
- key: AssetDetail.QUANTUM,
- label: t('Quantum'),
- tooltip: t('The minimum economically meaningful amount of the asset'),
- value: (asset) => num(asset, asset.quantum),
- },
- {
- key: AssetDetail.STATUS,
- label: t('Status'),
- tooltip: t('The status of the asset in the Vega network'),
- value: (asset) => AssetStatusMapping[asset.status].value,
- valueTooltip: (asset) => AssetStatusMapping[asset.status].tooltip,
- },
- {
- key: AssetDetail.CONTRACT_ADDRESS,
- label: t('Contract address'),
- tooltip: t(
- 'The address of the contract for the token, on the ethereum network'
- ),
- value: (asset) => {
- if (asset.source.__typename !== 'ERC20') {
- return;
- }
+export const useRows = () => {
+ const t = useT();
+ const AssetTypeMapping = useAssetTypeMapping();
+ const AssetStatusMapping = useAssetStatusMapping();
+ return useMemo
(
+ () => [
+ {
+ key: AssetDetail.ID,
+ label: t('ID'),
+ tooltip: '',
+ value: (asset) => (
+ <>
+ {truncateMiddle(asset.id)}{' '}
+
+
+
+ >
+ ),
+ },
+ {
+ key: AssetDetail.TYPE,
+ label: t('Type'),
+ tooltip: '',
+ value: (asset) => AssetTypeMapping[asset.source.__typename].value,
+ valueTooltip: (asset) =>
+ AssetTypeMapping[asset.source.__typename].tooltip,
+ },
+ {
+ key: AssetDetail.NAME,
+ label: t('Name'),
+ tooltip: '',
+ value: (asset) => asset.name,
+ },
+ {
+ key: AssetDetail.SYMBOL,
+ label: t('Symbol'),
+ tooltip: '',
+ value: (asset) => asset.symbol,
+ },
+ {
+ key: AssetDetail.DECIMALS,
+ label: t('Decimals'),
+ tooltip: t('Number of decimal / precision handled by this asset'),
+ value: (asset) => asset.decimals.toString(),
+ },
+ {
+ key: AssetDetail.QUANTUM,
+ label: t('Quantum'),
+ tooltip: t('The minimum economically meaningful amount of the asset'),
+ value: (asset) => num(asset, asset.quantum),
+ },
+ {
+ key: AssetDetail.STATUS,
+ label: t('Status'),
+ tooltip: t('The status of the asset in the Vega network'),
+ value: (asset) => AssetStatusMapping[asset.status].value,
+ valueTooltip: (asset) => AssetStatusMapping[asset.status].tooltip,
+ },
+ {
+ key: AssetDetail.CONTRACT_ADDRESS,
+ label: t('Contract address'),
+ tooltip: t(
+ 'The address of the contract for the token, on the ethereum network'
+ ),
+ value: (asset) => {
+ if (asset.source.__typename !== 'ERC20') {
+ return;
+ }
- return (
- <>
-
- {truncateMiddle(asset.source.contractAddress)}
- {' '}
-
-
-
- >
- );
- },
- },
- {
- key: AssetDetail.WITHDRAWAL_THRESHOLD,
- label: t('Withdrawal threshold'),
- tooltip: WITHDRAW_THRESHOLD_TOOLTIP_TEXT,
- value: (asset) =>
- num(asset, (asset.source as Schema.ERC20).withdrawThreshold),
- },
- {
- key: AssetDetail.LIFETIME_LIMIT,
- label: t('Lifetime limit'),
- tooltip: t(
- 'The lifetime deposit limit per address. Note: this is a temporary measure that can be changed or removed through governance'
- ),
- value: (asset) => num(asset, (asset.source as Schema.ERC20).lifetimeLimit),
- },
- {
- key: AssetDetail.MAX_FAUCET_AMOUNT_MINT,
- label: t('Max faucet amount'),
- tooltip: t(
- 'Maximum amount that can be requested by a party through the built-in asset faucet at a time'
- ),
- value: (asset) =>
- num(asset, (asset.source as Schema.BuiltinAsset).maxFaucetAmountMint),
- },
- {
- key: AssetDetail.INFRASTRUCTURE_FEE_ACCOUNT_BALANCE,
- label: t('Infrastructure fee account balance'),
- tooltip: t('The infrastructure fee account in this asset'),
- value: (asset) => num(asset, asset.infrastructureFeeAccount?.balance),
- },
- {
- key: AssetDetail.GLOBAL_REWARD_POOL_ACCOUNT_BALANCE,
- label: t('Global reward pool account balance'),
- tooltip: t('The global rewards acquired in this asset'),
- value: (asset) => num(asset, asset.globalRewardPoolAccount?.balance),
- },
- {
- key: AssetDetail.MAKER_PAID_FEES_ACCOUNT_BALANCE,
- label: t('Maker paid fees account balance'),
- tooltip: t(
- 'The rewards acquired based on the fees paid to makers in this asset'
- ),
- value: (asset) => num(asset, asset.takerFeeRewardAccount?.balance),
- },
- {
- key: AssetDetail.MAKER_RECEIVED_FEES_ACCOUNT_BALANCE,
- label: t('Maker received fees account balance'),
- tooltip: t(
- 'The rewards acquired based on fees received for being a maker on trades'
- ),
- value: (asset) => num(asset, asset.makerFeeRewardAccount?.balance),
- },
- {
- key: AssetDetail.LP_FEE_REWARD_ACCOUNT_BALANCE,
- label: t('Liquidity provision fee reward account balance'),
- tooltip: t(
- 'The rewards acquired based on the liquidity provision fees in this asset'
- ),
- value: (asset) => num(asset, asset.lpFeeRewardAccount?.balance),
- },
- {
- key: AssetDetail.MARKET_PROPOSER_REWARD_ACCOUNT_BALANCE,
- label: t('Market proposer reward account balance'),
- tooltip: t(
- 'The rewards acquired based on the market proposer reward in this asset'
- ),
- value: (asset) => num(asset, asset.marketProposerRewardAccount?.balance),
- },
-];
-
-export const AssetStatusMapping: Mapping = {
- STATUS_ENABLED: {
- value: t('Enabled'),
- tooltip: t('Asset can be used on the Vega network'),
- },
- STATUS_PENDING_LISTING: {
- value: t('Pending listing'),
- tooltip: t('Asset needs to be added to the Ethereum bridge'),
- },
- STATUS_PROPOSED: {
- value: t('Proposed'),
- tooltip: t('Asset has been proposed to the network'),
- },
- STATUS_REJECTED: {
- value: t('Rejected'),
- tooltip: t('Asset has been rejected'),
- },
+ return (
+ <>
+
+ {truncateMiddle(asset.source.contractAddress)}
+ {' '}
+
+
+
+ >
+ );
+ },
+ },
+ {
+ key: AssetDetail.WITHDRAWAL_THRESHOLD,
+ label: t('Withdrawal threshold'),
+ tooltip: t('WITHDRAW_THRESHOLD_TOOLTIP_TEXT', {
+ defaultValue: WITHDRAW_THRESHOLD_TOOLTIP_TEXT,
+ }),
+ value: (asset) =>
+ num(asset, (asset.source as Schema.ERC20).withdrawThreshold),
+ },
+ {
+ key: AssetDetail.LIFETIME_LIMIT,
+ label: t('Lifetime limit'),
+ tooltip: t(
+ 'The lifetime deposit limit per address. Note: this is a temporary measure that can be changed or removed through governance'
+ ),
+ value: (asset) =>
+ num(asset, (asset.source as Schema.ERC20).lifetimeLimit),
+ },
+ {
+ key: AssetDetail.MAX_FAUCET_AMOUNT_MINT,
+ label: t('Max faucet amount'),
+ tooltip: t(
+ 'Maximum amount that can be requested by a party through the built-in asset faucet at a time'
+ ),
+ value: (asset) =>
+ num(asset, (asset.source as Schema.BuiltinAsset).maxFaucetAmountMint),
+ },
+ {
+ key: AssetDetail.INFRASTRUCTURE_FEE_ACCOUNT_BALANCE,
+ label: t('Infrastructure fee account balance'),
+ tooltip: t('The infrastructure fee account in this asset'),
+ value: (asset) => num(asset, asset.infrastructureFeeAccount?.balance),
+ },
+ {
+ key: AssetDetail.GLOBAL_REWARD_POOL_ACCOUNT_BALANCE,
+ label: t('Global reward pool account balance'),
+ tooltip: t('The global rewards acquired in this asset'),
+ value: (asset) => num(asset, asset.globalRewardPoolAccount?.balance),
+ },
+ {
+ key: AssetDetail.MAKER_PAID_FEES_ACCOUNT_BALANCE,
+ label: t('Maker paid fees account balance'),
+ tooltip: t(
+ 'The rewards acquired based on the fees paid to makers in this asset'
+ ),
+ value: (asset) => num(asset, asset.takerFeeRewardAccount?.balance),
+ },
+ {
+ key: AssetDetail.MAKER_RECEIVED_FEES_ACCOUNT_BALANCE,
+ label: t('Maker received fees account balance'),
+ tooltip: t(
+ 'The rewards acquired based on fees received for being a maker on trades'
+ ),
+ value: (asset) => num(asset, asset.makerFeeRewardAccount?.balance),
+ },
+ {
+ key: AssetDetail.LP_FEE_REWARD_ACCOUNT_BALANCE,
+ label: t('Liquidity provision fee reward account balance'),
+ tooltip: t(
+ 'The rewards acquired based on the liquidity provision fees in this asset'
+ ),
+ value: (asset) => num(asset, asset.lpFeeRewardAccount?.balance),
+ },
+ {
+ key: AssetDetail.MARKET_PROPOSER_REWARD_ACCOUNT_BALANCE,
+ label: t('Market proposer reward account balance'),
+ tooltip: t(
+ 'The rewards acquired based on the market proposer reward in this asset'
+ ),
+ value: (asset) =>
+ num(asset, asset.marketProposerRewardAccount?.balance),
+ },
+ ],
+ [t, AssetTypeMapping, AssetStatusMapping]
+ );
};
-export const AssetTypeMapping: Mapping = {
- BuiltinAsset: {
- value: 'Builtin asset',
- tooltip: t('A Vega builtin asset'),
- },
- ERC20: {
- value: 'ERC20',
- tooltip: t('An asset originated from an Ethereum ERC20 Token'),
- },
+export const useAssetStatusMapping = () => {
+ const t = useT();
+ return useMemo(
+ () => ({
+ STATUS_ENABLED: {
+ value: t('Enabled'),
+ tooltip: t('Asset can be used on the Vega network'),
+ },
+ STATUS_PENDING_LISTING: {
+ value: t('Pending listing'),
+ tooltip: t('Asset needs to be added to the Ethereum bridge'),
+ },
+ STATUS_PROPOSED: {
+ value: t('Proposed'),
+ tooltip: t('Asset has been proposed to the network'),
+ },
+ STATUS_REJECTED: {
+ value: t('Rejected'),
+ tooltip: t('Asset has been rejected'),
+ },
+ }),
+ [t]
+ );
+};
+
+export const useAssetTypeMapping = () => {
+ const t = useT();
+ return useMemo(
+ () => ({
+ BuiltinAsset: {
+ value: t('Builtin asset'),
+ tooltip: t('A Vega builtin asset'),
+ },
+ ERC20: {
+ value: t('ERC20'),
+ tooltip: t('An asset originated from an Ethereum ERC20 Token'),
+ },
+ }),
+ [t]
+ );
};
export const testId = (detail: AssetDetail, field: 'label' | 'value') =>
@@ -248,7 +273,7 @@ export const AssetDetailsTable = ({
? { className: 'break-all', title: value }
: {};
- const details = rows.map((r) => ({
+ const details = useRows().map((r) => ({
...r,
value: r.value(asset),
valueTooltip: r.valueTooltip?.(asset),
diff --git a/libs/assets/src/lib/asset-option.tsx b/libs/assets/src/lib/asset-option.tsx
index 689b389e9..a107bea89 100644
--- a/libs/assets/src/lib/asset-option.tsx
+++ b/libs/assets/src/lib/asset-option.tsx
@@ -1,7 +1,7 @@
import { TradingOption, truncateMiddle } from '@vegaprotocol/ui-toolkit';
import type { AssetFieldsFragment } from './__generated__/Asset';
import classNames from 'classnames';
-import { t } from '@vegaprotocol/i18n';
+import { useT } from './use-t';
import type { ReactNode } from 'react';
type AssetOptionProps = {
@@ -15,8 +15,9 @@ export const Balance = ({
}: {
balance?: string;
symbol: string;
-}) =>
- balance ? (
+}) => {
+ const t = useT();
+ return balance ? (
{balance} {symbol}
@@ -25,6 +26,7 @@ export const Balance = ({
{t('Fetching balance…')}
);
+};
export const AssetOption = ({ asset, balance }: AssetOptionProps) => {
return (
diff --git a/libs/assets/src/lib/assets-data-provider.ts b/libs/assets/src/lib/assets-data-provider.ts
index 09a01c3b0..7a9f5c57f 100644
--- a/libs/assets/src/lib/assets-data-provider.ts
+++ b/libs/assets/src/lib/assets-data-provider.ts
@@ -3,10 +3,9 @@ import {
makeDerivedDataProvider,
} from '@vegaprotocol/data-provider';
import { useDataProvider } from '@vegaprotocol/data-provider';
-import { AssetsDocument } from './__generated__/Assets';
+import { AssetsDocument, type AssetsQuery } from './__generated__/Assets';
import { AssetStatus } from '@vegaprotocol/types';
-import type { AssetsQuery } from './__generated__/Assets';
-import type { Asset } from './asset-data-provider';
+import { type Asset } from './asset-data-provider';
import { DENY_LIST } from './constants';
export interface BuiltinAssetSource {
diff --git a/libs/assets/src/lib/constants.ts b/libs/assets/src/lib/constants.ts
index d28ea806a..503e4e460 100644
--- a/libs/assets/src/lib/constants.ts
+++ b/libs/assets/src/lib/constants.ts
@@ -1,8 +1,5 @@
-import { t } from '@vegaprotocol/i18n';
-
-export const WITHDRAW_THRESHOLD_TOOLTIP_TEXT = t(
- "The maximum you can withdraw instantly. There's no limit on the size of a withdrawal, but all withdrawals over the threshold will have a delay time added to them"
-);
+export const WITHDRAW_THRESHOLD_TOOLTIP_TEXT =
+ "The maximum you can withdraw instantly. There's no limit on the size of a withdrawal, but all withdrawals over the threshold will have a delay time added to them";
// List of defunct and no longer used assets that were created for various testnets
export const DENY_LIST: Record
= {
diff --git a/libs/assets/src/lib/index.ts b/libs/assets/src/lib/index.ts
index f85c8581f..5eb9eddab 100644
--- a/libs/assets/src/lib/index.ts
+++ b/libs/assets/src/lib/index.ts
@@ -7,3 +7,4 @@ export * from './asset-option';
export * from './assets-data-provider';
export * from './constants';
export * from './use-balances-store';
+export * from './utils';
diff --git a/libs/assets/src/lib/use-t.ts b/libs/assets/src/lib/use-t.ts
new file mode 100644
index 000000000..76d98b89d
--- /dev/null
+++ b/libs/assets/src/lib/use-t.ts
@@ -0,0 +1,3 @@
+import { useTranslation } from 'react-i18next';
+
+export const useT = () => useTranslation('assets').t;
diff --git a/libs/assets/src/lib/utils.spec.ts b/libs/assets/src/lib/utils.spec.ts
new file mode 100644
index 000000000..2ee978581
--- /dev/null
+++ b/libs/assets/src/lib/utils.spec.ts
@@ -0,0 +1,15 @@
+import { getQuantumValue } from './utils';
+
+describe('getQuantumValue', () => {
+ it('converts a value into its value in quantum AKA (qUSD)', () => {
+ expect(getQuantumValue('1000000', '1000000').toString()).toEqual('1');
+ expect(getQuantumValue('2000000', '1000000').toString()).toEqual('2');
+ expect(getQuantumValue('2500000', '1000000').toString()).toEqual('2.5');
+ expect(getQuantumValue('10000', '1000000').toString()).toEqual('0.01');
+ expect(
+ getQuantumValue('1000000000000000000', '1000000000000000000').toString()
+ ).toEqual('1');
+ expect(getQuantumValue('100000000', '100000000').toString()).toEqual('1');
+ expect(getQuantumValue('150000000', '100000000').toString()).toEqual('1.5');
+ });
+});
diff --git a/libs/assets/src/lib/utils.ts b/libs/assets/src/lib/utils.ts
new file mode 100644
index 000000000..1c4089f29
--- /dev/null
+++ b/libs/assets/src/lib/utils.ts
@@ -0,0 +1,5 @@
+import { toBigNum } from '@vegaprotocol/utils';
+
+export const getQuantumValue = (value: string, quantum: string) => {
+ return toBigNum(value, 0).dividedBy(toBigNum(quantum, 0));
+};
diff --git a/libs/candles-chart/project.json b/libs/candles-chart/project.json
index b0eb037d1..d99163c70 100644
--- a/libs/candles-chart/project.json
+++ b/libs/candles-chart/project.json
@@ -6,7 +6,7 @@
"tags": [],
"targets": {
"lint": {
- "executor": "@nx/linter:eslint",
+ "executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/candles-chart/**/*.{ts,tsx,js,jsx}"]
@@ -16,14 +16,7 @@
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/libs/candles-chart"],
"options": {
- "jestConfig": "libs/candles-chart/jest.config.ts",
- "passWithNoTests": true
- },
- "configurations": {
- "ci": {
- "ci": true,
- "codeCoverage": true
- }
+ "jestConfig": "libs/candles-chart/jest.config.ts"
}
},
"build-spec": {
diff --git a/libs/candles-chart/src/lib/candles-chart.tsx b/libs/candles-chart/src/lib/candles-chart.tsx
index 1cc76603a..48049d0ce 100644
--- a/libs/candles-chart/src/lib/candles-chart.tsx
+++ b/libs/candles-chart/src/lib/candles-chart.tsx
@@ -6,12 +6,12 @@ import { useMemo } from 'react';
import debounce from 'lodash/debounce';
import AutoSizer from 'react-virtualized-auto-sizer';
import { useVegaWallet } from '@vegaprotocol/wallet';
-import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
-import { t } from '@vegaprotocol/i18n';
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;
@@ -25,6 +25,7 @@ export const CandlesChartContainer = ({
const client = useApolloClient();
const { pubKey } = useVegaWallet();
const { theme } = useThemeSwitcher();
+ const t = useT();
const {
interval,
diff --git a/libs/candles-chart/src/lib/candles-menu.tsx b/libs/candles-chart/src/lib/candles-menu.tsx
index 06d83fbb3..b00c4298d 100644
--- a/libs/candles-chart/src/lib/candles-menu.tsx
+++ b/libs/candles-chart/src/lib/candles-menu.tsx
@@ -20,10 +20,10 @@ import {
TradingDropdownTrigger,
Icon,
} from '@vegaprotocol/ui-toolkit';
-import type { IconName } from '@blueprintjs/icons';
+import { type IconName } from '@blueprintjs/icons';
import { IconNames } from '@blueprintjs/icons';
-import { t } from '@vegaprotocol/i18n';
import { useCandlesChartSettings } from './use-candles-chart-settings';
+import { useT } from './use-t';
const chartTypeIcon = new Map([
[ChartType.AREA, IconNames.TIMELINE_AREA_CHART],
@@ -43,6 +43,7 @@ export const CandlesMenu = () => {
setStudies,
setOverlays,
} = useCandlesChartSettings();
+ const t = useT();
const triggerClasses = 'text-xs';
const contentAlign = 'end';
const triggerButtonProps = { size: 'extra-small' } as const;
@@ -53,7 +54,9 @@ export const CandlesMenu = () => {
trigger={
- {t(`Interval: ${intervalLabels[interval]}`)}
+ {t('Interval: {{interval}}', {
+ interval: intervalLabels[interval],
+ })}
}
diff --git a/libs/candles-chart/src/lib/data-source.ts b/libs/candles-chart/src/lib/data-source.ts
index 21de68c79..2bdb0441e 100644
--- a/libs/candles-chart/src/lib/data-source.ts
+++ b/libs/candles-chart/src/lib/data-source.ts
@@ -1,29 +1,33 @@
-import type { ApolloClient } from '@apollo/client';
-import type { Duration } from 'date-fns';
+import { type ApolloClient } from '@apollo/client';
+import { type Duration } from 'date-fns';
import {
add,
differenceInDays,
differenceInHours,
differenceInMinutes,
} from 'date-fns';
-import type { Candle, DataSource, PriceMonitoringBounds } from 'pennant';
+import {
+ type Candle,
+ type DataSource,
+ type PriceMonitoringBounds,
+} from 'pennant';
import { Interval as PennantInterval } from 'pennant';
-
import { addDecimal } from '@vegaprotocol/utils';
-import { ChartDocument } from './__generated__/Chart';
-import type { ChartQuery, ChartQueryVariables } from './__generated__/Chart';
+import {
+ ChartDocument,
+ type ChartQuery,
+ type ChartQueryVariables,
+} from './__generated__/Chart';
import {
CandlesDocument,
CandlesEventsDocument,
+ type CandlesQuery,
+ type CandlesQueryVariables,
+ type CandlesEventsSubscription,
+ type CandlesEventsSubscriptionVariables,
+ type CandleFieldsFragment,
} from './__generated__/Candles';
-import type {
- CandlesQuery,
- CandlesQueryVariables,
- CandleFieldsFragment,
- CandlesEventsSubscription,
- CandlesEventsSubscriptionVariables,
-} from './__generated__/Candles';
-import type { Subscription } from 'zen-observable-ts';
+import { type Subscription } from 'zen-observable-ts';
import * as Schema from '@vegaprotocol/types';
const INTERVAL_TO_PENNANT_MAP = {
diff --git a/libs/candles-chart/src/lib/use-t.ts b/libs/candles-chart/src/lib/use-t.ts
new file mode 100644
index 000000000..8511a3520
--- /dev/null
+++ b/libs/candles-chart/src/lib/use-t.ts
@@ -0,0 +1,2 @@
+import { useTranslation } from 'react-i18next';
+export const useT = () => useTranslation('candles-chart').t;
diff --git a/libs/cypress/project.json b/libs/cypress/project.json
index df6a6dad0..0a7da8907 100644
--- a/libs/cypress/project.json
+++ b/libs/cypress/project.json
@@ -5,7 +5,7 @@
"projectType": "library",
"targets": {
"lint": {
- "executor": "@nx/linter:eslint",
+ "executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/cypress/**/*.ts"]
@@ -15,14 +15,7 @@
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/libs/cypress"],
"options": {
- "jestConfig": "libs/cypress/jest.config.ts",
- "passWithNoTests": true
- },
- "configurations": {
- "ci": {
- "ci": true,
- "codeCoverage": true
- }
+ "jestConfig": "libs/cypress/jest.config.ts"
}
},
"build-spec": {
diff --git a/libs/data-provider/project.json b/libs/data-provider/project.json
index c2eea7228..14382007c 100644
--- a/libs/data-provider/project.json
+++ b/libs/data-provider/project.json
@@ -6,7 +6,7 @@
"tags": [],
"targets": {
"lint": {
- "executor": "@nx/linter:eslint",
+ "executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/data-provider/**/*.{ts,tsx,js,jsx}"]
@@ -16,14 +16,7 @@
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/libs/data-provider"],
"options": {
- "jestConfig": "libs/data-provider/jest.config.ts",
- "passWithNoTests": true
- },
- "configurations": {
- "ci": {
- "ci": true,
- "codeCoverage": true
- }
+ "jestConfig": "libs/data-provider/jest.config.ts"
}
}
}
diff --git a/libs/data-provider/src/generic-data-provider.spec.ts b/libs/data-provider/src/generic-data-provider.spec.ts
index 3bf82bd50..2d4b02f36 100644
--- a/libs/data-provider/src/generic-data-provider.spec.ts
+++ b/libs/data-provider/src/generic-data-provider.spec.ts
@@ -3,29 +3,29 @@ import {
makeDerivedDataProvider,
defaultAppend,
} from './generic-data-provider';
-import type {
- CombineDerivedData,
- CombineDerivedDelta,
- CombineInsertionData,
- Query,
- UpdateCallback,
- Update,
- PageInfo,
- Reload,
- Load,
+import {
+ type CombineDerivedData,
+ type CombineDerivedDelta,
+ type CombineInsertionData,
+ type UpdateCallback,
+ type Update,
+ type Query,
+ type PageInfo,
+ type Reload,
+ type Load,
} from './generic-data-provider';
-import type {
- ApolloClient,
- FetchResult,
- SubscriptionOptions,
- OperationVariables,
- ApolloQueryResult,
- QueryOptions,
+import {
+ type FetchResult,
+ type SubscriptionOptions,
+ type OperationVariables,
+ type ApolloQueryResult,
+ type QueryOptions,
+ type ApolloClient,
} from '@apollo/client';
import { ApolloError } from '@apollo/client';
import type { GraphQLErrors } from '@apollo/client/errors';
import { GraphQLError } from 'graphql';
-import type { Subscription, Observable } from 'zen-observable-ts';
+import { type Subscription, type Observable } from 'zen-observable-ts';
import { waitFor } from '@testing-library/react';
type Item = {
diff --git a/libs/data-provider/src/pagination.ts b/libs/data-provider/src/pagination.ts
index be9686b11..1e4d19cbe 100644
--- a/libs/data-provider/src/pagination.ts
+++ b/libs/data-provider/src/pagination.ts
@@ -1,5 +1,10 @@
import type { IGetRowsParams } from 'ag-grid-community';
-import type { Load, DerivedPart, Node, Edge } from './generic-data-provider';
+import {
+ type Edge,
+ type Load,
+ type DerivedPart,
+ type Node,
+} from './generic-data-provider';
import type { MutableRefObject } from 'react';
const getLastRow = (
diff --git a/libs/data-provider/src/use-data-provider.spec.ts b/libs/data-provider/src/use-data-provider.spec.ts
index b999f89da..a137b012b 100644
--- a/libs/data-provider/src/use-data-provider.spec.ts
+++ b/libs/data-provider/src/use-data-provider.spec.ts
@@ -1,7 +1,10 @@
import { renderHook, act } from '@testing-library/react';
-import { useDataProvider, useThrottledDataProvider } from './use-data-provider';
-import type { useDataProviderParams } from './use-data-provider';
-import type { Subscribe, UpdateCallback } from './generic-data-provider';
+import {
+ useDataProvider,
+ useThrottledDataProvider,
+ type useDataProviderParams,
+} from './use-data-provider';
+import { type Subscribe, type UpdateCallback } from './generic-data-provider';
import { MockedProvider } from '@apollo/client/testing';
type Data = number;
diff --git a/libs/data-provider/src/use-data-provider.ts b/libs/data-provider/src/use-data-provider.ts
index 6344d29d2..af5aee9d8 100644
--- a/libs/data-provider/src/use-data-provider.ts
+++ b/libs/data-provider/src/use-data-provider.ts
@@ -3,11 +3,11 @@ import throttle from 'lodash/throttle';
import isEqualWith from 'lodash/isEqualWith';
import { useApolloClient } from '@apollo/client';
import type { OperationVariables } from '@apollo/client';
-import type {
- Subscribe,
- Load,
- UpdateCallback,
- PageInfo,
+import {
+ type UpdateCallback,
+ type PageInfo,
+ type Subscribe,
+ type Load,
} from './generic-data-provider';
import { variablesIsEqualCustomizer } from './generic-data-provider';
diff --git a/libs/datagrid/project.json b/libs/datagrid/project.json
index 5ee54583c..7b23e31ff 100644
--- a/libs/datagrid/project.json
+++ b/libs/datagrid/project.json
@@ -6,7 +6,7 @@
"tags": [],
"targets": {
"lint": {
- "executor": "@nx/linter:eslint",
+ "executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/datagrid/**/*.{ts,tsx,js,jsx}"]
@@ -16,14 +16,7 @@
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/libs/datagrid"],
"options": {
- "jestConfig": "libs/datagrid/jest.config.ts",
- "passWithNoTests": true
- },
- "configurations": {
- "ci": {
- "ci": true,
- "codeCoverage": true
- }
+ "jestConfig": "libs/datagrid/jest.config.ts"
}
}
}
diff --git a/libs/datagrid/src/lib/ag-grid/ag-grid-themed.tsx b/libs/datagrid/src/lib/ag-grid/ag-grid-themed.tsx
index 033b2dc18..0184e46d9 100644
--- a/libs/datagrid/src/lib/ag-grid/ag-grid-themed.tsx
+++ b/libs/datagrid/src/lib/ag-grid/ag-grid-themed.tsx
@@ -1,14 +1,12 @@
import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
import { AgGridReact } from 'ag-grid-react';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
-import { t } from '@vegaprotocol/i18n';
import classNames from 'classnames';
import type { ColDef } from 'ag-grid-community';
+import { useT } from '../use-t';
const defaultProps: AgGridReactProps = {
enableCellTextSelection: true,
- overlayLoadingTemplate: t('Loading...'),
- overlayNoRowsTemplate: t('No data'),
suppressCellFocus: true,
suppressColumnMoveAnimation: true,
};
@@ -26,6 +24,7 @@ export const AgGridThemed = ({
style?: React.CSSProperties;
gridRef?: React.ForwardedRef;
}) => {
+ const t = useT();
const { theme } = useThemeSwitcher();
const wrapperClasses = classNames('vega-ag-grid', 'w-full h-full', {
diff --git a/libs/datagrid/src/lib/ag-grid/ag-grid.tsx b/libs/datagrid/src/lib/ag-grid/ag-grid.tsx
index 2f0d99c1a..a2d0bdf21 100644
--- a/libs/datagrid/src/lib/ag-grid/ag-grid.tsx
+++ b/libs/datagrid/src/lib/ag-grid/ag-grid.tsx
@@ -1,5 +1,5 @@
import { forwardRef } from 'react';
-import type { AgGridReactProps, AgGridReact } from 'ag-grid-react';
+import { type AgGridReactProps, type AgGridReact } from 'ag-grid-react';
import { AgGridThemed } from './ag-grid-themed';
type Props = AgGridReactProps & {
diff --git a/libs/datagrid/src/lib/cells/order-type-cell.tsx b/libs/datagrid/src/lib/cells/order-type-cell.tsx
index 47f462af4..5eb5ba0bd 100644
--- a/libs/datagrid/src/lib/cells/order-type-cell.tsx
+++ b/libs/datagrid/src/lib/cells/order-type-cell.tsx
@@ -1,9 +1,9 @@
import type { MouseEvent } from 'react';
import { useMemo } from 'react';
import { useCallback } from 'react';
-import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
+import { useT } from '../use-t';
interface OrderTypeCellProps {
value?: Schema.OrderType;
@@ -17,6 +17,7 @@ export const OrderTypeCell = ({
onClick,
}: OrderTypeCellProps) => {
const id = order?.market?.id ?? '';
+ const t = useT();
const label = useMemo(() => {
if (!order) {
@@ -25,7 +26,9 @@ export const OrderTypeCell = ({
if (!value) return '-';
if (order?.icebergOrder) {
- return t('%s (Iceberg)', [Schema.OrderTypeMapping[value]]);
+ return t('{{orderType}} (Iceberg)', {
+ orderType: Schema.OrderTypeMapping[value],
+ });
}
if (order?.peggedOrder) {
@@ -37,14 +40,18 @@ export const OrderTypeCell = ({
order.peggedOrder?.offset,
order.market.decimalPlaces
);
- return t('%s %s %s Peg limit', [reference, side, offset]);
+ return t('{{reference}} {{side}} {{offset}} Peg limit', {
+ reference,
+ side,
+ offset,
+ });
}
if (order?.liquidityProvision) {
return t('Liquidity provision');
}
return Schema.OrderTypeMapping[value];
- }, [order, value]);
+ }, [order, value, t]);
const handleOnClick = useCallback(
(ev: MouseEvent) => {
diff --git a/libs/datagrid/src/lib/filters/date-range-filter.tsx b/libs/datagrid/src/lib/filters/date-range-filter.tsx
index 364d8289f..ecf03219e 100644
--- a/libs/datagrid/src/lib/filters/date-range-filter.tsx
+++ b/libs/datagrid/src/lib/filters/date-range-filter.tsx
@@ -1,6 +1,6 @@
import type { ChangeEvent } from 'react';
import { useEffect, useMemo, useRef } from 'react';
-import type * as Schema from '@vegaprotocol/types';
+import { type DateRange } from '@vegaprotocol/types';
import { forwardRef, useImperativeHandle, useState } from 'react';
import type { IDoesFilterPassParams, IFilterParams } from 'ag-grid-community';
import {
@@ -14,12 +14,12 @@ import {
isValid,
} from 'date-fns';
import { formatForInput } from '@vegaprotocol/utils';
-import { t } from '@vegaprotocol/i18n';
import { TradingInputError } from '@vegaprotocol/ui-toolkit';
+import { useT } from '../use-t';
-const defaultValue: Schema.DateRange = {};
+const defaultValue: DateRange = {};
export interface DateRangeFilterProps extends IFilterParams {
- defaultValue?: Schema.DateRange;
+ defaultValue?: DateRange;
maxSubDays?: number;
maxNextDays?: number;
maxDaysRange?: number;
@@ -27,9 +27,10 @@ export interface DateRangeFilterProps extends IFilterParams {
export const DateRangeFilter = forwardRef(
(props: DateRangeFilterProps, ref) => {
+ const t = useT();
const defaultDates = props?.defaultValue || defaultValue;
- const [value, setValue] = useState(defaultDates);
- const valueRef = useRef(value);
+ const [value, setValue] = useState(defaultDates);
+ const valueRef = useRef(value);
const [error, setError] = useState('');
const [minStartDate, maxStartDate, minEndDate, maxEndDate] = useMemo(() => {
const minStartDate =
@@ -105,26 +106,24 @@ export const DateRangeFilter = forwardRef(
return { value: valueRef.current };
},
- setModel(model?: { value: Schema.DateRange } | null) {
+ setModel(model?: { value: DateRange } | null) {
valueRef.current =
model?.value || props?.defaultValue || defaultValue;
setValue(valueRef.current);
},
};
});
- const validate = (
- name: string,
- timeValue: Date,
- update?: Schema.DateRange
- ) => {
+ const validate = (name: string, timeValue: Date, update?: DateRange) => {
if (
props.maxSubDays !== undefined &&
isBefore(new Date(timeValue), subDays(Date.now(), props.maxSubDays + 1))
) {
setError(
t(
- 'The earliest data that can be queried is %s days ago.',
- String(props.maxSubDays)
+ 'The earliest data that can be queried is {{maxSubDays}} days ago.',
+ {
+ maxSubDays: String(props.maxSubDays),
+ }
)
);
return false;
@@ -141,8 +140,8 @@ export const DateRangeFilter = forwardRef(
) {
setError(
t(
- 'The maximum time range that can be queried is %s days.',
- String(props.maxDaysRange)
+ 'The maximum time range that can be queried is {{maxDaysRange}} days.',
+ { maxDaysRange: String(props.maxDaysRange) }
)
);
return false;
@@ -209,7 +208,7 @@ export const DateRangeFilter = forwardRef(