diff --git a/.eslintrc.json b/.eslintrc.json index 88d9bd572..e58c2f737 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -73,7 +73,8 @@ "error", { "prefer": "type-imports", - "disallowTypeAnnotations": true + "disallowTypeAnnotations": true, + "fixStyle": "inline-type-imports" } ], "curly": ["error", "multi-line"] diff --git a/.github/workflows/ci-cd-trigger.yml b/.github/workflows/ci-cd-trigger.yml index ec26eeb56..bdb570d9b 100644 --- a/.github/workflows/ci-cd-trigger.yml +++ b/.github/workflows/ci-cd-trigger.yml @@ -34,7 +34,7 @@ jobs: ${{ runner.os }}-cache-node-modules- - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 if: steps.cache.outputs.cache-hit != 'true' with: node-version-file: '.nvmrc' @@ -57,7 +57,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha || github.sha }} - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' # https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions @@ -96,7 +96,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha || github.sha }} - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: yarn @@ -128,7 +128,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha || github.sha }} - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: yarn @@ -182,7 +182,7 @@ jobs: run: | if [[ "${{ github.base_ref }}" == "develop" ]]; then echo "e2e-needed=true" >> $GITHUB_OUTPUT - elif [[ "${{ github.base_ref }}" == "main" ]]; then + elif [[ "${{ github.base_ref }}" == "main" ]]; then echo "e2e-needed=true" >> $GITHUB_OUTPUT elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref_name }}" == *"release/"* ]]; then echo "e2e-needed=true" >> $GITHUB_OUTPUT @@ -205,7 +205,7 @@ jobs: console-e2e: needs: [build-sources, check-e2e-needed] - name: '(CI) console python' + name: '(CI) trading e2e python' uses: ./.github/workflows/console-test-run.yml secrets: inherit if: needs.check-e2e-needed.outputs.run-tests == 'true' && contains(needs.build-sources.outputs.projects, 'trading') diff --git a/.github/workflows/console-test-run.yml b/.github/workflows/console-test-run.yml index 661ba9472..f39a6dced 100644 --- a/.github/workflows/console-test-run.yml +++ b/.github/workflows/console-test-run.yml @@ -10,7 +10,7 @@ on: inputs: console-test-branch: type: choice - description: 'main: v0.72.14, develop: v0.73.0-preview7' + description: 'main: v0.73.5, develop: v0.73.5' options: - main - develop @@ -32,9 +32,9 @@ jobs: # cache node modules #---------------------------------------------- - name: setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: '16' + node-version-file: '.nvmrc' cache: yarn - name: Cache node modules @@ -153,25 +153,19 @@ jobs: run: | docker load --input /tmp/console-image.tar docker image ls -a - #---------------------------------------------- - # check-out tests repo + # check-out frontend-monorepo #---------------------------------------------- - - name: Checkout console test repo + - name: Checkout frontend-monorepo uses: actions/checkout@v3 with: - repository: vegaprotocol/console-test - ref: ${{ needs.console-test-branch.outputs.console-branch }} - - - name: Load console test envs - id: console-test-env - uses: falti/dotenv-action@v1.0.4 - with: - path: '.env.${{ needs.console-test-branch.outputs.console-branch }}' - export-variables: true - keys-case: upper - log-variables: true - + ref: ${{ inputs.github-sha || github.sha }} + #---------------------------------------------- + # get vega version + #---------------------------------------------- + - name: Set VEGA_VERSION from .env + id: set_vega_version + run: echo "VEGA_VERSION=$(grep VEGA_VERSION apps/trading/e2e/.env | cut -d '=' -f2)" >> $GITHUB_ENV #---------------------------------------------- # ----- Setup python ----- #---------------------------------------------- @@ -194,22 +188,25 @@ jobs: #---------------------------------------------- - name: Install dependencies run: poetry install --no-interaction --no-root + working-directory: apps/trading/e2e #---------------------------------------------- # install vega binaries #---------------------------------------------- - name: Install vega binaries run: poetry run python -m vega_sim.tools.load_binaries --force --version ${{ env.VEGA_VERSION }} + working-directory: apps/trading/e2e #---------------------------------------------- - # install playwright + # install playwrightworking-directory: apps/trading/e2e #---------------------------------------------- - name: install playwright run: poetry run playwright install --with-deps chromium + working-directory: apps/trading/e2e #---------------------------------------------- # run tests #---------------------------------------------- - name: Run tests run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v -s --numprocesses 4 --dist loadfile --durations=15 - + working-directory: apps/trading/e2e #---------------------------------------------- # upload traces #---------------------------------------------- @@ -218,7 +215,7 @@ jobs: if: always() with: name: playwright-trace - path: ./traces/ + path: apps/trading/e2e/traces/ retention-days: 15 #---------------------------------------------- # ----- upload logs ----- diff --git a/.github/workflows/cypress-live-test.yml b/.github/workflows/cypress-live-test.yml index dcd813036..4c39a6e28 100644 --- a/.github/workflows/cypress-live-test.yml +++ b/.github/workflows/cypress-live-test.yml @@ -18,11 +18,11 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Use Node.js 16 + - name: Use Node.js 20 id: Node - uses: actions/setup-node@v2 + uses: actions/setup-node@v4 with: - node-version: 16.15.1 + node-version-file: '.nvmrc' - name: Run Cypress tests uses: cypress-io/github-action@v4 diff --git a/.github/workflows/cypress-run.yml b/.github/workflows/cypress-run.yml index fe41f1c89..4411bd54b 100644 --- a/.github/workflows/cypress-run.yml +++ b/.github/workflows/cypress-run.yml @@ -57,6 +57,14 @@ jobs: path: './frontend-monorepo' ref: ${{ github.event.pull_request.head.sha || github.sha }} + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version-file: './frontend-monorepo/.nvmrc' + # https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions + cache: yarn + cache-dependency-path: './frontend-monorepo/yarn.lock' + # Restore node_modules from cache if possible - name: Restore node_modules from cache id: cache-node-modules diff --git a/.github/workflows/generate-queries.yml b/.github/workflows/generate-queries.yml index 43db3338c..c4d628947 100644 --- a/.github/workflows/generate-queries.yml +++ b/.github/workflows/generate-queries.yml @@ -15,7 +15,7 @@ jobs: uses: actions/checkout@v3 - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' # https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions diff --git a/.github/workflows/lint-pr.yml b/.github/workflows/lint-pr.yml index 8763b67cd..325641ca6 100644 --- a/.github/workflows/lint-pr.yml +++ b/.github/workflows/lint-pr.yml @@ -18,13 +18,13 @@ jobs: uses: actions/checkout@v3 - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: 16 + node-version-file: '.nvmrc' - name: Install dependencies run: | - rm package.json + rm package.json npm install --no-save @commitlint/cli @commitlint/config-conventional @commitlint/config-nx-scopes nx - name: Check PR title diff --git a/.github/workflows/publish-dist.yml b/.github/workflows/publish-dist.yml index 622d25b71..309264f90 100644 --- a/.github/workflows/publish-dist.yml +++ b/.github/workflows/publish-dist.yml @@ -93,7 +93,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' # https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 672bb7da0..a40cb66e9 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -29,7 +29,7 @@ jobs: uses: actions/checkout@v3 - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' # https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions diff --git a/.gitignore b/.gitignore index 6fd2a0956..9cd427913 100644 --- a/.gitignore +++ b/.gitignore @@ -48,8 +48,15 @@ cypress.env.json # Next.js .next -#cypress +# cypress /apps/**/cypress/reports/ /apps/**/cypress/downloads/ /apps/**/fixtures/wallet/node** + +# apps/trading/e2e +__pycache__/ +apps/trading/e2e/logs/ +apps/trading/e2e/.pytest_cache/ +apps/trading/e2e/traces/ + .nx/ diff --git a/.nvmrc b/.nvmrc index cb406c60c..f3f52b42d 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -16.20.2 +20.9.0 diff --git a/.prettierignore b/.prettierignore index 365c3d4f7..296f30097 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,7 @@ # Add files here to ignore them from prettier formatting /dist +/dist-result /coverage __generated__ __generated___ @@ -8,3 +9,15 @@ __generated___ apps/static/src/assets/devnet-tranches.json apps/static/src/assets/mainnet-tranches.json apps/static/src/assets/testnet-tranches.json + +/apps/**/cypress/reports/ +/apps/**/cypress/downloads/ + +/.nx/cache + +# apps/trading/e2e +__pycache__/ +apps/trading/e2e/logs/ +apps/trading/e2e/.pytest_cache/ +apps/trading/e2e/traces/ +.pytest_cache/ diff --git a/CODEOWNERS b/CODEOWNERS index 8117415dd..a63dde642 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,2 +1,4 @@ * @vegaprotocol/frontend +apps/ @vegaprotocol/frontend-qa +libs/ @vegaprotocol/frontend-qa *.graphql @vegaprotocol/core diff --git a/README.md b/README.md index 57c165cd6..9e657a93a 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ The [`docker`](./docker) subfolder has some docker configurations for easily set Using multistage dockerfile dist is compiled using [node](https://hub.docker.com/_/node) image and later packed to nginx as in [dist build](#dist-build). The multistage builds ensures consistent CPU architecture and build toolchains are used so that the result will be identical. ```bash -docker build --build-arg APP=[YOUR APP] --build-arg NODE_VERSION=16.5.1 --build-arg ENV_NAME=mainnet -t [TAG] -f docker/node-inside-docker.Dockerfile . +docker build --build-arg APP=[YOUR APP] --build-arg NODE_VERSION=20.9.1 --build-arg ENV_NAME=mainnet -t [TAG] -f docker/node-inside-docker.Dockerfile . ``` ### Computing ipfs-hash of the build diff --git a/apps/explorer-e2e/.eslintrc.json b/apps/explorer-e2e/.eslintrc.json index 696cb8b12..70128adc7 100644 --- a/apps/explorer-e2e/.eslintrc.json +++ b/apps/explorer-e2e/.eslintrc.json @@ -4,7 +4,9 @@ "overrides": [ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "rules": {} + "rules": { + "cypress/unsafe-to-chain-command": 0 + } } ] } diff --git a/apps/explorer-e2e/project.json b/apps/explorer-e2e/project.json index f553e9f1b..3e40a7e58 100644 --- a/apps/explorer-e2e/project.json +++ b/apps/explorer-e2e/project.json @@ -17,7 +17,7 @@ } }, "lint": { - "executor": "@nx/linter:eslint", + "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"], "options": { "lintFilePatterns": ["apps/explorer-e2e/**/*.{js,ts}"] diff --git a/apps/explorer/netlify.toml b/apps/explorer/netlify.toml deleted file mode 100644 index b87b8d3dd..000000000 --- a/apps/explorer/netlify.toml +++ /dev/null @@ -1,4 +0,0 @@ -[[redirects]] - from = "/*" - to = "/index.html" - status = 200 diff --git a/apps/explorer/project.json b/apps/explorer/project.json index c2aec3281..1a0cff4c1 100644 --- a/apps/explorer/project.json +++ b/apps/explorer/project.json @@ -53,7 +53,7 @@ } }, "lint": { - "executor": "@nx/linter:eslint", + "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"], "options": { "lintFilePatterns": ["apps/explorer/**/*.{ts,tsx,js,jsx}"] @@ -63,14 +63,7 @@ "executor": "@nx/jest:jest", "outputs": ["{workspaceRoot}/coverage/apps/explorer"], "options": { - "jestConfig": "apps/explorer/jest.config.ts", - "passWithNoTests": true - }, - "configurations": { - "ci": { - "ci": true, - "codeCoverage": true - } + "jestConfig": "apps/explorer/jest.config.ts" } }, "generate-types": { @@ -81,15 +74,6 @@ ] } }, - "build-netlify": { - "executor": "nx:run-commands", - "options": { - "commands": [ - "cp apps/explorer/netlify.toml netlify.toml", - "nx build explorer" - ] - } - }, "build-spec": { "executor": "nx:run-commands", "outputs": [], diff --git a/apps/explorer/src/app/app.tsx b/apps/explorer/src/app/app.tsx index 5ad134417..17d6260cb 100644 --- a/apps/explorer/src/app/app.tsx +++ b/apps/explorer/src/app/app.tsx @@ -1,3 +1,4 @@ +import '../i18n'; import { NetworkLoader, NodeFailure, @@ -28,20 +29,24 @@ function App() { ); return ( - - {t('Loading')}} - failure={} - > - - - - - - + + + {t('Loading')}} + failure={ + + } + > + + + + + + + ); } diff --git a/apps/explorer/src/app/components/asset-balance/asset-balance.tsx b/apps/explorer/src/app/components/asset-balance/asset-balance.tsx index a64a43623..758fed005 100644 --- a/apps/explorer/src/app/components/asset-balance/asset-balance.tsx +++ b/apps/explorer/src/app/components/asset-balance/asset-balance.tsx @@ -1,5 +1,5 @@ import { useAssetDataProvider } from '@vegaprotocol/assets'; -import { addDecimalsFormatNumber } from '@vegaprotocol/utils'; +import { addDecimalsFixedFormatNumber } from '@vegaprotocol/utils'; import { AssetLink } from '../links'; export type AssetBalanceProps = { @@ -23,12 +23,12 @@ const AssetBalance = ({ const label = !loading && asset && asset.decimals - ? addDecimalsFormatNumber(price, asset.decimals) + ? addDecimalsFixedFormatNumber(price, asset.decimals) : price; return (
- {label}{' '} + {label}{' '} {showAssetLink && asset?.id ? ( ) : null} diff --git a/apps/explorer/src/app/components/assets/assets-table.tsx b/apps/explorer/src/app/components/assets/assets-table.tsx index af3959d7a..55b551c34 100644 --- a/apps/explorer/src/app/components/assets/assets-table.tsx +++ b/apps/explorer/src/app/components/assets/assets-table.tsx @@ -1,20 +1,26 @@ import { useMemo } from 'react'; -import type { AssetFieldsFragment } from '@vegaprotocol/assets'; -import { AssetTypeMapping, AssetStatusMapping } from '@vegaprotocol/assets'; +import { + useAssetTypeMapping, + useAssetStatusMapping, + type AssetFieldsFragment, +} from '@vegaprotocol/assets'; import { t } from '@vegaprotocol/i18n'; import { ButtonLink } from '@vegaprotocol/ui-toolkit'; -import type { AgGridReact } from 'ag-grid-react'; +import { type AgGridReact } from 'ag-grid-react'; import { AgGrid } from '@vegaprotocol/datagrid'; -import type { VegaICellRendererParams } from '@vegaprotocol/datagrid'; +import { type VegaICellRendererParams } from '@vegaprotocol/datagrid'; import { useRef, useLayoutEffect } from 'react'; import { BREAKPOINT_MD } from '../../config/breakpoints'; import { useNavigate } from 'react-router-dom'; -import type { RowClickedEvent, ColDef } from 'ag-grid-community'; +import { type ColDef } from 'ag-grid-community'; +import type { RowClickedEvent } from 'ag-grid-community'; type AssetsTableProps = { data: AssetFieldsFragment[] | null; }; export const AssetsTable = ({ data }: AssetsTableProps) => { + const assetTypeMapping = useAssetTypeMapping(); + const assetStatusMapping = useAssetStatusMapping(); const navigate = useNavigate(); const ref = useRef(null); const showColumnsOnDesktop = () => { @@ -47,14 +53,14 @@ export const AssetsTable = ({ data }: AssetsTableProps) => { field: 'source.__typename', hide: window.innerWidth < BREAKPOINT_MD, valueFormatter: ({ value }: { value?: string }) => - value ? AssetTypeMapping[value].value : '', + value ? assetTypeMapping[value].value : '', }, { headerName: t('Status'), field: 'status', hide: window.innerWidth < BREAKPOINT_MD, valueFormatter: ({ value }: { value?: string }) => - value ? AssetStatusMapping[value].value : '', + value ? assetStatusMapping[value].value : '', }, { colId: 'actions', @@ -69,7 +75,7 @@ export const AssetsTable = ({ data }: AssetsTableProps) => { }: VegaICellRendererParams) => value ? ( { + onClick={() => { navigate(value); }} > @@ -80,7 +86,7 @@ export const AssetsTable = ({ data }: AssetsTableProps) => { ), }, ], - [navigate] + [navigate, assetStatusMapping, assetTypeMapping] ); return ( diff --git a/apps/explorer/src/app/components/links/market-link/market-link.spec.tsx b/apps/explorer/src/app/components/links/market-link/market-link.spec.tsx index 9f67adf64..78240bdcb 100644 --- a/apps/explorer/src/app/components/links/market-link/market-link.spec.tsx +++ b/apps/explorer/src/app/components/links/market-link/market-link.spec.tsx @@ -91,6 +91,6 @@ describe('Market link component', () => { }; const res = render(renderComponent('123', [mock])); - expect(await res.findByText('123')).toBeInTheDocument(); + expect(await res.findByTitle('123')).toBeInTheDocument(); }); }); diff --git a/apps/explorer/src/app/components/markets/markets-table.tsx b/apps/explorer/src/app/components/markets/markets-table.tsx index fd02c4d59..0ecbe5769 100644 --- a/apps/explorer/src/app/components/markets/markets-table.tsx +++ b/apps/explorer/src/app/components/markets/markets-table.tsx @@ -2,18 +2,18 @@ import { useMemo } from 'react'; import { getAsset, type MarketFieldsFragment } from '@vegaprotocol/markets'; import { t } from '@vegaprotocol/i18n'; import { ButtonLink } from '@vegaprotocol/ui-toolkit'; -import type { AgGridReact } from 'ag-grid-react'; -import type { ColDef } from 'ag-grid-community'; +import { type AgGridReact } from 'ag-grid-react'; +import { type ColDef } from 'ag-grid-community'; import { AgGrid } from '@vegaprotocol/datagrid'; -import type { - VegaICellRendererParams, - VegaValueGetterParams, +import { + type VegaICellRendererParams, + type VegaValueGetterParams, } from '@vegaprotocol/datagrid'; import { useRef, useLayoutEffect } from 'react'; import { BREAKPOINT_MD } from '../../config/breakpoints'; import { MarketStateMapping } from '@vegaprotocol/types'; import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; -import type { RowClickedEvent } from 'ag-grid-community'; +import { type RowClickedEvent } from 'ag-grid-community'; import { Link, useNavigate } from 'react-router-dom'; type MarketsTableProps = { diff --git a/apps/explorer/src/app/components/order-details/amend-order-details.spec.tsx b/apps/explorer/src/app/components/order-details/amend-order-details.spec.tsx index 3314fdf3e..94696d296 100644 --- a/apps/explorer/src/app/components/order-details/amend-order-details.spec.tsx +++ b/apps/explorer/src/app/components/order-details/amend-order-details.spec.tsx @@ -17,7 +17,7 @@ function renderAmendOrderDetails( mocks: MockedResponse[] ) { return render( - + @@ -44,18 +44,18 @@ function renderExistingAmend( orderByID: { __typename: 'Order', id: '123', - type: 'GTT', + type: Schema.OrderType.TYPE_LIMIT, status: Schema.OrderStatus.STATUS_ACTIVE, - version: version, + version: version ? version.toString() : '', createdAt: '123', updatedAt: '456', expiresAt: '789', timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC, price: '200', - side: 'BUY', + side: Schema.Side.SIDE_BUY, peggedOrder: null, remaining: '99', - rejectionReason: 'rejection', + rejectionReason: Schema.OrderRejectionReason.ORDER_ERROR_NOT_FOUND, reference: '123', size: '100', party: { @@ -65,18 +65,15 @@ function renderExistingAmend( market: { __typename: 'Market', id: '789', - state: 'STATUS_ACTIVE', + state: Schema.MarketState.STATE_ACTIVE, positionDecimalPlaces: 2, - decimalPlaces: '5', + decimalPlaces: 0, tradableInstrument: { instrument: { name: 'test', product: { __typename: 'Future', quoteName: '123', - settlementAsset: { - decimals: 8, - }, }, }, }, @@ -97,18 +94,18 @@ function renderExistingAmend( orderByID: { __typename: 'Order', id: '123', - type: 'GTT', + type: Schema.OrderType.TYPE_LIMIT, status: Schema.OrderStatus.STATUS_ACTIVE, - version: 100, + version: '100', createdAt: '123', updatedAt: '456', expiresAt: '789', timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC, peggedOrder: null, price: '200', - side: 'BUY', + side: Schema.Side.SIDE_BUY, remaining: '99', - rejectionReason: 'rejection', + rejectionReason: Schema.OrderRejectionReason.ORDER_ERROR_NOT_FOUND, reference: '123', size: '200', party: { @@ -117,19 +114,16 @@ function renderExistingAmend( }, market: { __typename: 'Market', - id: 'amend-to-order-latest-version', - state: 'STATUS_ACTIVE', + id: '8888', + state: Schema.MarketState.STATE_ACTIVE, positionDecimalPlaces: 2, - decimalPlaces: '5', + decimalPlaces: 0, tradableInstrument: { instrument: { name: 'amend-to-order-latest-version-test', product: { __typename: 'Future', quoteName: '123', - settlementAsset: { - decimals: 8, - }, }, }, }, @@ -142,13 +136,13 @@ function renderExistingAmend( request: { query: ExplorerMarketDocument, variables: { - id: '789', + id: '8888', }, }, result: { data: { market: { - id: '789', + id: '8888', decimalPlaces: 5, positionDecimalPlaces: 2, state: 'irrelevant-test-data', @@ -225,12 +219,10 @@ describe('Amend order details', () => { it('Fetches latest version when version is not specified', async () => { const amend: Amend = { - price: '-7879', + price: '123', }; const res = renderExistingAmend('123', undefined, amend); - expect( - await res.findByText('amend-to-order-latest-version') - ).toBeInTheDocument(); + expect(await res.findByText('test-label')).toBeInTheDocument(); }); }); diff --git a/apps/explorer/src/app/components/order-details/amend-order-details.tsx b/apps/explorer/src/app/components/order-details/amend-order-details.tsx index 8a762c9da..7c603b0ad 100644 --- a/apps/explorer/src/app/components/order-details/amend-order-details.tsx +++ b/apps/explorer/src/app/components/order-details/amend-order-details.tsx @@ -46,10 +46,10 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
-

+

{t('Order not found')}

-

+

{t('No order created from this transaction')}

@@ -68,24 +68,24 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
-

+

{t('Edits to ')} {sideText[o.side]} {t(' order')}

-

+

In , updated at{' '}
-
+
{amend.sizeDelta && amend.sizeDelta !== '0' ? (
-

+

{t('New size')}

@@ -96,10 +96,10 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => { {amend.price && amend.price !== '0' ? (
-

+

{t('New price')}

-
+
@@ -108,10 +108,10 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => { {amend.peggedReference && amend.peggedReference !== 'PEGGED_REFERENCE_UNSPECIFIED' ? (
-

+

{t('New reference')}

-
+
{peggedReference[amend.peggedReference]}
@@ -119,10 +119,10 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => { {amend.peggedOffset ? (
-

+

{t('New offset')}

-
+
{amend.peggedOffset}
diff --git a/apps/explorer/src/app/components/proposals/proposals-table.tsx b/apps/explorer/src/app/components/proposals/proposals-table.tsx index 2bfb32992..017d12082 100644 --- a/apps/explorer/src/app/components/proposals/proposals-table.tsx +++ b/apps/explorer/src/app/components/proposals/proposals-table.tsx @@ -1,14 +1,15 @@ -import type { ProposalListFieldsFragment } from '@vegaprotocol/proposals'; +import { type ProposalListFieldsFragment } from '@vegaprotocol/proposals'; import { VoteProgress } from '@vegaprotocol/proposals'; -import type { AgGridReact } from 'ag-grid-react'; +import { type AgGridReact } from 'ag-grid-react'; import { ExternalLink } from '@vegaprotocol/ui-toolkit'; import { AgGrid } from '@vegaprotocol/datagrid'; -import type { - VegaICellRendererParams, - VegaValueFormatterParams, +import { + type VegaICellRendererParams, + type VegaValueFormatterParams, } from '@vegaprotocol/datagrid'; import { useLayoutEffect, useMemo, useRef, useState } from 'react'; -import type { RowClickedEvent, ColDef } from 'ag-grid-community'; +import { type ColDef } from 'ag-grid-community'; +import type { RowClickedEvent } from 'ag-grid-community'; import { getDateTimeFormat } from '@vegaprotocol/utils'; import { t } from '@vegaprotocol/i18n'; import { @@ -105,7 +106,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => { ? new BigNumber(0) : yesTokens.multipliedBy(100).dividedBy(totalTokensVoted); return ( -
+
{ + const [isOpen, setIsOpen] = useState(false); + + if (!signature || !signature.value || !signature.version || !signature.algo) { + return null; + } + + return ( +
+ + {signature.algo} + +
+ + {signature.value} + +
+ +
+ ); +}; diff --git a/apps/explorer/src/app/components/txs/details/chain-events/index.tsx b/apps/explorer/src/app/components/txs/details/chain-events/index.tsx index 1b70ddd96..a019658b7 100644 --- a/apps/explorer/src/app/components/txs/details/chain-events/index.tsx +++ b/apps/explorer/src/app/components/txs/details/chain-events/index.tsx @@ -15,6 +15,7 @@ import isUndefined from 'lodash/isUndefined'; import type { BlockExplorerTransactionResult } from '../../../../routes/types/block-explorer-response'; import { TxDetailsChainEventWithdrawal } from './tx-erc20-withdrawal'; import { TxDetailsChainEventErc20AssetDelist } from './tx-erc20-asset-delist'; +import { TxDetailsContractCall } from './tx-contract-call'; interface ChainEventProps { txData: BlockExplorerTransactionResult | undefined; @@ -38,7 +39,7 @@ export const ChainEvent = ({ txData }: ChainEventProps) => { return null; } - const { builtin, erc20, erc20Multisig, stakingEvent } = + const { builtin, erc20, erc20Multisig, stakingEvent, contractCall } = txData.command.chainEvent; // Builtin Asset events @@ -140,6 +141,10 @@ export const ChainEvent = ({ txData }: ChainEventProps) => { } } + if (contractCall) { + return ; + } + // If we hit this return, tx-shared-details should give a basic overview return null; }; diff --git a/apps/explorer/src/app/components/txs/details/chain-events/tx-contract-call.spec.tsx b/apps/explorer/src/app/components/txs/details/chain-events/tx-contract-call.spec.tsx new file mode 100644 index 000000000..5dde3e863 --- /dev/null +++ b/apps/explorer/src/app/components/txs/details/chain-events/tx-contract-call.spec.tsx @@ -0,0 +1,26 @@ +import { decodeEthCallResult } from './tx-contract-call'; +import { base64 } from 'ethers/lib/utils'; +import { defaultAbiCoder } from '@ethersproject/abi'; +import { BigNumber } from '@ethersproject/bignumber'; + +describe('decodeEthCallResult', () => { + it('should decode contractData correctly (mocked)', () => { + const mockContractData = base64.encode( + defaultAbiCoder.encode(['int256'], [BigNumber.from(123)]) + ); + const result = decodeEthCallResult(mockContractData); + expect(result).toBe('123'); + }); + + it('should decode contractData correctly (known data)', () => { + const mockContractData = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADH8cueyY='; + const result = decodeEthCallResult(mockContractData); + expect(result).toBe('3435020581670'); + }); + + it('should return "-" when an error occurs', () => { + const mockContractData = 'invalid_data'; + const result = decodeEthCallResult(mockContractData); + expect(result).toBe('-'); + }); +}); diff --git a/apps/explorer/src/app/components/txs/details/chain-events/tx-contract-call.tsx b/apps/explorer/src/app/components/txs/details/chain-events/tx-contract-call.tsx new file mode 100644 index 000000000..f7c367eaf --- /dev/null +++ b/apps/explorer/src/app/components/txs/details/chain-events/tx-contract-call.tsx @@ -0,0 +1,88 @@ +import { TableCell, TableRow } from '../../../table'; +import { t } from '@vegaprotocol/i18n'; +import { + EthExplorerLink, + EthExplorerLinkTypes, +} from '../../../links/eth-explorer-link/eth-explorer-link'; +import type { components } from '../../../../../types/explorer'; +import { defaultAbiCoder, base64 } from 'ethers/lib/utils'; +import { BigNumber } from 'ethers'; +import OracleLink from '../../../links/oracle-link/oracle-link'; +import { useExplorerOracleSpecByIdQuery } from '../../../../routes/oracles/__generated__/Oracles'; +import { OracleEthSource } from '../../../../routes/oracles/components/oracle-eth-source'; + +/** + * Decodes the b64/ABIcoded result from an eth cal + * @param data + * @returns + */ +export function decodeEthCallResult(contractData: string): string { + try { + const rawResult = defaultAbiCoder.decode( + ['int256'], + base64.decode(contractData) + ); + + // Finally, convert the resulting BigNumber in to a string + const res = BigNumber.from(rawResult[0]).toString(); + return res; + } catch (e) { + return '-'; + } +} + +interface TxDetailsContractCallProps { + contractCall: components['schemas']['vegaEthContractCallEvent']; +} + +export const TxDetailsContractCall = ({ + contractCall, +}: TxDetailsContractCallProps) => { + const { data } = useExplorerOracleSpecByIdQuery({ + variables: { + id: contractCall.specId || '1', + }, + }); + + if (!contractCall || !contractCall.result) { + return null; + } + + return ( + <> + {contractCall.specId && ( + + {t('Oracle')} + + + + + )} + {contractCall.blockHeight && ( + + {t('ETH block')} + + + + + )} + {data?.oracleSpec?.dataSourceSpec && ( + + )} + + + {t('Result')} + {decodeEthCallResult(contractCall.result)} + + + ); +}; diff --git a/apps/explorer/src/app/components/txs/details/shared/tx-details-shared.tsx b/apps/explorer/src/app/components/txs/details/shared/tx-details-shared.tsx index b9b202adf..f093646c0 100644 --- a/apps/explorer/src/app/components/txs/details/shared/tx-details-shared.tsx +++ b/apps/explorer/src/app/components/txs/details/shared/tx-details-shared.tsx @@ -9,6 +9,7 @@ import { Time } from '../../../time'; import { ChainResponseCode } from '../chain-response-code/chain-reponse.code'; import { TxDataView } from '../../tx-data-view'; import Hash from '../../../links/hash'; +import { Signature } from '../../../signature/signature'; interface TxDetailsSharedProps { txData: BlockExplorerTransactionResult | undefined; @@ -75,6 +76,12 @@ export const TxDetailsShared = ({ + + {t('Signature')} + + + + {t('Time')} diff --git a/apps/explorer/src/app/components/txs/details/tx-chain-event.tsx b/apps/explorer/src/app/components/txs/details/tx-chain-event.tsx index 2b87a74aa..45ebc2448 100644 --- a/apps/explorer/src/app/components/txs/details/tx-chain-event.tsx +++ b/apps/explorer/src/app/components/txs/details/tx-chain-event.tsx @@ -1,51 +1,11 @@ import { t } from '@vegaprotocol/i18n'; import { TxDetailsShared } from './shared/tx-details-shared'; import { TableWithTbody } from '../../table'; -import { defaultAbiCoder, base64 } from 'ethers/lib/utils'; import { ChainEvent } from './chain-events'; -import { BigNumber } from 'ethers'; -import type { AbiType } from '../../../lib/encoders/abis/abi-types'; import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response'; import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response'; -interface AbiOutput { - type: AbiType; - internalType: AbiType; - name: string; -} - -/** - * Decodes the b64/ABIcoded result from an eth cal - * @param data - * @returns - */ -export function decodeEthCallResult( - data: BlockExplorerTransactionResult -): string { - const ethResult = data.command.chainEvent?.contractCall.result; - - try { - // Decode the result string: base64 => uint8array - const data = base64.decode(ethResult); - - // Parse the escaped ABI in to an object - const abi = JSON.parse( - '[{"inputs":[],"name":"latestAnswer","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"}]' - ); - // Pull the expected types out of the Oracles ABI - const types: AbiType[] = abi[0].outputs.map((o: AbiOutput) => o.type); - - const rawResult = defaultAbiCoder.decode(types, data); - - // Finally, convert the resulting BigNumber in to a string - const res = BigNumber.from(rawResult[0]).toString(); - return res; - } catch (e) { - return '-'; - } -} - interface TxDetailsChainEventProps { txData: BlockExplorerTransactionResult | undefined; pubKey: string | undefined; diff --git a/apps/explorer/src/app/components/txs/details/tx-details-wrapper.tsx b/apps/explorer/src/app/components/txs/details/tx-details-wrapper.tsx index f8292e9fd..6c6478d76 100644 --- a/apps/explorer/src/app/components/txs/details/tx-details-wrapper.tsx +++ b/apps/explorer/src/app/components/txs/details/tx-details-wrapper.tsx @@ -3,8 +3,8 @@ import { DATA_SOURCES } from '../../../config'; import { t } from '@vegaprotocol/i18n'; import { useFetch } from '@vegaprotocol/react-helpers'; import { TxDetailsOrder } from './tx-order'; -import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response'; -import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response'; +import { type BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response'; +import { type TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response'; import { TxDetailsHeartbeat } from './tx-hearbeat'; import { TxDetailsGeneric } from './tx-generic'; import { TxDetailsBatch } from './tx-batch'; diff --git a/apps/explorer/src/app/components/txs/tx-transfer.spec.tsx b/apps/explorer/src/app/components/txs/tx-transfer.spec.tsx index 1d8ad8413..f9c67e18d 100644 --- a/apps/explorer/src/app/components/txs/tx-transfer.spec.tsx +++ b/apps/explorer/src/app/components/txs/tx-transfer.spec.tsx @@ -98,6 +98,8 @@ describe('TxDetailsTransfer', () => { }, }, signature: { + version: '1', + algo: 'vega/ed25519', value: '610c2e196a7d4fed4413b9e82af267b1ff3e30e943df3a3d28096fd60604d430d752fbaf6dd4f84d496be78885bb6118f40560bff7832c06bd7a3d67b718b700', }, diff --git a/apps/explorer/src/app/components/txs/txs-infinite-list.spec.tsx b/apps/explorer/src/app/components/txs/txs-infinite-list.spec.tsx index 107a17521..95e4fa4ef 100644 --- a/apps/explorer/src/app/components/txs/txs-infinite-list.spec.tsx +++ b/apps/explorer/src/app/components/txs/txs-infinite-list.spec.tsx @@ -20,6 +20,8 @@ const generateTxs = (number: number): BlockExplorerTransactionResult[] => { '4b782482f587d291e8614219eb9a5ee9280fa2c58982dee71d976782a9be1964', type: 'Submit Order', signature: { + version: '1', + algo: 'vega/ed25519', value: '123', }, code: 0, diff --git a/apps/explorer/src/app/components/txs/txs-per-block.tsx b/apps/explorer/src/app/components/txs/txs-per-block.tsx index 63b9a76ef..b15a1fe0d 100644 --- a/apps/explorer/src/app/components/txs/txs-per-block.tsx +++ b/apps/explorer/src/app/components/txs/txs-per-block.tsx @@ -1,7 +1,7 @@ import { Table, TableRow } from '../table'; import { t } from '@vegaprotocol/i18n'; import { useFetch } from '@vegaprotocol/react-helpers'; -import type { BlockExplorerTransactions } from '../../routes/types/block-explorer-response'; +import { type BlockExplorerTransactions } from '../../routes/types/block-explorer-response'; import { getTxsDataUrl } from '../../hooks/get-txs-data-url'; import { AsyncRenderer, Loader } from '@vegaprotocol/ui-toolkit'; import EmptyList from '../empty-list/empty-list'; @@ -22,7 +22,7 @@ export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => { return ( {data && data.transactions.length > 0 ? ( -
+
diff --git a/apps/explorer/src/app/hooks/use-txs-data.ts b/apps/explorer/src/app/hooks/use-txs-data.ts index 13ab7fd98..7d8f638a8 100644 --- a/apps/explorer/src/app/hooks/use-txs-data.ts +++ b/apps/explorer/src/app/hooks/use-txs-data.ts @@ -1,14 +1,14 @@ import { useSearchParams } from 'react-router-dom'; -import type { URLSearchParamsInit } from 'react-router-dom'; +import { type URLSearchParamsInit } from 'react-router-dom'; import { useCallback } from 'react'; import { useFetch } from '@vegaprotocol/react-helpers'; -import type { - BlockExplorerTransactionResult, - BlockExplorerTransactions, +import { + type BlockExplorerTransactionResult, + type BlockExplorerTransactions, } from '../routes/types/block-explorer-response'; import isNumber from 'lodash/isNumber'; import { AllFilterOptions } from '../components/txs/tx-filter'; -import type { FilterOption } from '../components/txs/tx-filter'; +import { type FilterOption } from '../components/txs/tx-filter'; import { BE_TXS_PER_REQUEST, getTxsDataUrl } from './get-txs-data-url'; export function getTypeFilters(filters?: Set) { diff --git a/apps/explorer/src/app/routes/assets/asset-page.tsx b/apps/explorer/src/app/routes/assets/asset-page.tsx index 9288bafab..ccf949100 100644 --- a/apps/explorer/src/app/routes/assets/asset-page.tsx +++ b/apps/explorer/src/app/routes/assets/asset-page.tsx @@ -9,11 +9,13 @@ import { JsonViewerDialog } from '../../components/dialogs/json-viewer-dialog'; import { useState } from 'react'; import { PageTitle } from '../../components/page-helpers/page-title'; +type Params = { assetId: string }; + export const AssetPage = () => { useDocumentTitle(['Assets']); useScrollToLocation(); - const { assetId } = useParams<{ assetId: string }>(); + const { assetId } = useParams(); const { data, loading, error } = useAssetDataProvider(assetId || ''); const title = data ? data.name : error ? t('Asset not found') : ''; @@ -41,7 +43,7 @@ export const AssetPage = () => { loading={loading} error={error} > -
+
diff --git a/apps/explorer/src/app/routes/blocks/home/index.tsx b/apps/explorer/src/app/routes/blocks/home/index.tsx index a92050a7d..abfa8f820 100644 --- a/apps/explorer/src/app/routes/blocks/home/index.tsx +++ b/apps/explorer/src/app/routes/blocks/home/index.tsx @@ -1,8 +1,8 @@ import { useCallback, useState } from 'react'; import { DATA_SOURCES } from '../../../config'; -import type { - BlockMeta, - TendermintBlockchainResponse, +import { + type BlockMeta, + type TendermintBlockchainResponse, } from '../tendermint-blockchain-response'; import { RouteTitle } from '../../../components/route-title'; import { BlocksRefetch } from '../../../components/blocks'; diff --git a/apps/explorer/src/app/routes/blocks/id/block.tsx b/apps/explorer/src/app/routes/blocks/id/block.tsx index 888988b0a..7d54a7461 100644 --- a/apps/explorer/src/app/routes/blocks/id/block.tsx +++ b/apps/explorer/src/app/routes/blocks/id/block.tsx @@ -17,8 +17,10 @@ import { NodeLink } from '../../../components/links'; import { useDocumentTitle } from '../../../hooks/use-document-title'; import EmptyList from '../../../components/empty-list/empty-list'; +type Params = { block: string }; + const Block = () => { - const { block } = useParams<{ block: string }>(); + const { block } = useParams(); useDocumentTitle(['Blocks', `Block #${block}`]); const { state: { data: blockData, loading, error }, @@ -29,7 +31,7 @@ const Block = () => { {t(`BLOCK ${block}`)} <> -
+
{ diff --git a/apps/explorer/src/app/routes/markets/market-page.tsx b/apps/explorer/src/app/routes/markets/market-page.tsx index dae69f227..4a1b21db3 100644 --- a/apps/explorer/src/app/routes/markets/market-page.tsx +++ b/apps/explorer/src/app/routes/markets/market-page.tsx @@ -11,10 +11,12 @@ import { JsonViewerDialog } from '../../components/dialogs/json-viewer-dialog'; import { marketInfoWithDataProvider } from '@vegaprotocol/markets'; import { PageTitle } from '../../components/page-helpers/page-title'; +type Params = { marketId: string }; + export const MarketPage = () => { useScrollToLocation(); - const { marketId } = useParams<{ marketId: string }>(); + const { marketId } = useParams(); const { data, loading, error } = useDataProvider({ dataProvider: marketInfoWithDataProvider, diff --git a/apps/explorer/src/app/routes/oracles/components/oracle-markets.tsx b/apps/explorer/src/app/routes/oracles/components/oracle-markets.tsx index fa1bbd45e..0bb02e8b0 100644 --- a/apps/explorer/src/app/routes/oracles/components/oracle-markets.tsx +++ b/apps/explorer/src/app/routes/oracles/components/oracle-markets.tsx @@ -1,8 +1,10 @@ import { getNodes } from '@vegaprotocol/utils'; import { MarketLink } from '../../../components/links'; import { TableRow, TableCell, TableHeader } from '../../../components/table'; -import type { ExplorerOracleForMarketsMarketFragment } from '../__generated__/OraclesForMarkets'; -import { useExplorerOracleFormMarketsQuery } from '../__generated__/OraclesForMarkets'; +import { + useExplorerOracleFormMarketsQuery, + type ExplorerOracleForMarketsMarketFragment, +} from '../__generated__/OraclesForMarkets'; interface OracleMarketsProps { id: string; diff --git a/apps/explorer/src/app/routes/oracles/id/index.tsx b/apps/explorer/src/app/routes/oracles/id/index.tsx index de979d949..52ccdf4d4 100644 --- a/apps/explorer/src/app/routes/oracles/id/index.tsx +++ b/apps/explorer/src/app/routes/oracles/id/index.tsx @@ -9,8 +9,10 @@ import { AsyncRenderer, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit'; import filter from 'recursive-key-filter'; import { TruncateInline } from '../../../components/truncate/truncate'; +type Params = { id: string }; + export const Oracle = () => { - const { id } = useParams<{ id: string }>(); + const { id } = useParams(); useDocumentTitle(['Oracle', `Oracle #${truncateByChars(id || '1', 5, 5)}`]); diff --git a/apps/explorer/src/app/routes/parties/id/accounts/index.tsx b/apps/explorer/src/app/routes/parties/id/accounts/index.tsx index 1a7ba9aa7..40c37acd5 100644 --- a/apps/explorer/src/app/routes/parties/id/accounts/index.tsx +++ b/apps/explorer/src/app/routes/parties/id/accounts/index.tsx @@ -6,8 +6,10 @@ import { useDocumentTitle } from '../../../../hooks/use-document-title'; import { PartyAccounts } from '../components/party-accounts'; +type Params = { party: string }; + const PartyAccountsByAsset = () => { - const { party } = useParams<{ party: string }>(); + const { party } = useParams(); useDocumentTitle(['Public keys', party || '-']); const partyId = toNonHex(party ? party : ''); diff --git a/apps/explorer/src/app/routes/parties/id/components/party-accounts.tsx b/apps/explorer/src/app/routes/parties/id/components/party-accounts.tsx index 4edd52548..016fe6ae4 100644 --- a/apps/explorer/src/app/routes/parties/id/components/party-accounts.tsx +++ b/apps/explorer/src/app/routes/parties/id/components/party-accounts.tsx @@ -1,6 +1,7 @@ -import { AccountManager } from '@vegaprotocol/accounts'; -import { useCallback } from 'react'; -import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; +import { useExplorerPartyAssetsQuery } from '../__generated__/Party-assets'; +import { AssetLink, MarketLink } from '../../../../components/links'; +import AssetBalance from '../../../../components/asset-balance/asset-balance'; +import { AccountTypeMapping } from '@vegaprotocol/types'; interface PartyAccountsProps { partyId: string; @@ -12,21 +13,71 @@ interface PartyAccountsProps { * appearing first and... tbd */ export const PartyAccounts = ({ partyId }: PartyAccountsProps) => { - const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); - const onClickAsset = useCallback( - (assetId?: string) => { - assetId && openAssetDetailsDialog(assetId); - }, - [openAssetDetailsDialog] - ); + const { data } = useExplorerPartyAssetsQuery({ + variables: { partyId }, + }); + + const party = data?.partiesConnection?.edges[0]?.node; + const accounts = + party?.accountsConnection?.edges?.filter((edge) => edge?.node) || []; return (
- +
+ + + + + + + + + + {accounts + .sort((a, b) => { + // Sort by asset id, then market id, with general accounts first + if (!a) { + return 1; + } + if (!b) { + return -1; + } + if (a.node.asset.id !== b.node.asset.id) { + return a.node.asset.id.localeCompare(b.node.asset.id); + } + if (a.node.type === 'ACCOUNT_TYPE_GENERAL') return -1; + if (b.node.type === 'ACCOUNT_TYPE_GENERAL') return 1; + if (a.node.market && b.node.market) { + return a.node.market.id.localeCompare(b.node.market.id); + } else { + return a.node.type.localeCompare(b.node.type); + } + }) + .map((e) => { + if (!e) return null; + const { type, asset, balance, market } = e.node; + + return ( + + + + + + + ); + })} + +
BalanceTypeMarketAsset
+ + {AccountTypeMapping[type]} + {market?.id ? : '-'} + + +
); }; diff --git a/apps/explorer/src/app/routes/parties/id/index.tsx b/apps/explorer/src/app/routes/parties/id/index.tsx index 196616fed..319d69101 100644 --- a/apps/explorer/src/app/routes/parties/id/index.tsx +++ b/apps/explorer/src/app/routes/parties/id/index.tsx @@ -19,11 +19,13 @@ import type { FilterOption } from '../../../components/txs/tx-filter'; import { AllFilterOptions, TxsFilter } from '../../../components/txs/tx-filter'; import { useSearchParams } from 'react-router-dom'; +type Params = { party: string }; + const Party = () => { const [params] = useSearchParams(); const [filters, setFilters] = useState(new Set(AllFilterOptions)); - const { party } = useParams<{ party: string }>(); + const { party } = useParams(); useDocumentTitle(['Public keys', party || '-']); const navigate = useNavigate(); @@ -60,7 +62,7 @@ const Party = () => { if (!isValidPartyId(partyId)) { return ( -
+
{ truncateEnd={visibleChars} /> -
+
{ - const { txHash } = useParams<{ txHash: string }>(); + const { txHash } = useParams(); const hash = txHash ? toNonHex(txHash) : ''; let errorMessage: string | undefined = undefined; diff --git a/apps/explorer/src/app/routes/txs/id/tx-details.spec.tsx b/apps/explorer/src/app/routes/txs/id/tx-details.spec.tsx index 7b58423bb..1bb3a0a2f 100644 --- a/apps/explorer/src/app/routes/txs/id/tx-details.spec.tsx +++ b/apps/explorer/src/app/routes/txs/id/tx-details.spec.tsx @@ -23,6 +23,8 @@ const txData: BlockExplorerTransactionResult = { type: 'type', command: {} as ValidatorHeartbeat, signature: { + version: '1', + algo: 'vega/ed25519', value: '123', }, }; diff --git a/apps/explorer/src/app/routes/types/block-explorer-response.d.ts b/apps/explorer/src/app/routes/types/block-explorer-response.d.ts index 13a422cd5..3b1c874a9 100644 --- a/apps/explorer/src/app/routes/types/block-explorer-response.d.ts +++ b/apps/explorer/src/app/routes/types/block-explorer-response.d.ts @@ -11,6 +11,8 @@ export interface BlockExplorerTransactionResult { cursor: string; command: components['schemas']['blockexplorerv1transaction']; signature: { + version: string; + algo: string; value: string; }; error?: string; diff --git a/apps/explorer/src/app/setup-tests.ts b/apps/explorer/src/app/setup-tests.ts index 25ebbd3be..30696b54e 100644 --- a/apps/explorer/src/app/setup-tests.ts +++ b/apps/explorer/src/app/setup-tests.ts @@ -3,6 +3,9 @@ // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import '@testing-library/jest-dom'; +import { locales } from '@vegaprotocol/i18n'; +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; Object.defineProperty(window, 'ResizeObserver', { writable: false, @@ -13,3 +16,14 @@ Object.defineProperty(window, 'ResizeObserver', { disconnect: jest.fn(), })), }); + +// 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', + nsSeparator: false, + ns: ['explorer'], + defaultNS: 'explorer', +}); diff --git a/apps/explorer/src/assets/locales b/apps/explorer/src/assets/locales new file mode 120000 index 000000000..5f39c8875 --- /dev/null +++ b/apps/explorer/src/assets/locales @@ -0,0 +1 @@ +../../../../libs/i18n/src/locales \ No newline at end of file diff --git a/apps/explorer/src/i18n/index.ts b/apps/explorer/src/i18n/index.ts new file mode 100644 index 000000000..7b52db9d1 --- /dev/null +++ b/apps/explorer/src/i18n/index.ts @@ -0,0 +1,45 @@ +import type { Module } from 'i18next'; +import i18n from 'i18next'; +import HttpBackend from 'i18next-http-backend'; +import LocizeBackend from 'i18next-locize-backend'; +import LanguageDetector from 'i18next-browser-languagedetector'; +import { initReactI18next } from 'react-i18next'; + +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: '/assets/locales/{{lng}}/{{ns}}.json', + }; + +const Backend: Module = useLocize ? LocizeBackend : HttpBackend; + +i18n + .use(Backend) + .use(LanguageDetector) + .use(initReactI18next) + .init({ + lng: 'en', + fallbackLng: 'en', + supportedLngs: ['en'], + load: 'languageOnly', + debug: isInDev, + // have a common namespace used around the full app + ns: ['explorer'], + defaultNS: 'explorer', + keySeparator: false, // we use content as keys + nsSeparator: false, + backend, + saveMissing: useLocize && !!process.env.NX_LOCIZE_API_KEY, + interpolation: { + escapeValue: false, + }, + }); + +export default i18n; diff --git a/apps/explorer/tsconfig.app.json b/apps/explorer/tsconfig.app.json index 621db72d7..fcf58fcd6 100644 --- a/apps/explorer/tsconfig.app.json +++ b/apps/explorer/tsconfig.app.json @@ -2,7 +2,11 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "types": ["node"] + "types": [ + "node", + "@nx/react/typings/cssmodule.d.ts", + "@nx/react/typings/image.d.ts" + ] }, "files": [ "../../node_modules/@nx/react/typings/cssmodule.d.ts", diff --git a/apps/explorer/tsconfig.spec.json b/apps/explorer/tsconfig.spec.json index f0cb466b1..a789290d7 100644 --- a/apps/explorer/tsconfig.spec.json +++ b/apps/explorer/tsconfig.spec.json @@ -3,7 +3,13 @@ "compilerOptions": { "outDir": "../../dist/out-tsc", "module": "commonjs", - "types": ["jest", "node", "@testing-library/jest-dom"] + "types": [ + "jest", + "node", + "@testing-library/jest-dom", + "@nx/react/typings/cssmodule.d.ts", + "@nx/react/typings/image.d.ts" + ] }, "include": [ "**/*.test.ts", diff --git a/apps/governance-e2e/.eslintrc.json b/apps/governance-e2e/.eslintrc.json index 696cb8b12..225e97426 100644 --- a/apps/governance-e2e/.eslintrc.json +++ b/apps/governance-e2e/.eslintrc.json @@ -1,10 +1,12 @@ { "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "cypress"], "overrides": [ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "rules": {} + "rules": { + "cypress/unsafe-to-chain-command": 0 + } } ] } diff --git a/apps/governance-e2e/project.json b/apps/governance-e2e/project.json index 1b46e211e..b9cd50d0e 100644 --- a/apps/governance-e2e/project.json +++ b/apps/governance-e2e/project.json @@ -17,7 +17,7 @@ } }, "lint": { - "executor": "@nx/linter:eslint", + "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"], "options": { "lintFilePatterns": ["apps/governance-e2e/**/*.{js,ts}"] diff --git a/apps/governance-e2e/src/integration/flow/proposal-details.cy.ts b/apps/governance-e2e/src/integration/flow/proposal-details.cy.ts index c3a1a01ce..c57424cb7 100644 --- a/apps/governance-e2e/src/integration/flow/proposal-details.cy.ts +++ b/apps/governance-e2e/src/integration/flow/proposal-details.cy.ts @@ -31,7 +31,7 @@ import { switchVegaWalletPubKey, vegaWalletSetSpecifiedApprovalAmount, } from '../../support/wallet-functions'; -import type { testFreeformProposal } from '../../support/common-interfaces'; +import { type testFreeformProposal } from '../../support/common-interfaces'; import { formatDateWithLocalTimezone } from '@vegaprotocol/utils'; import { createGovernanceTransferProposalTxBody, @@ -61,6 +61,7 @@ const marketProposalType = 'proposal-type'; describe( 'Governance flow for proposal details', { tags: '@slow' }, + // @ts-ignore clash between jest and cypress function () { before('connect wallets and set approval limit', function () { cy.visit('/'); @@ -78,6 +79,7 @@ describe( getProposalInformationFromTable('ID').invoke('text').as('parentMarketId'); }); + // @ts-ignore clash between jest and cypress beforeEach('visit proposals tab', function () { cy.clearLocalStorage(); turnTelemetryOff(); @@ -323,6 +325,7 @@ describe( }); }); cy.VegaWalletSubmitProposal( + // @ts-ignore this is any createSuccessorMarketProposalTxBody(this.parentMarketId) ); navigateTo(navigation.proposals); @@ -334,6 +337,7 @@ describe( cy.getByTestId(proposalTermsToggle).click(); cy.get('.language-json').within(() => { cy.get('.hljs-attr').should('contain.text', 'parentMarketId'); + // @ts-ignore this is any cy.get('.hljs-string').should('contain.text', this.parentMarketId); cy.get('.hljs-attr').should('contain.text', 'insurancePoolFraction'); cy.get('.hljs-string').should('contain.text', '0.75'); @@ -352,6 +356,7 @@ describe( validateProposalDetailsDiff( 'Parent Market ID', proposalChangeType.ADDED, + // @ts-ignore this is any this.parentMarketId ); validateProposalDetailsDiff( @@ -450,6 +455,7 @@ describe( const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(2); submitUniqueRawProposal({ proposalBody: proposalPath, + // @ts-ignore this is any updateMarketId: this.parentMarketId, enactmentTimestamp: enactmentTimestamp, closingTimestamp: closingTimestamp, @@ -466,6 +472,7 @@ describe( cy.getByTestId('proposal-update-market-state').within(() => { getProposalInformationFromTable('Market ID') .invoke('text') + // @ts-ignore this is any .and('eq', this.parentMarketId); }); }); @@ -476,6 +483,7 @@ describe( const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(2); submitUniqueRawProposal({ proposalBody: proposalPath, + // @ts-ignore this is any updateMarketId: this.parentMarketId, enactmentTimestamp: enactmentTimestamp, closingTimestamp: closingTimestamp, @@ -489,6 +497,7 @@ describe( cy.getByTestId('proposal-update-market-state').within(() => { getProposalInformationFromTable('Market ID') .invoke('text') + // @ts-ignore this is any .and('eq', this.parentMarketId); }); }); @@ -499,6 +508,7 @@ describe( const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(2); submitUniqueRawProposal({ proposalBody: proposalPath, + // @ts-ignore this is any updateMarketId: this.parentMarketId, enactmentTimestamp: enactmentTimestamp, closingTimestamp: closingTimestamp, @@ -518,6 +528,7 @@ describe( cy.getByTestId('proposal-update-market-state').within(() => { getProposalInformationFromTable('Market ID') .invoke('text') + // @ts-ignore this is any .and('eq', this.parentMarketId); getProposalDetailsValue('Termination Price').should( 'contain.text', diff --git a/apps/governance-e2e/src/integration/flow/proposal-enacted.cy.ts b/apps/governance-e2e/src/integration/flow/proposal-enacted.cy.ts index e805f3b75..d10b13dad 100644 --- a/apps/governance-e2e/src/integration/flow/proposal-enacted.cy.ts +++ b/apps/governance-e2e/src/integration/flow/proposal-enacted.cy.ts @@ -37,6 +37,7 @@ context( vegaWalletSetSpecifiedApprovalAmount('1000'); }); + // @ts-ignore clash between jest and cypress beforeEach('visit proposals', function () { cy.clearLocalStorage(); turnTelemetryOff(); diff --git a/apps/governance-e2e/src/integration/flow/proposal-flow.cy.ts b/apps/governance-e2e/src/integration/flow/proposal-flow.cy.ts index 4b16a2793..fbb115d34 100644 --- a/apps/governance-e2e/src/integration/flow/proposal-flow.cy.ts +++ b/apps/governance-e2e/src/integration/flow/proposal-flow.cy.ts @@ -34,7 +34,7 @@ import { vegaWalletTeardown, } from '../../support/wallet-functions'; import { ethereumWalletConnect } from '../../support/wallet-eth.functions'; -import type { testFreeformProposal } from '../../support/common-interfaces'; +import { type testFreeformProposal } from '../../support/common-interfaces'; const vegaWalletStakedBalances = 'vega-wallet-balance-staked-validators'; const vegaWalletAssociatedBalance = 'associated-amount'; @@ -78,6 +78,7 @@ context( vegaWalletSetSpecifiedApprovalAmount('1000'); }); + // @ts-ignore clash between jest and cypress beforeEach('visit governance tab', function () { cy.clearLocalStorage(); turnTelemetryOff(); @@ -301,6 +302,7 @@ context( cy.getByTestId(voteButtons).should('not.exist'); cy.getByTestId('min-proposal-requirements').should( 'have.text', + // @ts-ignore this is any `You must have at least ${this.minVoterBalance} VEGA associated to vote on this proposal` ); }); diff --git a/apps/governance-e2e/src/integration/flow/proposal-forms.cy.ts b/apps/governance-e2e/src/integration/flow/proposal-forms.cy.ts index fafc7f5a1..95fdb8611 100644 --- a/apps/governance-e2e/src/integration/flow/proposal-forms.cy.ts +++ b/apps/governance-e2e/src/integration/flow/proposal-forms.cy.ts @@ -70,6 +70,7 @@ context( vegaWalletSetSpecifiedApprovalAmount('1000'); }); + // @ts-ignore clash between jest and cypress beforeEach('visit governance tab', function () { cy.clearLocalStorage(); turnTelemetryOff(); @@ -216,6 +217,7 @@ context( // 3003-PMAN-001 it( 'Able to submit valid new market proposal', + // @ts-ignore clash between jest and cypress { tags: '@smoke' }, function () { const proposalTitle = 'Test new market proposal'; @@ -631,6 +633,7 @@ context( ); cy.fixture('/proposals/successor-market').then((newMarketProposal) => { newMarketProposal.changes.successor.parentMarketId = + // @ts-ignore clash between jest and cypress this.parentMarketId; const newMarketPayload = JSON.stringify(newMarketProposal); cy.getByTestId(newProposalTerms).type(newMarketPayload, { @@ -658,6 +661,7 @@ context( .should('have.text', 'Successor market to: TEST.24h') .find('a') .should('have.attr', 'href') + // @ts-ignore clash between jest and cypress .and('contain', this.parentMarketId); }); }); diff --git a/apps/governance-e2e/src/integration/flow/proposal-list.cy.ts b/apps/governance-e2e/src/integration/flow/proposal-list.cy.ts index 59d1b88da..e94c8200e 100644 --- a/apps/governance-e2e/src/integration/flow/proposal-list.cy.ts +++ b/apps/governance-e2e/src/integration/flow/proposal-list.cy.ts @@ -1,4 +1,4 @@ -import type { testFreeformProposal } from '../../support/common-interfaces'; +import { type testFreeformProposal } from '../../support/common-interfaces'; import { navigateTo, navigation, @@ -33,12 +33,14 @@ const voteMajorityNotMet = 'token-majority-not-met'; const voteMajorityMet = 'token-majority-met'; const votesForPercentage = 'votes-for-percentage'; +// @ts-ignore clash between jest and cypress describe('Governance flow for proposal list', { tags: '@slow' }, function () { before('connect wallets and set approval limit', function () { vegaWalletSetSpecifiedApprovalAmount('1000'); cy.visit('/'); }); + // @ts-ignore clash between jest and cypress beforeEach('visit proposals tab', function () { cy.clearLocalStorage(); turnTelemetryOff(); @@ -106,6 +108,7 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () { it('Newly created proposals list - shows open proposals in an open state', function () { // 3001-VOTE-004 // 3001-VOTE-035 + // @ts-ignore clash between jest and cypress createRawProposal(this.minProposerBalance); cy.get('@rawProposal').then((rawProposal) => { getProposalFromTitle(rawProposal.rationale.title).within(() => { diff --git a/apps/governance-e2e/src/integration/flow/staking-flow.cy.ts b/apps/governance-e2e/src/integration/flow/staking-flow.cy.ts index f41bab520..6c5c1f298 100644 --- a/apps/governance-e2e/src/integration/flow/staking-flow.cy.ts +++ b/apps/governance-e2e/src/integration/flow/staking-flow.cy.ts @@ -64,6 +64,7 @@ context( describe('Eth wallet - contains VEGA tokens', function () { beforeEach( + // @ts-ignore clash between jest and cypress 'teardown wallet & drill into a specific validator', function () { cy.clearLocalStorage(); @@ -237,6 +238,7 @@ context( // 1002-STKE-041 1002-STKE-053 it( 'Able to remove part of a stake against a validator', + // @ts-ignore clash between jest and cypress { tags: '@smoke' }, function () { ensureSpecifiedUnstakedTokensAreAssociated('4'); diff --git a/apps/governance-e2e/src/integration/flow/token-association-flow.cy.ts b/apps/governance-e2e/src/integration/flow/token-association-flow.cy.ts index 540dc87fb..af57804e5 100644 --- a/apps/governance-e2e/src/integration/flow/token-association-flow.cy.ts +++ b/apps/governance-e2e/src/integration/flow/token-association-flow.cy.ts @@ -52,6 +52,7 @@ context( describe('Eth wallet - contains VEGA tokens', function () { beforeEach( + // @ts-ignore clash between jest and cypress 'teardown wallet & drill into a specific validator', function () { cy.clearLocalStorage(); @@ -67,6 +68,7 @@ context( it( 'Able to associate tokens - from wallet', + // @ts-ignore clash between jest and cypress { tags: '@smoke' }, function () { //1004-ASSO-003 diff --git a/apps/governance-e2e/src/integration/flow/withdrawal-flow.cy.ts b/apps/governance-e2e/src/integration/flow/withdrawal-flow.cy.ts index 61a469e87..9013ffef6 100644 --- a/apps/governance-e2e/src/integration/flow/withdrawal-flow.cy.ts +++ b/apps/governance-e2e/src/integration/flow/withdrawal-flow.cy.ts @@ -48,6 +48,7 @@ context( depositAsset(usdcEthAddress, '1000', 5); }); + // @ts-ignore clash between jest and cypress beforeEach('Navigate to withdrawal page', function () { cy.clearLocalStorage(); turnTelemetryOff(); @@ -101,6 +102,7 @@ context( // eslint-disable-next-line it.skip( 'Able to withdraw asset: -eth wallet connected -withdraw funds button', + // @ts-ignore clash between jest and cypress { tags: '@smoke' }, function () { // fill in withdrawal form diff --git a/apps/governance-e2e/src/integration/view/pubkey-view.cy.ts b/apps/governance-e2e/src/integration/view/pubkey-view.cy.ts index edd20d669..fe3a5fbe6 100644 --- a/apps/governance-e2e/src/integration/view/pubkey-view.cy.ts +++ b/apps/governance-e2e/src/integration/view/pubkey-view.cy.ts @@ -25,6 +25,7 @@ context('View functionality with public key', { tags: '@smoke' }, function () { ); }); + // @ts-ignore clash between jest and cypress beforeEach('visit home page', function () { cy.clearLocalStorage(); turnTelemetryOff(); diff --git a/apps/governance-e2e/src/integration/view/rewards.cy.ts b/apps/governance-e2e/src/integration/view/rewards.cy.ts index 6100f9838..3db50cfb9 100644 --- a/apps/governance-e2e/src/integration/view/rewards.cy.ts +++ b/apps/governance-e2e/src/integration/view/rewards.cy.ts @@ -52,6 +52,7 @@ context( .invoke('text') .then(($newPageNumber) => { const newPageNumber = Number($newPageNumber.slice(5)); + // @ts-ignore clash between jest and cypress expect(newPageNumber).to.be.greaterThan(currentPageNumber); cy.getByTestId('goto-previous-page').click(); cy.getByTestId('page-info').should( diff --git a/apps/governance-e2e/src/integration/view/validators.cy.ts b/apps/governance-e2e/src/integration/view/validators.cy.ts index 496a29069..791bf01d4 100644 --- a/apps/governance-e2e/src/integration/view/validators.cy.ts +++ b/apps/governance-e2e/src/integration/view/validators.cy.ts @@ -44,6 +44,7 @@ context('Validators Page - verify elements on page', function () { cy.mockChainId(); }); + // @ts-ignore clash between jest and cypress describe('with wallets disconnected', { tags: '@smoke' }, function () { it('Should have validators tab highlighted', function () { verifyTabHighlighted(navigation.validators); @@ -76,6 +77,7 @@ context('Validators Page - verify elements on page', function () { describe( 'Should be able to see validator list from the staking page', { tags: '@regression' }, + // @ts-ignore clash between jest and cypress function () { // 1002-STKE-050 it('Should be able to see validator names', function () { @@ -180,6 +182,7 @@ context('Validators Page - verify elements on page', function () { describe( 'Should be able to see static information about a validator', { tags: '@smoke' }, + // @ts-ignore clash between jest and cypress function () { before('connect wallets and click on validator', function () { cy.mockChainId(); diff --git a/apps/governance-e2e/src/integration/view/wallet-eth.cy.ts b/apps/governance-e2e/src/integration/view/wallet-eth.cy.ts index 503ee7e1b..5e1f3276d 100644 --- a/apps/governance-e2e/src/integration/view/wallet-eth.cy.ts +++ b/apps/governance-e2e/src/integration/view/wallet-eth.cy.ts @@ -198,6 +198,7 @@ context( }); }) .then(function () { + // @ts-ignore clash between jest and cypress expect(parseFloat(this.value).toFixed(1)).to.equal( (Math.round((this.locked + this.unlocked) * 100) / 100).toFixed( 1 @@ -270,6 +271,7 @@ context( }); }) .then(function () { + // @ts-ignore clash between jest and cypress expect(this.value).to.equal(this.locked + this.unlocked); }); }); diff --git a/apps/governance-e2e/src/integration/view/wallet-vega.cy.ts b/apps/governance-e2e/src/integration/view/wallet-vega.cy.ts index 942490c08..58011cc60 100644 --- a/apps/governance-e2e/src/integration/view/wallet-vega.cy.ts +++ b/apps/governance-e2e/src/integration/view/wallet-vega.cy.ts @@ -105,6 +105,7 @@ context( // 0002-WCON-008 it( 'should have truncated account number visible', + // @ts-ignore clash between jest and cypress { tags: '@smoke' }, function () { cy.get(walletContainer).within(() => { @@ -125,6 +126,7 @@ context( it( 'should have Vega Associated currency value visible', + // @ts-ignore clash between jest and cypress { tags: '@smoke' }, function () { cy.get(walletContainer).within(() => { @@ -135,14 +137,19 @@ context( } ); - it('should have Unstaked value visible', { tags: '@smoke' }, function () { - cy.get(walletContainer).within(() => { - cy.get(vegaUnstaked) - .should('be.visible') - .invoke('text') - .and('not.be.empty'); - }); - }); + it( + 'should have Unstaked value visible', + // @ts-ignore clash between jest and cypress + { tags: '@smoke' }, + function () { + cy.get(walletContainer).within(() => { + cy.get(vegaUnstaked) + .should('be.visible') + .invoke('text') + .and('not.be.empty'); + }); + } + ); it('should have Governance button visible', function () { cy.get(walletContainer).within(() => { @@ -295,13 +302,17 @@ context( cy.getByTestId(vegaWalletCurrencyTitle) .contains(name) - .parent() - .siblings(txTimeout) - .should((elementAmount) => { - const displayedAmount = parseFloat(elementAmount.text()); - expect(displayedAmount).be.gte(expectedAmount); + .parent() // back to currency-title + .parent() // back to container + .within(() => { + cy.get( + '[data-account-type="account_type_general"] [data-value]' + ).should((elementAmount) => { + const displayedAmount = parseFloat(elementAmount.text()); + // @ts-ignore clash between jest and cypress + expect(displayedAmount).be.gte(expectedAmount); + }); }); - cy.getByTestId(vegaWalletCurrencyTitle) .contains(name) .parent() diff --git a/apps/governance-e2e/src/support/staking.functions.ts b/apps/governance-e2e/src/support/staking.functions.ts index 81a0452a9..c4cf49619 100644 --- a/apps/governance-e2e/src/support/staking.functions.ts +++ b/apps/governance-e2e/src/support/staking.functions.ts @@ -256,10 +256,7 @@ export function validateWalletCurrency( .parent() .parent() .within(() => { - cy.getByTestId('currency-value', txTimeout).should( - 'have.text', - expectedAmount - ); + cy.get('[data-value]', txTimeout).should('have.text', expectedAmount); }); } diff --git a/apps/governance/README.md b/apps/governance/README.md index f42175f6b..dcd7db2c9 100644 --- a/apps/governance/README.md +++ b/apps/governance/README.md @@ -49,10 +49,6 @@ There are a few different configuration options offered for this app: | `NX_ETH_WALLET_MNEMONIC` (optional) | The mnemonic to be used to sign transactions with in browser | | `NX_LOCAL_PROVIDER_URL` (optional) | The local node to use to send transaction to when signing in browser | -## Example configs: - -For example configurations, check out our [netlify.toml](./netlify.toml). - ## Testing To run the minimal set of unit tests, run the following: diff --git a/apps/governance/netlify.toml b/apps/governance/netlify.toml deleted file mode 100644 index b87b8d3dd..000000000 --- a/apps/governance/netlify.toml +++ /dev/null @@ -1,4 +0,0 @@ -[[redirects]] - from = "/*" - to = "/index.html" - status = 200 diff --git a/apps/governance/project.json b/apps/governance/project.json index 82b9cd991..101d259cb 100644 --- a/apps/governance/project.json +++ b/apps/governance/project.json @@ -56,7 +56,7 @@ } }, "lint": { - "executor": "@nx/linter:eslint", + "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"], "options": { "lintFilePatterns": ["apps/governance/**/*.{ts,tsx,js,jsx}"] @@ -66,23 +66,7 @@ "executor": "@nx/jest:jest", "outputs": ["{workspaceRoot}/coverage/apps/governance"], "options": { - "jestConfig": "apps/governance/jest.config.ts", - "passWithNoTests": true - }, - "configurations": { - "ci": { - "ci": true, - "codeCoverage": true - } - } - }, - "build-netlify": { - "executor": "nx:run-commands", - "options": { - "commands": [ - "cp apps/governance/netlify.toml netlify.toml", - "nx build token" - ] + "jestConfig": "apps/governance/jest.config.ts" } }, "build-spec": { diff --git a/apps/governance/src/app-loader.tsx b/apps/governance/src/app-loader.tsx index 150ecac4b..7536cc519 100644 --- a/apps/governance/src/app-loader.tsx +++ b/apps/governance/src/app-loader.tsx @@ -4,7 +4,7 @@ import { Splash } from '@vegaprotocol/ui-toolkit'; import { useVegaWallet, useEagerConnect } from '@vegaprotocol/wallet'; import { FLAGS, useEnvironment } from '@vegaprotocol/environment'; import { useWeb3React } from '@web3-react/core'; -import React from 'react'; +import React, { Suspense } from 'react'; import { useTranslation } from 'react-i18next'; import { SplashError } from './components/splash-error'; @@ -164,13 +164,14 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => { ); } - if (!loaded) { - return ( - - - - ); - } + const loading = ( + + + + ); - return children; + if (!loaded) { + return loading; + } + return {children}; }; diff --git a/apps/governance/src/app.tsx b/apps/governance/src/app.tsx index 39f77bd65..56b35ccee 100644 --- a/apps/governance/src/app.tsx +++ b/apps/governance/src/app.tsx @@ -42,6 +42,7 @@ import { useNodeSwitcherStore, DocsLinks, NodeFailure, + AppLoader as Loader, } from '@vegaprotocol/environment'; import { ENV } from './config'; import type { InMemoryCacheConfig } from '@apollo/client'; @@ -352,9 +353,11 @@ function App() { useInitializeEnv(); return ( - - - + }> + + + + ); } diff --git a/apps/governance/src/assets/locales b/apps/governance/src/assets/locales new file mode 120000 index 000000000..5f39c8875 --- /dev/null +++ b/apps/governance/src/assets/locales @@ -0,0 +1 @@ +../../../../libs/i18n/src/locales \ No newline at end of file diff --git a/apps/governance/src/components/vega-wallet/hooks.ts b/apps/governance/src/components/vega-wallet/hooks.ts index 569de10ec..38f9f9d10 100644 --- a/apps/governance/src/components/vega-wallet/hooks.ts +++ b/apps/governance/src/components/vega-wallet/hooks.ts @@ -10,7 +10,7 @@ import noIcon from '../../images/token-no-icon.png'; import vegaBlack from '../../images/vega_black.png'; import vegaVesting from '../../images/vega_vesting.png'; import { BigNumber } from '../../lib/bignumber'; -import type { WalletCardAssetProps } from '../wallet-card'; +import { type WalletCardAssetProps } from '../wallet-card'; import { useVegaWallet } from '@vegaprotocol/wallet'; import { useContracts } from '../../contexts/contracts/contracts-context'; import * as Schema from '@vegaprotocol/types'; @@ -21,12 +21,12 @@ import { toBigNum, } from '@vegaprotocol/utils'; import { useAppState } from '../../contexts/app-state/app-state-context'; -import type { - DelegationsQuery, - DelegationsQueryVariables, - WalletDelegationFieldsFragment, +import { + DelegationsDocument, + type DelegationsQuery, + type DelegationsQueryVariables, + type WalletDelegationFieldsFragment, } from './__generated__/Delegations'; -import { DelegationsDocument } from './__generated__/Delegations'; import { isPartyNotFoundError } from '../../lib/party'; export const usePollForDelegations = () => { @@ -44,6 +44,7 @@ export const usePollForDelegations = () => { const [delegatedNodes, setDelegatedNodes] = React.useState< { nodeId: string; + // eslint-disable-next-line name: string; hasStakePending: boolean; currentEpochStake?: BigNumber; @@ -113,6 +114,16 @@ export const usePollForDelegations = () => { isAssetTypeERC20(a.asset) && a.asset.source.contractAddress === vegaToken.address; + const isVesting = + a.type === Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS || + a.type === Schema.AccountType.ACCOUNT_TYPE_VESTING_REWARDS; + + let icon = noIcon; + if (isVega) { + if (isVesting) icon = vegaVesting; + else icon = vegaBlack; + } + return { isVega, name: a.asset.name, @@ -123,14 +134,7 @@ export const usePollForDelegations = () => { balance: new BigNumber( addDecimal(a.balance, a.asset.decimals) ), - image: isVega - ? vegaBlack - : a.type === - Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS || - a.type === - Schema.AccountType.ACCOUNT_TYPE_VESTING_REWARDS - ? vegaVesting - : noIcon, + image: icon, border: isVega, address: isAssetTypeERC20(a.asset) ? a.asset.source.contractAddress diff --git a/apps/governance/src/components/vega-wallet/vega-wallet.tsx b/apps/governance/src/components/vega-wallet/vega-wallet.tsx index b03e37409..0e6d5c8fe 100644 --- a/apps/governance/src/components/vega-wallet/vega-wallet.tsx +++ b/apps/governance/src/components/vega-wallet/vega-wallet.tsx @@ -11,7 +11,10 @@ import { BigNumber } from '../../lib/bignumber'; import { truncateMiddle } from '../../lib/truncate-middle'; import Routes from '../../routes/routes'; import { BulletHeader } from '../bullet-header'; -import type { WalletCardAssetProps } from '../wallet-card'; +import type { + WalletCardAssetProps, + WalletCardAssetWithMultipleBalancesProps, +} from '../wallet-card'; import { WalletCard, WalletCardActions, @@ -27,6 +30,7 @@ import { Button, ButtonLink } from '@vegaprotocol/ui-toolkit'; import { toBigNum } from '@vegaprotocol/utils'; import { usePendingBalancesStore } from '../../hooks/use-pending-balances-manager'; import { StakingEventType } from '../../hooks/use-get-association-breakdown'; +import omit from 'lodash/omit'; export const VegaWallet = () => { const { t } = useTranslation(); @@ -99,12 +103,29 @@ const VegaWalletAssetList = ({ accounts }: VegaWalletAssetsListProps) => { if (!accounts.length) { return null; } + + const groupedByAsset = accounts.reduce((all, a) => { + const foundIndex = all.findIndex((acc) => acc.assetId === a.assetId); + if (foundIndex > -1) { + const found = all[foundIndex]; + all[foundIndex] = { + ...found, + balances: [...found.balances, { balance: a.balance, type: a.type }], + }; + return all; + } + const acc = { + ...omit(a, 'balance', 'type'), + balances: [{ balance: a.balance, type: a.type }], + }; + return [...all, acc]; + }, [] as WalletCardAssetWithMultipleBalancesProps[]); return ( <> {t('assets')} - {accounts.map((a, i) => ( + {groupedByAsset.map((a, i) => ( ))} @@ -182,6 +203,7 @@ const VegaWalletConnected = ({ vegaKeys }: VegaWalletConnectedProps) => { subheading={t('Associated')} symbol="VEGA" balance={currentStakeAvailable} + allowZeroBalance={true} /> {totalPending.eq(0) ? null : ( <> @@ -192,6 +214,7 @@ const VegaWalletConnected = ({ vegaKeys }: VegaWalletConnectedProps) => { subheading={t('Pending association')} symbol="VEGA" balance={totalPending} + allowZeroBalance={true} /> { subheading={t('Total associated after pending')} symbol="VEGA" balance={pendingStakeAmount} + allowZeroBalance={true} /> )} diff --git a/apps/governance/src/components/wallet-card/wallet-card.tsx b/apps/governance/src/components/wallet-card/wallet-card.tsx index b2346dcfd..eabf2c737 100644 --- a/apps/governance/src/components/wallet-card/wallet-card.tsx +++ b/apps/governance/src/components/wallet-card/wallet-card.tsx @@ -103,7 +103,7 @@ export const WalletCardActions = ({ return
{children}
; }; -export interface WalletCardAssetProps { +export type WalletCardAssetProps = { image: string; name: string; symbol: string; @@ -113,42 +113,61 @@ export interface WalletCardAssetProps { border?: boolean; subheading?: string; type?: Schema.AccountType; -} + allowZeroBalance?: boolean; +}; + +export type WalletCardAssetWithMultipleBalancesProps = Omit< + WalletCardAssetProps, + 'balance' | 'type' +> & { + balances: { balance: BigNumber; type?: Schema.AccountType }[]; +}; export const WalletCardAsset = ({ image, name, symbol, - balance, decimals, assetId, border, subheading, - type, -}: WalletCardAssetProps) => { - const [integers, decimalsPlaces, separator] = useNumberParts( - balance, - decimals - ); - const { t } = useTranslation(); - const consoleLink = useLinks(DApp.Console); - const transferAssetLink = (assetId: string) => - consoleLink(CONSOLE_TRANSFER_ASSET.replace(':assetId', assetId)); - const { param: baseRate } = useNetworkParam('rewards_vesting_baseRate'); + allowZeroBalance = false, + ...props +}: WalletCardAssetProps | WalletCardAssetWithMultipleBalancesProps) => { + const balance = 'balance' in props ? props.balance : undefined; + const type = 'type' in props ? props.type : undefined; + const balances = + 'balances' in props + ? props.balances + : balance + ? [{ balance, type }] + : undefined; - const isRedeemable = - type === Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS && assetId; + const values = + balances && + balances.length > 0 && + balances + .filter((b) => allowZeroBalance || !b.balance.isZero()) + .sort((a, b) => { + const order = [ + Schema.AccountType.ACCOUNT_TYPE_VESTING_REWARDS, + Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS, + Schema.AccountType.ACCOUNT_TYPE_GENERAL, + undefined, + ]; + return order.indexOf(a.type) - order.indexOf(b.type); + }) + .map(({ balance, type }, i) => ( + + )); - const accountTypeTooltip = useMemo(() => { - if (type === Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS) { - return t('VestedRewardsTooltip'); - } - if (type === Schema.AccountType.ACCOUNT_TYPE_VESTING_REWARDS && baseRate) { - return t('VestingRewardsTooltip', { baseRate }); - } - - return null; - }, [baseRate, t, type]); + if (!values || values.length === 0) return; return (
@@ -169,35 +188,92 @@ export const WalletCardAsset = ({ {subheading || symbol}
- {type ? ( -
- - - {Schema.AccountTypeMapping[type]} - - - {isRedeemable ? ( - - - {t('Redeem')} - - - ) : null} -
- ) : null} -
- - {integers} - {separator} - - {decimalsPlaces} + {values} +
+
+ ); +}; + +const useAccountTypeTooltip = (type?: Schema.AccountType) => { + const { t } = useTranslation(); + const { param: baseRate } = useNetworkParam('rewards_vesting_baseRate'); + const accountTypeTooltip = useMemo(() => { + if (type === Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS) { + return t('VestedRewardsTooltip'); + } + if (type === Schema.AccountType.ACCOUNT_TYPE_VESTING_REWARDS && baseRate) { + return t('VestingRewardsTooltip', { baseRate }); + } + + return null; + }, [baseRate, t, type]); + + return accountTypeTooltip; +}; + +const CurrencyValue = ({ + balance, + decimals, + type, + assetId, +}: { + balance: BigNumber; + decimals: number; + type?: Schema.AccountType; + assetId?: string; +}) => { + const { t } = useTranslation(); + const consoleLink = useLinks(DApp.Console); + const transferAssetLink = (assetId: string) => + consoleLink(CONSOLE_TRANSFER_ASSET.replace(':assetId', assetId)); + + const [integers, decimalsPlaces, separator] = useNumberParts( + balance, + decimals + ); + const accountTypeTooltip = useAccountTypeTooltip(type); + + const accountType = type && ( + + + {Schema.AccountTypeMapping[type]} + + + ); + const isRedeemable = + type === Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS && assetId; + const redeemBtn = isRedeemable ? ( + + + {t('Redeem')} + + + ) : null; + + return ( +
+ {type && ( +
+ {accountType} + {redeemBtn}
+ )} +
+ + {integers} + {separator} + + {decimalsPlaces}
); diff --git a/apps/governance/src/contexts/contracts/contracts-provider.tsx b/apps/governance/src/contexts/contracts/contracts-provider.tsx index f4a4c470f..d416a674b 100644 --- a/apps/governance/src/contexts/contracts/contracts-provider.tsx +++ b/apps/governance/src/contexts/contracts/contracts-provider.tsx @@ -9,7 +9,7 @@ import { useWeb3React } from '@web3-react/core'; import React from 'react'; import { SplashLoader } from '../../components/splash-loader'; -import type { ContractsContextShape } from './contracts-context'; +import { type ContractsContextShape } from './contracts-context'; import { ContractsContext } from './contracts-context'; import { createDefaultProvider } from '../../lib/web3-connectors'; import { useEthereumConfig } from '@vegaprotocol/web3'; diff --git a/apps/governance/src/i18n/index.ts b/apps/governance/src/i18n/index.ts index ce094b69e..6837b559e 100644 --- a/apps/governance/src/i18n/index.ts +++ b/apps/governance/src/i18n/index.ts @@ -1,29 +1,42 @@ +import type { Module } from 'i18next'; import i18n from 'i18next'; +import HttpBackend from 'i18next-http-backend'; +import LocizeBackend from 'i18next-locize-backend'; import LanguageDetector from 'i18next-browser-languagedetector'; import { initReactI18next } from 'react-i18next'; -import dev from './translations/dev.json'; +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: '/assets/locales/{{lng}}/{{ns}}.json', + }; + +const Backend: Module = useLocize ? LocizeBackend : HttpBackend; i18n + .use(Backend) .use(LanguageDetector) .use(initReactI18next) .init({ - // we init with resources - resources: { - en: { - translations: { - ...dev, - }, - }, - }, - lng: undefined, + lng: 'en', fallbackLng: 'en', - debug: true, + supportedLngs: ['en'], + load: 'languageOnly', + debug: isInDev, // have a common namespace used around the full app - ns: ['translations'], - defaultNS: 'translations', + ns: ['governance'], + defaultNS: 'governance', keySeparator: false, // we use content as keys - + nsSeparator: false, + backend, + saveMissing: useLocize && !!process.env.NX_LOCIZE_API_KEY, interpolation: { escapeValue: false, }, diff --git a/apps/governance/src/routes/proposals/components/list-asset/list-asset.tsx b/apps/governance/src/routes/proposals/components/list-asset/list-asset.tsx index c4dc20d62..41b49a713 100644 --- a/apps/governance/src/routes/proposals/components/list-asset/list-asset.tsx +++ b/apps/governance/src/routes/proposals/components/list-asset/list-asset.tsx @@ -1,4 +1,4 @@ -import type { CollateralBridge } from '@vegaprotocol/smart-contracts'; +import { type CollateralBridge } from '@vegaprotocol/smart-contracts'; import * as Schema from '@vegaprotocol/types'; import { Button } from '@vegaprotocol/ui-toolkit'; import { useBridgeContract, useEthereumTransaction } from '@vegaprotocol/web3'; @@ -88,7 +88,7 @@ export const ListAsset = ({ assetData.erc20ListAssetBundle; return (
-

{t('ListAsset')}

+

{t('ListAsset')}

{t('ListAssetDescription')}

)} -

{t('OR')}

+

{t('OR')}

{ const { account: address } = useWeb3React(); const { vesting } = useContracts(); @@ -34,7 +36,7 @@ export const RedeemFromTranche = () => { tranches: state.tranches, getTranches: state.getTranches, })); - const { id } = useParams<{ id: string }>(); + const { id } = useParams(); const numberId = Number(id); const tranche = React.useMemo( () => tranches?.find(({ tranche_id }) => tranche_id === numberId) || null, @@ -86,7 +88,7 @@ export const RedeemFromTranche = () => { i18nKey="noVestingTokens" components={{ tranchesLink: ( - + ), }} /> @@ -128,13 +130,13 @@ export const RedeemFromTranche = () => { components={{ stakingLink: ( ), governanceLink: ( ), diff --git a/apps/governance/src/routes/staking/associate/hooks.ts b/apps/governance/src/routes/staking/associate/hooks.ts index 394fb7918..d42fe8b09 100644 --- a/apps/governance/src/routes/staking/associate/hooks.ts +++ b/apps/governance/src/routes/staking/associate/hooks.ts @@ -11,12 +11,12 @@ import { useTransaction } from '../../../hooks/use-transaction'; import { useAppState } from '../../../contexts/app-state/app-state-context'; import { removeDecimal, removePaginationWrapper } from '@vegaprotocol/utils'; import * as Schema from '@vegaprotocol/types'; -import type { - LinkingsFieldsFragment, - PartyStakeLinkingsQuery, - PartyStakeLinkingsQueryVariables, +import { + PartyStakeLinkingsDocument, + type LinkingsFieldsFragment, + type PartyStakeLinkingsQuery, + type PartyStakeLinkingsQueryVariables, } from './__generated__/PartyStakeLinkings'; -import { PartyStakeLinkingsDocument } from './__generated__/PartyStakeLinkings'; export const useAddStake = ( address: string, diff --git a/apps/governance/src/routes/staking/home/validator-tables/consensus-validators-table.tsx b/apps/governance/src/routes/staking/home/validator-tables/consensus-validators-table.tsx index 2a07ddc76..6fb29be83 100644 --- a/apps/governance/src/routes/staking/home/validator-tables/consensus-validators-table.tsx +++ b/apps/governance/src/routes/staking/home/validator-tables/consensus-validators-table.tsx @@ -26,9 +26,9 @@ import { ValidatorRenderer, VotingPowerRenderer, } from './shared'; -import type { AgGridReact } from 'ag-grid-react'; -import type { ColDef, RowHeightParams } from 'ag-grid-community'; -import type { ValidatorsTableProps } from './shared'; +import { type AgGridReact } from 'ag-grid-react'; +import { type ColDef, type RowHeightParams } from 'ag-grid-community'; +import { type ValidatorsTableProps } from './shared'; import { formatNumber, formatNumberPercentage, @@ -83,19 +83,19 @@ const TopThirdCellRenderer = ( e.preventDefault(); setHideTopThird(false); }} - className="grid grid-cols-[60px_1fr] w-full h-full py-4 px-0 text-sm text-white text-center overflow-scroll" + className="grid h-full w-full grid-cols-[60px_1fr] overflow-scroll px-0 py-4 text-center text-sm text-white" > -
+
{params?.data?.rankingDisplay}
-
+
- ); - })} + + if (perpOnlyViews.includes(key)) { + return false; + } + + return true; + }) + .map((_key) => { + const key = _key as TradingView; + const isActive = view === key; + return ( + setView(key)} + /> + ); + })}
); }; + +export const NoMarketSplash = () => { + const t = useT(); + return {t('No market')}; +}; + +const ViewButton = ({ + view, + isActive, + onClick, +}: { + view: TradingView; + isActive: boolean; + onClick: () => void; +}) => { + const label = useViewLabel(view); + const className = classNames('py-2 px-4 min-w-[100px] capitalize text-sm', { + 'bg-vega-clight-500 dark:bg-vega-cdark-500': isActive, + }); + + return ( + + ); +}; + +const useViewLabel = (view: TradingView) => { + const t = useT(); + + const labels = { + candles: t('Candles'), + depth: t('Depth'), + liquidity: t('Liquidity'), + funding: t('Funding'), + fundingPayments: t('Funding Payments'), + orderbook: t('Orderbook'), + trades: t('Trades'), + positions: t('Positions'), + activeOrders: t('Active'), + closedOrders: t('Closed'), + rejectedOrders: t('Rejected'), + orders: t('All'), + stopOrders: t('Stop'), + collateral: t('Collateral'), + fills: t('Fills'), + }; + + return labels[view]; +}; diff --git a/apps/trading/client-pages/market/trade-views.tsx b/apps/trading/client-pages/market/trade-views.tsx index 98069dc0b..959e2797f 100644 --- a/apps/trading/client-pages/market/trade-views.tsx +++ b/apps/trading/client-pages/market/trade-views.tsx @@ -1,12 +1,9 @@ -import type { ComponentProps } from 'react'; -import { Splash } from '@vegaprotocol/ui-toolkit'; import { DepthChartContainer } from '@vegaprotocol/market-depth'; import { CandlesChartContainer, CandlesMenu, } from '@vegaprotocol/candles-chart'; import { Filter, OpenOrdersMenu } from '@vegaprotocol/orders'; -import { NO_MARKET } from './constants'; import { TradesContainer } from '../../components/trades-container'; import { OrderbookContainer } from '../../components/orderbook-container'; import { FillsContainer } from '../../components/fills-container'; @@ -15,96 +12,60 @@ import { AccountsContainer } from '../../components/accounts-container'; import { LiquidityContainer } from '../../components/liquidity-container'; import { FundingContainer } from '../../components/funding-container'; import { FundingPaymentsContainer } from '../../components/funding-payments-container'; -import type { OrderContainerProps } from '../../components/orders-container'; import { OrdersContainer } from '../../components/orders-container'; import { StopOrdersContainer } from '../../components/stop-orders-container'; import { AccountsMenu } from '../../components/accounts-menu'; import { PositionsMenu } from '../../components/positions-menu'; -type MarketDependantView = - | typeof CandlesChartContainer - | typeof DepthChartContainer - | typeof OrderbookContainer - | typeof TradesContainer; - -type MarketDependantViewProps = ComponentProps; - -const requiresMarket = (View: MarketDependantView) => { - const WrappedComponent = (props: MarketDependantViewProps) => - props.marketId ? : {NO_MARKET}; - WrappedComponent.displayName = `RequiresMarket(${View.name})`; - return WrappedComponent; -}; - export type TradingView = keyof typeof TradingViews; export const TradingViews = { candles: { - label: 'Candles', - component: requiresMarket(CandlesChartContainer), + component: CandlesChartContainer, menu: CandlesMenu, }, depth: { - label: 'Depth', - component: requiresMarket(DepthChartContainer), + component: DepthChartContainer, }, liquidity: { - label: 'Liquidity', - component: requiresMarket(LiquidityContainer), + component: LiquidityContainer, }, funding: { - label: 'Funding', - component: requiresMarket(FundingContainer), + component: FundingContainer, }, fundingPayments: { - label: 'Funding Payments', component: FundingPaymentsContainer, }, orderbook: { - label: 'Orderbook', - component: requiresMarket(OrderbookContainer), + component: OrderbookContainer, }, trades: { - label: 'Trades', - component: requiresMarket(TradesContainer), + component: TradesContainer, }, positions: { - label: 'Positions', component: PositionsContainer, menu: PositionsMenu, }, activeOrders: { - label: 'Active', - component: (props: OrderContainerProps) => ( - - ), + component: () => , menu: OpenOrdersMenu, }, closedOrders: { - label: 'Closed', - component: (props: OrderContainerProps) => ( - - ), + component: () => , }, rejectedOrders: { - label: 'Rejected', - component: (props: OrderContainerProps) => ( - - ), + component: () => , }, orders: { - label: 'All', component: OrdersContainer, menu: OpenOrdersMenu, }, stopOrders: { - label: 'Stop', component: StopOrdersContainer, }, collateral: { - label: 'Collateral', component: AccountsContainer, menu: AccountsMenu, }, - fills: { label: 'Fills', component: FillsContainer }, -}; + fills: { component: FillsContainer }, +} as const; diff --git a/apps/trading/client-pages/markets/closed.tsx b/apps/trading/client-pages/markets/closed.tsx index 4529fbbd3..c2082dc1a 100644 --- a/apps/trading/client-pages/markets/closed.tsx +++ b/apps/trading/client-pages/markets/closed.tsx @@ -8,7 +8,6 @@ import type { import { AgGrid, COL_DEFS } from '@vegaprotocol/datagrid'; import { useDataProvider } from '@vegaprotocol/data-provider'; import { useMemo } from 'react'; -import { t } from '@vegaprotocol/i18n'; import type { Asset } from '@vegaprotocol/types'; import type { ProductType } from '@vegaprotocol/types'; import { MarketState, MarketStateMapping } from '@vegaprotocol/types'; @@ -24,6 +23,7 @@ import { SettlementDateCell } from './settlement-date-cell'; import { SettlementPriceCell } from './settlement-price-cell'; import { MarketCodeCell } from './market-code-cell'; import { MarketActionsDropdown } from './market-table-actions'; +import { useT } from '../../lib/use-t'; type SettlementAsset = Pick< Asset, @@ -127,6 +127,7 @@ const ClosedMarketsDataGrid = ({ rowData: Row[]; error: Error | undefined; }) => { + const t = useT(); const handleOnSelect = useMarketClickHandler(); const openAssetDialog = useAssetDetailsDialogStore((store) => store.open); @@ -274,7 +275,7 @@ const ClosedMarketsDataGrid = ({ }, }, ]; - }, [openAssetDialog]); + }, [openAssetDialog, t]); return ( { + const t = useT(); if (!value || !data || !data.productType) return null; const infoSpanClasses = diff --git a/apps/trading/client-pages/markets/market-list-table.tsx b/apps/trading/client-pages/markets/market-list-table.tsx index 46c3112ff..a7dabc782 100644 --- a/apps/trading/client-pages/markets/market-list-table.tsx +++ b/apps/trading/client-pages/markets/market-list-table.tsx @@ -1,7 +1,14 @@ import type { TypedDataAgGrid } from '@vegaprotocol/datagrid'; -import { AgGrid, PriceFlashCell } from '@vegaprotocol/datagrid'; +import { + AgGrid, + PriceFlashCell, + useDataGridEvents, +} from '@vegaprotocol/datagrid'; import type { MarketMaybeWithData } from '@vegaprotocol/markets'; import { useColumnDefs } from './use-column-defs'; +import type { DataGridStore } from '../../stores/datagrid-store-slice'; +import { type StateCreator, create } from 'zustand'; +import { persist } from 'zustand/middleware'; export const getRowId = ({ data }: { data: { id: string } }) => data.id; @@ -18,8 +25,37 @@ const components = { type Props = TypedDataAgGrid; +export type DataGridSlice = { + gridStore: DataGridStore; + updateGridStore: (gridStore: DataGridStore) => void; +}; + +export const createDataGridSlice: StateCreator = (set) => ({ + gridStore: {}, + updateGridStore: (newStore) => { + set((curr) => ({ + gridStore: { + ...curr.gridStore, + ...newStore, + }, + })); + }, +}); + +const useMarketsStore = create()( + persist(createDataGridSlice, { + name: 'vega_market_list_store', + }) +); + export const MarketListTable = (props: Props) => { const columnDefs = useColumnDefs(); + const gridStore = useMarketsStore((store) => store.gridStore); + const updateGridStore = useMarketsStore((store) => store.updateGridStore); + + const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => { + updateGridStore(colState); + }); return ( { columnDefs={columnDefs} components={components} rowHeight={45} + {...gridStoreCallbacks} {...props} /> ); diff --git a/apps/trading/client-pages/markets/market-table-actions.tsx b/apps/trading/client-pages/markets/market-table-actions.tsx index e239ff7f8..b368de202 100644 --- a/apps/trading/client-pages/markets/market-table-actions.tsx +++ b/apps/trading/client-pages/markets/market-table-actions.tsx @@ -1,4 +1,3 @@ -import { t } from '@vegaprotocol/i18n'; import { TradingDropdownItem, TradingDropdownCopyItem, @@ -11,6 +10,7 @@ import { DApp, EXPLORER_MARKET, useLinks } from '@vegaprotocol/environment'; import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; import { useNavigate } from 'react-router-dom'; import { Links } from '../../lib/links'; +import { useT } from '../../lib/use-t'; export const MarketActionsDropdown = ({ marketId, @@ -23,6 +23,7 @@ export const MarketActionsDropdown = ({ successorMarketID: string | null | undefined; parentMarketID: string | null | undefined; }) => { + const t = useT(); const navigate = useNavigate(); const open = useAssetDetailsDialogStore((store) => store.open); const linkCreator = useLinks(DApp.Explorer); diff --git a/apps/trading/client-pages/markets/markets-page.tsx b/apps/trading/client-pages/markets/markets-page.tsx index 666e63d81..f38924556 100644 --- a/apps/trading/client-pages/markets/markets-page.tsx +++ b/apps/trading/client-pages/markets/markets-page.tsx @@ -1,6 +1,5 @@ import React, { useEffect } from 'react'; import { titlefy } from '@vegaprotocol/utils'; -import { t } from '@vegaprotocol/i18n'; import { LocalStoragePersistTabs as Tabs, Tab, @@ -15,8 +14,10 @@ import { TOKEN_NEW_MARKET_PROPOSAL, useLinks, } from '@vegaprotocol/environment'; +import { useT } from '../../lib/use-t'; export const MarketsPage = () => { + const t = useT(); const { updateTitle } = usePageTitleStore((store) => ({ updateTitle: store.updateTitle, })); @@ -25,8 +26,8 @@ export const MarketsPage = () => { const externalLink = governanceLink(TOKEN_NEW_MARKET_PROPOSAL); useEffect(() => { - updateTitle(titlefy(['Markets'])); - }, [updateTitle]); + updateTitle(titlefy([t('Markets')])); + }, [updateTitle, t]); return (
diff --git a/apps/trading/client-pages/markets/markets-sidebar.tsx b/apps/trading/client-pages/markets/markets-sidebar.tsx index cc6edc3ef..1e2f1ed6a 100644 --- a/apps/trading/client-pages/markets/markets-sidebar.tsx +++ b/apps/trading/client-pages/markets/markets-sidebar.tsx @@ -1,5 +1,4 @@ import { Route, Routes } from 'react-router-dom'; -import { t } from '@vegaprotocol/i18n'; import { VegaIconNames } from '@vegaprotocol/ui-toolkit'; import { SidebarButton, @@ -7,8 +6,10 @@ import { ViewType, } from '../../components/sidebar'; import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id'; +import { useT } from '../../lib/use-t'; export const MarketsSidebar = () => { + const t = useT(); const currentRouteId = useGetCurrentRouteId(); return ( diff --git a/apps/trading/client-pages/markets/open-markets.tsx b/apps/trading/client-pages/markets/open-markets.tsx index ee8a20473..807615248 100644 --- a/apps/trading/client-pages/markets/open-markets.tsx +++ b/apps/trading/client-pages/markets/open-markets.tsx @@ -2,16 +2,17 @@ import { useDataProvider } from '@vegaprotocol/data-provider'; import type { MarketMaybeWithData } from '@vegaprotocol/markets'; import { marketListProvider } from '@vegaprotocol/markets'; import { useEffect } from 'react'; -import { t } from '@vegaprotocol/i18n'; import type { CellClickedEvent } from 'ag-grid-community'; import MarketListTable from './market-list-table'; import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler'; import { Interval } from '@vegaprotocol/types'; import { useYesterday } from '@vegaprotocol/react-helpers'; +import { useT } from '../../lib/use-t'; const POLLING_TIME = 2000; export const OpenMarkets = () => { + const t = useT(); const handleOnSelect = useMarketClickHandler(); const yesterday = useYesterday(); const { data, error, reload } = useDataProvider({ @@ -42,7 +43,7 @@ export const OpenMarkets = () => { if (!data) return; // prevent navigating to the market page if any of the below cells are clicked - // event.preventDefault or event.stopPropagation dont seem to apply for aggird + // event.preventDefault or event.stopPropagation do not seem to apply for ag-grid const colId = column.getColId(); if ( diff --git a/apps/trading/client-pages/markets/settlement-date-cell.tsx b/apps/trading/client-pages/markets/settlement-date-cell.tsx index 80fb9cf5c..7d9efa264 100644 --- a/apps/trading/client-pages/markets/settlement-date-cell.tsx +++ b/apps/trading/client-pages/markets/settlement-date-cell.tsx @@ -1,8 +1,8 @@ import { DApp, EXPLORER_ORACLE, useLinks } from '@vegaprotocol/environment'; -import { t } from '@vegaprotocol/i18n'; import { MarketState } from '@vegaprotocol/types'; import { Link } from '@vegaprotocol/ui-toolkit'; import { getDateTimeFormat } from '@vegaprotocol/utils'; +import { useT } from '../../lib/use-t'; import { formatDistanceToNowStrict, isAfter } from 'date-fns'; export interface SettlementDataCellProps { @@ -18,6 +18,7 @@ export const SettlementDateCell = ({ closeTimestamp, marketState, }: SettlementDataCellProps) => { + const t = useT(); const linkCreator = useLinks(DApp.Explorer); const date = closeTimestamp ? new Date(closeTimestamp) : metaDate; @@ -31,12 +32,12 @@ export const SettlementDateCell = ({ if (expiryHasPassed) { if (marketState !== MarketState.STATE_SETTLED) { - text = t('Expected %s ago', distance); + text = t('Expected {{distance}} ago', { distance }); } else { - text = t('%s ago', distance); + text = t('{{distance}} ago', { distance }); } } else { - text = t('Expected in %s', distance); + text = t('Expected in {{distance}}', { distance }); } } diff --git a/apps/trading/client-pages/markets/settlement-price-cell.tsx b/apps/trading/client-pages/markets/settlement-price-cell.tsx index 47fb280b3..4593ba7ed 100644 --- a/apps/trading/client-pages/markets/settlement-price-cell.tsx +++ b/apps/trading/client-pages/markets/settlement-price-cell.tsx @@ -1,10 +1,10 @@ import { DApp, EXPLORER_ORACLE, useLinks } from '@vegaprotocol/environment'; -import { t } from '@vegaprotocol/i18n'; import type { DataSourceFilterFragment } from '@vegaprotocol/markets'; import { useOracleSpecBindingData } from '@vegaprotocol/markets'; import { PropertyKeyType } from '@vegaprotocol/types'; import { Link } from '@vegaprotocol/ui-toolkit'; import { addDecimalsFormatNumber } from '@vegaprotocol/utils'; +import { useT } from '../../lib/use-t'; export interface SettlementPriceCellProps { oracleSpecId: string | undefined; @@ -17,6 +17,7 @@ export const SettlementPriceCell = ({ settlementDataSpecBinding, filter, }: SettlementPriceCellProps) => { + const t = useT(); const linkCreator = useLinks(DApp.Explorer); const { property, loading } = useOracleSpecBindingData( oracleSpecId, diff --git a/apps/trading/client-pages/markets/use-column-defs.tsx b/apps/trading/client-pages/markets/use-column-defs.tsx index e2b506348..84ba52d86 100644 --- a/apps/trading/client-pages/markets/use-column-defs.tsx +++ b/apps/trading/client-pages/markets/use-column-defs.tsx @@ -1,6 +1,5 @@ import { useMemo } from 'react'; import type { ColDef, ValueFormatterParams } from 'ag-grid-community'; -import { t } from '@vegaprotocol/i18n'; import type { VegaICellRendererParams, VegaValueFormatterParams, @@ -18,10 +17,12 @@ import type { import { MarketActionsDropdown } from './market-table-actions'; import { calcCandleVolume, getAsset } from '@vegaprotocol/markets'; import { MarketCodeCell } from './market-code-cell'; +import { useT } from '../../lib/use-t'; const { MarketTradingMode, AuctionTrigger } = Schema; export const useColumnDefs = () => { + const t = useT(); const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); return useMemo( () => [ @@ -50,6 +51,29 @@ export const useColumnDefs = () => { headerName: t('Description'), field: 'tradableInstrument.instrument.name', }, + { + headerName: t('Settlement asset'), + field: 'tradableInstrument.instrument.product.settlementAsset.symbol', + cellRenderer: ({ + data, + }: VegaICellRendererParams< + MarketMaybeWithData, + 'tradableInstrument.instrument.product.settlementAsset.symbol' + >) => { + const value = data && getAsset(data); + return value ? ( + { + openAssetDetailsDialog(value.id, e.target as HTMLElement); + }} + > + {value.symbol} + + ) : ( + '' + ); + }, + }, { headerName: t('Trading mode'), field: 'tradingMode', @@ -141,27 +165,21 @@ export const useColumnDefs = () => { }, }, { - headerName: t('Settlement asset'), - field: 'tradableInstrument.instrument.product.settlementAsset.symbol', - cellRenderer: ({ + headerName: t('Open Interest'), + field: 'data.openInterest', + type: 'rightAligned', + valueFormatter: ({ data, - }: VegaICellRendererParams< + }: VegaValueFormatterParams< MarketMaybeWithData, - 'tradableInstrument.instrument.product.settlementAsset.symbol' - >) => { - const value = data && getAsset(data); - return value ? ( - { - openAssetDetailsDialog(value.id, e.target as HTMLElement); - }} - > - {value.symbol} - - ) : ( - '' - ); - }, + 'data.openInterest' + >) => + data?.data?.openInterest === undefined + ? '-' + : addDecimalsFormatNumber( + data?.data?.openInterest, + data?.positionDecimalPlaces + ), }, { headerName: t('Spread'), @@ -216,6 +234,6 @@ export const useColumnDefs = () => { }, }, ], - [openAssetDetailsDialog] + [openAssetDetailsDialog, t] ); }; diff --git a/apps/trading/client-pages/portfolio/portfolio-sidebar.tsx b/apps/trading/client-pages/portfolio/portfolio-sidebar.tsx index a1d40a439..dc390c6eb 100644 --- a/apps/trading/client-pages/portfolio/portfolio-sidebar.tsx +++ b/apps/trading/client-pages/portfolio/portfolio-sidebar.tsx @@ -1,9 +1,10 @@ -import { t } from '@vegaprotocol/i18n'; import { VegaIconNames } from '@vegaprotocol/ui-toolkit'; import { SidebarButton, ViewType } from '../../components/sidebar'; import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id'; +import { useT } from '../../lib/use-t'; export const PortfolioSidebar = () => { + const t = useT(); const currentRouteId = useGetCurrentRouteId(); return ( diff --git a/apps/trading/client-pages/portfolio/portfolio.tsx b/apps/trading/client-pages/portfolio/portfolio.tsx index 854981fc8..ad17b1cf5 100644 --- a/apps/trading/client-pages/portfolio/portfolio.tsx +++ b/apps/trading/client-pages/portfolio/portfolio.tsx @@ -2,7 +2,6 @@ import { useEffect } from 'react'; import type { ReactNode } from 'react'; import { LayoutPriority } from 'allotment'; import { titlefy } from '@vegaprotocol/utils'; -import { t } from '@vegaprotocol/i18n'; import { useIncompleteWithdrawals } from '@vegaprotocol/withdraws'; import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit'; import { usePageTitleStore } from '../../stores'; @@ -25,6 +24,7 @@ import { AccountsMenu } from '../../components/accounts-menu'; import { DepositsMenu } from '../../components/deposits-menu'; import { WithdrawalsMenu } from '../../components/withdrawals-menu'; import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id'; +import { useT } from '../../lib/use-t'; const WithdrawalsIndicator = () => { const { ready } = useIncompleteWithdrawals(); @@ -39,6 +39,7 @@ const WithdrawalsIndicator = () => { }; export const Portfolio = () => { + const t = useT(); const currentRouteId = useGetCurrentRouteId(); const { getView, setViews } = useSidebar(); const view = getView(currentRouteId); @@ -49,7 +50,7 @@ export const Portfolio = () => { useEffect(() => { updateTitle(titlefy([t('Portfolio')])); - }, [updateTitle]); + }, [updateTitle, t]); // Make transfer sidebar open by default useEffect(() => { diff --git a/apps/trading/client-pages/referrals/apply-code-form.tsx b/apps/trading/client-pages/referrals/apply-code-form.tsx index b4121aee4..c9065f642 100644 --- a/apps/trading/client-pages/referrals/apply-code-form.tsx +++ b/apps/trading/client-pages/referrals/apply-code-form.tsx @@ -10,19 +10,19 @@ import { useForm } from 'react-hook-form'; import classNames from 'classnames'; import { Navigate, useNavigate, useSearchParams } from 'react-router-dom'; import type { ButtonHTMLAttributes, MouseEventHandler } from 'react'; -import { useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { RainbowButton } from './buttons'; import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet'; import { useReferral } from './hooks/use-referral'; import { Routes } from '../../lib/links'; import { useTransactionEventSubscription } from '@vegaprotocol/web3'; -import { t } from '@vegaprotocol/i18n'; -import { Statistics } from './referral-statistics'; +import { Statistics, useStats } from './referral-statistics'; import { useReferralProgram } from './hooks/use-referral-program'; +import { useT } from '../../lib/use-t'; const RELOAD_DELAY = 3000; -const validateCode = (value: string) => { +const validateCode = (value: string, t: ReturnType) => { const number = +`0x${value}`; if (!value || value.length !== 64) { return t('Code must be 64 characters in length'); @@ -32,7 +32,21 @@ const validateCode = (value: string) => { return true; }; +export const ApplyCodeFormContainer = () => { + const { pubKey } = useVegaWallet(); + const { data: referee } = useReferral({ pubKey, role: 'referee' }); + const { data: referrer } = useReferral({ pubKey, role: 'referrer' }); + + // go to main page if the current pubkey is already a referrer or referee + if (referee || referrer) { + return ; + } + + return ; +}; + export const ApplyCodeForm = () => { + const t = useT(); const program = useReferralProgram(); const navigate = useNavigate(); const openWalletDialog = useVegaWalletDialogStore( @@ -54,14 +68,29 @@ export const ApplyCodeForm = () => { } = useForm(); const [params] = useSearchParams(); - const { data: referee } = useReferral({ pubKey, role: 'referee' }); - const { data: referrer } = useReferral({ pubKey, role: 'referrer' }); - const codeField = watch('code'); const { data: previewData, loading: previewLoading } = useReferral({ - code: validateCode(codeField) ? codeField : undefined, + code: validateCode(codeField, t) ? codeField : undefined, }); + /** + * Validates the set a user tries to apply to. + */ + const validateSet = useCallback(() => { + if ( + codeField && + !previewLoading && + previewData && + !previewData.isEligible + ) { + return t('The code is no longer valid.'); + } + if (codeField && !previewLoading && !previewData) { + return t('The code is invalid'); + } + return true; + }, [codeField, previewData, previewLoading, t]); + useEffect(() => { const code = params.get('code'); if (code) setValue('code', code); @@ -132,6 +161,8 @@ export const ApplyCodeForm = () => { }), }); + const { epochsValue, nextBenefitTierValue } = useStats({ program }); + // go to main page when successfully applied useEffect(() => { if (status === 'successful') { @@ -141,16 +172,11 @@ export const ApplyCodeForm = () => { } }, [navigate, status]); - // go to main page if the current pubkey is already a referrer or referee - if (referee || referrer) { - return ; - } - // show "code applied" message when successfully applied if (status === 'successful') { return ( -
-

+
+

{' '} @@ -196,17 +222,24 @@ export const ApplyCodeForm = () => { }; }; + const nextBenefitTierEpochsValue = nextBenefitTierValue + ? nextBenefitTierValue.epochs - epochsValue + : 0; + return ( <> -
-

+
+

{t('Apply a referral code')}

{t('Enter a referral code to get trading discounts.')}

{ hasError={Boolean(errors.code)} {...register('code', { required: t('You have to provide a code to apply it.'), - validate: validateCode, + validate: (value) => { + const err = validateCode(value, t); + if (err !== true) return err; + return validateSet(); + }, })} placeholder="Enter a code" - className="mb-2 bg-vega-clight-900 dark:bg-vega-cdark-700" + className="bg-vega-clight-900 dark:bg-vega-cdark-700 mb-2" /> {errors.code && ( - + {errors.code.message?.toString()} )}
- {previewLoading && !previewData ? ( + {validateCode(codeField, t) === true && previewLoading && !previewData ? (
) : null} - {previewData ? ( + {/* TODO: Re-check plural forms once i18n is updated */} + {previewData && previewData.isEligible ? (
-

{t('You are joining')}

+

+ {t( + 'youAreJoiningTheGroup', + 'You are joining the group shown, but will not have access to benefits until you have completed at least {{count}} epochs.', + { count: nextBenefitTierEpochsValue } + )} +

) : null} diff --git a/apps/trading/client-pages/referrals/create-code-form.tsx b/apps/trading/client-pages/referrals/create-code-form.tsx index fa189f36d..405e60a92 100644 --- a/apps/trading/client-pages/referrals/create-code-form.tsx +++ b/apps/trading/client-pages/referrals/create-code-form.tsx @@ -24,13 +24,14 @@ import { DISCLAIMER_REFERRAL_DOCS_LINK, } from './constants'; import { useReferral } from './hooks/use-referral'; -import { t } from '@vegaprotocol/i18n'; +import { useT } from '../../lib/use-t'; export const CreateCodeContainer = () => { return ; }; export const CreateCodeForm = () => { + const t = useT(); const [dialogOpen, setDialogOpen] = useState(false); const openWalletDialog = useVegaWalletDialogStore( (store) => store.openVegaWalletDialog @@ -38,7 +39,10 @@ export const CreateCodeForm = () => { const { pubKey, isReadOnly } = useVegaWallet(); return ( -
+

{t('Create a referral code')}

@@ -81,6 +85,7 @@ const CreateCodeDialog = ({ }: { setDialogOpen: (open: boolean) => void; }) => { + const t = useT(); const createLink = useLinks(DApp.Governance); const { isReadOnly, pubKey, sendTx } = useVegaWallet(); const { refetch } = useReferral({ pubKey, role: 'referrer' }); @@ -93,6 +98,11 @@ const CreateCodeDialog = ({ const { stakeAvailable: currentStakeAvailable, requiredStake } = useStakeAvailable(); + const { data: referralSets } = useReferral({ + pubKey, + role: 'referrer', + }); + const onSubmit = () => { if (isReadOnly || !pubKey) { setErr('Not connected'); @@ -170,10 +180,14 @@ const CreateCodeDialog = ({ return (

- {t('You need at least')}{' '} - {addDecimalsFormatNumber(requiredStake.toString(), 18)}{' '} {t( - 'VEGA staked to generate a referral code and participate in the referral program.' + 'You need at least {{requiredStake}} VEGA staked to generate a referral code and participate in the referral program.', + { + requiredStake: addDecimalsFormatNumber( + requiredStake.toString(), + 18 + ), + } )}

+ {(status === 'idle' || status === 'loading' || status === 'error') && ( + <> + { +

+ {t( + 'There is currently no referral program active, are you sure you want to create a code?' + )} +

+ } + + )} + {status === 'success' && code && ( +
+
+

+ {code} +

+
+ + } + > + {t('Copy')} + + +
+ )} + onSubmit()} + {...getButtonProps()} + > + {status === 'idle' && ( + { + refetch(); + setDialogOpen(false); + }} + > + {t('No')} + + )} + {err && {err}} +
+ + {t('About the referral program')} + + + {t('Disclaimer')} + +
+
+ ); + } + return (
{(status === 'idle' || status === 'loading' || status === 'error') && ( diff --git a/apps/trading/client-pages/referrals/error-boundary.tsx b/apps/trading/client-pages/referrals/error-boundary.tsx index b9c8ddf6e..2e42ca240 100644 --- a/apps/trading/client-pages/referrals/error-boundary.tsx +++ b/apps/trading/client-pages/referrals/error-boundary.tsx @@ -3,21 +3,22 @@ import { RainbowButton } from './buttons'; import { AnimatedDudeWithWire } from './graphics/dude'; import { LayoutWithSky } from './layout'; import { Routes } from '../../lib/links'; -import { t } from '@vegaprotocol/i18n'; +import { useT } from '../../lib/use-t'; export const ErrorBoundary = () => { + const t = useT(); const error = useRouteError(); const navigate = useNavigate(); const title = isRouteErrorResponse(error) ? `${error.status} ${error.statusText}` - : 'Something went wrong'; + : t('Something went wrong'); const code = isRouteErrorResponse(error) ? error.status : 0; const messages: Record = { - 0: 'An unknown error occurred.', - 404: "The page you're looking for doesn't exists.", + 0: t('An unknown error occurred.'), + 404: t("The page you're looking for doesn't exists."), }; return ( @@ -48,6 +49,7 @@ export const ErrorBoundary = () => { }; export const NotFound = () => { + const t = useT(); const navigate = useNavigate(); return ( diff --git a/apps/trading/client-pages/referrals/hooks/ReferralSetStats.graphql b/apps/trading/client-pages/referrals/hooks/ReferralSetStats.graphql index 7d0fba752..d13d28d90 100644 --- a/apps/trading/client-pages/referrals/hooks/ReferralSetStats.graphql +++ b/apps/trading/client-pages/referrals/hooks/ReferralSetStats.graphql @@ -11,6 +11,7 @@ query ReferralSetStats($code: ID!, $epoch: Int) { rewardsMultiplier rewardsFactorMultiplier referrerTakerVolume + wasEligible } } } diff --git a/apps/trading/client-pages/referrals/hooks/StakeAvailable.graphql b/apps/trading/client-pages/referrals/hooks/StakeAvailable.graphql new file mode 100644 index 000000000..5ccbb4b30 --- /dev/null +++ b/apps/trading/client-pages/referrals/hooks/StakeAvailable.graphql @@ -0,0 +1,10 @@ +query StakeAvailable($partyId: ID!) { + party(id: $partyId) { + stakingSummary { + currentStakeAvailable + } + } + networkParameter(key: "referralProgram.minStakedVegaTokens") { + value + } +} diff --git a/apps/trading/client-pages/referrals/hooks/__generated__/ReferralSetStats.ts b/apps/trading/client-pages/referrals/hooks/__generated__/ReferralSetStats.ts index 7647ede92..d60b1fef4 100644 --- a/apps/trading/client-pages/referrals/hooks/__generated__/ReferralSetStats.ts +++ b/apps/trading/client-pages/referrals/hooks/__generated__/ReferralSetStats.ts @@ -9,7 +9,7 @@ export type ReferralSetStatsQueryVariables = Types.Exact<{ }>; -export type ReferralSetStatsQuery = { __typename?: 'Query', referralSetStats: { __typename?: 'ReferralSetStatsConnection', edges: Array<{ __typename?: 'ReferralSetStatsEdge', node: { __typename?: 'ReferralSetStats', atEpoch: number, partyId: string, discountFactor: string, rewardFactor: string, epochNotionalTakerVolume: string, referralSetRunningNotionalTakerVolume: string, rewardsMultiplier: string, rewardsFactorMultiplier: string, referrerTakerVolume: string } } | null> } }; +export type ReferralSetStatsQuery = { __typename?: 'Query', referralSetStats: { __typename?: 'ReferralSetStatsConnection', edges: Array<{ __typename?: 'ReferralSetStatsEdge', node: { __typename?: 'ReferralSetStats', atEpoch: number, partyId: string, discountFactor: string, rewardFactor: string, epochNotionalTakerVolume: string, referralSetRunningNotionalTakerVolume: string, rewardsMultiplier: string, rewardsFactorMultiplier: string, referrerTakerVolume: string, wasEligible: boolean } } | null> } }; export const ReferralSetStatsDocument = gql` @@ -26,6 +26,7 @@ export const ReferralSetStatsDocument = gql` rewardsMultiplier rewardsFactorMultiplier referrerTakerVolume + wasEligible } } } diff --git a/apps/trading/client-pages/referrals/hooks/__generated__/StakeAvailable.ts b/apps/trading/client-pages/referrals/hooks/__generated__/StakeAvailable.ts new file mode 100644 index 000000000..7acc953c2 --- /dev/null +++ b/apps/trading/client-pages/referrals/hooks/__generated__/StakeAvailable.ts @@ -0,0 +1,53 @@ +import * as Types from '@vegaprotocol/types'; + +import { gql } from '@apollo/client'; +import * as Apollo from '@apollo/client'; +const defaultOptions = {} as const; +export type StakeAvailableQueryVariables = Types.Exact<{ + partyId: Types.Scalars['ID']; +}>; + + +export type StakeAvailableQuery = { __typename?: 'Query', party?: { __typename?: 'Party', stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string } } | null, networkParameter?: { __typename?: 'NetworkParameter', value: string } | null }; + + +export const StakeAvailableDocument = gql` + query StakeAvailable($partyId: ID!) { + party(id: $partyId) { + stakingSummary { + currentStakeAvailable + } + } + networkParameter(key: "referralProgram.minStakedVegaTokens") { + value + } +} + `; + +/** + * __useStakeAvailableQuery__ + * + * To run a query within a React component, call `useStakeAvailableQuery` and pass it any options that fit your needs. + * When your component renders, `useStakeAvailableQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useStakeAvailableQuery({ + * variables: { + * partyId: // value for 'partyId' + * }, + * }); + */ +export function useStakeAvailableQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(StakeAvailableDocument, options); + } +export function useStakeAvailableLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(StakeAvailableDocument, options); + } +export type StakeAvailableQueryHookResult = ReturnType; +export type StakeAvailableLazyQueryHookResult = ReturnType; +export type StakeAvailableQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/apps/trading/client-pages/referrals/hooks/use-referral-toasts.tsx b/apps/trading/client-pages/referrals/hooks/use-referral-toasts.tsx new file mode 100644 index 000000000..362855fb6 --- /dev/null +++ b/apps/trading/client-pages/referrals/hooks/use-referral-toasts.tsx @@ -0,0 +1,111 @@ +import { + Intent, + type Toast, + useToasts, + ToastHeading, + Button, +} from '@vegaprotocol/ui-toolkit'; +import { useReferral } from './use-referral'; +import { useVegaWallet } from '@vegaprotocol/wallet'; +import { useEffect } from 'react'; +import { useT } from '../../../lib/use-t'; +import { matchPath, useLocation, useNavigate } from 'react-router-dom'; +import { Routes } from '../../../lib/links'; +import { useCurrentEpochInfoQuery } from './__generated__/Epoch'; + +const REFETCH_INTERVAL = 60 * 60 * 1000; // 1h +const NON_ELIGIBLE_REFERRAL_SET_TOAST_ID = 'non-eligible-referral-set'; + +const useNonEligibleReferralSet = () => { + const { pubKey } = useVegaWallet(); + const { data, loading, refetch } = useReferral({ pubKey, role: 'referee' }); + const { + data: epochData, + loading: epochLoading, + refetch: epochRefetch, + } = useCurrentEpochInfoQuery(); + + useEffect(() => { + const interval = setInterval(() => { + refetch(); + epochRefetch(); + }, REFETCH_INTERVAL); + + return () => { + clearInterval(interval); + }; + }, [epochRefetch, refetch]); + + return { data, epoch: epochData?.epoch.id, loading: loading || epochLoading }; +}; + +export const useReferralToasts = () => { + const navigate = useNavigate(); + const { pathname } = useLocation(); + const t = useT(); + const [setToast, hasToast, updateToast] = useToasts((store) => [ + store.setToast, + store.hasToast, + store.update, + ]); + + const { data, epoch, loading } = useNonEligibleReferralSet(); + + useEffect(() => { + if ( + data && + epoch && + !loading && + !data.isEligible && + !hasToast(NON_ELIGIBLE_REFERRAL_SET_TOAST_ID + epoch) + ) { + const nonEligibleReferralToast: Toast = { + id: NON_ELIGIBLE_REFERRAL_SET_TOAST_ID + epoch, + intent: Intent.Warning, + content: ( + <> + {t('Referral code no longer valid')} +

+ {t( + 'Your referral code is no longer valid as the referrer no longer meets the minimum requirements.' + )} +

+

+ +

+ + ), + onClose: () => + updateToast(NON_ELIGIBLE_REFERRAL_SET_TOAST_ID + epoch, { + hidden: true, + }), + }; + setToast(nonEligibleReferralToast); + } + }, [ + data, + epoch, + hasToast, + loading, + navigate, + pathname, + setToast, + t, + updateToast, + ]); +}; diff --git a/apps/trading/client-pages/referrals/hooks/use-referral.ts b/apps/trading/client-pages/referrals/hooks/use-referral.ts index 67f471bad..8265bafce 100644 --- a/apps/trading/client-pages/referrals/hooks/use-referral.ts +++ b/apps/trading/client-pages/referrals/hooks/use-referral.ts @@ -4,6 +4,7 @@ import { useRefereesQuery } from './__generated__/Referees'; import compact from 'lodash/compact'; import type { ReferralSetsQueryVariables } from './__generated__/ReferralSets'; import { useReferralSetsQuery } from './__generated__/ReferralSets'; +import { useStakeAvailable } from './use-stake-available'; export const DEFAULT_AGGREGATION_DAYS = 30; @@ -62,6 +63,8 @@ export const useReferral = (args: UseReferralArgs) => { ? referralData.referralSets.edges[0]?.node : undefined; + const { isEligible } = useStakeAvailable(referralSet?.referrer); + const { data: refereesData, loading: refereesLoading, @@ -103,6 +106,7 @@ export const useReferral = (args: UseReferralArgs) => { referee: referee, referrerId: referralSet.referrer, createdAt: referralSet.createdAt, + isEligible, referees, } : undefined; diff --git a/apps/trading/client-pages/referrals/hooks/use-stake-available.ts b/apps/trading/client-pages/referrals/hooks/use-stake-available.ts index af0b8836c..9706c80ba 100644 --- a/apps/trading/client-pages/referrals/hooks/use-stake-available.ts +++ b/apps/trading/client-pages/referrals/hooks/use-stake-available.ts @@ -1,34 +1,35 @@ -import { gql, useQuery } from '@apollo/client'; import { useVegaWallet } from '@vegaprotocol/wallet'; +import { useStakeAvailableQuery } from './__generated__/StakeAvailable'; -const STAKE_QUERY = gql` - query CreateCode($partyId: ID!) { - party(id: $partyId) { - stakingSummary { - currentStakeAvailable - } - } - networkParameter(key: "referralProgram.minStakedVegaTokens") { - value - } - } -`; - -export const useStakeAvailable = () => { - const { pubKey } = useVegaWallet(); - const { data } = useQuery(STAKE_QUERY, { - variables: { partyId: pubKey || '' }, - skip: !pubKey, +/** + * Gets the current stake available for given public key and required stake for + * the referral program. + * + * (Uses currently connected public key if left empty) + */ +export const useStakeAvailable = (pubKey?: string) => { + const { pubKey: currentPubKey } = useVegaWallet(); + const partyId = pubKey || currentPubKey; + const { data } = useStakeAvailableQuery({ + variables: { partyId: partyId || '' }, + skip: !partyId, // TODO: remove when network params available errorPolicy: 'ignore', }); + const stakeAvailable = data + ? BigInt(data.party?.stakingSummary.currentStakeAvailable || '0') + : undefined; + const requiredStake = data + ? BigInt(data.networkParameter?.value || '0') + : undefined; + return { - stakeAvailable: data - ? BigInt(data.party?.stakingSummary.currentStakeAvailable || '0') - : undefined, - requiredStake: data - ? BigInt(data.networkParameter?.value || '0') - : undefined, + stakeAvailable, + requiredStake, + isEligible: + stakeAvailable != null && + requiredStake != null && + stakeAvailable >= requiredStake, }; }; diff --git a/apps/trading/client-pages/referrals/how-it-works-table.tsx b/apps/trading/client-pages/referrals/how-it-works-table.tsx index 9fdc3acd6..965cf9395 100644 --- a/apps/trading/client-pages/referrals/how-it-works-table.tsx +++ b/apps/trading/client-pages/referrals/how-it-works-table.tsx @@ -1,63 +1,66 @@ -import { t } from '@vegaprotocol/i18n'; +import { useT } from '../../lib/use-t'; import { Table } from './table'; -export const HowItWorksTable = () => ( - - 1 - - ), - step: t( - 'Referrers generate a code assigned to their key via an on chain transaction' - ), - }, - { - number: ( - - 2 - - ), - step: t( - 'Anyone with the referral link can apply it to their key(s) of choice via an on chain transaction' - ), - }, - { - number: ( - - 3 - - ), - step: t( - 'Discounts are applied automatically during trading based on the key(s) used' - ), - }, - { - number: ( - - 4 - - ), - step: t( - 'Referrers earn commission based on a percentage of the taker fees their referees pay' - ), - }, - { - number: ( - - 5 - - ), - step: t( - 'The commission is taken from the infrastructure fee, maker fee, and liquidity provider fee, not from the referee' - ), - }, - ]} - >
-); +export const HowItWorksTable = () => { + const t = useT(); + return ( + + 1 + + ), + step: t( + 'Referrers generate a code assigned to their key via an on chain transaction' + ), + }, + { + number: ( + + 2 + + ), + step: t( + 'Anyone with the referral link can apply it to their key(s) of choice via an on chain transaction' + ), + }, + { + number: ( + + 3 + + ), + step: t( + 'Discounts are applied automatically during trading based on the key(s) used' + ), + }, + { + number: ( + + 4 + + ), + step: t( + 'Referrers earn commission based on a percentage of the taker fees their referees pay' + ), + }, + { + number: ( + + 5 + + ), + step: t( + 'The commission is taken from the infrastructure fee, maker fee, and liquidity provider fee, not from the referee' + ), + }, + ]} + >
+ ); +}; diff --git a/apps/trading/client-pages/referrals/landing-banner.tsx b/apps/trading/client-pages/referrals/landing-banner.tsx index 9dbb5f65f..f9e3d6e98 100644 --- a/apps/trading/client-pages/referrals/landing-banner.tsx +++ b/apps/trading/client-pages/referrals/landing-banner.tsx @@ -1,8 +1,9 @@ import classNames from 'classnames'; import { AnimatedDudeWithWire } from './graphics/dude'; -import { t } from '@vegaprotocol/i18n'; +import { useT } from '../../lib/use-t'; export const LandingBanner = () => { + const t = useT(); return (
diff --git a/apps/trading/client-pages/referrals/referral-statistics.spec.tsx b/apps/trading/client-pages/referrals/referral-statistics.spec.tsx new file mode 100644 index 000000000..9a0c88e18 --- /dev/null +++ b/apps/trading/client-pages/referrals/referral-statistics.spec.tsx @@ -0,0 +1,374 @@ +import { MockedProvider, type MockedResponse } from '@apollo/react-testing'; +import { render, waitFor } from '@testing-library/react'; +import { type VegaWalletContextShape } from '@vegaprotocol/wallet'; +import { ReferralStatistics } from './referral-statistics'; +import { + ReferralProgramDocument, + type ReferralProgramQuery, +} from './hooks/__generated__/CurrentReferralProgram'; +import { + ReferralSetsDocument, + type ReferralSetsQueryVariables, + type ReferralSetsQuery, +} from './hooks/__generated__/ReferralSets'; +import { + StakeAvailableDocument, + type StakeAvailableQueryVariables, + type StakeAvailableQuery, +} from './hooks/__generated__/StakeAvailable'; +import { + RefereesDocument, + type RefereesQueryVariables, + type RefereesQuery, +} from './hooks/__generated__/Referees'; +import { MemoryRouter } from 'react-router-dom'; + +const MOCK_PUBKEY = + '1234567890123456789012345678901234567890123456789012345678901234'; + +const MOCK_STAKE_AVAILABLE: StakeAvailableQuery = { + networkParameter: { + __typename: 'NetworkParameter', + value: '1', + }, + party: { + __typename: 'Party', + stakingSummary: { + __typename: 'StakingSummary', + currentStakeAvailable: '1', + }, + }, +}; + +const MOCK_NON_ELIGIBILE_STAKE_AVAILABLE: StakeAvailableQuery = { + networkParameter: { + __typename: 'NetworkParameter', + value: '1', + }, + party: { + __typename: 'Party', + stakingSummary: { + __typename: 'StakingSummary', + currentStakeAvailable: '0', + }, + }, +}; + +const MOCK_REFERRAL_PROGRAM: ReferralProgramQuery = { + currentReferralProgram: { + __typename: 'CurrentReferralProgram', + benefitTiers: [ + { + __typename: 'BenefitTier', + minimumEpochs: 1, + minimumRunningNotionalTakerVolume: '0', + referralDiscountFactor: '0.01', + referralRewardFactor: '0.01', + }, + { + __typename: 'BenefitTier', + minimumEpochs: 2, + minimumRunningNotionalTakerVolume: '10', + referralDiscountFactor: '0.02', + referralRewardFactor: '0.02', + }, + ], + endOfProgramTimestamp: '202411012023-11-26T05:58:24.045158Z', + id: '123', + stakingTiers: [ + { + __typename: 'StakingTier', + minimumStakedTokens: '100', + referralRewardMultiplier: '1', + }, + { + __typename: 'StakingTier', + minimumStakedTokens: '1000', + referralRewardMultiplier: '2', + }, + ], + version: 2, + windowLength: 3, + endedAt: null, + }, +}; + +const MOCK_REFERRER_SET: ReferralSetsQuery = { + referralSets: { + __typename: 'ReferralSetConnection', + edges: [ + { + __typename: 'ReferralSetEdge', + node: { + __typename: 'ReferralSet', + createdAt: '2023-11-26T05:58:24.045158Z', + id: '3772e570fbab89e50e563036b01dd949c554e5b5fe7908449672dfce9a8adffa', + referrer: MOCK_PUBKEY, + updatedAt: '2023-11-26T05:58:24.045158Z', + }, + }, + ], + }, +}; + +const MOCK_REFERREE_SET: ReferralSetsQuery = { + referralSets: { + __typename: 'ReferralSetConnection', + edges: [ + { + __typename: 'ReferralSetEdge', + node: { + __typename: 'ReferralSet', + createdAt: '2023-11-26T05:58:24.045158Z', + id: '3772e570fbab89e50e563036b01dd949c554e5b5fe7908449672dfce9a8adffa', + referrer: + '1111111111111111111111111111111111111111111111111111111111111111', + updatedAt: '2023-11-26T05:58:24.045158Z', + }, + }, + ], + }, +}; + +const MOCK_REFEREES: RefereesQuery = { + referralSetReferees: { + __typename: 'ReferralSetRefereeConnection', + edges: [ + { + node: { + atEpoch: 1, + joinedAt: '2023-11-21T14:17:09.257235Z', + refereeId: + '0987654321098765432109876543210987654321098765432109876543219876', + referralSetId: + '3772e570fbab89e50e563036b01dd949c554e5b5fe7908449672dfce9a8adffa', + totalRefereeGeneratedRewards: '1234', + totalRefereeNotionalTakerVolume: '5678', + __typename: 'ReferralSetReferee', + }, + }, + ], + }, +}; + +const programMock: MockedResponse = { + request: { + query: ReferralProgramDocument, + }, + result: { data: MOCK_REFERRAL_PROGRAM }, +}; + +const referralSetAsReferrerMock: MockedResponse< + ReferralSetsQuery, + ReferralSetsQueryVariables +> = { + request: { + query: ReferralSetsDocument, + variables: { + referrer: MOCK_PUBKEY, + }, + }, + result: { + data: MOCK_REFERRER_SET, + }, +}; + +const noReferralSetAsReferrerMock: MockedResponse< + ReferralSetsQuery, + ReferralSetsQueryVariables +> = { + request: { + query: ReferralSetsDocument, + variables: { + referrer: MOCK_PUBKEY, + }, + }, + result: { + data: { referralSets: { edges: [] } }, + }, +}; + +const referralSetAsRefereeMock: MockedResponse< + ReferralSetsQuery, + ReferralSetsQueryVariables +> = { + request: { + query: ReferralSetsDocument, + variables: { + referee: MOCK_PUBKEY, + }, + }, + result: { + data: MOCK_REFERREE_SET, + }, +}; + +const noReferralSetAsRefereeMock: MockedResponse< + ReferralSetsQuery, + ReferralSetsQueryVariables +> = { + request: { + query: ReferralSetsDocument, + variables: { + referee: MOCK_PUBKEY, + }, + }, + result: { + data: { referralSets: { edges: [] } }, + }, +}; + +const stakeAvailableMock: MockedResponse< + StakeAvailableQuery, + StakeAvailableQueryVariables +> = { + request: { + query: StakeAvailableDocument, + variables: { + partyId: MOCK_PUBKEY, + }, + }, + result: { + data: MOCK_STAKE_AVAILABLE, + }, +}; + +const nonEligibleStakeAvailableMock: MockedResponse< + StakeAvailableQuery, + StakeAvailableQueryVariables +> = { + request: { + query: StakeAvailableDocument, + variables: { + partyId: MOCK_PUBKEY, + }, + }, + result: { + data: MOCK_NON_ELIGIBILE_STAKE_AVAILABLE, + }, +}; + +const refereesMock: MockedResponse = { + request: { + query: RefereesDocument, + variables: { + code: MOCK_REFERRER_SET.referralSets.edges[0]?.node.id as string, + aggregationEpochs: + MOCK_REFERRAL_PROGRAM.currentReferralProgram?.windowLength, + }, + }, + result: { + data: MOCK_REFEREES, + }, +}; + +jest.mock('@vegaprotocol/wallet', () => { + return { + ...jest.requireActual('@vegaprotocol/wallet'), + useVegaWallet: () => { + const ctx: Partial = { + pubKey: MOCK_PUBKEY, + }; + return ctx; + }, + }; +}); + +describe('ReferralStatistics', () => { + it('displays create code when no data has been found for given pubkey', () => { + const { queryByTestId } = render( + + + + ); + + expect(queryByTestId('referral-create-code-form')).toBeInTheDocument(); + }); + + it('displays referrer stats when given pubkey is a referrer', async () => { + const { queryByTestId } = render( + + + + ); + + await waitFor(() => { + expect( + queryByTestId('referral-create-code-form') + ).not.toBeInTheDocument(); + expect(queryByTestId('referral-statistics')).toBeInTheDocument(); + expect(queryByTestId('referral-statistics')?.dataset.as).toEqual( + 'referrer' + ); + }); + }); + + it('displays referee stats when given pubkey is a referee', async () => { + const { queryByTestId } = render( + + + + + + ); + + await waitFor(() => { + expect( + queryByTestId('referral-create-code-form') + ).not.toBeInTheDocument(); + expect(queryByTestId('referral-statistics')).toBeInTheDocument(); + expect(queryByTestId('referral-statistics')?.dataset.as).toEqual( + 'referee' + ); + }); + }); + + it('displays eligibility warning when the set is no longer valid due to the referrers stake', async () => { + const { queryByTestId } = render( + + + + + + ); + + await waitFor(() => { + expect( + queryByTestId('referral-create-code-form') + ).not.toBeInTheDocument(); + expect(queryByTestId('referral-statistics')).toBeInTheDocument(); + expect(queryByTestId('referral-statistics')?.dataset.as).toEqual( + 'referee' + ); + expect(queryByTestId('referral-eligibility-warning')).toBeInTheDocument(); + expect(queryByTestId('referral-apply-code-form')).toBeInTheDocument(); + }); + }); +}); diff --git a/apps/trading/client-pages/referrals/referral-statistics.tsx b/apps/trading/client-pages/referrals/referral-statistics.tsx index 628732687..5f76f0aef 100644 --- a/apps/trading/client-pages/referrals/referral-statistics.tsx +++ b/apps/trading/client-pages/referrals/referral-statistics.tsx @@ -1,3 +1,4 @@ +import minBy from 'lodash/minBy'; import { CodeTile, StatTile } from './tile'; import { VegaIcon, @@ -25,12 +26,13 @@ import compact from 'lodash/compact'; import { useReferralProgram } from './hooks/use-referral-program'; import { useStakeAvailable } from './hooks/use-stake-available'; import sortBy from 'lodash/sortBy'; -import { useLayoutEffect, useRef, useState } from 'react'; +import { useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useCurrentEpochInfoQuery } from './hooks/__generated__/Epoch'; import BigNumber from 'bignumber.js'; -import { t } from '@vegaprotocol/i18n'; import { DocsLinks } from '@vegaprotocol/environment'; -import minBy from 'lodash/minBy'; +import { useT, ns } from '../../lib/use-t'; +import { Trans } from 'react-i18next'; +import { ApplyCodeForm } from './apply-code-form'; export const ReferralStatistics = () => { const { pubKey } = useVegaWallet(); @@ -49,31 +51,40 @@ export const ReferralStatistics = () => { }); if (referee?.code) { - return ; + return ( + <> + ; + {!referee.isEligible && } + + ); } if (referrer?.code) { - return ; + return ( + <> + ; + + + ); } return ; }; -export const Statistics = ({ +export const useStats = ({ data, program, as, }: { - data: NonNullable['data']>; + data?: NonNullable['data']>; program: ReturnType; - as: 'referrer' | 'referee'; + as?: 'referrer' | 'referee'; }) => { - const { benefitTiers, details } = program; + const { benefitTiers } = program; const { data: epochData } = useCurrentEpochInfoQuery(); - const { stakeAvailable } = useStakeAvailable(); const { data: statsData } = useReferralSetStatsQuery({ variables: { - code: data.code, + code: data?.code || '', }, skip: !data?.code, fetchPolicy: 'cache-and-network', @@ -81,19 +92,12 @@ export const Statistics = ({ const currentEpoch = Number(epochData?.epoch.id); - const compactNumFormat = new Intl.NumberFormat(getUserLocale(), { - minimumFractionDigits: 0, - maximumFractionDigits: 2, - notation: 'compact', - compactDisplay: 'short', - }); - const stats = statsData?.referralSetStats.edges && compact(removePaginationWrapper(statsData.referralSetStats.edges)); - const refereeInfo = data.referee; + const refereeInfo = data?.referee; const refereeStats = stats?.find( - (r) => r.partyId === data.referee?.refereeId + (r) => r.partyId === data?.referee?.refereeId ); const statsAvailable = stats && stats.length > 0 && stats[0]; @@ -136,24 +140,95 @@ export const Statistics = ({ ? nextBenefitTierValue.epochs - epochsValue : 0; + return { + baseCommissionValue, + runningVolumeValue, + referrerVolumeValue, + multiplier, + finalCommissionValue, + discountFactorValue, + currentBenefitTierValue, + nextBenefitTierValue, + epochsValue, + nextBenefitTierVolumeValue, + nextBenefitTierEpochsValue, + }; +}; + +export const Statistics = ({ + data, + program, + as, +}: { + data: NonNullable['data']>; + program: ReturnType; + as: 'referrer' | 'referee'; +}) => { + const t = useT(); + const { + baseCommissionValue, + runningVolumeValue, + referrerVolumeValue, + multiplier, + finalCommissionValue, + discountFactorValue, + currentBenefitTierValue, + epochsValue, + nextBenefitTierVolumeValue, + nextBenefitTierEpochsValue, + } = useStats({ data, program, as }); + + const isApplyCodePreview = useMemo( + () => data.referee === null, + [data.referee] + ); + + const { benefitTiers } = useReferralProgram(); + + const { stakeAvailable, isEligible } = useStakeAvailable(); + const { details } = program; + + const compactNumFormat = new Intl.NumberFormat(getUserLocale(), { + minimumFractionDigits: 0, + maximumFractionDigits: 2, + notation: 'compact', + compactDisplay: 'short', + }); + const baseCommissionTile = ( {baseCommissionValue * 100}% ); + const stakingMultiplierTile = ( + {t('{{amount}} $VEGA staked', { + amount: addDecimalsFormatNumber( + stakeAvailable?.toString() || 0, + 18 + ), + })} + + } > {multiplier || t('None')} @@ -186,10 +261,9 @@ export const Statistics = ({ const referrerVolumeTile = ( {compactNumFormat.format(referrerVolumeValue)} @@ -200,10 +274,9 @@ export const Statistics = ({ .reduce((all, r) => all.plus(r), new BigNumber(0)); const totalCommissionTile = ( } > {getNumberFormat(0).format(Number(totalCommissionValue))} @@ -229,17 +302,27 @@ export const Statistics = ({ const currentBenefitTierTile = ( - {currentBenefitTierValue?.tier || 'None'} + {isApplyCodePreview + ? currentBenefitTierValue?.tier || benefitTiers[0]?.tier || 'None' + : currentBenefitTierValue?.tier || 'None'} ); const discountFactorTile = ( - {discountFactorValue * 100}% + + {isApplyCodePreview + ? benefitTiers[0].discountFactor * 100 + : discountFactorValue * 100} + % + ); const runningVolumeTile = ( {compactNumFormat.format(runningVolumeValue)} @@ -265,11 +348,11 @@ export const Statistics = ({ <>
{currentBenefitTierTile} - {discountFactorTile} + {runningVolumeTile} {codeTile}
- {runningVolumeTile} + {discountFactorTile} {nextTierVolumeTile} {epochsTile} {nextTierEpochsTile} @@ -277,28 +360,60 @@ export const Statistics = ({ ); - const [collapsed, setCollapsed] = useState(false); - const tableRef = useRef(null); - useLayoutEffect(() => { - if ((tableRef.current?.getBoundingClientRect().height || 0) > 384) { - setCollapsed(true); - } - }, []); + const eligibilityWarning = as === 'referee' && !isEligible && ( +
+

{t('Referral code no longer valid')}

+

+ {t( + 'Your referral code is no longer valid as the referrer no longer meets the minimum requirements. Apply a new code to continue receiving discounts.' + )} +

+
+ ); return ( - <> - {/* Stats tiles */} +
{as === 'referrer' && referrerTiles} {as === 'referee' && refereeTiles}
+ {eligibilityWarning} +
+ ); +}; + +export const RefereesTable = ({ + data, + program, +}: { + data: NonNullable['data']>; + program: ReturnType; +}) => { + const t = useT(); + const [collapsed, setCollapsed] = useState(false); + const tableRef = useRef(null); + const { details } = program; + useLayoutEffect(() => { + if ((tableRef.current?.getBoundingClientRect().height || 0) > 384) { + setCollapsed(true); + } + }, []); + return ( + <> {/* Referees (only for referrer view) */} - {as === 'referrer' && data.referees.length > 0 && ( + {data.referees.length > 0 && (

{t('Referees')}

- {t('Commission earned in')} {' '} - {t( - '(last %s epochs)', - ( - details?.windowLength || DEFAULT_AGGREGATION_DAYS - ).toString() - )} - + ), }, ]} @@ -377,24 +493,27 @@ export const Statistics = ({ ); }; -export const QUSDTooltip = () => ( - -

- {t( - 'qUSD provides a rough USD equivalent of balances across all assets using the value of "Quantum" for that asset' +export const QUSDTooltip = () => { + const t = useT(); + return ( + +

+ {t( + 'qUSD provides a rough USD equivalent of balances across all assets using the value of "Quantum" for that asset' + )} +

+ {DocsLinks && ( + + {t('Find out more')} + )} -

- {DocsLinks && ( - - {t('Find out more')} - - )} - - } - underline={true} - > - {t('qUSD')} -
-); + + } + underline={true} + > + {t('qUSD')} + + ); +}; diff --git a/apps/trading/client-pages/referrals/referrals.tsx b/apps/trading/client-pages/referrals/referrals.tsx index ad4448512..ac06f7257 100644 --- a/apps/trading/client-pages/referrals/referrals.tsx +++ b/apps/trading/client-pages/referrals/referrals.tsx @@ -17,18 +17,22 @@ import classNames from 'classnames'; import { usePageTitleStore } from '../../stores'; import { useEffect } from 'react'; import { titlefy } from '@vegaprotocol/utils'; -import { t } from '@vegaprotocol/i18n'; +import { useT } from '../../lib/use-t'; -const Nav = () => ( -
- - {t('I want a code')} - - {t('I have a code')} -
-); +const Nav = () => { + const t = useT(); + return ( +
+ + {t('I want a code')} + + {t('I have a code')} +
+ ); +}; export const Referrals = () => { + const t = useT(); const { pubKey } = useVegaWallet(); const { @@ -58,7 +62,7 @@ export const Referrals = () => { useEffect(() => { updateTitle(titlefy([t('Referrals')])); - }, [updateTitle]); + }, [updateTitle, t]); return ( <> diff --git a/apps/trading/client-pages/referrals/tiers.tsx b/apps/trading/client-pages/referrals/tiers.tsx index 46b73b616..8e765bd2e 100644 --- a/apps/trading/client-pages/referrals/tiers.tsx +++ b/apps/trading/client-pages/referrals/tiers.tsx @@ -6,8 +6,14 @@ import { BORDER_COLOR, GRADIENT } from './constants'; import { Tag } from './tag'; import type { ComponentProps, ReactNode } from 'react'; import { ExternalLink } from '@vegaprotocol/ui-toolkit'; -import { DApp, TOKEN_PROPOSALS, useLinks } from '@vegaprotocol/environment'; -import { t } from '@vegaprotocol/i18n'; +import { + DApp, + DocsLinks, + TOKEN_PROPOSALS, + useLinks, +} from '@vegaprotocol/environment'; +import { useT, ns } from '../../lib/use-t'; +import { Trans } from 'react-i18next'; const Loading = ({ variant }: { variant: 'large' | 'inline' }) => (
{ + const t = useT(); const color: Record['color']> = { 1: 'green', 2: 'blue', @@ -62,7 +69,9 @@ const StakingTier = ({ Multiplier {referralRewardMultiplier}x

{label}

- {t('Stake a minimum of')} {minimumStakedTokens} {t('$VEGA tokens')} + {t('Stake a minimum of {{minimumStakedTokens}} $VEGA tokens', { + minimumStakedTokens, + })}

@@ -70,6 +79,7 @@ const StakingTier = ({ }; export const TiersContainer = () => { + const t = useT(); const { benefitTiers, stakingTiers, details, loading, error } = useReferralProgram(); @@ -82,13 +92,24 @@ export const TiersContainer = () => { if ((!loading && !details) || error) { return (
- {t( - "We're sorry but we don't have an active referral programme currently running. You can propose a new programme" - )}{' '} - - {t('here')} - - . + + {t('Governance App')} + , + ]} + ns={ns} + /> + + {t('Docs')} + , + ]} + ns={ns} + />
); } @@ -174,6 +195,7 @@ const TiersTable = ({ }>; windowLength?: number; }) => { + const t = useT(); return ( { + const t = useT(); + const consoleLink = useLinks(DApp.Console); + const applyCodeLink = consoleLink( + `#${Routes.REFERRALS_APPLY_CODE}?code=${code}` + ); return (
- + + + + {t('Copy shareable apply code link')} + {': '} + + {truncate(applyCodeLink, { length: 32 })} + + + } + > +
diff --git a/apps/trading/client-pages/rewards/index.ts b/apps/trading/client-pages/rewards/index.ts new file mode 100644 index 000000000..b41d3a1d5 --- /dev/null +++ b/apps/trading/client-pages/rewards/index.ts @@ -0,0 +1 @@ +export { Rewards } from './rewards'; diff --git a/apps/trading/client-pages/rewards/rewards.tsx b/apps/trading/client-pages/rewards/rewards.tsx new file mode 100644 index 000000000..0daf75611 --- /dev/null +++ b/apps/trading/client-pages/rewards/rewards.tsx @@ -0,0 +1,22 @@ +import { useT } from '../../lib/use-t'; +import { RewardsContainer } from '../../components/rewards-container'; +import { usePageTitleStore } from '../../stores'; +import { titlefy } from '@vegaprotocol/utils'; +import { useEffect } from 'react'; + +export const Rewards = () => { + const t = useT(); + const title = t('Rewards'); + const { updateTitle } = usePageTitleStore((store) => ({ + updateTitle: store.updateTitle, + })); + useEffect(() => { + updateTitle(titlefy([title])); + }, [updateTitle, title]); + return ( +
+

{title}

+ +
+ ); +}; diff --git a/apps/trading/client-pages/transfer/transfer.tsx b/apps/trading/client-pages/transfer/transfer.tsx index 62134d565..1b489bfb5 100644 --- a/apps/trading/client-pages/transfer/transfer.tsx +++ b/apps/trading/client-pages/transfer/transfer.tsx @@ -1,6 +1,6 @@ import { useSearchParams } from 'react-router-dom'; import { TransferContainer } from '@vegaprotocol/accounts'; -import { GetStarted } from '../../components/welcome-dialog'; +import { GetStarted } from '../../components/welcome-dialog/get-started'; export const Transfer = () => { const [searchParams] = useSearchParams(); diff --git a/apps/trading/client-pages/withdraw/withdraw.tsx b/apps/trading/client-pages/withdraw/withdraw.tsx index 3e0686362..16a87b775 100644 --- a/apps/trading/client-pages/withdraw/withdraw.tsx +++ b/apps/trading/client-pages/withdraw/withdraw.tsx @@ -1,5 +1,5 @@ import { useSearchParams } from 'react-router-dom'; -import { GetStarted } from '../../components/welcome-dialog'; +import { GetStarted } from '../../components/welcome-dialog/get-started'; import { WithdrawContainer } from '../../components/withdraw-container'; export const Withdraw = () => { diff --git a/apps/trading/components/accounts-container/accounts-container.tsx b/apps/trading/components/accounts-container/accounts-container.tsx index 9574fa9f1..2241ae99a 100644 --- a/apps/trading/components/accounts-container/accounts-container.tsx +++ b/apps/trading/components/accounts-container/accounts-container.tsx @@ -1,5 +1,4 @@ import { useCallback } from 'react'; -import { t } from '@vegaprotocol/i18n'; import { Splash } from '@vegaprotocol/ui-toolkit'; import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; import { useVegaWallet } from '@vegaprotocol/wallet'; @@ -13,12 +12,14 @@ import { createDataGridSlice } from '../../stores/datagrid-store-slice'; import { ViewType, useSidebar } from '../sidebar'; import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler'; import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id'; +import { useT } from '../../lib/use-t'; export const AccountsContainer = ({ pinnedAsset, }: { pinnedAsset?: PinnedAsset; }) => { + const t = useT(); const onMarketClick = useMarketClickHandler(true); const { pubKey, isReadOnly } = useVegaWallet(); const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); diff --git a/apps/trading/components/accounts-menu/accounts-menu.tsx b/apps/trading/components/accounts-menu/accounts-menu.tsx index 8bfed5d76..6c29122cc 100644 --- a/apps/trading/components/accounts-menu/accounts-menu.tsx +++ b/apps/trading/components/accounts-menu/accounts-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 AccountsMenu = () => { + const t = useT(); const currentRouteId = useGetCurrentRouteId(); const setViews = useSidebar((store) => store.setViews); diff --git a/apps/trading/components/bootstrapper/bootstrapper.tsx b/apps/trading/components/bootstrapper/bootstrapper.tsx index 1766a81c8..d25436d05 100644 --- a/apps/trading/components/bootstrapper/bootstrapper.tsx +++ b/apps/trading/components/bootstrapper/bootstrapper.tsx @@ -8,12 +8,13 @@ import { NodeGuard, useEnvironment, } from '@vegaprotocol/environment'; -import { t } from '@vegaprotocol/i18n'; import { VegaWalletProvider } from '@vegaprotocol/wallet'; import type { ReactNode } from 'react'; import { Web3Provider } from './web3-provider'; +import { useT } from '../../lib/use-t'; export const Bootstrapper = ({ children }: { children: ReactNode }) => { + const t = useT(); const { error, VEGA_URL, @@ -45,12 +46,16 @@ export const Bootstrapper = ({ children }: { children: ReactNode }) => { > } - failure={} + failure={ + + } > } failure={ - + } > { + return ( +
+
+

{title}

+ {loading ? : children} +
+
+ ); +}; + +export const CardLoader = () => { + return ( +
+
+
+
+ ); +}; + +export const CardStat = ({ + value, + text, + highlight, + description, + testId, +}: { + value: ReactNode; + text?: string; + highlight?: boolean; + description?: ReactNode; + testId?: string; +}) => { + const val = ( + + {value} + + ); + + return ( +

+ {description ? {val} : val} + {text && ( + {text} + )} +

+ ); +}; + +export const CardTable = (props: HTMLProps) => { + return ( +
+ {props.children} +
+ ); +}; + +export const CardTableTH = (props: HTMLProps) => { + return ( + + ); +}; + +export const CardTableTD = (props: HTMLProps) => { + return ( + + ); +}; diff --git a/apps/trading/components/card/index.ts b/apps/trading/components/card/index.ts new file mode 100644 index 000000000..a4b7d3b15 --- /dev/null +++ b/apps/trading/components/card/index.ts @@ -0,0 +1 @@ +export { Card, CardStat, CardTable, CardTableTH, CardTableTD } from './card'; diff --git a/apps/trading/components/deposits-container/deposits-container.tsx b/apps/trading/components/deposits-container/deposits-container.tsx index f888ce958..c4c707e73 100644 --- a/apps/trading/components/deposits-container/deposits-container.tsx +++ b/apps/trading/components/deposits-container/deposits-container.tsx @@ -1,11 +1,12 @@ import { Splash } from '@vegaprotocol/ui-toolkit'; import { DepositsTable } from '@vegaprotocol/deposits'; import { depositsProvider } from '@vegaprotocol/deposits'; -import { t } from '@vegaprotocol/i18n'; import { useDataProvider } from '@vegaprotocol/data-provider'; import { useVegaWallet } from '@vegaprotocol/wallet'; +import { useT } from '../../lib/use-t'; export const DepositsContainer = () => { + const t = useT(); const { pubKey } = useVegaWallet(); const { data, error } = useDataProvider({ dataProvider: depositsProvider, diff --git a/apps/trading/components/deposits-menu/deposits-menu.tsx b/apps/trading/components/deposits-menu/deposits-menu.tsx index d67225692..afade3b73 100644 --- a/apps/trading/components/deposits-menu/deposits-menu.tsx +++ b/apps/trading/components/deposits-menu/deposits-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 DepositsMenu = () => { + const t = useT(); const currentRouteId = useGetCurrentRouteId(); const setViews = useSidebar((store) => store.setViews); diff --git a/apps/trading/components/fees-container/fees-card.tsx b/apps/trading/components/fees-container/fees-card.tsx deleted file mode 100644 index 48d773956..000000000 --- a/apps/trading/components/fees-container/fees-card.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import classNames from 'classnames'; -import type { ReactNode } from 'react'; - -export const FeeCard = ({ - children, - title, - className, - loading = false, -}: { - children: ReactNode; - title: string; - className?: string; - loading?: boolean; -}) => { - return ( -
-

{title}

- {loading ? : children} -
- ); -}; - -export const FeeCardLoader = () => { - return ( -
-
-
-
- ); -}; diff --git a/apps/trading/components/fees-container/fees-container.tsx b/apps/trading/components/fees-container/fees-container.tsx index 2e156b821..1c199ae39 100644 --- a/apps/trading/components/fees-container/fees-container.tsx +++ b/apps/trading/components/fees-container/fees-container.tsx @@ -1,6 +1,5 @@ import maxBy from 'lodash/maxBy'; import minBy from 'lodash/minBy'; -import { t } from '@vegaprotocol/i18n'; import { useVegaWallet } from '@vegaprotocol/wallet'; import { useNetworkParams, @@ -9,9 +8,8 @@ import { import { useMarketList } from '@vegaprotocol/markets'; import { formatNumber, formatNumberRounded } from '@vegaprotocol/utils'; import { useDiscountProgramsQuery, useFeesQuery } from './__generated__/Fees'; -import { FeeCard } from './fees-card'; +import { Card, CardStat, CardTable, CardTableTD, CardTableTH } from '../card'; import { MarketFees } from './market-fees'; -import { Stat } from './stat'; import { useVolumeStats } from './use-volume-stats'; import { useReferralStats } from './use-referral-stats'; import { formatPercentage, getAdjustedFee } from './utils'; @@ -25,8 +23,10 @@ import { VegaIconNames, truncateMiddle, } from '@vegaprotocol/ui-toolkit'; +import { useT } from '../../lib/use-t'; export const FeesContainer = () => { + const t = useT(); const { pubKey } = useVegaWallet(); const { params, loading: paramsLoading } = useNetworkParams([ NetworkParams.market_fee_factors_makerFee, @@ -87,7 +87,7 @@ export const FeesContainer = () => {
{isConnected && ( <> - { referralDiscount={referralDiscount} volumeDiscount={volumeDiscount} /> - - + { isReferralProgramRunning={isReferralProgramRunning} isVolumeDiscountProgramRunning={isVolumeDiscountProgramRunning} /> - - + { windowLength={volumeDiscountWindowLength} /> ) : ( -

+

{t('No volume discount program active')}

)} -
- + { epochs={referralDiscountWindowLength} /> ) : ( -

+

{t('No referral program active')}

)} -
+ )} - { lastEpochVolume={volumeInWindow} windowLength={volumeDiscountWindowLength} /> - - + { epochsInSet={epochsInSet} referralVolumeInWindow={referralVolumeInWindow} /> - - + { referralDiscount={referralDiscount} volumeDiscount={volumeDiscount} /> - +
); }; @@ -203,6 +203,7 @@ export const TradingFees = ({ referralDiscount: number; volumeDiscount: number; }) => { + const t = useT(); const referralDiscountBigNum = new BigNumber(referralDiscount); const volumeDiscountBigNum = new BigNumber(volumeDiscount); @@ -244,8 +245,8 @@ export const TradingFees = ({ } return ( -
-
+
+

{minAdjustedTotal !== undefined && maxAdjustedTotal !== undefined ? `${formatPercentage(minAdjustedTotal)}%-${formatPercentage( @@ -253,47 +254,43 @@ export const TradingFees = ({ )}%` : `${formatPercentage(adjustedTotal)}%`}

- - + + + {t('Total fee before discount')} + + {minTotal !== undefined && maxTotal !== undefined + ? `${formatPercentage(minTotal.toNumber())}%-${formatPercentage( + maxTotal.toNumber() + )}%` + : `${formatPercentage(total.toNumber())}%`} + + + + {t('Infrastructure')} + + {formatPercentage( + Number(params.market_fee_factors_infrastructureFee) + )} + % + + + + {t('Maker')} + + {formatPercentage(Number(params.market_fee_factors_makerFee))}% + + + {minLiq && maxLiq && ( - - + {t('Liquidity')} + + {formatPercentage(Number(minLiq.fees.factors.liquidityFee))}% + {'-'} + {formatPercentage(Number(maxLiq.fees.factors.liquidityFee))}% + - - - - - - - - - {minLiq && maxLiq && ( - - - - - )} - -
- {t('Total fee before discount')} - - {minTotal !== undefined && maxTotal !== undefined - ? `${formatPercentage( - minTotal.toNumber() - )}%-${formatPercentage(maxTotal.toNumber())}%` - : `${formatPercentage(total.toNumber())}%`} -
{t('Infrastructure')} - {formatPercentage( - Number(params.market_fee_factors_infrastructureFee) - )} - % -
{t('Maker')} - {formatPercentage(Number(params.market_fee_factors_makerFee))}% -
{t('Liquidity')} - {formatPercentage(Number(minLiq.fees.factors.liquidityFee))}% - {'-'} - {formatPercentage(Number(maxLiq.fees.factors.liquidityFee))}% -
+ )} +
); @@ -310,19 +307,20 @@ export const CurrentVolume = ({ windowLengthVolume: number; windowLength: number; }) => { + const t = useT(); const nextTier = tiers[tierIndex + 1]; const requiredForNextTier = nextTier ? Number(nextTier.minimumRunningNotionalTakerVolume) - windowLengthVolume : 0; return ( -
- + {requiredForNextTier > 0 && ( - @@ -340,17 +338,21 @@ const ReferralBenefits = ({ setRunningNotionalTakerVolume: number; epochs: number; }) => { + const t = useT(); return ( -
- + - +
); }; @@ -366,6 +368,7 @@ const TotalDiscount = ({ isReferralProgramRunning: boolean; isVolumeDiscountProgramRunning: boolean; }) => { + const t = useT(); const totalDiscount = 1 - (1 - volumeDiscount) * (1 - referralDiscount); const totalDiscountDescription = t( 'The total discount is calculated according to the following formula: ' @@ -377,8 +380,8 @@ const TotalDiscount = ({ ); return ( -
- + {totalDiscountDescription} @@ -388,38 +391,36 @@ const TotalDiscount = ({ value={formatPercentage(totalDiscount) + '%'} highlight={true} /> - - - - - - - - - - - -
{t('Volume discount')} - {formatPercentage(volumeDiscount)}% - {!isVolumeDiscountProgramRunning && ( - - - {' '} - - - - )} -
{t('Referral discount')} - {formatPercentage(referralDiscount)}% - {!isReferralProgramRunning && ( - - - {' '} - - - - )} -
+ + + {t('Volume discount')} + + {formatPercentage(volumeDiscount)}% + {!isVolumeDiscountProgramRunning && ( + + + {' '} + + + + )} + + + + {t('Referral discount')} + + {formatPercentage(referralDiscount)}% + {!isReferralProgramRunning && ( + + + {' '} + + + + )} + + +
); }; @@ -438,9 +439,10 @@ const VolumeTiers = ({ lastEpochVolume: number; windowLength: number; }) => { + const t = useT(); if (!tiers.length) { return ( -

+

{t('No volume discount program active')}

); @@ -454,7 +456,11 @@ const VolumeTiers = ({ {t('Tier')} {t('Discount')} {t('Min. trading volume')} - {t('My volume (last %s epochs)', windowLength.toString())} + + {t('myVolume', 'My volume (last {{count}} epochs)', { + count: windowLength, + })} + @@ -493,9 +499,11 @@ const ReferralTiers = ({ epochsInSet: number; referralVolumeInWindow: number; }) => { + const t = useT(); + if (!tiers.length) { return ( -

{t('No referral program active')}

+

{t('No referral program active')}

); } @@ -546,37 +554,43 @@ const ReferralTiers = ({ }; const YourTier = () => { + const t = useT(); + return ( - + {t('Your tier')} ); }; -const ReferrerInfo = ({ code }: { code?: string }) => ( -
-

- {t('Connected key is owner of the referral set')} - {code && ( - <> - {' '} - - {truncateMiddle(code)} - - - )} - {'. '} - {t('As owner, it is eligible for commission not fee discounts.')} -

-

- {t('See')}{' '} - - {t('Referrals')} - {' '} - {t('for more information.')} -

-
-); +const ReferrerInfo = ({ code }: { code?: string }) => { + const t = useT(); + + return ( +
+

+ {t('Connected key is owner of the referral set')} + {code && ( + <> + {' '} + + {truncateMiddle(code)} + + + )} + {'. '} + {t('As owner, it is eligible for commission not fee discounts.')} +

+

+ {t('See')}{' '} + + {t('Referrals')} + {' '} + {t('for more information.')} +

+
+ ); +}; diff --git a/apps/trading/components/fees-container/market-fees.tsx b/apps/trading/components/fees-container/market-fees.tsx index daba27956..47d307300 100644 --- a/apps/trading/components/fees-container/market-fees.tsx +++ b/apps/trading/components/fees-container/market-fees.tsx @@ -1,38 +1,45 @@ import compact from 'lodash/compact'; import type { MarketMaybeWithDataAndCandles } from '@vegaprotocol/markets'; import { AgGrid } from '@vegaprotocol/datagrid'; -import { t } from '@vegaprotocol/i18n'; import { formatPercentage, getAdjustedFee } from './utils'; import { MarketCodeCell } from '../../client-pages/markets/market-code-cell'; import BigNumber from 'bignumber.js'; import { useNavigateWithMeta } from '../../lib/hooks/use-market-click-handler'; import { Links } from '../../lib/links'; +import { useT } from '../../lib/use-t'; +import { useMemo } from 'react'; -const feesTableColumnDefs = [ - { field: 'code', cellRenderer: 'MarketCodeCell' }, - { - field: 'feeAfterDiscount', - headerName: t('Total fee after discount'), - valueFormatter: ({ value }: { value: number }) => value + '%', - }, - { - field: 'infraFee', - valueFormatter: ({ value }: { value: number }) => value + '%', - }, - { - field: 'makerFee', - valueFormatter: ({ value }: { value: number }) => value + '%', - }, - { - field: 'liquidityFee', - valueFormatter: ({ value }: { value: number }) => value + '%', - }, - { - field: 'totalFee', - headerName: t('Total fee before discount'), - valueFormatter: ({ value }: { value: number }) => value + '%', - }, -]; +const useFeesTableColumnDefs = () => { + const t = useT(); + return useMemo( + () => [ + { field: 'code', cellRenderer: 'MarketCodeCell' }, + { + field: 'feeAfterDiscount', + headerName: t('Total fee after discount'), + valueFormatter: ({ value }: { value: number }) => value + '%', + }, + { + field: 'infraFee', + valueFormatter: ({ value }: { value: number }) => value + '%', + }, + { + field: 'makerFee', + valueFormatter: ({ value }: { value: number }) => value + '%', + }, + { + field: 'liquidityFee', + valueFormatter: ({ value }: { value: number }) => value + '%', + }, + { + field: 'totalFee', + headerName: t('Total fee before discount'), + valueFormatter: ({ value }: { value: number }) => value + '%', + }, + ], + [t] + ); +}; const feesTableDefaultColDef = { flex: 1, @@ -83,7 +90,7 @@ export const MarketFees = ({ return (
data.id} defaultColDef={feesTableDefaultColDef} diff --git a/apps/trading/components/fees-container/stat.tsx b/apps/trading/components/fees-container/stat.tsx deleted file mode 100644 index 307b77ff0..000000000 --- a/apps/trading/components/fees-container/stat.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Tooltip } from '@vegaprotocol/ui-toolkit'; -import classNames from 'classnames'; -import type { ReactNode } from 'react'; - -export const Stat = ({ - value, - text, - highlight, - description, -}: { - value: string | number; - text?: string; - highlight?: boolean; - description?: ReactNode; -}) => { - const val = ( - - {value} - - ); - return ( -

- {description ? {val} : val} - {text && ( - {text} - )} -

- ); -}; diff --git a/apps/trading/components/fills-container/fills-container.tsx b/apps/trading/components/fills-container/fills-container.tsx index 32e0de34f..da26c8f9f 100644 --- a/apps/trading/components/fills-container/fills-container.tsx +++ b/apps/trading/components/fills-container/fills-container.tsx @@ -3,13 +3,14 @@ import { FillsManager } from '@vegaprotocol/fills'; import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import { useDataGridEvents } from '@vegaprotocol/datagrid'; -import { t } from '@vegaprotocol/i18n'; import { Splash } from '@vegaprotocol/ui-toolkit'; import type { DataGridSlice } from '../../stores/datagrid-store-slice'; import { createDataGridSlice } from '../../stores/datagrid-store-slice'; import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler'; +import { useT } from '../../lib/use-t'; export const FillsContainer = () => { + const t = useT(); const onMarketClick = useMarketClickHandler(true); const { pubKey } = useVegaWallet(); diff --git a/apps/trading/components/funding-container/funding-container.tsx b/apps/trading/components/funding-container/funding-container.tsx index d689f245a..e4aa562b8 100644 --- a/apps/trading/components/funding-container/funding-container.tsx +++ b/apps/trading/components/funding-container/funding-container.tsx @@ -6,9 +6,9 @@ import 'pennant/dist/style.css'; import { useFundingPeriodsQuery } from '@vegaprotocol/markets'; import { LineChart } from 'pennant'; import { useMemo } from 'react'; -import { t } from '@vegaprotocol/i18n'; import { useThemeSwitcher } from '@vegaprotocol/react-helpers'; import { Splash } from '@vegaprotocol/ui-toolkit'; +import { useT } from '../../lib/use-t'; const calculateStartDate = (range: string): string | undefined => { const now = new Date(); @@ -41,6 +41,7 @@ const DateRange = { }; export const FundingContainer = ({ marketId }: { marketId: string }) => { + const t = useT(); const { theme } = useThemeSwitcher(); const variables = useMemo( () => ({ @@ -64,7 +65,7 @@ export const FundingContainer = ({ marketId }: { marketId: string }) => { if (edge.node.endTime) { acc?.push({ endTime: fromNanoSeconds(edge.node.endTime), - fundingRate: Number(edge.node.fundingRate) * 100, + fundingRate: Number(edge.node.fundingRate), }); } return acc; @@ -73,7 +74,7 @@ export const FundingContainer = ({ marketId }: { marketId: string }) => { cols: ['Date', t('Funding rate')], rows: sortBy(rows, 'endTime').map((d) => [d.endTime, d.fundingRate]), }; - }, [data?.fundingPeriods.edges]); + }, [data?.fundingPeriods.edges, t]); if (!data || !values?.rows.length) { return {t('No funding history data')}; } @@ -81,7 +82,8 @@ export const FundingContainer = ({ marketId }: { marketId: string }) => { `${fundingRate.toFixed(4)}%`} + priceFormat={(fundingRate) => `${(fundingRate * 100).toFixed(4)}%`} + yAxisTickFormat="%" /> ); }; diff --git a/apps/trading/components/funding-payments-container/funding-payments-container.tsx b/apps/trading/components/funding-payments-container/funding-payments-container.tsx index bd4a8ca68..a053c6c21 100644 --- a/apps/trading/components/funding-payments-container/funding-payments-container.tsx +++ b/apps/trading/components/funding-payments-container/funding-payments-container.tsx @@ -3,17 +3,18 @@ import { FundingPaymentsManager } from '@vegaprotocol/funding-payments'; import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import { useDataGridEvents } from '@vegaprotocol/datagrid'; -import { t } from '@vegaprotocol/i18n'; import { Splash } from '@vegaprotocol/ui-toolkit'; import type { DataGridSlice } from '../../stores/datagrid-store-slice'; import { createDataGridSlice } from '../../stores/datagrid-store-slice'; import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler'; +import { useT } from '../../lib/use-t'; export const FundingPaymentsContainer = ({ marketId, }: { marketId?: string; }) => { + const t = useT(); const onMarketClick = useMarketClickHandler(true); const { pubKey } = useVegaWallet(); diff --git a/apps/trading/components/ledger-container/ledger-container.tsx b/apps/trading/components/ledger-container/ledger-container.tsx index 5d9f62fde..ba66dd5d3 100644 --- a/apps/trading/components/ledger-container/ledger-container.tsx +++ b/apps/trading/components/ledger-container/ledger-container.tsx @@ -1,12 +1,13 @@ -import { t } from '@vegaprotocol/i18n'; import { LedgerExportForm } from '@vegaprotocol/ledger'; import { Loader, Splash } from '@vegaprotocol/ui-toolkit'; import { useVegaWallet } from '@vegaprotocol/wallet'; import { useEnvironment } from '@vegaprotocol/environment'; import type { PartyAssetFieldsFragment } from '@vegaprotocol/assets'; import { usePartyAssetsQuery } from '@vegaprotocol/assets'; +import { useT } from '../../lib/use-t'; export const LedgerContainer = () => { + const t = useT(); const VEGA_URL = useEnvironment((store) => store.VEGA_URL); const { pubKey } = useVegaWallet(); const { data, loading } = usePartyAssetsQuery({ diff --git a/apps/trading/components/liquidity-container/liquidity-container.tsx b/apps/trading/components/liquidity-container/liquidity-container.tsx index 8d1589b81..7ddea435b 100644 --- a/apps/trading/components/liquidity-container/liquidity-container.tsx +++ b/apps/trading/components/liquidity-container/liquidity-container.tsx @@ -1,6 +1,5 @@ import { useDataProvider } from '@vegaprotocol/data-provider'; import { useDataGridEvents } from '@vegaprotocol/datagrid'; -import { t } from '@vegaprotocol/i18n'; import { lpAggregatedDataProvider, type Filter, @@ -17,6 +16,7 @@ import { createDataGridSlice } from '../../stores/datagrid-store-slice'; import { useEffect } from 'react'; import { create } from 'zustand'; import { persist } from 'zustand/middleware'; +import { useT } from '../../lib/use-t'; export const LiquidityContainer = ({ marketId, @@ -25,6 +25,7 @@ export const LiquidityContainer = ({ marketId: string | undefined; filter?: Filter; }) => { + const t = useT(); const gridStore = useLiquidityStore((store) => store.gridStore); const updateGridStore = useLiquidityStore((store) => store.updateGridStore); diff --git a/apps/trading/components/liquidity-header/liquidity-header.tsx b/apps/trading/components/liquidity-header/liquidity-header.tsx index 2e8fe08cf..dd5444187 100644 --- a/apps/trading/components/liquidity-header/liquidity-header.tsx +++ b/apps/trading/components/liquidity-header/liquidity-header.tsx @@ -1,6 +1,6 @@ import { getAsset, - tooltipMapping, + useTooltipMapping, useMarket, useStaticMarketData, } from '@vegaprotocol/markets'; @@ -9,7 +9,6 @@ import { addDecimalsFormatNumber, formatNumberPercentage, } from '@vegaprotocol/utils'; -import { t } from '@vegaprotocol/i18n'; import { CopyWithTooltip, ExternalLink, @@ -24,8 +23,11 @@ import { usePaidFeesQuery, } from '@vegaprotocol/liquidity'; import { useParams } from 'react-router-dom'; +import { useT } from '../../lib/use-t'; export const LiquidityHeader = () => { + const t = useT(); + const tooltipMapping = useTooltipMapping(); const { marketId } = useParams(); const { data: market } = useMarket(marketId); const { data: marketData } = useStaticMarketData(marketId); @@ -60,10 +62,9 @@ export const LiquidityHeader = () => { marketId && ( {market.tradableInstrument.instrument.code && - t( - '%s liquidity provision', - market.tradableInstrument.instrument.code - )} + t('{{instrumentCode}} liquidity provision', { + instrumentCode: market.tradableInstrument.instrument.code, + })} ) } @@ -102,8 +103,8 @@ export const LiquidityHeader = () => { @@ -122,7 +123,7 @@ export const LiquidityHeader = () => { + + + + ); +}; diff --git a/apps/trading/components/rewards-container/use-reward-row-data.ts b/apps/trading/components/rewards-container/use-reward-row-data.ts new file mode 100644 index 000000000..dc85cc515 --- /dev/null +++ b/apps/trading/components/rewards-container/use-reward-row-data.ts @@ -0,0 +1,109 @@ +import groupBy from 'lodash/groupBy'; +import { AccountType } from '@vegaprotocol/types'; +import BigNumber from 'bignumber.js'; +import { removePaginationWrapper } from '@vegaprotocol/utils'; +import { type Asset } from '@vegaprotocol/assets'; +import { type PartyRewardsConnection } from './rewards-history'; +import { type RewardsHistoryQuery } from './__generated__/Rewards'; + +const REWARD_ACCOUNT_TYPES = [ + AccountType.ACCOUNT_TYPE_GLOBAL_REWARD, + AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES, + AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES, + AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, + AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS, + AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION, + AccountType.ACCOUNT_TYPE_REWARD_RELATIVE_RETURN, + AccountType.ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY, + AccountType.ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING, +]; + +const getRewards = ( + rewards: Array<{ + rewardType: AccountType; + assetId: string; + amount: string; + }>, + assets: Record | null +) => { + const assetMap = groupBy( + rewards.filter((r) => REWARD_ACCOUNT_TYPES.includes(r.rewardType)), + 'assetId' + ); + + return Object.keys(assetMap).map((assetId) => { + const r = assetMap[assetId]; + const asset = assets ? assets[assetId] : undefined; + + const totals = new Map(); + + REWARD_ACCOUNT_TYPES.forEach((type) => { + const amountsByType = r + .filter((a) => a.rewardType === type) + .map((a) => a.amount); + const typeTotal = BigNumber.sum.apply( + null, + amountsByType.length ? amountsByType : [0] + ); + + totals.set(type, typeTotal.toNumber()); + }); + + const total = BigNumber.sum.apply( + null, + Array.from(totals).map((entry) => entry[1]) + ); + + return { + asset, + staking: totals.get(AccountType.ACCOUNT_TYPE_GLOBAL_REWARD), + priceTaking: totals.get(AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES), + priceMaking: totals.get( + AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES + ), + liquidityProvision: totals.get( + AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES + ), + marketCreation: totals.get( + AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS + ), + averagePosition: totals.get( + AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION + ), + relativeReturns: totals.get( + AccountType.ACCOUNT_TYPE_REWARD_RELATIVE_RETURN + ), + returnsVolatility: totals.get( + AccountType.ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY + ), + validatorRanking: totals.get( + AccountType.ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING + ), + total: total.toNumber(), + }; + }); +}; + +export const useRewardsRowData = ({ + partyRewards, + epochRewardSummaries, + assets, + partyId, +}: { + partyRewards: PartyRewardsConnection; + epochRewardSummaries: RewardsHistoryQuery['epochRewardSummaries']; + assets: Record | null; + partyId: string | null; +}) => { + if (partyId) { + const rewards = removePaginationWrapper(partyRewards?.edges).map((r) => ({ + rewardType: r.rewardType, + assetId: r.asset.id, + amount: r.amount, + })); + return getRewards(rewards, assets); + } + + const rewards = removePaginationWrapper(epochRewardSummaries?.edges); + return getRewards(rewards, assets); +}; diff --git a/apps/trading/components/settings/settings.tsx b/apps/trading/components/settings/settings.tsx index 84759a781..30d00c1c1 100644 --- a/apps/trading/components/settings/settings.tsx +++ b/apps/trading/components/settings/settings.tsx @@ -1,4 +1,3 @@ -import { t } from '@vegaprotocol/i18n'; import { Dialog, Intent, @@ -9,8 +8,11 @@ import { import { useThemeSwitcher } from '@vegaprotocol/react-helpers'; import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval'; import { useState, type ReactNode } from 'react'; +import classNames from 'classnames'; +import { useT } from '../../lib/use-t'; export const Settings = () => { + const t = useT(); const { theme, setTheme } = useThemeSwitcher(); const [isApproved, setIsApproved] = useTelemetryApproval(); const [open, setOpen] = useState(false); @@ -84,6 +86,18 @@ export const Settings = () => {
+ +
+ {process.env.GIT_TAG && ( + <> +
{t('Version')}
+
{process.env.GIT_TAG}
+ + )} +
{t('Git commit hash')}
+
{process.env.GIT_COMMIT}
+
+
); }; @@ -92,16 +106,22 @@ const SettingsGroup = ({ label, helpText, children, + inline = true, }: { label: string; - helpText?: string; children: ReactNode; + helpText?: string; + inline?: boolean; }) => { return ( -
-
+
+
- {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(