Compare commits

..
Author SHA1 Message Date
maciek c7c99fd2ca Merge remote-tracking branch 'origin/develop' into chore/3188-contitionally-external-link 2023-03-31 14:57:46 +02:00
maciek 61c9e1fdb0 chore: add metaKey detection - fix lint error 2023-03-31 14:57:21 +02:00
maciek dcff29b50d chore: add metaKey detection - add hook for return a handler 2023-03-31 12:21:58 +02:00
maciek 9737c58d2c Merge remote-tracking branch 'origin/develop' into chore/3188-contitionally-external-link 2023-03-31 10:31:59 +02:00
maciek 51262dba76 chore: add metaKey detection - remove of router implementation dependecy from the libs 2023-03-31 10:31:32 +02:00
maciek b72344798c chore: add metaKey detection - remove of router implementation dependecy from the libs 2023-03-31 10:31:04 +02:00
maciek e2d5345f97 Merge remote-tracking branch 'origin/develop' into chore/3188-contitionally-external-link 2023-03-31 10:01:21 +02:00
MaciekandMatthew Russell e732240d2e Update libs/datagrid/src/lib/cells/market-name-cell.tsx
commit form PR ui

Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-03-31 09:54:30 +02:00
maciek d77cc462df Merge remote-tracking branch 'origin/develop' into chore/3188-contitionally-external-link 2023-03-29 12:05:39 +02:00
maciek 95b55ea6aa chore: add metaKey detection - improve marketNameCell template 2023-03-29 12:05:19 +02:00
maciek 48ed158756 Merge remote-tracking branch 'origin/develop' into chore/3188-contitionally-external-link 2023-03-29 09:34:41 +02:00
maciek f423652f8e remove hook with onMarketClick and onSelect simplification
Revert "chore: add metaKey detection - refactor all onSelect and onMarketClick callbacks"

This reverts commit 9ca7386035.
2023-03-29 09:30:10 +02:00
maciek 3092133817 Merge remote-tracking branch 'origin/develop' into chore/3188-contitionally-external-link 2023-03-28 17:24:54 +02:00
maciek 864f28311b chore: add metaKey detection - fix failling unit test 2023-03-28 17:24:40 +02:00
maciek 58e08b9e33 Merge remote-tracking branch 'origin/develop' into chore/3188-contitionally-external-link 2023-03-28 16:47:36 +02:00
maciek b04c9e80fe chore: add metaKey detection - fix failling int test 2023-03-28 16:47:07 +02:00
maciek 4278a15de1 Merge remote-tracking branch 'origin/develop' into chore/3188-contitionally-external-link 2023-03-28 15:46:34 +02:00
maciek 9ca7386035 chore: add metaKey detection - refactor all onSelect and onMarketClick callbacks 2023-03-28 15:45:34 +02:00
maciek 8781304edf chore: add metaKey detection to onSelect and onMarketSelect - change link logic 2023-03-28 12:30:09 +02:00
maciek c8f89887ec Merge remote-tracking branch 'origin/develop' into chore/3188-contitionally-external-link 2023-03-28 11:26:10 +02:00
maciek 4a5f86567d chore: add metaKey detection to onSelect and onMarketSelect 2023-03-28 11:25:52 +02:00
maciek 0d0273142d chore: add market name ag-grid cell renderer to the all tables - fix lint err 2023-03-27 16:23:49 +02:00
maciek 467bd0fa58 chore: add market name ag-grid cell renderer to the all tables - clean up old files 2023-03-27 15:38:24 +02:00
maciek ef46bc11a0 chore: add market name ag-grid cell renderer to the all tables - fix failling tests 2023-03-27 15:32:33 +02:00
maciek 8511f0bd9d Merge remote-tracking branch 'origin/develop' into chore/3188-contitionally-external-link 2023-03-27 14:49:59 +02:00
maciek 6b3469dbf5 chore: add market name ag-grid cell renderer to the all tables 2023-03-27 14:49:33 +02:00
maciek e8c1170ee8 Merge remote-tracking branch 'origin/develop' into chore/3188-contitionally-external-link 2023-03-27 09:09:47 +02:00
maciek 9b08cdc216 Merge remote-tracking branch 'origin/develop' into chore/3188-contitionally-external-link 2023-03-24 16:40:53 +01:00
maciek fcd70f68a7 chore: add key hold handlers for conditionally external link - add market name ag-grid cell renderer 2023-03-24 16:40:36 +01:00
maciek 427baeb7aa chore: add key hold handlers for conditionally external link 2023-03-24 13:55:41 +01:00
135 changed files with 4195 additions and 4857 deletions
+4 -3
View File
@@ -1,14 +1,15 @@
---
name: Release
about: A template to outline the steps needed to for a successful release of our frontend apps
about:
A template to outline the steps needed to for a successful release of our frontend apps
title: 'Release [add dapp version]-core-[add core version]'
labels:
labels:
assignees: ''
---
### Tasks
- [ ] Review [link to core release](xxx)
- [ ] Review [link to core release](xxx)
- [ ] Tag frontend-monorepo
- [ ] Create release and generate release notes
- [ ] Run `@smoke` tests
+1 -1
View File
@@ -13,7 +13,7 @@ env:
jobs:
add_issue:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: 'Add issue to project board'
run: |
-110
View File
@@ -1,110 +0,0 @@
name: CI/CD
on:
push:
branches:
- release/*
pull_request:
types:
- opened
- reopened
- synchronize
- ready_for_review
jobs:
lint-test-build:
runs-on: ubuntu-22.04
name: '(CI) lint + unit test + build'
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup node
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
cache: yarn
- name: Install root dependencies
run: yarn install --frozen-lockfile
- name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v3
with:
main-branch-name: develop
- name: Check formatting
run: yarn nx format:check
- name: Lint affected
run: yarn nx affected:lint --max-warnings=0
- name: Build affected spec
run: yarn nx affected --target=build-spec
- name: Test affected
run: yarn nx affected:test
- name: Build affected
run: yarn nx affected:build
# See affected apps
- name: See affected apps
run: |
echo ">>>> debug"
echo "NX Version: $nx_version"
echo "NX_BASE: ${{ env.NX_BASE }}"
echo "NX_HEAD: ${{ env.NX_HEAD }}"
echo ">>>> eof debug"
affected="$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects)"
echo -n "Affected projects: $affected"
projects_e2e=""
if [[ $affected == *"governance"* ]]; then projects_e2e+='"governance-e2e" '; fi
if [[ $affected == *"trading"* ]]; then projects_e2e+='"trading-e2e" '; fi
if [[ $affected == *"explorer"* ]]; then projects_e2e+='"explorer-e2e" '; fi
if [[ -z "$projects_e2e" ]]; then projects_e2e+='"governance-e2e" "trading-e2e" "explorer-e2e" '; fi
projects_e2e=${projects_e2e%?}
projects_e2e=[${projects_e2e// /,}]
echo PROJECTS_E2E=$projects_e2e >> $GITHUB_ENV
echo PROJECTS=$(echo $projects_e2e | sed 's|-e2e||g') >> $GITHUB_ENV
outputs:
projects: ${{ env.PROJECTS }}
projects-e2e: ${{ env.PROJECTS_E2E }}
cypress:
needs: lint-test-build
name: '(CI) cypress'
if: ${{ needs.lint-test-build.outputs.projects != '[]' }}
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
with:
projects: ${{ needs.lint-test-build.outputs.projects-e2e }}
tags: '@smoke @regression'
publish-dist:
needs: lint-test-build
name: '(CD) publish dist'
if: ${{ needs.lint-test-build.outputs.projects != '[]' }}
uses: ./.github/workflows/publish-dist.yml
secrets: inherit
with:
projects: ${{ needs.lint-test-build.outputs.projects }}
# Report single result at the end, to avoid mess with required checks in PR
cypress-result:
if: ${{ always() }}
needs: cypress
runs-on: ubuntu-22.04
steps:
- run: |
result="${{ needs.cypress.result }}"
if [[ $result == "success" || $result == "skipped" ]]; then
exit 0
else
exit 1
fi
+1 -1
View File
@@ -13,7 +13,7 @@ on:
jobs:
cypress-run:
name: Run Cypress Trading tests -- live environment
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
+1 -1
View File
@@ -1,4 +1,4 @@
name: (CI) Cypress Run
name: Cypress Run
on:
workflow_call:
inputs:
+9 -12
View File
@@ -8,25 +8,22 @@ on:
jobs:
master:
name: Generate Queries
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v3
uses: actions/checkout@v2
with:
node-version-file: '.nvmrc'
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
cache: yarn
fetch-depth: 0
- name: Use Node.js 16
id: Node
uses: actions/setup-node@v2
with:
node-version: 16.15.1
- name: Install root dependencies
run: yarn install --frozen-lockfile
run: yarn install
- name: Generate queries
run: node ./scripts/get-queries.js
- uses: actions/upload-artifact@v2
with:
name: queries
+10 -17
View File
@@ -3,28 +3,21 @@ name: Verify PR title
on:
pull_request:
types:
- opened
- ready_for_review
- reopened
- edited
- synchronize
types: [opened, ready_for_review, reopened, edited, synchronize]
jobs:
lint_pr:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v3
uses: actions/checkout@v2
with:
node-version-file: '.nvmrc'
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
cache: yarn
fetch-depth: 0
- name: Use Node.js 16
id: Node
uses: actions/setup-node@v2
with:
node-version: 16.15.1
- name: Install root dependencies
run: yarn install --frozen-lockfile
run: yarn install
- name: Check PR title
run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js
+121
View File
@@ -0,0 +1,121 @@
name: PR Validations
on:
push:
branches:
- develop
- main
pull_request:
types:
- opened
- reopened
- synchronize
- ready_for_review
jobs:
pr:
runs-on: ubuntu-latest
steps:
- name: Checkout frontend mono repo
uses: actions/checkout@v3
with:
# We need to fetch all branches and commits so that Nx affected has a base to compare against.
fetch-depth: 0
- name: Check node version
id: node-version
run: |
npmVersion=$(cat .nvmrc | head -n 1)
echo ::set-output name=npmVersion::${npmVersion}
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: ${{ steps.node-version.outputs.npmVersion }}
# Check SHAs
- name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v3
with:
main-branch-name: develop
# See affected apps
- name: See affected apps
run: |
nx_version=$(cat package.json | grep '"nx"' | cut -d ':' -f 2 | tr -d '",[:space:]')
rm package.json yarn.lock
yarn add nx@$nx_version
echo ">>>> debug"
echo "NX Version: $nx_version"
echo "NX_BASE: ${{ env.NX_BASE }}"
echo "NX_HEAD: ${{ env.NX_HEAD }}"
# echo "Main branch name: ${{ github.base_ref || github.ref_name }}"
# echo "git rev-parse HEAD: $(git rev-parse HEAD)"
# echo "Head: ${{ github.head_ref }}"
# echo "command to execute: 'yarn nx print-affected --base=${{ github.base_ref || github.ref_name }} --head=${{ github.head_ref }} --select=projects'"
# merge_base=$(git merge-base origin/develop HEAD)
# echo "git merge-base origin/develop HEAD: $merge_base"
# head_sha="${{ github.event.pull_request.head.sha || github.sha }}"
# echo "Head SHA: $head_sha"
# echo "command to execute (without nx-set-sha): 'yarn nx print-affected --base=$merge_base --head=$head_sha --select=projects'"
echo ">>>> eof debug"
# affected_1=$(yarn nx print-affected --base=$merge_base --head=$head_sha --select=projects || true)
# echo -n "Affected projects (allowed to fail): $affected_1"
# affected=$(yarn nx print-affected --base=${{ github.base_ref || github.ref_name }} --head=${{ github.head_ref }} --select=projects)
# echo -n "Affected projects: $affected"
affected="$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=HEAD --select=projects)"
echo -n "Affected projects: $affected"
projects_e2e=""
if [[ $affected == *"governance"* ]]; then projects_e2e+='"governance-e2e" '; fi
if [[ $affected == *"trading"* ]]; then projects_e2e+='"trading-e2e" '; fi
if [[ $affected == *"explorer"* ]]; then projects_e2e+='"explorer-e2e" '; fi
if [[ -z "$projects_e2e" ]]; then projects_e2e+='"governance-e2e" "trading-e2e" "explorer-e2e" '; fi
projects_e2e=${projects_e2e%?}
projects_e2e=[${projects_e2e// /,}]
echo PROJECTS_E2E=$projects_e2e >> $GITHUB_ENV
echo PROJECTS=$(echo $projects_e2e | sed 's|-e2e||g') >> $GITHUB_ENV
outputs:
projects: ${{ env.PROJECTS }}
projects-e2e: ${{ env.PROJECTS_E2E }}
run-cypress:
needs: pr
if: ${{ needs.pr.outputs.projects != '[]' }}
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
with:
projects: ${{ needs.pr.outputs.projects-e2e }}
tags: '@smoke @regression'
run-docker-build:
needs: pr
if: ${{ needs.pr.outputs.projects != '[]' }}
uses: ./.github/workflows/publish-docker-containers.yml
secrets: inherit
with:
projects: ${{ needs.pr.outputs.projects }}
# Report single result at the end, to avoid mess with required checks in PR
result:
if: ${{ always() }}
needs: run-cypress
runs-on: ubuntu-latest
name: Cypress result
steps:
- run: |
result="${{ needs.run-cypress.result }}"
if [[ $result == "success" || $result == "skipped" ]]; then
exit 0
else
exit 1
fi
+1 -1
View File
@@ -7,7 +7,7 @@ on:
jobs:
master:
name: Generate Queries
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -1,4 +1,4 @@
name: (CD) Publish docker + s3
name: Docker build
on:
workflow_call:
@@ -8,13 +8,13 @@ on:
type: string
jobs:
publish-dist:
master:
strategy:
fail-fast: false
matrix:
app: ${{ fromJSON(inputs.projects) }}
name: ${{ matrix.app }}
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
@@ -29,6 +29,41 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
# https://docs.github.com/en/actions/learn-github-actions/contexts
# https://github.com/actions/checkout#Checkout-pull-request-HEAD-commit-instead-of-merge-commit
- name: Determine Docker Image tag
id: tags
run: |
npmVersion=$(cat .nvmrc | head -n 1)
versionTag=${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || github.event.pull_request.head.sha }}
echo ::set-output name=npmVersion::${npmVersion}
echo ::set-output name=version::${versionTag}
- name: Print config
run: |
git rev-parse --verify HEAD
git status
echo "steps.tags.outputs.version=${{ steps.tags.outputs.version }}"
- name: Build and export to local Docker
uses: docker/build-push-action@v3
with:
load: true
build-args: |
APP=${{ matrix.app }}
NODE_VERSION=${{ steps.tags.outputs.npmVersion }}
tags: |
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
- name: Sanity check docker image
run: |
echo "Check .env file"
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat .env
echo "Check ipfs-hash"
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat ipfs-hash
echo "List html directory"
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local ls -lah
- name: Log in to the Container registry
uses: docker/login-action@v2
with:
@@ -36,66 +71,17 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# https://docs.github.com/en/actions/learn-github-actions/contexts
- name: Check node version
id: tags
run: |
nodeVersion=$(cat .nvmrc | head -n 1)
echo ::set-output name=nodeVersion::${nodeVersion}
if [[ "${{ github.event_name }}" = "push" ]]; then
envName="$(echo ${{ github.ref }} | rev | cut -d '/' -f 1 | rev)"
bucketName="${{ github.event.repository.name }}-$envName"
echo ::set-output name=bucketName::${bucketName}
echo ::set-output name=envName::${envName}
fi
- name: Build and export to local Docker
- name: Build and push
id: docker_build
uses: docker/build-push-action@v3
with:
load: true
build-args: |
APP=${{ matrix.app }}
NODE_VERSION=${{ steps.tags.outputs.nodeVersion }}
ENV_NAME=${{ steps.tags.outputs.envName || '' }}
tags: |
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
- name: Sanity check docker image
run: |
echo "Check ipfs-hash"
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat ipfs-hash
echo "List html directory"
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local ls -lah
echo "Copy dist to local filesystem"
docker create --name=dist ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
docker cp dist:/usr/share/nginx/html dist
echo "Check local dist"
ls -al dist
- name: Publish dist as docker image
uses: docker/build-push-action@v3
if: ${{ github.event_name == 'pull_request' }}
with:
push: true
build-args: |
APP=${{ matrix.app }}
NODE_VERSION=${{ steps.tags.outputs.nodeVersion }}
NODE_VERSION=${{ steps.tags.outputs.npmVersion }}
tags: |
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:${{ github.event.pull_request.head.sha || github.sha }}
# - uses: shallwefootball/s3-upload-action@master
# if: ${{ github.event_name == 'push' }}
# name: Upload dist S3
# with:
# aws_key_id: ${{ secrets.AWS_KEY_ID }}
# aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY}}
# aws_bucket: ${{ steps.tags.outputs.bucketName }}
# source_dir: 'dist'
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:latest
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:${{ steps.tags.outputs.version }}
- name: Add preview label
uses: actions-ecosystem/action-add-labels@v1
+11 -9
View File
@@ -19,27 +19,29 @@ on:
jobs:
publish:
name: Build & Publish - Tag
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
permissions:
contents: 'read'
actions: 'read'
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup node
with:
fetch-depth: 0
- name: User Node.js 16
id: Node
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
cache: yarn
node-version: 16.15.1
- name: Restore node_modules from cache
uses: actions/cache@v3
with:
path: '**/node_modules'
key: node_modules-${{ hashFiles('**/yarn.lock') }}
- name: Install root dependencies
run: yarn install --frozen-lockfile
- name: Build project
run: yarn nx build ${{inputs.project}}
- name: Publish project to @vegaprotocol
uses: JS-DevTools/npm-publish@v1
with:
+46
View File
@@ -0,0 +1,46 @@
name: Unit tests & build
on:
push:
branches:
- develop
- main
pull_request:
jobs:
pr:
name: Test and lint - PR
runs-on: ubuntu-latest
permissions:
contents: 'read'
actions: 'read'
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v2
with:
main-branch-name: ${{ github.base_ref }}
- name: Use Node.js 16
id: Node
uses: actions/setup-node@v3
with:
node-version: 16.15.1
- name: Restore node_modules from cache
uses: actions/cache@v3
with:
path: '**/node_modules'
key: node_modules-${{ hashFiles('**/yarn.lock') }}
- name: Install root dependencies
run: yarn install --frozen-lockfile
- name: Check formatting
run: yarn nx format:check
- name: Lint affected
run: yarn nx affected:lint --max-warnings=0
- name: Test affected
run: yarn nx affected:test
- name: Build affected
run: yarn nx affected:build
- name: Build affected spec
run: yarn nx affected --target=build-spec
+7 -2
View File
@@ -4,7 +4,6 @@ FROM --platform=amd64 node:${NODE_VERSION}-alpine3.16 as build
WORKDIR /app
# Argument to allow building of different apps
ARG APP
ARG ENV_NAME=""
RUN apk add --update --no-cache \
python3 \
make \
@@ -19,10 +18,16 @@ RUN sh ./docker-build.sh
# if this fails you need to docker pull nginx:1.23-alpine and pin new SHA
# this is to ensure that we run always same version of alpine to make sure ipfs is indempotent
FROM --platform=amd64 nginx:1.23-alpine@sha256:6318314189b40e73145a48060bff4783a116c34cc7241532d0d94198fb2c9629
ARG APP
# configuration of system
RUN apk add --no-cache bash go-ipfs
EXPOSE 80
COPY entrypoint.sh /entrypoint.sh
CMD ["/entrypoint.sh"]
# Copy dist
WORKDIR /usr/share/nginx/html
COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist/apps/${APP} /usr/share/nginx/html
RUN apk add --no-cache go-ipfs; ipfs init && echo "$(ipfs add -rQ .)" > ipfs-hash; apk del go-ipfs
COPY ./apps/${APP}/.env .env
RUN ipfs init && echo "$(ipfs add -rQ .)" > ipfs-hash
+1 -1
View File
@@ -1,7 +1,7 @@
NX_TENDERMINT_URL=https://tm.n00.stagnet3.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n00.stagnet3.vega.xyz/websocket
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet3/vegawallet-stagnet3.toml
NX_VEGA_NETWORKS='{"STAGNET1":"https://stagnet1.token.vega.xyz", "STAGNET3":"https://stagnet3.explorer.vega.xyz","VALIDATOR_TESTNET":"https://validator-testnet.fairground.wtf","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_VEGA_NETWORKS='{"SANDBOX":"https://sandbox.explorer.vega.xyz","STAGNET1":"https://stagnet1.token.vega.xyz", "STAGNET3":"https://stagnet3.explorer.vega.xyz","VALIDATOR_TESTNET":"https://validator-testnet.fairground.wtf","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_VEGA_ENV=STAGNET3
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
+20
View File
@@ -0,0 +1,20 @@
# App configuration variables
NX_TENDERMINT_URL=https://tm.be.mainnet-mirror.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.xyz/websocket
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/mainnet-mirror/vegawallet-mainnet-mirror.toml
NX_VEGA_ENV=MIRROR
NX_BLOCK_EXPLORER=https://be.mainnet-mirror.vega.xyz/rest/
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://mainnet-mirror.token.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
# App flags
NX_EXPLORER_ASSETS=1
NX_EXPLORER_GENESIS=1
NX_EXPLORER_GOVERNANCE=1
NX_EXPLORER_NETWORK_PARAMETERS=1
NX_EXPLORER_PARTIES=1
NX_EXPLORER_VALIDATORS=1
NX_EXPLORER_MARKETS=0
NX_EXPLORER_ORACLES=0
NX_EXPLORER_TXS_LIST=1
+12
View File
@@ -0,0 +1,12 @@
# App configuration variables
NX_VEGA_ENV=SANDBOX
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/sandbox/vegawallet-sandbox.toml
NX_VEGA_EXPLORER_URL=https://sandbox.explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_TENDERMINT_URL=https://tm.sandbox.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.sandbox.vega.xyz/websocket
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://sandbox.token.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
+1 -1
View File
@@ -9,5 +9,5 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
NX_VEGA_GOVERNANCE_URL=https://validator-testnet.governance.vega.xyz
NX_TENDERMINT_URL=https://tm-be.validators-testnet.vega.rocks
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/rest
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket
-2
View File
@@ -13,8 +13,6 @@ NX_LOCAL_PROVIDER_URL=http://localhost:8545/
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
#Test configuration variables
CYPRESS_FAIRGROUND=false
@@ -6,203 +6,159 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
cy.get('nav', { timeout: 10000 }).should('be.visible');
});
describe('Links and buttons', function () {
it('should have link for proposal page', function () {
cy.getByTestId('home-proposals').within(() => {
cy.get('[href="/proposals"]')
.should('exist')
.and('have.text', 'Browse, vote, and propose');
});
});
it('should display announcement banner', function () {
cy.getByTestId('app-announcement')
.should('be.visible')
.within(() => {
cy.getByTestId('external-link').should('exist');
describe('with wallets disconnected', function () {
describe('Links and buttons', function () {
it('should have link for proposal page', function () {
cy.getByTestId('home-proposals').within(() => {
cy.get('[href="/proposals"]')
.should('exist')
.and('have.text', 'Browse, vote, and propose');
});
cy.getByTestId('app-announcement-close').should('be.visible').click();
cy.getByTestId('app-announcement').should('not.exist');
});
it('should show open or enacted proposals with proposal summary', function () {
cy.get('body').then(($body) => {
if (!$body.find('[data-testid="proposals-list-item"]').length) {
cy.createMarket();
cy.reload();
waitForSpinner();
}
});
cy.getByTestId('proposals-list-item')
.should('have.length.at.least', 1)
.first()
.within(() => {
cy.getByTestId('proposal-title')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('proposal-type').invoke('text').should('not.be.empty');
cy.getByTestId('proposal-description')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('proposal-details')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('proposal-status')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('vote-details').invoke('text').should('not.be.empty');
cy.getByTestId('view-proposal-btn').should('be.visible');
it('should show open or enacted proposals with proposal summary', function () {
cy.get('body').then(($body) => {
if (!$body.find('[data-testid="proposals-list-item"]').length) {
cy.createMarket();
cy.reload();
waitForSpinner();
}
});
});
it('should have external link for governance', function () {
cy.getByTestId('home-proposals').within(() => {
cy.getByTestId('external-link')
.should('have.attr', 'href')
.and('contain', 'https://vega.xyz/governance');
});
});
it('should have link for validator page', function () {
cy.getByTestId('home-validators').within(() => {
cy.get('[href="/validators"]')
cy.getByTestId('proposals-list-item')
.should('have.length.at.least', 1)
.first()
.should('exist')
.and('have.text', 'Browse, and stake');
.within(() => {
cy.getByTestId('proposal-title')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('proposal-type')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('proposal-description')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('proposal-details')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('proposal-status')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('vote-details')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('view-proposal-btn').should('be.visible');
});
});
});
it('should have external link for validators', function () {
cy.getByTestId('home-validators').within(() => {
cy.getByTestId('external-link')
.should('have.attr', 'href')
.and(
'contain',
'https://community.vega.xyz/c/mainnet-validator-candidates'
);
});
});
it('should have information on active nodes', function () {
cy.getByTestId('node-information')
.first()
.should('contain.text', '2')
.and('contain.text', 'active nodes');
});
it('should have information on consensus nodes', function () {
cy.getByTestId('node-information')
.last()
.should('contain.text', '2')
.and('contain.text', 'consensus nodes');
});
it('should contain link to specific validators', function () {
cy.getByTestId('validators')
.should('have.length', '2')
.each(($validator) => {
cy.wrap($validator).find('a').should('have.attr', 'href');
it('should have external link for governance', function () {
cy.getByTestId('home-proposals').within(() => {
cy.getByTestId('external-link')
.should('have.attr', 'href')
.and('contain', 'https://vega.xyz/governance');
});
});
it('should have link for rewards page', function () {
cy.getByTestId('home-rewards').within(() => {
cy.get('[href="/rewards"]')
.first()
.should('exist')
.and('have.text', 'See rewards');
});
});
it('should have link for withdrawal page', function () {
cy.getByTestId('home-vega-token').within(() => {
cy.get('[href="/token/withdraw"]')
.first()
.should('exist')
.and('have.text', 'Manage tokens');
});
});
it('should display network data', function () {
cy.getByTestId('git-network-data')
.should('contain.text', 'Reading network data from')
.within(() => {
cy.get('span')
it('should have link for validator page', function () {
cy.getByTestId('home-validators').within(() => {
cy.get('[href="/validators"]')
.first()
.should('have.text', 'http://localhost:3028/query');
cy.getByTestId('link').should('exist');
.should('exist')
.and('have.text', 'Browse, and stake');
});
});
it('should display eth data', function () {
cy.getByTestId('git-eth-data')
.should('contain.text', 'Reading Ethereum data from')
.within(() => {
cy.get('span').should('have.text', 'http://localhost:8545');
});
it('should have external link for validators', function () {
cy.getByTestId('home-validators').within(() => {
cy.getByTestId('external-link')
.should('have.attr', 'href')
.and(
'contain',
'https://community.vega.xyz/c/mainnet-validator-candidates'
);
});
});
it('should contain link for known issues on Github', function () {
cy.getByTestId('git-info').within(() => {
cy.contains('Known issues and feedback on')
.find('[data-testid="link"]')
.should(
'have.attr',
'href',
'https://github.com/vegaprotocol/feedback/discussions'
);
});
});
});
describe('Mobile view - navigation bar', function () {
before('Change to mobile resolution', function () {
cy.viewport('iphone-xr');
});
it('should have burger button', () => {
cy.getByTestId('button-menu-drawer').should('be.visible').click();
cy.getByTestId('menu-drawer').should('be.visible');
});
it('should have link for proposal page', function () {
cy.getByTestId('menu-drawer').within(() => {
cy.get('[href="/proposals"]')
.should('exist')
.and('have.text', 'Proposals');
});
});
it('should have link for validator page', function () {
cy.getByTestId('menu-drawer').within(() => {
cy.get('[href="/validators"]')
it('should have information on active nodes', function () {
cy.getByTestId('node-information')
.first()
.should('exist')
.and('have.text', 'Validators');
.should('contain.text', '2')
.and('contain.text', 'active nodes');
});
it('should have information on consensus nodes', function () {
cy.getByTestId('node-information')
.last()
.should('contain.text', '2')
.and('contain.text', 'consensus nodes');
});
it('should contain link to specific validators', function () {
cy.getByTestId('validators')
.should('have.length', '2')
.each(($validator) => {
cy.wrap($validator).find('a').should('have.attr', 'href');
});
});
it('should have link for rewards page', function () {
cy.getByTestId('home-rewards').within(() => {
cy.get('[href="/rewards"]')
.first()
.should('exist')
.and('have.text', 'See rewards');
});
});
it('should have link for withdrawal page', function () {
cy.getByTestId('home-vega-token').within(() => {
cy.get('[href="/token/withdraw"]')
.first()
.should('exist')
.and('have.text', 'Manage tokens');
});
});
});
it('should have link for rewards page', function () {
cy.getByTestId('menu-drawer').within(() => {
cy.get('[href="/rewards"]')
.first()
.should('exist')
.and('have.text', 'Rewards');
describe('Mobile view - navigation bar', function () {
before('Change to mobile resolution', function () {
cy.viewport('iphone-xr');
});
});
it('should have link for withdrawal page', function () {
cy.getByTestId('menu-drawer').within(() => {
cy.get('[href="/token/withdraw"]')
.first()
.should('exist')
.and('have.text', 'Withdraw');
});
});
after(function () {
cy.viewport(
Cypress.config('viewportWidth'),
Cypress.config('viewportHeight')
);
it('should have burger button', () => {
cy.getByTestId('button-menu-drawer').should('be.visible').click();
cy.getByTestId('menu-drawer').should('be.visible');
});
it('should have link for proposal page', function () {
cy.getByTestId('menu-drawer').within(() => {
cy.get('[href="/proposals"]')
.should('exist')
.and('have.text', 'Proposals');
});
});
it('should have link for validator page', function () {
cy.getByTestId('menu-drawer').within(() => {
cy.get('[href="/validators"]')
.first()
.should('exist')
.and('have.text', 'Validators');
});
});
it('should have link for rewards page', function () {
cy.getByTestId('menu-drawer').within(() => {
cy.get('[href="/rewards"]')
.first()
.should('exist')
.and('have.text', 'Rewards');
});
});
it('should have link for withdrawal page', function () {
cy.getByTestId('menu-drawer').within(() => {
cy.get('[href="/token/withdraw"]')
.first()
.should('exist')
.and('have.text', 'Withdraw');
});
});
after(function () {
cy.viewport(
Cypress.config('viewportWidth'),
Cypress.config('viewportHeight')
);
});
});
});
});
+1 -1
View File
@@ -5,7 +5,6 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_FAIRGROUND=false
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_CONFIG_URL=''
NX_VEGA_URL=http://localhost:3028/query
@@ -17,6 +16,7 @@ NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
#Test configuration variables
CYPRESS_FAIRGROUND=false
+12
View File
@@ -0,0 +1,12 @@
# App configuration variables
NX_VEGA_ENV=MIRROR
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/mainnet-mirror/vegawallet-mainnet-mirror.toml
NX_VEGA_URL=https://api.n00.mainnet-mirror.vega.xyz/graphql
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_EXPLORER_URL=https://mirror.explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
NX_DELEGATIONS_PAGINATION=50
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
+9
View File
@@ -0,0 +1,9 @@
# App configuration variables
NX_VEGA_URL=https://api.sandbox.vega.xyz/graphql
NX_VEGA_ENV=SANDBOX
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","STAGNET1":"https://stagnet1.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/sandbox/vegawallet-sandbox.toml
NX_VEGA_EXPLORER_URL=https://sandbox.explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
-1
View File
@@ -8,4 +8,3 @@ NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
+8 -4
View File
@@ -12,7 +12,6 @@ const TRUTHY = ['1', 'true'];
interface VegaContracts {
claimAddress: string;
lockedAddress: string;
tokenVestingAddress?: string;
}
const customClaimAddress = process.env['NX_CUSTOM_CLAIM_ADDRESS'] as string;
@@ -37,16 +36,21 @@ export const ContractAddresses: {
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
},
SANDBOX: {
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
},
TESTNET: {
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
},
MIRROR: {
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
},
VALIDATOR_TESTNET: {
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
// This is a fallback contract address for the validator testnet network which does not
// have a vesting contract address set and is therefore not in the ethereum config
tokenVestingAddress: '0xadFcb7f93a24F8743a8e548d74d2ecB373c92866',
},
MAINNET: {
claimAddress: '0x0ee1fb382caf98e86e97e51f9f42f8b4654020f3',
@@ -49,13 +49,6 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
signer = provider.getSigner();
}
const tokenVestingAddress =
config.token_vesting_contract?.address ||
ENV.addresses.tokenVestingAddress;
if (!tokenVestingAddress) {
throw new Error('No token vesting address found');
}
if (provider && config) {
const staking = new StakingBridge(
config.staking_bridge_contract.address,
@@ -70,7 +63,7 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
signer || provider
),
vesting: new TokenVesting(
tokenVestingAddress,
config.token_vesting_contract.address,
signer || provider
),
claim: new Claim(ENV.addresses.claimAddress, signer || provider),
@@ -7,10 +7,9 @@ query PreviousEpoch($epochId: ID) {
id
rewardScore {
rawValidatorScore
performanceScore
}
rankingScore {
stakeScore
performanceScore
}
}
}
@@ -8,7 +8,7 @@ export type PreviousEpochQueryVariables = Types.Exact<{
}>;
export type PreviousEpochQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string, validatorsConnection?: { __typename?: 'NodesConnection', edges?: Array<{ __typename?: 'NodeEdge', node: { __typename?: 'Node', id: string, rewardScore?: { __typename?: 'RewardScore', rawValidatorScore: string, performanceScore: string } | null, rankingScore: { __typename?: 'RankingScore', stakeScore: string } } } | null> | null } | null } };
export type PreviousEpochQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string, validatorsConnection?: { __typename?: 'NodesConnection', edges?: Array<{ __typename?: 'NodeEdge', node: { __typename?: 'Node', id: string, rewardScore?: { __typename?: 'RewardScore', rawValidatorScore: string } | null, rankingScore: { __typename?: 'RankingScore', performanceScore: string } } } | null> | null } | null } };
export const PreviousEpochDocument = gql`
@@ -21,10 +21,9 @@ export const PreviousEpochDocument = gql`
id
rewardScore {
rawValidatorScore
performanceScore
}
rankingScore {
stakeScore
performanceScore
}
}
}
@@ -81,10 +81,9 @@ const MOCK_PREVIOUS_EPOCH: PreviousEpochQuery = {
id: 'ccc022b7e63a4d0a6d3a193c3940c88574060e58a184964c994998d86835a1b4',
rewardScore: {
rawValidatorScore: '0.25',
performanceScore: '0.9998677767864936',
},
rankingScore: {
stakeScore: '0.2499583402766206',
performanceScore: '0.9998677767864936',
},
},
},
@@ -93,10 +92,9 @@ const MOCK_PREVIOUS_EPOCH: PreviousEpochQuery = {
id: '966438c6bffac737cfb08173ffcb3f393c4692b099ad80cb45a82e2dc0a8cf99',
rewardScore: {
rawValidatorScore: '0.3',
performanceScore: '1',
},
rankingScore: {
stakeScore: '0.25',
performanceScore: '1',
},
},
},
@@ -105,10 +103,9 @@ const MOCK_PREVIOUS_EPOCH: PreviousEpochQuery = {
id: '12c81b738e8051152e1afe44376ec37bca9216466e6d44cdd772194bad0ada81',
rewardScore: {
rawValidatorScore: '0.35',
performanceScore: '0.999629748500531',
},
rankingScore: {
stakeScore: '0.2312',
performanceScore: '0.999629748500531',
},
},
},
@@ -10,6 +10,7 @@ import {
getFormattedPerformanceScore,
getLastEpochScoreAndPerformance,
getNormalisedVotingPower,
getOverstakedAmount,
getOverstakingPenalty,
getPerformancePenalty,
getTotalPenalties,
@@ -51,6 +52,7 @@ interface CanonisedConsensusNodeProps {
[ValidatorFields.STAKED_BY_OPERATOR]: string;
[ValidatorFields.PERFORMANCE_SCORE]: string;
[ValidatorFields.PERFORMANCE_PENALTY]: string;
[ValidatorFields.OVERSTAKED_AMOUNT]: string;
[ValidatorFields.OVERSTAKING_PENALTY]: string;
[ValidatorFields.TOTAL_PENALTIES]: string;
[ValidatorFields.PENDING_STAKE]: string;
@@ -162,11 +164,14 @@ export const ConsensusValidatorsTable = ({
pendingUserStake,
userStakeShare,
}) => {
const {
rawValidatorScore: previousEpochValidatorScore,
performanceScore: previousEpochPerformanceScore,
stakeScore: previousEpochStakeScore,
} = getLastEpochScoreAndPerformance(previousEpochData, id);
const { rawValidatorScore, performanceScore } =
getLastEpochScoreAndPerformance(previousEpochData, id);
const overstakedAmount = getOverstakedAmount(
rawValidatorScore,
stakedTotal,
totalStake
);
return {
id,
@@ -179,7 +184,7 @@ export const ConsensusValidatorsTable = ({
[ValidatorFields.NORMALISED_VOTING_POWER]:
getNormalisedVotingPower(votingPower),
[ValidatorFields.UNNORMALISED_VOTING_POWER]:
getUnnormalisedVotingPower(previousEpochValidatorScore),
getUnnormalisedVotingPower(rawValidatorScore),
[ValidatorFields.STAKE_SHARE]: stakedTotalPercentage(stakeScore),
[ValidatorFields.STAKED_BY_DELEGATES]: formatNumber(
toBigNum(stakedByDelegates, decimals),
@@ -189,19 +194,18 @@ export const ConsensusValidatorsTable = ({
toBigNum(stakedByOperator, decimals),
2
),
[ValidatorFields.PERFORMANCE_SCORE]: getFormattedPerformanceScore(
previousEpochPerformanceScore
).toString(),
[ValidatorFields.PERFORMANCE_PENALTY]: getPerformancePenalty(
previousEpochPerformanceScore
),
[ValidatorFields.PERFORMANCE_SCORE]:
getFormattedPerformanceScore(performanceScore).toString(),
[ValidatorFields.PERFORMANCE_PENALTY]:
getPerformancePenalty(performanceScore),
[ValidatorFields.OVERSTAKED_AMOUNT]: overstakedAmount.toString(),
[ValidatorFields.OVERSTAKING_PENALTY]: getOverstakingPenalty(
previousEpochValidatorScore,
previousEpochStakeScore
overstakedAmount,
totalStake
),
[ValidatorFields.TOTAL_PENALTIES]: getTotalPenalties(
previousEpochValidatorScore,
previousEpochPerformanceScore,
rawValidatorScore,
performanceScore,
stakedTotal,
totalStake
),
@@ -7,6 +7,7 @@ import { BigNumber } from '../../../../lib/bignumber';
import {
getFormattedPerformanceScore,
getLastEpochScoreAndPerformance,
getOverstakedAmount,
getOverstakingPenalty,
getPerformancePenalty,
getTotalPenalties,
@@ -81,21 +82,21 @@ export const StandbyPendingValidatorsTable = ({
pendingUserStake,
userStakeShare,
}) => {
const {
rawValidatorScore: previousEpochValidatorScore,
performanceScore: previousEpochPerformanceScore,
stakeScore: previousEpochStakeScore,
} = getLastEpochScoreAndPerformance(previousEpochData, id);
const { rawValidatorScore, performanceScore } =
getLastEpochScoreAndPerformance(previousEpochData, id);
const overstakedAmount = getOverstakedAmount(
rawValidatorScore,
stakedTotal,
totalStake
);
let individualStakeNeededForPromotion,
individualStakeNeededForPromotionDescription;
if (stakeNeededForPromotion && previousEpochPerformanceScore) {
if (stakeNeededForPromotion && performanceScore) {
const stakedTotalBigNum = new BigNumber(stakedTotal);
const stakeNeededBigNum = new BigNumber(stakeNeededForPromotion);
const performanceScoreBigNum = new BigNumber(
previousEpochPerformanceScore
);
const performanceScoreBigNum = new BigNumber(performanceScore);
const calc = stakeNeededBigNum
.dividedBy(performanceScoreBigNum)
@@ -141,19 +142,18 @@ export const StandbyPendingValidatorsTable = ({
toBigNum(stakedByOperator, decimals),
2
),
[ValidatorFields.PERFORMANCE_SCORE]: getFormattedPerformanceScore(
previousEpochPerformanceScore
).toString(),
[ValidatorFields.PERFORMANCE_PENALTY]: getPerformancePenalty(
previousEpochPerformanceScore
),
[ValidatorFields.PERFORMANCE_SCORE]:
getFormattedPerformanceScore(performanceScore).toString(),
[ValidatorFields.PERFORMANCE_PENALTY]:
getPerformancePenalty(performanceScore),
[ValidatorFields.OVERSTAKED_AMOUNT]: overstakedAmount.toString(),
[ValidatorFields.OVERSTAKING_PENALTY]: getOverstakingPenalty(
previousEpochValidatorScore,
previousEpochStakeScore
overstakedAmount,
totalStake
),
[ValidatorFields.TOTAL_PENALTIES]: getTotalPenalties(
previousEpochValidatorScore,
previousEpochPerformanceScore,
rawValidatorScore,
performanceScore,
stakedTotal,
totalStake
),
@@ -20,6 +20,7 @@ import { SubHeading } from '../../../components/heading';
import {
getLastEpochScoreAndPerformance,
getNormalisedVotingPower,
getOverstakedAmount,
getOverstakingPenalty,
getPerformancePenalty,
getTotalPenalties,
@@ -74,9 +75,15 @@ export const ValidatorTable = ({
const stakedOnNode = toBigNum(node.stakedTotal, decimals);
const { rawValidatorScore, performanceScore, stakeScore } =
const { rawValidatorScore, performanceScore } =
getLastEpochScoreAndPerformance(previousEpochData, node.id);
const overstakedAmount = getOverstakedAmount(
rawValidatorScore,
stakedTotal,
node.stakedTotal
);
const stakePercentage = getStakePercentage(total, stakedOnNode);
const totalPenaltiesAmount = getTotalPenalties(
@@ -238,7 +245,7 @@ export const ValidatorTable = ({
<Tooltip description={t('OverstakedPenaltyDescription')}>
<span data-testid="overstaking-penalty">
{getOverstakingPenalty(rawValidatorScore, stakeScore)}
{getOverstakingPenalty(overstakedAmount, node.stakedTotal)}
</span>
</Tooltip>
</KeyValueTableRow>
@@ -4,6 +4,7 @@ import {
getNormalisedVotingPower,
getUnnormalisedVotingPower,
getOverstakingPenalty,
getOverstakedAmount,
getFormattedPerformanceScore,
getPerformancePenalty,
getTotalPenalties,
@@ -21,10 +22,9 @@ describe('getLastEpochScoreAndPerformance', () => {
id: '0x123',
rewardScore: {
rawValidatorScore: '0.25',
performanceScore: '0.75',
},
rankingScore: {
stakeScore: '0.25',
performanceScore: '0.75',
},
},
},
@@ -33,10 +33,9 @@ describe('getLastEpochScoreAndPerformance', () => {
id: '0x234',
rewardScore: {
rawValidatorScore: '0.35',
performanceScore: '0.85',
},
rankingScore: {
stakeScore: '0.25',
performanceScore: '0.85',
},
},
},
@@ -51,14 +50,12 @@ describe('getLastEpochScoreAndPerformance', () => {
).toEqual({
rawValidatorScore: '0.25',
performanceScore: '0.75',
stakeScore: '0.25',
});
expect(
getLastEpochScoreAndPerformance(mockPreviousEpochData, '0x234')
).toEqual({
rawValidatorScore: '0.35',
performanceScore: '0.85',
stakeScore: '0.25',
});
});
});
@@ -82,34 +79,40 @@ describe('getUnnormalisedVotingPower', () => {
});
describe('getOverstakingPenalty', () => {
it('returns "0%" when both arguments are null or undefined', () => {
expect(getOverstakingPenalty(null, null)).toBe('0%');
expect(getOverstakingPenalty(undefined, undefined)).toBe('0%');
expect(getOverstakingPenalty(null, undefined)).toBe('0%');
expect(getOverstakingPenalty(undefined, null)).toBe('0%');
it('should return the overstaking penalty', () => {
expect(
getOverstakingPenalty(new BigNumber(100), Number(1000).toString())
).toEqual('10.00%');
expect(
getOverstakingPenalty(new BigNumber(500), Number(2000).toString())
).toEqual('25.00%');
});
});
describe('getOverstakedAmount', () => {
it('should return the overstaked amount', () => {
expect(
// If a validator score is 0, any amount staked on the node is considered overstaked
getOverstakedAmount('0', Number(100).toString(), Number(20).toString())
).toEqual(new BigNumber(20));
expect(
getOverstakedAmount('0.05', Number(100).toString(), Number(20).toString())
).toEqual(new BigNumber(15));
expect(
getOverstakedAmount('0.1', Number(100).toString(), Number(20).toString())
).toEqual(new BigNumber(10));
expect(
getOverstakedAmount('0.15', Number(100).toString(), Number(20).toString())
).toEqual(new BigNumber(5));
expect(
getOverstakedAmount('0.2', Number(100).toString(), Number(20).toString())
).toEqual(new BigNumber(0));
});
it('returns "0%" when one argument is null or undefined', () => {
expect(getOverstakingPenalty('10', null)).toBe('0%');
expect(getOverstakingPenalty(null, '20')).toBe('0%');
expect(getOverstakingPenalty('10', undefined)).toBe('0%');
expect(getOverstakingPenalty(undefined, '20')).toBe('0%');
});
it('returns "0%" when validatorScore or stakeScore is zero', () => {
expect(getOverstakingPenalty('0', '20')).toBe('0%');
expect(getOverstakingPenalty('10', '0')).toBe('0%');
});
it('returns the correct overstaking penalty', () => {
expect(getOverstakingPenalty('0.18', '0.2')).toBe('10.00%');
expect(getOverstakingPenalty('0.2', '0.2')).toBe('0.00%');
expect(getOverstakingPenalty('0.04', '0.2')).toBe('80.00%');
});
it('handles string numbers with decimals', () => {
expect(getOverstakingPenalty('7.5', '15')).toBe('50.00%');
expect(getOverstakingPenalty('12.5', '25')).toBe('50.00%');
it('should return 0 if the overstaked amount is negative', () => {
expect(
getOverstakedAmount('0.8', Number(100).toString(), Number(20).toString())
).toEqual(new BigNumber(0));
});
});
+19 -15
View File
@@ -15,8 +15,7 @@ export const getLastEpochScoreAndPerformance = (
return {
rawValidatorScore: validator?.rewardScore?.rawValidatorScore,
performanceScore: validator?.rewardScore?.performanceScore,
stakeScore: validator?.rankingScore?.stakeScore,
performanceScore: validator?.rankingScore?.performanceScore,
};
};
@@ -43,26 +42,31 @@ export const getPerformancePenalty = (performanceScore?: string) =>
2
);
export const getOverstakingPenalty = (
export const getOverstakedAmount = (
validatorScore: string | null | undefined,
stakeScore: string | null | undefined
totalStake: string,
stakedOnNode: string
) => {
if (!validatorScore || !stakeScore) {
return '0%';
}
const toReturn = validatorScore
? new BigNumber(stakedOnNode).minus(
new BigNumber(validatorScore).times(new BigNumber(totalStake))
)
: new BigNumber(0);
return toReturn.isNegative() ? new BigNumber(0) : toReturn;
};
export const getOverstakingPenalty = (
overstakedAmount: BigNumber,
stakedOnNode: string
) => {
// avoid division by zero
if (
new BigNumber(validatorScore).isZero() ||
new BigNumber(stakeScore).isZero()
) {
return '0%';
if (new BigNumber(stakedOnNode).isZero() || overstakedAmount.isZero()) {
return '0';
}
return formatNumberPercentage(
new BigNumber(1)
.minus(new BigNumber(validatorScore).dividedBy(new BigNumber(stakeScore)))
.times(100),
overstakedAmount.dividedBy(new BigNumber(stakedOnNode)).times(100),
2
);
};
@@ -14,7 +14,6 @@ import { TokenDetailsCirculating } from './token-details-circulating';
import { SplashLoader } from '../../../components/splash-loader';
import { useEthereumConfig } from '@vegaprotocol/web3';
import { useContracts } from '../../../contexts/contracts/contracts-context';
import { ENV } from '../../../config';
export const TokenDetails = ({
totalSupply,
@@ -50,9 +49,6 @@ export const TokenDetails = ({
);
}
const tokenVestingContractAddress =
config.token_vesting_contract?.address || ENV.addresses.tokenVestingAddress;
return (
<div className="token-details">
<RoundedWrapper>
@@ -69,20 +65,18 @@ export const TokenDetails = ({
{token.address}
</Link>
</KeyValueTableRow>
{tokenVestingContractAddress && (
<KeyValueTableRow>
{t('Vesting contract').toUpperCase()}
<Link
data-testid="token-contract"
title={t('View on Etherscan (opens in a new tab)')}
className="font-mono text-white text-right"
href={`${ETHERSCAN_URL}/address/${tokenVestingContractAddress}`}
target="_blank"
>
{tokenVestingContractAddress}
</Link>
</KeyValueTableRow>
)}
<KeyValueTableRow>
{t('Vesting contract').toUpperCase()}
<Link
data-testid="token-contract"
title={t('View on Etherscan (opens in a new tab)')}
className="font-mono text-white text-right"
href={`${ETHERSCAN_URL}/address/${config.token_vesting_contract.address}`}
target="_blank"
>
{config.token_vesting_contract.address}
</Link>
</KeyValueTableRow>
<KeyValueTableRow>
{t('Total supply').toUpperCase()}
<span className="font-mono" data-testid="total-supply">
+5
View File
@@ -0,0 +1,5 @@
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/sandbox/vegawallet-sandbox.toml
NX_VEGA_URL=https://api.sandbox.vega.xyz/graphql
NX_VEGA_ENV=SANDBOX
NX_VEGA_NETWORKS={\"DEVNET\":\"https://dev.token.vega.xyz\",\"STAGNET3\":\"https://stagnet3.token.vega.xyz\",\"STAGNET1\":\"https://stagnet1.token.vega.xyz\",\"TESTNET\":\"https://token.fairground.wtf\",\"MAINNET\":\"https://token.vega.xyz\"}
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/sandbox/vegawallet-sandbox.toml
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
{
"hosts": ["https://api-n00.mainnet-mirror.vega.rocks/graphql"]
}
@@ -0,0 +1,3 @@
{
"hosts": ["https://api-n01.sandbox.vega.rocks/graphql"]
}
@@ -411,7 +411,6 @@ describe('capsule', { tags: '@slow' }, () => {
it('approved amount is less than deposit', function () {
// 1001-DEPO-006
// 1001-DEPO-007
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
@@ -431,8 +430,8 @@ describe('capsule', { tags: '@slow' }, () => {
});
it('withdraw - delay verification', function () {
// 1001-DEPO-007
// 1001-DEPO-024
// 1002-WITH-007
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
@@ -76,7 +76,6 @@ describe('deposit form validation', { tags: '@smoke' }, () => {
});
it('insufficient funds', () => {
// 1001-DEPO-004
mockWeb3DepositCalls({
allowance: '1000',
depositLifetimeLimit: '1000',
@@ -95,16 +95,6 @@ describe('Console - market info - live env', { tags: '@live' }, () => {
cy.wrap(element).should('have.text', subtitles[index]);
});
});
it('renders correctly liquidity in trading tab', () => {
cy.getByTestId('Liquidity').click();
cy.contains('Loading').should('not.exist');
cy.contains('Something went wrong').should('not.exist');
cy.contains('Application error').should('not.exist');
cy.getByTestId('tab-liquidity').within(() => {
cy.get('[col-id="party.id"]').eq(1).should('not.be.empty');
});
});
});
describe('Console - market summary - live env', { tags: '@live' }, () => {
+1 -81
View File
@@ -1,5 +1,5 @@
import * as Schema from '@vegaprotocol/types';
import { aliasGQLQuery, checkSorting } from '@vegaprotocol/cypress';
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { marketsQuery } from '@vegaprotocol/mock';
import { getDateTimeFormat } from '@vegaprotocol/utils';
@@ -85,7 +85,6 @@ describe('markets table', { tags: '@smoke' }, () => {
cy.getByTestId('view-market-list-link')
.should('have.attr', 'href', '#/markets/all')
.click();
cy.get('[data-testid="All markets"]').should(
'have.attr',
'data-state',
@@ -118,85 +117,6 @@ describe('markets table', { tags: '@smoke' }, () => {
`${Cypress.env('VEGA_TOKEN_URL')}/proposals/propose/new-market`
);
});
it('proposed markets tab should be sorted properly', () => {
cy.getByTestId('view-market-list-link').click();
cy.get('[data-testid="Proposed markets"]').click();
const marketColDefault = [
'ETHUSD',
'LINKUSD',
'ETHUSD',
'ETHDAI.MF21',
'AAPL.MF21',
'BTCUSD.MF21',
'TSLA.QM21',
'AAVEDAI.MF21',
'ETHBTC.QM21',
'UNIDAI.MF21',
];
const marketColAsc = [
'AAPL.MF21',
'AAVEDAI.MF21',
'BTCUSD.MF21',
'ETHBTC.QM21',
'ETHDAI.MF21',
'ETHUSD',
'ETHUSD',
'LINKUSD',
'TSLA.QM21',
'UNIDAI.MF21',
];
const marketColDesc = [
'UNIDAI.MF21',
'TSLA.QM21',
'LINKUSD',
'ETHUSD',
'ETHUSD',
'ETHDAI.MF21',
'ETHBTC.QM21',
'BTCUSD.MF21',
'AAVEDAI.MF21',
'AAPL.MF21',
];
checkSorting('market', marketColDefault, marketColAsc, marketColDesc);
const stateColDefault = [
'Open',
'Passed',
'Waiting for Node Vote',
'Open',
'Passed',
'Open',
'Passed',
'Open',
'Waiting for Node Vote',
'Open',
];
const stateColAsc = [
'Open',
'Open',
'Open',
'Open',
'Open',
'Passed',
'Passed',
'Passed',
'Waiting for Node Vote',
'Waiting for Node Vote',
];
const stateColDesc = [
'Waiting for Node Vote',
'Waiting for Node Vote',
'Passed',
'Passed',
'Passed',
'Open',
'Open',
'Open',
'Open',
'Open',
];
checkSorting('state', stateColDefault, stateColAsc, stateColDesc);
});
it('opening auction subsets should be properly displayed', () => {
cy.mockTradingPage(
@@ -1,329 +0,0 @@
import * as Schema from '@vegaprotocol/types';
import { mockConnectWallet } from '@vegaprotocol/cypress';
const orderSizeField = 'order-size';
const orderPriceField = 'order-price';
const orderTIFDropDown = 'order-tif';
const placeOrderBtn = 'place-order';
const toggleShort = 'order-side-SIDE_SELL';
const toggleLong = 'order-side-SIDE_BUY';
const toggleLimit = 'order-type-TYPE_LIMIT';
const toggleMarket = 'order-type-TYPE_MARKET';
const TIFlist = Object.values(Schema.OrderTimeInForce).map((value) => {
return {
code: Schema.OrderTimeInForceCode[value],
value,
text: Schema.OrderTimeInForceMapping[value],
};
});
describe('time in force default values', () => {
before(() => {
cy.setVegaWallet();
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
});
it('must have market order set up to IOC by default', function () {
// 7002-SORD-030
cy.getByTestId(toggleMarket).click();
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
TIFlist.filter((item) => item.code === 'IOC')[0].text
);
});
it('must have time in force set to GTC for limit order', function () {
// 7002-SORD-031
cy.getByTestId(toggleLimit).click();
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
TIFlist.filter((item) => item.code === 'GTC')[0].text
);
});
});
describe('deal ticket validation', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.mockTradingPage();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
});
it('must show place order button and connect wallet if wallet is not connected', () => {
// 0003-WTXN-001
cy.getByTestId('connect-vega-wallet'); // Not connected
cy.getByTestId('order-connect-wallet').should('exist');
cy.getByTestId(placeOrderBtn).should('exist');
cy.getByTestId('deal-ticket-connect-wallet').should('exist');
});
it('must be able to select order direction - long/short', function () {
// 7002-SORD-004
cy.getByTestId(toggleShort).click().children('input').should('be.checked');
cy.getByTestId(toggleLong).click().children('input').should('be.checked');
});
it('must be able to select order type - limit/market', function () {
// 7002-SORD-005
// 7002-SORD-006
// 7002-SORD-007
cy.getByTestId(toggleLimit).click().children('input').should('be.checked');
cy.getByTestId(toggleMarket).click().children('input').should('be.checked');
});
it('order connect vega wallet button should connect', () => {
mockConnectWallet();
cy.getByTestId(toggleLimit).click();
cy.getByTestId(orderPriceField).clear().type('101');
cy.getByTestId('order-connect-wallet').click();
cy.getByTestId('dialog-content').should('be.visible');
cy.getByTestId('connectors-list')
.find('[data-testid="connector-jsonRpc"]')
.click();
cy.wait('@walletReq');
cy.getByTestId(placeOrderBtn).should('be.visible');
cy.getByTestId(toggleLimit).children('input').should('be.checked');
cy.getByTestId(orderPriceField).should('have.value', '101');
});
});
describe('deal ticket size validation', { tags: '@smoke' }, function () {
beforeEach(() => {
cy.setVegaWallet();
cy.mockTradingPage();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
});
it('must warn if order size input has too many digits after the decimal place', function () {
// 7002-SORD-016
cy.getByTestId('order-type-TYPE_MARKET').click();
cy.getByTestId(orderSizeField).clear().type('1.234');
cy.getByTestId(placeOrderBtn).should('not.be.disabled');
cy.getByTestId(placeOrderBtn).click();
cy.getByTestId(placeOrderBtn).should('be.disabled');
cy.getByTestId('dealticket-error-message-size-market').should(
'have.text',
'Size must be whole numbers for this market'
);
});
it('must warn if order size is set to 0', function () {
cy.getByTestId('order-type-TYPE_MARKET').click();
cy.getByTestId(orderSizeField).clear().type('0');
cy.getByTestId(placeOrderBtn).should('not.be.disabled');
cy.getByTestId(placeOrderBtn).click();
cy.getByTestId(placeOrderBtn).should('be.disabled');
cy.getByTestId('dealticket-error-message-size-market').should(
'have.text',
'Size cannot be lower than 1'
);
});
});
describe('limit order validations', { tags: '@smoke' }, () => {
before(() => {
cy.setVegaWallet();
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
cy.getByTestId(toggleLimit).click();
});
beforeEach(() => {
cy.setVegaWallet();
});
it('must see the price unit', function () {
// 7002-SORD-018
cy.getByTestId(orderPriceField)
.siblings('label')
.should('have.text', 'Price (DAI)');
});
it('must see warning when placing an order with expiry date in past', () => {
const expiresAt = new Date(Date.now() - 24 * 60 * 60 * 1000);
const expiresAtInputValue = expiresAt.toISOString().substring(0, 16);
cy.getByTestId(toggleLimit).click();
cy.getByTestId(orderPriceField).clear().type('0.1');
cy.getByTestId(orderSizeField).clear().type('1');
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_GTT');
cy.log('choosing yesterday');
cy.getByTestId('date-picker-field').type(expiresAtInputValue);
cy.getByTestId(placeOrderBtn).click();
cy.getByTestId('dealticket-error-message-expiry').should(
'have.text',
'The expiry date that you have entered appears to be in the past'
);
});
it('must see warning if price has too many digits after decimal place', function () {
// 7002-SORD-059
cy.getByTestId(toggleLimit).click();
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_GTC');
cy.getByTestId(orderSizeField).clear().type('1');
cy.getByTestId(orderPriceField).clear().type('1.123456');
cy.getByTestId('dealticket-error-message-price-limit').should(
'have.text',
'Price accepts up to 5 decimal places'
);
});
describe('time in force validations', function () {
const validTIF = TIFlist;
validTIF.forEach((tif) => {
// 7002-SORD-023
// 7002-SORD-024
// 7002-SORD-025
// 7002-SORD-026
// 7002-SORD-027
// 7002-SORD-028
it(`must be able to select ${tif.code}`, function () {
cy.getByTestId(orderTIFDropDown).select(tif.value);
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
tif.text
);
});
});
it('selections should be remembered', () => {
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_GTT');
cy.getByTestId(toggleMarket).click();
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
TIFlist.filter((item) => item.code === 'IOC')[0].text
);
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_FOK');
cy.getByTestId(toggleLimit).click();
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
TIFlist.filter((item) => item.code === 'GTT')[0].text
);
cy.getByTestId(toggleMarket).click();
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
TIFlist.filter((item) => item.code === 'FOK')[0].text
);
});
});
});
describe('market order validations', { tags: '@smoke' }, () => {
before(() => {
cy.setVegaWallet();
cy.mockTradingPage();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
cy.getByTestId(toggleMarket).click();
});
beforeEach(() => {
cy.setVegaWallet();
});
it('must not see the price unit', function () {
// 7002-SORD-019
cy.getByTestId(orderPriceField).should('not.exist');
});
describe('time in force validations', function () {
const validTIF = TIFlist.filter((tif) => ['FOK', 'IOC'].includes(tif.code));
const invalidTIF = TIFlist.filter(
(tif) => !['FOK', 'IOC'].includes(tif.code)
);
validTIF.forEach((tif) => {
// 7002-SORD-025
// 7002-SORD-026
it(`must be able to select ${tif.code}`, function () {
cy.getByTestId(orderTIFDropDown).select(tif.value);
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
tif.text
);
});
});
invalidTIF.forEach((tif) => {
// 7002-SORD-023
// 7002-SORD-024
// 7002-SORD-027
// 7002-SORD-028
it(`must not be able to select ${tif.code}`, function () {
cy.getByTestId(orderTIFDropDown).should('not.contain', tif.text);
});
});
});
});
describe('post and reduce order validations', { tags: '@smoke' }, () => {
before(() => {
cy.setVegaWallet();
cy.mockTradingPage();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
cy.getByTestId(toggleMarket).click();
});
beforeEach(() => {
cy.setVegaWallet();
});
const validTIF = TIFlist.filter((tif) => ['FOK', 'IOC'].includes(tif.code));
validTIF.forEach((tif) => {
// 7002-SORD-025
// 7002-SORD-026
it(`post and reduce order market for ${tif.code}`, function () {
cy.getByTestId(orderTIFDropDown).select(tif.value);
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
tif.text
);
cy.getByTestId('post-only').should('be.disabled');
cy.getByTestId('reduce-only').should('be.enabled');
});
});
validTIF.forEach((tif) => {
it(`post and reduce order limit for ${tif.code}`, function () {
cy.getByTestId(toggleLimit).click();
cy.getByTestId(orderTIFDropDown).select(tif.value);
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
tif.text
);
cy.getByTestId('post-only').should('be.disabled');
cy.getByTestId('reduce-only').should('be.enabled');
});
});
const validTIFLimit = TIFlist.filter((tif) =>
['GFA', 'GFN', 'GTC', 'GTT'].includes(tif.code)
);
validTIFLimit.forEach((tif) => {
it(`post and reduce order for limit ${tif.code}`, function () {
cy.getByTestId(toggleLimit).click();
cy.getByTestId(orderTIFDropDown).select(tif.value);
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
tif.text
);
cy.getByTestId('post-only').should('be.enabled');
cy.getByTestId('reduce-only').should('be.disabled');
});
});
});
@@ -1,5 +1,5 @@
import * as Schema from '@vegaprotocol/types';
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { aliasGQLQuery, mockConnectWallet } from '@vegaprotocol/cypress';
import { testOrderSubmission } from '../support/order-validation';
import type { OrderSubmission } from '@vegaprotocol/wallet';
import {
@@ -13,6 +13,8 @@ const orderSizeField = 'order-size';
const orderPriceField = 'order-price';
const orderTIFDropDown = 'order-tif';
const placeOrderBtn = 'place-order';
const toggleShort = 'order-side-SIDE_SELL';
const toggleLong = 'order-side-SIDE_BUY';
const toggleLimit = 'order-type-TYPE_LIMIT';
const toggleMarket = 'order-type-TYPE_MARKET';
@@ -30,6 +32,34 @@ const displayTomorrow = () => {
return tomorrow.toISOString().substring(0, 16);
};
describe('time in force default values', () => {
before(() => {
cy.setVegaWallet();
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
});
it('must have market order set up to IOC by default', function () {
// 7002-SORD-031
cy.getByTestId(toggleMarket).click();
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
TIFlist.filter((item) => item.code === 'IOC')[0].text
);
});
it('must have time in force set to GTC for limit order', function () {
// 7002-SORD-031
cy.getByTestId(toggleLimit).click();
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
TIFlist.filter((item) => item.code === 'GTC')[0].text
);
});
});
describe('must submit order', { tags: '@smoke' }, () => {
// 7002-SORD-039
before(() => {
@@ -54,8 +84,6 @@ describe('must submit order', { tags: '@smoke' }, () => {
type: Schema.OrderType.TYPE_MARKET,
side: Schema.Side.SIDE_BUY,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
postOnly: false,
reduceOnly: false,
size: '100',
};
createOrder(order);
@@ -70,8 +98,6 @@ describe('must submit order', { tags: '@smoke' }, () => {
side: Schema.Side.SIDE_SELL,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
size: '100',
postOnly: false,
reduceOnly: false,
};
createOrder(order);
testOrderSubmission(order);
@@ -86,8 +112,6 @@ describe('must submit order', { tags: '@smoke' }, () => {
side: Schema.Side.SIDE_BUY,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
size: '100',
postOnly: false,
reduceOnly: false,
price: '200',
};
createOrder(order);
@@ -102,8 +126,6 @@ describe('must submit order', { tags: '@smoke' }, () => {
side: Schema.Side.SIDE_SELL,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GFN,
size: '100',
postOnly: false,
reduceOnly: false,
price: '50000',
};
createOrder(order);
@@ -121,8 +143,6 @@ describe('must submit order', { tags: '@smoke' }, () => {
size: '100',
price: '1.00',
expiresAt: expiresAt.toISOString().substring(0, 16),
postOnly: false,
reduceOnly: false,
};
createOrder(order);
@@ -130,8 +150,6 @@ describe('must submit order', { tags: '@smoke' }, () => {
price: '100000',
expiresAt:
new Date(order.expiresAt as string).getTime().toString() + '000000',
postOnly: false,
reduceOnly: false,
});
});
});
@@ -164,8 +182,6 @@ describe(
side: Schema.Side.SIDE_BUY,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
size: '100',
postOnly: false,
reduceOnly: false,
price: '200',
};
createOrder(order);
@@ -180,8 +196,6 @@ describe(
side: Schema.Side.SIDE_SELL,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
size: '100',
postOnly: false,
reduceOnly: false,
price: '50000',
};
createOrder(order);
@@ -196,16 +210,12 @@ describe(
side: Schema.Side.SIDE_SELL,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTT,
size: '100',
postOnly: false,
reduceOnly: false,
price: '1.00',
expiresAt: displayTomorrow(),
};
createOrder(order);
testOrderSubmission(order, {
price: '100000',
postOnly: false,
reduceOnly: false,
expiresAt:
new Date(order.expiresAt as string).getTime().toString() + '000000',
});
@@ -241,8 +251,6 @@ describe(
side: Schema.Side.SIDE_BUY,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
size: '100',
postOnly: false,
reduceOnly: false,
price: '200',
};
createOrder(order);
@@ -257,8 +265,6 @@ describe(
side: Schema.Side.SIDE_SELL,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
size: '100',
postOnly: false,
reduceOnly: false,
price: '50000',
};
createOrder(order);
@@ -275,8 +281,6 @@ describe(
size: '100',
price: '1.00',
expiresAt: displayTomorrow(),
postOnly: false,
reduceOnly: false,
};
createOrder(order);
testOrderSubmission(order, {
@@ -316,8 +320,6 @@ describe(
side: Schema.Side.SIDE_BUY,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
size: '100',
postOnly: false,
reduceOnly: false,
price: '200',
};
createOrder(order);
@@ -333,8 +335,6 @@ describe(
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
size: '100',
price: '50000',
postOnly: false,
reduceOnly: false,
};
createOrder(order);
testOrderSubmission(order, { price: '5000000000' });
@@ -350,8 +350,6 @@ describe(
size: '100',
price: '1.00',
expiresAt: displayTomorrow(),
postOnly: false,
reduceOnly: false,
};
createOrder(order);
testOrderSubmission(order, {
@@ -363,6 +361,227 @@ describe(
}
);
describe('deal ticket validation', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.mockTradingPage();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
});
it('must show place order button and connect wallet if wallet is not connected', () => {
// 0003-WTXN-001
cy.getByTestId('connect-vega-wallet'); // Not connected
cy.getByTestId('order-connect-wallet').should('exist');
cy.getByTestId(placeOrderBtn).should('exist');
cy.getByTestId('deal-ticket-connect-wallet').should('exist');
});
it('must be able to select order direction - long/short', function () {
// 7002-SORD-004
cy.getByTestId(toggleShort).click().children('input').should('be.checked');
cy.getByTestId(toggleLong).click().children('input').should('be.checked');
});
it('must be able to select order type - limit/market', function () {
// 7002-SORD-005
// 7002-SORD-006
// 7002-SORD-007
cy.getByTestId(toggleLimit).click().children('input').should('be.checked');
cy.getByTestId(toggleMarket).click().children('input').should('be.checked');
});
it('order connect vega wallet button should connect', () => {
mockConnectWallet();
cy.getByTestId(toggleLimit).click();
cy.getByTestId(orderPriceField).clear().type('101');
cy.getByTestId('order-connect-wallet').click();
cy.getByTestId('dialog-content').should('be.visible');
cy.getByTestId('connectors-list')
.find('[data-testid="connector-jsonRpc"]')
.click();
cy.wait('@walletReq');
cy.getByTestId(placeOrderBtn).should('be.visible');
cy.getByTestId(toggleLimit).children('input').should('be.checked');
cy.getByTestId(orderPriceField).should('have.value', '101');
});
});
describe('deal ticket size validation', { tags: '@smoke' }, function () {
beforeEach(() => {
cy.setVegaWallet();
cy.mockTradingPage();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
});
it('must warn if order size input has too many digits after the decimal place', function () {
// 7002-SORD-016
cy.getByTestId('order-type-TYPE_MARKET').click();
cy.getByTestId(orderSizeField).clear().type('1.234');
cy.getByTestId(placeOrderBtn).should('not.be.disabled');
cy.getByTestId(placeOrderBtn).click();
cy.getByTestId(placeOrderBtn).should('be.disabled');
cy.getByTestId('dealticket-error-message-size-market').should(
'have.text',
'Size must be whole numbers for this market'
);
});
it('must warn if order size is set to 0', function () {
cy.getByTestId('order-type-TYPE_MARKET').click();
cy.getByTestId(orderSizeField).clear().type('0');
cy.getByTestId(placeOrderBtn).should('not.be.disabled');
cy.getByTestId(placeOrderBtn).click();
cy.getByTestId(placeOrderBtn).should('be.disabled');
cy.getByTestId('dealticket-error-message-size-market').should(
'have.text',
'Size cannot be lower than 1'
);
});
});
describe('limit order validations', { tags: '@smoke' }, () => {
before(() => {
cy.setVegaWallet();
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
cy.getByTestId(toggleLimit).click();
});
beforeEach(() => {
cy.setVegaWallet();
});
it('must see the price unit', function () {
// 7002-SORD-018
cy.getByTestId(orderPriceField)
.siblings('label')
.should('have.text', 'Price (DAI)');
});
it('must see warning when placing an order with expiry date in past', () => {
const expiresAt = new Date(Date.now() - 24 * 60 * 60 * 1000);
const expiresAtInputValue = expiresAt.toISOString().substring(0, 16);
cy.getByTestId(toggleLimit).click();
cy.getByTestId(orderPriceField).clear().type('0.1');
cy.getByTestId(orderSizeField).clear().type('1');
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_GTT');
cy.log('choosing yesterday');
cy.getByTestId('date-picker-field').type(expiresAtInputValue);
cy.getByTestId(placeOrderBtn).click();
cy.getByTestId('dealticket-error-message-expiry').should(
'have.text',
'The expiry date that you have entered appears to be in the past'
);
});
it('must see warning if price has too many digits after decimal place', function () {
// 7002-SORD-059
cy.getByTestId(toggleLimit).click();
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_GTC');
cy.getByTestId(orderSizeField).clear().type('1');
cy.getByTestId(orderPriceField).clear().type('1.123456');
cy.getByTestId('dealticket-error-message-price-limit').should(
'have.text',
'Price accepts up to 5 decimal places'
);
});
describe('time in force validations', function () {
const validTIF = TIFlist;
validTIF.forEach((tif) => {
// 7002-SORD-023
// 7002-SORD-024
// 7002-SORD-025
// 7002-SORD-026
// 7002-SORD-027
// 7002-SORD-028
it(`must be able to select ${tif.code}`, function () {
cy.getByTestId(orderTIFDropDown).select(tif.value);
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
tif.text
);
});
});
it('selections should be remembered', () => {
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_GTT');
cy.getByTestId(toggleMarket).click();
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
TIFlist.filter((item) => item.code === 'IOC')[0].text
);
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_FOK');
cy.getByTestId(toggleLimit).click();
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
TIFlist.filter((item) => item.code === 'GTT')[0].text
);
cy.getByTestId(toggleMarket).click();
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
TIFlist.filter((item) => item.code === 'FOK')[0].text
);
});
});
});
describe('market order validations', { tags: '@smoke' }, () => {
before(() => {
cy.setVegaWallet();
cy.mockTradingPage();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
cy.getByTestId(toggleMarket).click();
});
beforeEach(() => {
cy.setVegaWallet();
});
it('must not see the price unit', function () {
// 7002-SORD-019
cy.getByTestId(orderPriceField).should('not.exist');
});
describe('time in force validations', function () {
const validTIF = TIFlist.filter((tif) => ['FOK', 'IOC'].includes(tif.code));
const invalidTIF = TIFlist.filter(
(tif) => !['FOK', 'IOC'].includes(tif.code)
);
validTIF.forEach((tif) => {
// 7002-SORD-025
// 7002-SORD-026
it(`must be able to select ${tif.code}`, function () {
cy.getByTestId(orderTIFDropDown).select(tif.value);
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
'have.text',
tif.text
);
});
});
invalidTIF.forEach((tif) => {
// 7002-SORD-023
// 7002-SORD-024
// 7002-SORD-027
// 7002-SORD-028
it(`must not be able to select ${tif.code}`, function () {
cy.getByTestId(orderTIFDropDown).should('not.contain', tif.text);
});
});
});
});
describe('suspended market validation', { tags: '@regression' }, () => {
before(() => {
cy.setVegaWallet();
@@ -514,8 +733,6 @@ describe('account validation', { tags: '@regression' }, () => {
it('must show error returned by wallet ', () => {
// 0003-WTXN-009
// 0003-WTXN-011
// 0002-WCON-016
// 0003-WTXN-008
//trigger error from the wallet
cy.intercept('POST', 'http://localhost:1789/api/v2/requests', (req) => {
@@ -539,8 +756,6 @@ describe('account validation', { tags: '@regression' }, () => {
'contain.text',
'The connection to your Vega Wallet has been lost.'
);
cy.getByTestId('connect-vega-wallet').click();
cy.getByTestId('dialog-content').should('be.visible');
});
it('must see that the order was rejected by the connected wallet', () => {
@@ -117,7 +117,6 @@ describe('connect vega wallet', { tags: '@smoke' }, () => {
// 0002-WCON-002
// 0002-WCON-005
// 0002-WCON-007
// 0002-WCON-009
mockConnectWallet();
cy.getByTestId(connectVegaBtn).click();
@@ -125,10 +124,6 @@ describe('connect vega wallet', { tags: '@smoke' }, () => {
.find('[data-testid="connector-jsonRpc"]')
.click();
cy.wait('@walletReq');
cy.getByTestId(dialogContent).should(
'contain.text',
'Approve the connection from your Vega wallet app.'
);
cy.getByTestId(dialogContent).should('not.exist');
cy.getByTestId(manageVegaBtn).should('exist');
});
@@ -137,7 +132,6 @@ describe('connect vega wallet', { tags: '@smoke' }, () => {
// 0002-WCON-002
// 0002-WCON-005
// 0002-WCON-007
// 0002-WCON-015
mockConnectWalletWithUserError();
cy.getByTestId(connectVegaBtn).click();
+163 -52
View File
@@ -1,5 +1,5 @@
import {
matchFilter,
getId,
liquidityProvisionsDataProvider,
LiquidityTable,
lpAggregatedDataProvider,
@@ -8,7 +8,6 @@ import {
import { tooltipMapping } from '@vegaprotocol/market-info';
import {
addDecimalsFormatNumber,
createDocsLinks,
formatNumberPercentage,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
@@ -18,28 +17,28 @@ import {
useNetworkParams,
updateGridData,
} from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types';
import {
AsyncRenderer,
Tab,
Tabs,
Link as UiToolkitLink,
Indicator,
ExternalLink,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { memo, useCallback, useEffect, useRef, useState } from 'react';
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Header, HeaderStat, HeaderTitle } from '../../components/header';
import type { AgGridReact } from 'ag-grid-react';
import type { IGetRowsParams } from 'ag-grid-community';
import type { LiquidityProvisionData, Filter } from '@vegaprotocol/liquidity';
import type { LiquidityProvisionData } from '@vegaprotocol/liquidity';
import { Link, useParams } from 'react-router-dom';
import { Links, Routes } from '../../pages/client-router';
import { useMarket, useStaticMarketData } from '@vegaprotocol/market-list';
import { useEnvironment } from '@vegaprotocol/environment';
import isEqual from 'lodash/isEqual';
const enum LiquidityTabs {
Active = 'active',
@@ -68,10 +67,8 @@ const useReloadLiquidityData = (marketId: string | undefined) => {
export const LiquidityContainer = ({
marketId,
filter,
}: {
marketId: string | undefined;
filter?: Filter;
}) => {
const gridRef = useRef<AgGridReact | null>(null);
const { data: market } = useMarket(marketId);
@@ -90,7 +87,7 @@ export const LiquidityContainer = ({
const { data, loading, error } = useDataProvider({
dataProvider: lpAggregatedDataProvider,
update,
variables: { marketId: marketId || '', filter },
variables: { marketId: marketId || '' },
skip: !marketId,
});
@@ -147,7 +144,6 @@ const LiquidityViewHeader = memo(({ marketId }: { marketId?: string }) => {
market?.tradableInstrument.instrument.product.settlementAsset.decimals || 0;
const symbol =
market?.tradableInstrument.instrument.product.settlementAsset.symbol;
const { VEGA_DOCS_URL } = useEnvironment();
const { params } = useNetworkParams([
NetworkParams.market_liquidity_stakeToCcyVolume,
@@ -215,68 +211,183 @@ const LiquidityViewHeader = memo(({ marketId }: { marketId?: string }) => {
<HeaderStat heading={t('Market ID')}>
<div className="break-word">{marketId}</div>
</HeaderStat>
<HeaderStat heading={t('Learn more')}>
{VEGA_DOCS_URL && (
<ExternalLink href={createDocsLinks(VEGA_DOCS_URL).LIQUIDITY}>
{t('Providing liquidity')}
</ExternalLink>
)}
</HeaderStat>
</Header>
);
});
LiquidityViewHeader.displayName = 'LiquidityViewHeader';
const filterLiquidities = (
tab: string,
liquidities?: LiquidityProvisionData[] | null,
pubKey?: string | null
) => {
switch (tab) {
case LiquidityTabs.MyLiquidityProvision:
return pubKey
? (liquidities || []).filter((e) => e.party.id === pubKey)
: [];
break;
case LiquidityTabs.Active:
return (liquidities || []).filter(
(e) => e.status === Schema.LiquidityProvisionStatus.STATUS_ACTIVE
);
case LiquidityTabs.Inactive:
return (liquidities || []).filter(
(e) => e.status !== Schema.LiquidityProvisionStatus.STATUS_ACTIVE
);
default:
return [];
}
};
export const LiquidityViewContainer = ({
marketId,
}: {
marketId: string | undefined;
}) => {
const [tab, setTab] = useState<string | undefined>(undefined);
const [tab, setTab] = useState('');
const { pubKey } = useVegaWallet();
const [liquidityProviders, setLiquidityProviders] = useState<
LiquidityProvisionData[] | null
>();
const gridRef = useRef<AgGridReact | null>(null);
const { data: market } = useMarket(marketId);
const dataRef = useRef<LiquidityProvisionData[] | null>(null);
const { data } = useDataProvider({
// To be removed when liquidityProvision subscriptions are working
useReloadLiquidityData(marketId);
const update = useCallback(
({ data }: { data: LiquidityProvisionData[] | null }) => {
if (!dataRef.current) {
setLiquidityProviders(data);
dataRef.current = data;
}
if (!gridRef.current?.api) {
return false;
}
const updateRows: LiquidityProvisionData[] = [];
const addRows: LiquidityProvisionData[] = [];
if (gridRef.current?.api?.getModel().getType() === 'infinite') {
dataRef.current = data;
gridRef.current.api.refreshInfiniteCache();
} else {
const filteredData = filterLiquidities(tab, data, pubKey as string);
filteredData?.forEach((d) => {
const rowNode = gridRef.current?.api?.getRowNode(getId(d));
if (rowNode) {
if (!isEqual(rowNode.data, d)) {
updateRows.push(d);
}
} else {
addRows.push(d);
}
});
gridRef.current?.api?.applyTransaction({
update: updateRows,
add: addRows,
addIndex: 0,
});
}
return true;
},
[gridRef, tab, pubKey]
);
const { loading, error } = useDataProvider({
dataProvider: lpAggregatedDataProvider,
skipUpdates: true,
update,
variables: { marketId: marketId || '' },
skip: !marketId,
});
const assetDecimalPlaces =
market?.tradableInstrument.instrument.product.settlementAsset.decimals || 0;
const symbol =
market?.tradableInstrument.instrument.product.settlementAsset.symbol;
const { params } = useNetworkParams([
NetworkParams.market_liquidity_stakeToCcyVolume,
NetworkParams.market_liquidity_targetstake_triggering_ratio,
]);
const stakeToCcyVolume = params.market_liquidity_stakeToCcyVolume;
const myLpEdges = useMemo(
() =>
filterLiquidities(
LiquidityTabs.MyLiquidityProvision,
liquidityProviders,
pubKey
),
[liquidityProviders, pubKey]
);
const activeEdges = useMemo(
() => filterLiquidities(LiquidityTabs.Active, liquidityProviders),
[liquidityProviders]
);
const inactiveEdges = useMemo(
() => filterLiquidities(LiquidityTabs.Inactive, liquidityProviders),
[liquidityProviders]
);
useEffect(() => {
if (data) {
if (pubKey && data.some((lp) => matchFilter({ partyId: pubKey }, lp))) {
setTab(LiquidityTabs.MyLiquidityProvision);
return;
}
if (data.some((lp) => matchFilter({ active: true }, lp))) {
setTab(LiquidityTabs.Active);
return;
}
setTab(LiquidityTabs.Inactive);
if (tab) {
return;
}
}, [data, pubKey]);
let initialTab = LiquidityTabs.Active;
if (myLpEdges.length > 0) {
initialTab = LiquidityTabs.MyLiquidityProvision;
}
if (activeEdges?.length) {
initialTab = LiquidityTabs.Active;
} else if (inactiveEdges.length > 0) {
initialTab = LiquidityTabs.Inactive;
}
setTab(initialTab);
}, [tab, myLpEdges?.length, activeEdges?.length, inactiveEdges?.length]);
return (
<div className="h-full grid grid-rows-[min-content_1fr]">
<LiquidityViewHeader marketId={marketId} />
<Tabs value={tab || LiquidityTabs.Active} onValueChange={setTab}>
<Tab
id={LiquidityTabs.MyLiquidityProvision}
name={t('My liquidity provision')}
hidden={!pubKey}
>
<LiquidityContainer
marketId={marketId}
filter={{ partyId: pubKey || undefined }}
/>
</Tab>
<Tab id={LiquidityTabs.Active} name={t('Active')}>
<LiquidityContainer marketId={marketId} filter={{ active: true }} />
</Tab>
<Tab id={LiquidityTabs.Inactive} name={t('Inactive')}>
<LiquidityContainer marketId={marketId} filter={{ active: false }} />
</Tab>
</Tabs>
</div>
<AsyncRenderer loading={loading} error={error} data={liquidityProviders}>
<div className="h-full grid grid-rows-[min-content_1fr]">
<LiquidityViewHeader marketId={marketId} />
<Tabs value={tab} onValueChange={setTab}>
<Tab
id={LiquidityTabs.MyLiquidityProvision}
name={t('My liquidity provision')}
hidden={!pubKey}
>
{myLpEdges && (
<LiquidityTable
ref={gridRef}
rowData={myLpEdges}
symbol={symbol}
stakeToCcyVolume={stakeToCcyVolume}
assetDecimalPlaces={assetDecimalPlaces}
/>
)}
</Tab>
<Tab id={LiquidityTabs.Active} name={t('Active')}>
{activeEdges && (
<LiquidityTable
ref={gridRef}
rowData={activeEdges}
symbol={symbol}
assetDecimalPlaces={assetDecimalPlaces}
stakeToCcyVolume={stakeToCcyVolume}
/>
)}
</Tab>
<Tab id={LiquidityTabs.Inactive} name={t('Inactive')}>
{inactiveEdges && (
<LiquidityTable
ref={gridRef}
rowData={inactiveEdges}
symbol={symbol}
assetDecimalPlaces={assetDecimalPlaces}
stakeToCcyVolume={stakeToCcyVolume}
/>
)}
</Tab>
</Tabs>
</div>
</AsyncRenderer>
);
};
@@ -301,7 +301,7 @@ export const AccountHistoryChart = ({
asset: AssetFieldsFragment;
}) => {
const { theme } = useThemeSwitcher();
const values: { cols: [string, string]; rows: [Date, number][] } | null =
const values: { cols: string[]; rows: [Date, ...number[]][] } | null =
useMemo(() => {
if (!data?.balanceChanges.edges.length) {
return null;
@@ -8,7 +8,6 @@ import type { MarketData } from '@vegaprotocol/market-list';
import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list';
import { HeaderStat } from '../header';
import {
ExternalLink,
Indicator,
KeyValueTable,
KeyValueTableRow,
@@ -19,11 +18,9 @@ import { useCheckLiquidityStatus } from '@vegaprotocol/liquidity';
import { AuctionTrigger, MarketTradingMode } from '@vegaprotocol/types';
import {
addDecimalsFormatNumber,
createDocsLinks,
formatNumberPercentage,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useEnvironment } from '@vegaprotocol/environment';
interface Props {
marketId?: string;
@@ -47,8 +44,6 @@ export const MarketLiquiditySupplied = ({
params.market_liquidity_targetstake_triggering_ratio
);
const { VEGA_DOCS_URL } = useEnvironment();
const variables = useMemo(
() => ({
marketId: marketId || '',
@@ -131,14 +126,6 @@ export const MarketLiquiditySupplied = ({
<Link href={`/#/liquidity/${marketId}`} data-testid="view-liquidity-link">
{t('View liquidity provision table')}
</Link>
{VEGA_DOCS_URL && (
<ExternalLink
href={createDocsLinks(VEGA_DOCS_URL).LIQUIDITY}
className="mt-2"
>
{t('Learn about providing liquidity')}
</ExternalLink>
)}
{showMessage && (
<p className="mt-4">
{t(
+1 -5
View File
@@ -23,7 +23,6 @@ import {
} from '@vegaprotocol/ui-toolkit';
import { Links, Routes } from '../../pages/client-router';
import { createDocsLinks } from '@vegaprotocol/utils';
export const Navbar = ({
theme = 'system',
@@ -38,7 +37,6 @@ export const Navbar = ({
const tradingPath = marketId
? Links[Routes.MARKET](marketId)
: Links[Routes.MARKET]();
return (
<Navigation
appName="Console"
@@ -91,9 +89,7 @@ export const Navbar = ({
<NavigationContent>
<NavigationList>
<NavigationItem>
<NavExternalLink
href={createDocsLinks(VEGA_DOCS_URL).NEW_TO_VEGA}
>
<NavExternalLink href={VEGA_DOCS_URL}>
{t('Docs')}
</NavExternalLink>
</NavigationItem>
@@ -39,14 +39,10 @@ import { t } from '@vegaprotocol/i18n';
import { useAssetsDataProvider } from '@vegaprotocol/assets';
import { useEthWithdrawApprovalsStore } from '@vegaprotocol/web3';
import { DApp, EXPLORER_TX, useLinks } from '@vegaprotocol/environment';
import {
getOrderToastIntent,
getOrderToastTitle,
getRejectionReason,
useOrderByIdQuery,
} from '@vegaprotocol/orders';
import { getRejectionReason, useOrderByIdQuery } from '@vegaprotocol/orders';
import { useMarketList } from '@vegaprotocol/market-list';
import type { Side } from '@vegaprotocol/types';
import { OrderStatus } from '@vegaprotocol/types';
import { OrderStatusMapping } from '@vegaprotocol/types';
import { Size } from '@vegaprotocol/react-helpers';
@@ -478,18 +474,15 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
}
if (tx.order && tx.order.rejectionReason) {
const rejectionReason =
getRejectionReason(tx.order) || tx.order.rejectionReason || '';
return (
<>
<ToastHeading>{getOrderToastTitle(tx.order.status)}</ToastHeading>
{rejectionReason ? (
<p>
{t('Your order has been rejected because: %s', [rejectionReason])}
</p>
) : (
<p>{t('Your order has been rejected.')}</p>
)}
<ToastHeading>{t('Order rejected')}</ToastHeading>
<p>
{t(
'Your order has been rejected because: %s',
getRejectionReason(tx.order) || ''
)}
</p>
{tx.txHash && (
<p className="break-all">
<ExternalLink
@@ -508,7 +501,7 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
if (isOrderSubmissionTransaction(tx.body) && tx.order?.rejectionReason) {
return (
<div>
<h3 className="font-bold">{getOrderToastTitle(tx.order.status)}</h3>
<h3 className="font-bold">{t('Order rejected')}</h3>
<p>{t('Your order was rejected.')}</p>
{tx.txHash && (
<p className="break-all">
@@ -582,10 +575,11 @@ const VegaTxErrorToastContent = ({ tx }: VegaTxToastContentProps) => {
tx.error instanceof WalletError &&
walletNoConnectionCodes.includes(tx.error.code);
if (orderRejection) {
label = getOrderToastTitle(tx.order?.status) || t('Order rejected');
errorMessage = t('Your order has been rejected because: %s', [
orderRejection || tx.order?.rejectionReason || ' ',
]);
label = t('Order rejected');
errorMessage = t(
'Your order has been rejected because: %s',
orderRejection
);
}
if (walletError) {
label = t('Wallet disconnected');
@@ -651,8 +645,9 @@ export const useVegaTransactionToasts = () => {
// Transaction can be successful but the order can be rejected by the network
const intent =
(tx.order && getOrderToastIntent(tx.order.status)) ||
intentMap[tx.status];
tx.order && [OrderStatus.STATUS_REJECTED].includes(tx.order.status)
? Intent.Danger
: intentMap[tx.status];
return {
id: `vega-${tx.id}`,
-15
View File
@@ -34,18 +34,6 @@ html [data-theme='dark'] {
/* sell candles only use stroke as the candle is solid (without border) */
--pennant-color-sell-stroke: theme('colors.vega.pink.500');
/* studies */
--pennant-color-eldar-ray-bear-power: theme('colors.vega.pink.500');
--pennant-color-eldar-ray-bull-power: theme('colors.vega.green.650');
--pennant-color-macd-divergence-buy: theme('colors.vega.green.650');
--pennant-color-macd-divergence-sell: theme('colors.vega.pink.500');
--pennant-color-macd-signal: theme('colors.vega.blue.500');
--pennant-color-macd-macd: theme('colors.vega.yellow.500');
--pennant-color-volume-buy: theme('colors.vega.green.650');
--pennant-color-volume-sell: theme('colors.vega.pink.500');
/* depth chart */
--pennant-color-depth-buy-fill: theme('colors.vega.green.650');
--pennant-color-depth-buy-stroke: theme('colors.vega.green.500');
@@ -62,9 +50,6 @@ html [data-theme='light'] {
/* sell candles only use stroke as the candle is solid (without border) */
--pennant-color-sell-stroke: theme('colors.vega.pink.400');
--pennant-color-volume-buy: theme('colors.vega.green.400');
--pennant-color-volume-sell: theme('colors.vega.pink.500');
/* depth chart */
--pennant-color-depth-buy-fill: theme('colors.vega.green.400');
--pennant-color-depth-buy-stroke: theme('colors.vega.green.550');
+3 -15
View File
@@ -1,22 +1,10 @@
#!/bin/bash -ex
#!/bin/sh -eux
export PATH="/app/node_modules/.bin:$PATH"
flags="--network-timeout 100000 --pure-lockfile"
if [[ ! -z "${ENV_NAME}" ]]; then
flags="--env=${ENV_NAME} $flags"
fi
if [ "${APP}" = "trading" ]; then
yarn nx export ${APP} $flags
yarn nx export ${APP} --network-timeout 100000 --pure-lockfile
mv /app/dist/apps/trading/exported/ /app/tmp
rm -rf /app/dist/apps/trading
mv /app/tmp /app/dist/apps/trading
else
yarn nx build ${APP} $flags
yarn nx build ${APP} --network-timeout 100000 --pure-lockfile
fi
env_vars_file="/app/dist/apps/${APP}/.env"
# make sure there are no exposed .env files
rm $env_vars_file || echo "No env vars file"
Executable
+43
View File
@@ -0,0 +1,43 @@
#!/bin/bash
set -e
# Recreate config file
env_file=/usr/share/nginx/html/assets/env-config.js
mkdir -p $(dirname $env_file)
rm -rf $env_file || echo "no file to delete"
touch $env_file
env_vars_file=/usr/share/nginx/html/.env
sed -i '/^#/d' $env_vars_file # remove comment lines
sed -i '/^$/d' $env_vars_file # remove empty lines
# Add assignment
echo "window._env_ = {" >> $env_file
# Read each line in .env file
# Each line represents key=value pairs
while read -r line || [[ -n "$line" ]];
do
# Split env variables by character `=`
if printf '%s\n' "$line" | grep -q -e '='; then
varname=$(printf '%s\n' "$line" | sed -e 's/=.*//')
varvalue=$(printf '%s\n' "$line" | sed -e 's/^[^=]*=//')
fi
# Read value of current variable if exists as Environment variable
value=$(printf '%s\n' "${!varname}")
# Otherwise use value from .env file
[[ -z $value ]] && value=${varvalue}
# Append configuration property to JS file if non-empty
if [ ! -z "$varname" ]; then
echo " $varname: \"$value\"," >> $env_file
fi
done < $env_vars_file
rm $env_vars_file
echo "}" >> $env_file
# start serving
nginx -g 'daemon off;'
+72 -221
View File
@@ -1,10 +1,4 @@
import {
act,
fireEvent,
render,
screen,
waitFor,
} from '@testing-library/react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import BigNumber from 'bignumber.js';
import { AddressField, TransferFee, TransferForm } from './transfer-form';
import { AccountType } from '@vegaprotocol/types';
@@ -33,34 +27,6 @@ describe('TransferForm', () => {
submitTransfer: jest.fn(),
};
it('validates a manually entered address', async () => {
render(<TransferForm {...props} />);
submit();
expect(await screen.findAllByText('Required')).toHaveLength(3);
const toggle = screen.getByText('Enter manually');
fireEvent.click(toggle);
// has switched to input
expect(toggle).toHaveTextContent('Select from wallet');
expect(screen.getByLabelText('Vega key')).toHaveAttribute('type', 'text');
fireEvent.change(screen.getByLabelText('Vega key'), {
target: { value: 'invalid-address' },
});
await waitFor(() => {
const errors = screen.getAllByTestId('input-error-text');
expect(errors[0]).toHaveTextContent('Invalid Vega key');
});
// same pubkey
fireEvent.change(screen.getByLabelText('Vega key'), {
target: { value: pubKey },
});
await waitFor(() => {
const errors = screen.getAllByTestId('input-error-text');
expect(errors[0]).toHaveTextContent('Vega key is the same');
});
});
it('validates fields and submits', async () => {
render(<TransferForm {...props} />);
@@ -96,17 +62,15 @@ describe('TransferForm', () => {
formatNumber(asset.balance, asset.decimals)
);
const amountInput = screen.getByLabelText('Amount');
// Test amount validation
fireEvent.change(amountInput, {
fireEvent.change(screen.getByLabelText('Amount'), {
target: { value: '0.00000001' },
});
expect(
await screen.findByText('Value is below minimum')
).toBeInTheDocument();
fireEvent.change(amountInput, {
fireEvent.change(screen.getByLabelText('Amount'), {
target: { value: '9999999' },
});
expect(
@@ -114,7 +78,7 @@ describe('TransferForm', () => {
).toBeInTheDocument();
// set valid amount
fireEvent.change(amountInput, {
fireEvent.change(screen.getByLabelText('Amount'), {
target: { value: amount },
});
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(
@@ -136,191 +100,78 @@ describe('TransferForm', () => {
});
});
describe('IncludeFeesCheckbox', () => {
it('validates fields and submits when checkbox is checked', async () => {
render(<TransferForm {...props} />);
// check current pubkey not shown
const keySelect: HTMLSelectElement = screen.getByLabelText('Vega key');
expect(keySelect.children).toHaveLength(2);
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual([
'',
props.pubKeys[1],
]);
submit();
expect(await screen.findAllByText('Required')).toHaveLength(3);
// Select a pubkey
fireEvent.change(screen.getByLabelText('Vega key'), {
target: { value: props.pubKeys[1] },
});
// Select asset
fireEvent.change(
// Bypass RichSelect and target hidden native select
// eslint-disable-next-line
document.querySelector('select[name="asset"]')!,
{ target: { value: asset.id } }
);
// assert rich select as updated
expect(await screen.findByTestId('select-asset')).toHaveTextContent(
asset.name
);
expect(screen.getByTestId('asset-balance')).toHaveTextContent(
formatNumber(asset.balance, asset.decimals)
);
const amountInput = screen.getByLabelText('Amount');
const checkbox = screen.getByTestId('include-transfer-fee');
expect(checkbox).not.toBeChecked();
act(() => {
/* fire events that update state */
// set valid amount
fireEvent.change(amountInput, {
target: { value: amount },
});
// check include fees checkbox
fireEvent.click(checkbox);
});
expect(checkbox).toBeChecked();
const expectedFee = new BigNumber(amount)
.times(props.feeFactor)
.toFixed();
const expectedAmount = new BigNumber(amount).minus(expectedFee).toFixed();
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(
expectedAmount
);
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(
amount
);
submit();
await waitFor(() => {
expect(props.submitTransfer).toHaveBeenCalledTimes(1);
expect(props.submitTransfer).toHaveBeenCalledWith({
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
to: props.pubKeys[1],
asset: asset.id,
amount: removeDecimal(amount, asset.decimals),
oneOff: {},
});
});
it('validates a manually entered address', async () => {
render(<TransferForm {...props} />);
submit();
expect(await screen.findAllByText('Required')).toHaveLength(3);
const toggle = screen.getByText('Enter manually');
fireEvent.click(toggle);
// has switched to input
expect(toggle).toHaveTextContent('Select from wallet');
expect(screen.getByLabelText('Vega key')).toHaveAttribute('type', 'text');
fireEvent.change(screen.getByLabelText('Vega key'), {
target: { value: 'invalid-address' },
});
await waitFor(() => {
const errors = screen.getAllByTestId('input-error-text');
expect(errors[0]).toHaveTextContent('Invalid Vega key');
});
it('validates fields when checkbox is not checked', async () => {
render(<TransferForm {...props} />);
// check current pubkey not shown
const keySelect: HTMLSelectElement = screen.getByLabelText('Vega key');
expect(keySelect.children).toHaveLength(2);
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual([
'',
props.pubKeys[1],
]);
submit();
expect(await screen.findAllByText('Required')).toHaveLength(3);
// Select a pubkey
fireEvent.change(screen.getByLabelText('Vega key'), {
target: { value: props.pubKeys[1] },
});
// Select asset
fireEvent.change(
// Bypass RichSelect and target hidden native select
// eslint-disable-next-line
document.querySelector('select[name="asset"]')!,
{ target: { value: asset.id } }
);
// assert rich select as updated
expect(await screen.findByTestId('select-asset')).toHaveTextContent(
asset.name
);
expect(screen.getByTestId('asset-balance')).toHaveTextContent(
formatNumber(asset.balance, asset.decimals)
);
const amountInput = screen.getByLabelText('Amount');
const checkbox = screen.getByTestId('include-transfer-fee');
expect(checkbox).not.toBeChecked();
act(() => {
/* fire events that update state */
// set valid amount
fireEvent.change(amountInput, {
target: { value: amount },
});
});
expect(checkbox).not.toBeChecked();
const expectedFee = new BigNumber(amount)
.times(props.feeFactor)
.toFixed();
const total = new BigNumber(amount).plus(expectedFee).toFixed();
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(amount);
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(total);
});
});
describe('AddressField', () => {
const props = {
pubKeys: ['pubkey-1', 'pubkey-2'],
select: <div>select</div>,
input: <div>input</div>,
onChange: jest.fn(),
};
it('toggles content and calls onChange', async () => {
const mockOnChange = jest.fn();
render(<AddressField {...props} onChange={mockOnChange} />);
// select should be shown as multiple pubkeys provided
expect(screen.getByText('select')).toBeInTheDocument();
expect(screen.queryByText('input')).not.toBeInTheDocument();
fireEvent.click(screen.getByText('Enter manually'));
expect(screen.queryByText('select')).not.toBeInTheDocument();
expect(screen.getByText('input')).toBeInTheDocument();
expect(mockOnChange).toHaveBeenCalledTimes(1);
fireEvent.click(screen.getByText('Select from wallet'));
expect(screen.getByText('select')).toBeInTheDocument();
expect(screen.queryByText('input')).not.toBeInTheDocument();
expect(mockOnChange).toHaveBeenCalledTimes(2);
// same pubkey
fireEvent.change(screen.getByLabelText('Vega key'), {
target: { value: pubKey },
});
it('Does not provide select option if there is only a single key', () => {
render(<AddressField {...props} pubKeys={['single-pubKey']} />);
expect(screen.getByText('input')).toBeInTheDocument();
expect(screen.queryByText('Select from wallet')).not.toBeInTheDocument();
});
});
describe('TransferFee', () => {
const props = {
amount: '200',
feeFactor: '0.001',
fee: '0.2',
transferAmount: '200',
decimals: 8,
};
it('calculates and renders the transfer fee', () => {
render(<TransferFee {...props} />);
const expected = new BigNumber(props.amount)
.times(props.feeFactor)
.toFixed();
const total = new BigNumber(props.amount).plus(expected).toFixed();
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expected);
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(
props.amount
);
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(total);
await waitFor(() => {
const errors = screen.getAllByTestId('input-error-text');
expect(errors[0]).toHaveTextContent('Vega key is the same');
});
});
});
describe('AddressField', () => {
const props = {
pubKeys: ['pubkey-1', 'pubkey-2'],
select: <div>select</div>,
input: <div>input</div>,
onChange: jest.fn(),
};
it('toggles content and calls onChange', async () => {
const mockOnChange = jest.fn();
render(<AddressField {...props} onChange={mockOnChange} />);
// select should be shown as multiple pubkeys provided
expect(screen.getByText('select')).toBeInTheDocument();
expect(screen.queryByText('input')).not.toBeInTheDocument();
fireEvent.click(screen.getByText('Enter manually'));
expect(screen.queryByText('select')).not.toBeInTheDocument();
expect(screen.getByText('input')).toBeInTheDocument();
expect(mockOnChange).toHaveBeenCalledTimes(1);
fireEvent.click(screen.getByText('Select from wallet'));
expect(screen.getByText('select')).toBeInTheDocument();
expect(screen.queryByText('input')).not.toBeInTheDocument();
expect(mockOnChange).toHaveBeenCalledTimes(2);
});
it('Does not provide select option if there is only a single key', () => {
render(<AddressField {...props} pubKeys={['single-pubKey']} />);
expect(screen.getByText('input')).toBeInTheDocument();
expect(screen.queryByText('Select from wallet')).not.toBeInTheDocument();
});
});
describe('TransferFee', () => {
const props = {
amount: '200',
feeFactor: '0.001',
};
it('calculates and renders the transfer fee', () => {
render(<TransferFee {...props} />);
const expected = new BigNumber(props.amount)
.times(props.feeFactor)
.toFixed();
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expected);
});
});
+16 -102
View File
@@ -16,7 +16,6 @@ import {
RichSelect,
Select,
Tooltip,
Checkbox,
} from '@vegaprotocol/ui-toolkit';
import type { Transfer } from '@vegaprotocol/wallet';
import { normalizeTransfer } from '@vegaprotocol/wallet';
@@ -64,26 +63,6 @@ export const TransferForm = ({
const amount = watch('amount');
const assetId = watch('asset');
const [includeFee, setIncludeFee] = useState(false);
const transferAmount = useMemo(() => {
if (!amount) return undefined;
if (includeFee && feeFactor) {
return new BigNumber(1).minus(feeFactor).times(amount).toString();
}
return amount;
}, [amount, includeFee, feeFactor]);
const fee = useMemo(() => {
if (!transferAmount) return undefined;
if (includeFee) {
return new BigNumber(amount).minus(transferAmount).toString();
}
return (
feeFactor && new BigNumber(feeFactor).times(transferAmount).toString()
);
}, [amount, includeFee, transferAmount, feeFactor]);
const asset = useMemo(() => {
return assets.find((a) => a.id === assetId);
}, [assets, assetId]);
@@ -93,16 +72,13 @@ export const TransferForm = ({
if (!asset) {
throw new Error('Submitted transfer with no asset selected');
}
if (!transferAmount) {
throw new Error('Submitted transfer with no amount selected');
}
const transfer = normalizeTransfer(fields.toAddress, transferAmount, {
const transfer = normalizeTransfer(fields.toAddress, fields.amount, {
id: asset.id,
decimals: asset.decimals,
});
submitTransfer(transfer);
},
[asset, submitTransfer, transferAmount]
[asset, submitTransfer]
);
const min = useMemo(() => {
@@ -237,32 +213,7 @@ export const TransferForm = ({
<InputError forInput="amount">{errors.amount.message}</InputError>
)}
</FormGroup>
<div className="mb-4">
<Checkbox
name="include-transfer-fee"
disabled={!transferAmount}
label={
<Tooltip
description={t(
`The fee will be taken from the amount you are transferring.`
)}
>
<div>{t('Include transfer fee')}</div>
</Tooltip>
}
checked={includeFee}
onCheckedChange={() => setIncludeFee(!includeFee)}
/>
</div>
{transferAmount && fee && (
<TransferFee
amount={transferAmount}
transferAmount={transferAmount}
feeFactor={feeFactor}
fee={fee}
decimals={asset?.decimals}
/>
)}
<TransferFee amount={amount} feeFactor={feeFactor} />
<Button type="submit" variant="primary" fill={true}>
{t('Confirm transfer')}
</Button>
@@ -272,71 +223,34 @@ export const TransferForm = ({
export const TransferFee = ({
amount,
transferAmount,
feeFactor,
fee,
decimals,
}: {
amount: string;
transferAmount: string;
feeFactor: string | null;
fee?: string;
decimals?: number;
}) => {
if (!feeFactor || !amount || !transferAmount || !fee) return null;
if (!feeFactor || !amount) return null;
const totalValue = new BigNumber(transferAmount).plus(fee).toString();
// using toFixed without an argument will always return a
// number in normal notation without rounding, formatting functions
// arent working in a way which won't round the decimal places
const value = new BigNumber(amount).times(feeFactor).toFixed();
return (
<div className="mb-4 flex flex-col gap-2 text-xs">
<div className="flex justify-between gap-1 items-center flex-wrap">
<div className="mb-4 flex justify-between items-center gap-4 flex-wrap">
<div>
<Tooltip
description={t(
`The transfer fee is set by the network parameter transfer.fee.factor, currently set to %s`,
[feeFactor]
`The transfer fee is set by the network parameter transfer.fee.factor, currently set to ${feeFactor}`
)}
>
<div>{t('Transfer fee')}</div>
</Tooltip>
<div
data-testid="transfer-fee"
className="text-neutral-500 dark:text-neutral-300"
>
{formatNumber(fee, decimals)}
</div>
</div>
<div className="flex justify-between gap-1 items-center flex-wrap">
<Tooltip
description={t(
`The total amount to be transferred (without the fee)`
)}
>
<div>{t('Amount to be transferred')}</div>
</Tooltip>
<div
data-testid="transfer-amount"
className="text-neutral-500 dark:text-neutral-300"
>
{formatNumber(amount, decimals)}
</div>
</div>
<div className="flex justify-between gap-1 items-center flex-wrap">
<Tooltip
description={t(
`The total amount taken from your account. The amount to be transferred plus the fee.`
)}
>
<div>{t('Total amount (with fee)')}</div>
</Tooltip>
<div
data-testid="total-transfer-fee"
className="text-neutral-500 dark:text-neutral-300"
>
{formatNumber(totalValue, decimals)}
</div>
<div
data-testid="transfer-fee"
className="text-neutral-500 dark:text-neutral-300"
>
{value}
</div>
</div>
);
-5
View File
@@ -1,6 +1 @@
import '@testing-library/jest-dom';
import ResizeObserver from 'resize-observer-polyfill';
import { defaultFallbackInView } from 'react-intersection-observer';
defaultFallbackInView(true);
global.ResizeObserver = ResizeObserver;
+2 -2
View File
@@ -1,6 +1,6 @@
import 'pennant/dist/style.css';
import {
CandlestickChart,
Chart,
ChartType,
Interval,
Overlay,
@@ -234,7 +234,7 @@ export const CandlesChartContainer = ({
</DropdownMenu>
</div>
<div className="flex-1">
<CandlestickChart
<Chart
dataSource={dataSource}
options={{
chartType: chartType,
-3
View File
@@ -52,9 +52,6 @@ export const checkSorting = (
cy.get(`[col-id="${column}"]`).click();
});
checkSortChange(orderTabDesc, column);
cy.get('.ag-header-container').within(() => {
cy.get(`[col-id="${column}"]`).click();
});
};
const checkSortChange = (tabsArr: string[], column: string) => {
@@ -1,6 +1,6 @@
import { memo } from 'react';
import { BID_COLOR, ASK_COLOR } from './vol-cell';
import { addDecimalsFixedFormatNumber } from '@vegaprotocol/utils';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { NumericCell } from './numeric-cell';
export interface CumulativeVolProps {
@@ -55,7 +55,7 @@ export const CumulativeVol = memo(
(
<NumericCell
value={Number(indicativeVolume)}
valueFormatted={addDecimalsFixedFormatNumber(
valueFormatted={addDecimalsFormatNumber(
indicativeVolume,
positionDecimalPlaces ?? 0
)}
@@ -67,7 +67,7 @@ export const CumulativeVol = memo(
{ask ? (
<NumericCell
value={ask}
valueFormatted={addDecimalsFixedFormatNumber(
valueFormatted={addDecimalsFormatNumber(
ask,
positionDecimalPlaces ?? 0
)}
@@ -77,7 +77,7 @@ export const CumulativeVol = memo(
{bid ? (
<NumericCell
value={ask}
valueFormatted={addDecimalsFixedFormatNumber(
valueFormatted={addDecimalsFormatNumber(
bid,
positionDecimalPlaces ?? 0
)}
@@ -1,5 +1,4 @@
import { Tooltip } from '@vegaprotocol/ui-toolkit';
import classnames from 'classnames';
import type { ReactNode } from 'react';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import type { Market, MarketData } from '@vegaprotocol/market-list';
@@ -12,12 +11,9 @@ interface DealTicketFeeDetailsProps {
order: OrderSubmissionBody['orderSubmission'];
market: Market;
marketData: MarketData;
currentInitialMargin?: string;
currentMaintenanceMargin?: string;
estimatedInitialMargin: string;
estimatedTotalInitialMargin: string;
marginAccountBalance: string;
generalAccountBalance: string;
margin: string;
totalMargin: string;
balance: string;
}
export interface DealTicketFeeDetailProps {
@@ -49,22 +45,23 @@ export const DealTicketFeeDetails = ({
order,
market,
marketData,
...args
margin,
totalMargin,
balance,
}: DealTicketFeeDetailsProps) => {
const feeDetails = useFeeDealTicketDetails(order, market, marketData);
const details = getFeeDetailsValues({
...feeDetails,
...args,
margin,
totalMargin,
balance,
});
return (
<div>
{details.map(({ label, value, labelDescription, symbol, indent }) => (
{details.map(({ label, value, labelDescription, symbol }) => (
<div
key={typeof label === 'string' ? label : 'value-dropdown'}
className={classnames(
'text-xs mt-2 flex justify-between items-center gap-4 flex-wrap',
{ 'ml-2': indent }
)}
className="text-xs mt-2 flex justify-between items-center gap-4 flex-wrap"
>
<div>
<Tooltip description={labelDescription}>
@@ -107,102 +107,6 @@ describe('DealTicket', () => {
);
});
it('should set values for a non-persistent reduce only order and disable post only checkbox', () => {
const expectedOrder = {
marketId: market.id,
type: Schema.OrderType.TYPE_LIMIT,
side: Schema.Side.SIDE_SELL,
size: '0.1',
price: '300.22',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
persist: false,
reduceOnly: true,
postOnly: false,
};
useOrderStore.setState({
orders: {
[expectedOrder.marketId]: expectedOrder,
},
});
render(generateJsx());
// Assert correct defaults are used from store
expect(
screen
.getByTestId(`order-type-${Schema.OrderType.TYPE_LIMIT}`)
.querySelector('input')
).toBeChecked();
expect(
screen.queryByTestId('order-side-SIDE_SELL')?.querySelector('input')
).toBeChecked();
expect(
screen.queryByTestId('order-side-SIDE_BUY')?.querySelector('input')
).not.toBeChecked();
expect(screen.getByTestId('order-size')).toHaveDisplayValue(
expectedOrder.size
);
expect(screen.getByTestId('order-tif')).toHaveValue(
expectedOrder.timeInForce
);
expect(screen.getByTestId('order-price')).toHaveDisplayValue(
expectedOrder.price
);
expect(screen.getByTestId('post-only')).toBeDisabled();
expect(screen.getByTestId('reduce-only')).toBeEnabled();
expect(screen.getByTestId('reduce-only')).toBeChecked();
expect(screen.getByTestId('post-only')).not.toBeChecked();
});
it('should set values for a persistent post only order and disable reduce only checkbox', () => {
const expectedOrder = {
marketId: market.id,
type: Schema.OrderType.TYPE_LIMIT,
side: Schema.Side.SIDE_SELL,
size: '0.1',
price: '300.22',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
persist: true,
reduceOnly: false,
postOnly: true,
};
useOrderStore.setState({
orders: {
[expectedOrder.marketId]: expectedOrder,
},
});
render(generateJsx());
// Assert correct defaults are used from store
expect(
screen
.getByTestId(`order-type-${Schema.OrderType.TYPE_LIMIT}`)
.querySelector('input')
).toBeChecked();
expect(
screen.queryByTestId('order-side-SIDE_SELL')?.querySelector('input')
).toBeChecked();
expect(
screen.queryByTestId('order-side-SIDE_BUY')?.querySelector('input')
).not.toBeChecked();
expect(screen.getByTestId('order-size')).toHaveDisplayValue(
expectedOrder.size
);
expect(screen.getByTestId('order-tif')).toHaveValue(
expectedOrder.timeInForce
);
expect(screen.getByTestId('order-price')).toHaveDisplayValue(
expectedOrder.price
);
expect(screen.getByTestId('post-only')).toBeEnabled();
expect(screen.getByTestId('reduce-only')).toBeDisabled();
expect(screen.getByTestId('post-only')).toBeChecked();
expect(screen.getByTestId('reduce-only')).not.toBeChecked();
});
it('handles TIF select box dependent on order type', async () => {
render(generateJsx());
@@ -1,6 +1,6 @@
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import { memo, useCallback, useEffect, useState, useRef, useMemo } from 'react';
import { memo, useCallback, useEffect, useState, useRef } from 'react';
import { Controller } from 'react-hook-form';
import { DealTicketAmount } from './deal-ticket-amount';
import { DealTicketButton } from './deal-ticket-button';
@@ -16,12 +16,10 @@ import {
useVegaWalletDialogStore,
} from '@vegaprotocol/wallet';
import {
Checkbox,
ExternalLink,
InputError,
Intent,
Notification,
Tooltip,
TinyScroll,
} from '@vegaprotocol/ui-toolkit';
@@ -44,9 +42,6 @@ import {
import { OrderTimeInForce, OrderType } from '@vegaprotocol/types';
import { useOrderForm } from '../../hooks/use-order-form';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import { marketMarginDataProvider } from '@vegaprotocol/positions';
export interface DealTicketProps {
market: Market;
@@ -106,12 +101,6 @@ export const DealTicket = ({
const { margin, totalMargin } = useInitialMargin(market.id, normalizedOrder);
const { data: currentMargins } = useDataProvider({
dataProvider: marketMarginDataProvider,
variables: { marketId: market.id, partyId: pubKey || '' },
skip: !pubKey,
});
useEffect(() => {
if (!pubKey) {
setError('summary', {
@@ -157,26 +146,6 @@ export const DealTicket = ({
clearErrors,
]);
const disablePostOnlyCheckbox = useMemo(() => {
const disabled = order
? [
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
].includes(order.timeInForce)
: true;
return disabled;
}, [order]);
const disableReduceOnlyCheckbox = useMemo(() => {
const disabled = order
? ![
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
].includes(order.timeInForce)
: true;
return disabled;
}, [order]);
const onSubmit = useCallback(
(order: OrderSubmission) => {
const now = new Date().getTime();
@@ -221,18 +190,8 @@ export const DealTicket = ({
if (type === OrderType.TYPE_NETWORK) return;
update({
type,
// when changing type also update the TIF to what was last used of new type
// when changing type also update the tif to what was last used of new type
timeInForce: lastTIF[type] || order.timeInForce,
postOnly:
type === OrderType.TYPE_MARKET ? false : order.postOnly,
reduceOnly:
type === OrderType.TYPE_LIMIT &&
![
OrderTimeInForce.TIME_IN_FORCE_FOK,
OrderTimeInForce.TIME_IN_FORCE_IOC,
].includes(lastTIF[type] || order.timeInForce)
? false
: order.postOnly,
expiresAt: undefined,
});
clearErrors('expiresAt');
@@ -280,23 +239,8 @@ export const DealTicket = ({
value={order.timeInForce}
orderType={order.type}
onSelect={(timeInForce) => {
// Reset post only and reduce only when changing TIF
update({
timeInForce,
postOnly: [
OrderTimeInForce.TIME_IN_FORCE_FOK,
OrderTimeInForce.TIME_IN_FORCE_IOC,
].includes(timeInForce)
? false
: order.postOnly,
reduceOnly: ![
OrderTimeInForce.TIME_IN_FORCE_FOK,
OrderTimeInForce.TIME_IN_FORCE_IOC,
].includes(timeInForce)
? false
: order.reduceOnly,
});
// Set TIF value for the given order type, so that when switching
update({ timeInForce });
// Set tif value for the given order type, so that when switching
// types we know the last used TIF for the given order type
setLastTIF((curr) => ({
...curr,
@@ -332,70 +276,6 @@ export const DealTicket = ({
)}
/>
)}
<div className="flex gap-2 pb-2 justify-between">
<Controller
name="postOnly"
control={control}
render={() => (
<Checkbox
name="post-only"
checked={order.postOnly}
disabled={disablePostOnlyCheckbox}
onCheckedChange={() => {
update({ postOnly: !order.postOnly, reduceOnly: false });
}}
label={
<Tooltip
description={
<span>
{disablePostOnlyCheckbox
? t(
'"Post only" can not be used on "Fill or Kill" or "Immediate or Cancel" orders.'
)
: t(
'"Post only" will ensure the order is not filled immediately but is placed on the order book as a passive order. When the order is processed it is either stopped (if it would not be filled immediately), or placed in the order book as a passive order until the price taker matches with it.'
)}
</span>
}
>
<span className="text-xs">{t('Post only')}</span>
</Tooltip>
}
/>
)}
/>
<Controller
name="reduceOnly"
control={control}
render={() => (
<Checkbox
name="reduce-only"
checked={order.reduceOnly}
disabled={disableReduceOnlyCheckbox}
onCheckedChange={() => {
update({ postOnly: false, reduceOnly: !order.reduceOnly });
}}
label={
<Tooltip
description={
<span>
{disableReduceOnlyCheckbox
? t(
'"Reduce only" can be used only with non-persistent orders, such as "Fill or Kill" or "Immediate or Cancel".'
)
: t(
'"Reduce only" will ensure that this order will not increase the size of an open position. When the order is matched, it will only trade enough volume to bring your open volume towards 0 but never change the direction of your position. If applied to a limit order that is not instantly filled, the order will be stopped.'
)}
</span>
}
>
<span className="text-xs">{t('Reduce only')}</span>
</Tooltip>
}
/>
)}
/>
</div>
<SummaryMessage
errorMessage={errors.summary?.message}
asset={asset}
@@ -416,12 +296,9 @@ export const DealTicket = ({
order={normalizedOrder}
market={market}
marketData={marketData}
estimatedInitialMargin={margin}
estimatedTotalInitialMargin={totalMargin}
currentInitialMargin={currentMargins?.initialLevel}
currentMaintenanceMargin={currentMargins?.maintenanceLevel}
marginAccountBalance={marginAccountBalance}
generalAccountBalance={generalAccountBalance}
margin={margin}
totalMargin={totalMargin}
balance={marginAccountBalance}
/>
</form>
</TinyScroll>
@@ -96,6 +96,16 @@ export const TimeInForceSelector = ({
id="select-time-in-force"
value={value}
onChange={(e) => {
// setPreviousTimeInForce({
// ...previousTimeInForce,
// [orderType]: e.target.value,
// });
// if (previousOrderType !== orderType) {
// setPreviousOrderType(orderType);
// const prev = previousTimeInForce[orderType as OrderType];
// onSelect(prev);
// }
onSelect(e.target.value as Schema.OrderTimeInForce);
}}
className="w-full"
+2 -26
View File
@@ -10,36 +10,12 @@ export const EST_MARGIN_TOOLTIP_TEXT = (settlementAsset: string) =>
export const EST_TOTAL_MARGIN_TOOLTIP_TEXT = t(
'Estimated total margin that will cover open position, active orders and this order.'
);
export const MARGIN_ACCOUNT_TOOLTIP_TEXT = t('Margin account balance.');
export const MARGIN_ACCOUNT_TOOLTIP_TEXT = t('Margin account balance');
export const MARGIN_DIFF_TOOLTIP_TEXT = (settlementAsset: string) =>
t(
"The additional margin required for your new position (taking into account volume and open orders), compared to your current margin. Measured in the market's settlement asset (%s).",
[settlementAsset]
);
export const DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT = (
settlementAsset: string
) =>
t(
'To cover the required margin, this amount will be drawn from your general (%s) account.',
[settlementAsset]
);
export const TOTAL_MARGIN_AVAILABLE = (
generalAccountBalance: string,
marginAccountBalance: string,
marginMaintenance: string,
settlementAsset: string
) =>
t(
'Total margin available = general %s balance (%s) + margin balance (%s) - maintenance level (%s).',
[
settlementAsset,
`${generalAccountBalance} ${settlementAsset}`,
`${marginAccountBalance} ${settlementAsset}`,
`${marginMaintenance} ${settlementAsset}`,
]
);
export const CONTRACTS_MARGIN_TOOLTIP_TEXT = t(
'The number of contracts determines how many units of the futures contract to buy or sell. For example, this is similar to buying one share of a listed company. The value of 1 contract is equivalent to the price of the contract. For example, if the current price is $50, then one contract is worth $50.'
);
@@ -64,7 +40,7 @@ export const EST_SLIPPAGE = t(
);
export const ERROR_SIZE_DECIMAL = t(
'The size field accepts up to X decimal places.'
'The size field accepts up to X decimal places'
);
export enum MarketModeValidationType {
@@ -15,8 +15,6 @@ import {
NOTIONAL_SIZE_TOOLTIP_TEXT,
MARGIN_ACCOUNT_TOOLTIP_TEXT,
MARGIN_DIFF_TOOLTIP_TEXT,
DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT,
TOTAL_MARGIN_AVAILABLE,
} from '../constants';
import { useOrderCloseOut } from './use-order-closeout';
import { useMarketAccountBalance } from '@vegaprotocol/accounts';
@@ -87,32 +85,24 @@ export const useFeeDealTicketDetails = (
};
export interface FeeDetails {
generalAccountBalance?: string;
marginAccountBalance?: string;
balance: string;
market: Market;
assetSymbol: string;
notionalSize: string | null;
estCloseOut: string | null;
estimateOrder: EstimateOrderQuery['estimateOrder'] | undefined;
estimatedInitialMargin: string;
estimatedTotalInitialMargin: string;
currentInitialMargin?: string;
currentMaintenanceMargin?: string;
margin: string;
totalMargin: string;
}
export const getFeeDetailsValues = ({
marginAccountBalance,
generalAccountBalance,
balance,
assetSymbol,
estimateOrder,
market,
notionalSize,
estimatedTotalInitialMargin,
currentInitialMargin,
currentMaintenanceMargin,
totalMargin,
}: FeeDetails) => {
const totalBalance =
BigInt(generalAccountBalance || '0') + BigInt(marginAccountBalance || '0');
const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals;
const formatValueWithMarketDp = (
@@ -133,8 +123,7 @@ export const getFeeDetailsValues = ({
label: string;
value?: string | null;
symbol: string;
indent?: boolean;
labelDescription?: React.ReactNode;
labelDescription: React.ReactNode;
}[] = [
{
label: t('Notional'),
@@ -164,64 +153,38 @@ export const getFeeDetailsValues = ({
),
symbol: assetSymbol,
},
/*
{
label: t('Initial margin'),
value: margin && `~${formatValueWithAssetDp(margin)}`,
symbol: assetSymbol,
labelDescription: EST_MARGIN_TOOLTIP_TEXT(assetSymbol),
},
*/
{
label: t('Margin required'),
value: `~${formatValueWithAssetDp(
currentInitialMargin
? (
BigInt(estimatedTotalInitialMargin) - BigInt(currentInitialMargin)
).toString()
: estimatedTotalInitialMargin
balance
? (BigInt(totalMargin) - BigInt(balance)).toString()
: totalMargin
)}`,
symbol: assetSymbol,
labelDescription: MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol),
},
];
if (totalBalance) {
const totalMarginAvailable = (
currentMaintenanceMargin
? totalBalance - BigInt(currentMaintenanceMargin)
: totalBalance
).toString();
details.push({
indent: true,
label: t('Total margin available'),
value: `~${formatValueWithAssetDp(totalMarginAvailable)}`,
symbol: assetSymbol,
labelDescription: TOTAL_MARGIN_AVAILABLE(
formatValueWithAssetDp(generalAccountBalance),
formatValueWithAssetDp(marginAccountBalance),
formatValueWithAssetDp(currentMaintenanceMargin),
assetSymbol
),
});
if (marginAccountBalance) {
const deductionFromCollateral =
BigInt(estimatedTotalInitialMargin) - BigInt(marginAccountBalance);
details.push({
indent: true,
label: t('Deduction from collateral'),
value: `~${formatValueWithAssetDp(
deductionFromCollateral > 0 ? deductionFromCollateral.toString() : '0'
)}`,
symbol: assetSymbol,
labelDescription: DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT(assetSymbol),
});
}
if (balance) {
details.push({
label: t('Projected margin'),
value: `~${formatValueWithAssetDp(estimatedTotalInitialMargin)}`,
value: `~${formatValueWithAssetDp(totalMargin)}`,
symbol: assetSymbol,
labelDescription: EST_TOTAL_MARGIN_TOOLTIP_TEXT,
});
}
details.push({
label: t('Current margin allocation'),
value: `${formatValueWithAssetDp(marginAccountBalance)}`,
value: balance
? `~${formatValueWithAssetDp(balance)}`
: `${formatValueWithAssetDp(balance)}`,
symbol: assetSymbol,
labelDescription: MARGIN_ACCOUNT_TOOLTIP_TEXT,
});
@@ -65,11 +65,5 @@ export const useInitialMargin = (
sellMargin > buyMargin ? sellMargin.toString() : buyMargin.toString();
}
return useMemo(
() => ({
totalMargin,
margin,
}),
[totalMargin, margin]
);
return useMemo(() => ({ totalMargin, margin }), [totalMargin, margin]);
};
-5
View File
@@ -1,7 +1,2 @@
import '@testing-library/jest-dom';
import 'jest-canvas-mock';
import ResizeObserver from 'resize-observer-polyfill';
import { defaultFallbackInView } from 'react-intersection-observer';
defaultFallbackInView(true);
global.ResizeObserver = ResizeObserver;
@@ -174,7 +174,7 @@ const ApprovalTxFeedback = ({
<>
<p>
{t(
`Your ${selectedAsset?.symbol} approval is being confirmed by the Ethereum network. When this is complete, you can continue your deposit`
`Your ${selectedAsset?.symbol} is being confirmed by the Ethereum network. When this is complete, you can continue your deposit`
)}{' '}
</p>
{txLink && <p>{txLink}</p>}
@@ -61,7 +61,6 @@ beforeEach(() => {
submitDeposit: jest.fn(),
submitFaucet: jest.fn(),
onDisconnect: jest.fn(),
handleAmountChange: jest.fn(),
approveTxId: null,
faucetTxId: null,
isFaucetable: true,
+10 -16
View File
@@ -25,9 +25,11 @@ import {
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useWeb3React } from '@web3-react/core';
import BigNumber from 'bignumber.js';
import type { ButtonHTMLAttributes, ChangeEvent, ReactNode } from 'react';
import { useMemo, useState } from 'react';
import { useWatch, Controller, useForm } from 'react-hook-form';
import type { ButtonHTMLAttributes, ReactNode } from 'react';
import { useState } from 'react';
import { useMemo } from 'react';
import { useWatch } from 'react-hook-form';
import { Controller, useForm } from 'react-hook-form';
import { DepositLimits } from './deposit-limits';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import {
@@ -38,7 +40,6 @@ import {
import type { DepositBalances } from './use-deposit-balances';
import { FaucetNotification } from './faucet-notification';
import { ApproveNotification } from './approve-notification';
import { usePersistentDeposit } from './use-persistent-deposit';
interface FormFields {
asset: string;
@@ -52,7 +53,6 @@ export interface DepositFormProps {
selectedAsset?: Asset;
balances: DepositBalances | null;
onSelectAsset: (assetId: string) => void;
handleAmountChange: (amount: string) => void;
onDisconnect: () => void;
submitApprove: () => void;
approveTxId: number | null;
@@ -71,7 +71,6 @@ export const DepositForm = ({
selectedAsset,
balances,
onSelectAsset,
handleAmountChange,
onDisconnect,
submitApprove,
submitDeposit,
@@ -86,8 +85,6 @@ export const DepositForm = ({
const { pubKey, pubKeys: _pubKeys } = useVegaWallet();
const [approveNotificationIntent, setApproveNotificationIntent] =
useState<Intent>(Intent.Warning);
const [persistedDeposit] = usePersistentDeposit(selectedAsset?.id);
const {
register,
handleSubmit,
@@ -98,8 +95,7 @@ export const DepositForm = ({
} = useForm<FormFields>({
defaultValues: {
to: pubKey ? pubKey : undefined,
asset: selectedAsset?.id,
amount: persistedDeposit.amount,
asset: selectedAsset?.id || '',
},
});
@@ -337,9 +333,6 @@ export const DepositForm = ({
return maxSafe(balances?.balance || new BigNumber(0))(v);
},
},
onChange: (e: ChangeEvent<HTMLInputElement>) => {
handleAmountChange(e.target.value || '');
},
})}
/>
{errors.amount?.message && (
@@ -350,9 +343,10 @@ export const DepositForm = ({
{selectedAsset && balances && (
<UseButton
onClick={() => {
const amount = balances.balance.toFixed(selectedAsset.decimals);
setValue('amount', amount);
handleAmountChange(amount);
setValue(
'amount',
balances.balance.toFixed(selectedAsset.decimals)
);
clearErrors('amount');
}}
>
+2 -14
View File
@@ -5,7 +5,7 @@ import { prepend0x } from '@vegaprotocol/smart-contracts';
import sortBy from 'lodash/sortBy';
import { useSubmitApproval } from './use-submit-approval';
import { useSubmitFaucet } from './use-submit-faucet';
import { useCallback, useState } from 'react';
import { useState } from 'react';
import { useDepositBalances } from './use-deposit-balances';
import { useDepositDialog } from './deposit-dialog';
import type { Asset } from '@vegaprotocol/assets';
@@ -14,7 +14,6 @@ import {
useBridgeContract,
useEthereumConfig,
} from '@vegaprotocol/web3';
import { usePersistentDeposit } from './use-persistent-deposit';
interface DepositManagerProps {
assetId?: string;
@@ -29,9 +28,7 @@ export const DepositManager = ({
}: DepositManagerProps) => {
const createEthTransaction = useEthTransactionStore((state) => state.create);
const { config } = useEthereumConfig();
const [persistentDeposit, savePersistentDeposit] =
usePersistentDeposit(initialAssetId);
const [assetId, setAssetId] = useState(persistentDeposit?.assetId);
const [assetId, setAssetId] = useState(initialAssetId);
const asset = assets.find((a) => a.id === assetId);
const bridgeContract = useBridgeContract();
const closeDepositDialog = useDepositDialog((state) => state.close);
@@ -68,26 +65,17 @@ export const DepositManager = ({
closeDepositDialog();
};
const onAmountChange = useCallback(
(amount: string) => {
savePersistentDeposit({ ...persistentDeposit, amount });
},
[savePersistentDeposit, persistentDeposit]
);
return (
<DepositForm
selectedAsset={asset}
onDisconnect={reset}
onSelectAsset={(id) => {
setAssetId(id);
savePersistentDeposit({ assetId: id });
// When we change asset, also clear the tracked faucet/approve transactions so
// we dont render stale UI
approve.reset();
faucet.reset();
}}
handleAmountChange={onAmountChange}
assets={sortBy(assets, 'name')}
submitApprove={approve.perform}
submitDeposit={submitDeposit}
@@ -1,21 +0,0 @@
import { renderHook, waitFor, act } from '@testing-library/react';
import { usePersistentDeposit } from './use-persistent-deposit';
describe('usePersistenDeposit', () => {
it('should return empty data', () => {
const { result } = renderHook(() => usePersistentDeposit());
expect(result.current).toEqual([{ assetId: '' }, expect.any(Function)]);
});
it('should return empty and properly saved data', async () => {
const aId = 'test';
const retObj = { assetId: 'test', amount: '1.00000' };
const { result } = renderHook(() => usePersistentDeposit(aId));
expect(result.current).toEqual([{ assetId: 'test' }, expect.any(Function)]);
await act(() => {
result.current[1](retObj);
});
await waitFor(() => {
expect(result.current[0]).toEqual(retObj);
});
});
});
@@ -1,42 +0,0 @@
import { useMemo } from 'react';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
const STORAGE_KEY = 'vega_deposit_store';
interface PersistedDeposit {
assetId: string;
amount?: string;
}
type PersistedDepositData = Record<string, PersistedDeposit>;
const usePersistentDepositStore = create<{
deposits: PersistedDepositData;
saveValue: (entry: PersistedDeposit) => void;
lastVisited?: PersistedDeposit;
}>()(
persist(
immer((set) => ({
deposits: {},
saveValue: (entry) =>
set((state) => {
const oldValue = state.deposits[entry.assetId] || null;
state.deposits[entry.assetId] = { ...oldValue, ...entry };
state.lastVisited = { ...oldValue, ...entry };
return state;
}),
})),
{ name: STORAGE_KEY }
)
);
export const usePersistentDeposit = (
assetId?: string
): [PersistedDeposit, (entry: PersistedDeposit) => void] => {
const { deposits, lastVisited, saveValue } = usePersistentDepositStore();
const discoveredData = useMemo(() => {
return deposits[assetId || ''] || lastVisited || { assetId: assetId || '' };
}, [deposits, lastVisited, assetId]);
return [discoveredData, saveValue];
};
+3 -3
View File
@@ -1,5 +1,4 @@
import { MaxUint256 } from '@ethersproject/constants';
import { isAssetTypeERC20 } from '@vegaprotocol/utils';
import { isAssetTypeERC20, removeDecimal } from '@vegaprotocol/utils';
import {
EthTxStatus,
useEthereumConfig,
@@ -38,9 +37,10 @@ export const useSubmitApproval = (
},
perform: () => {
if (!asset || !config) return;
const amount = removeDecimal('1000000', asset.decimals);
const id = createEthTransaction(contract, 'approve', [
config?.collateral_bridge_contract.address,
MaxUint256.toString(),
amount,
]);
setId(id);
},
@@ -140,6 +140,8 @@ describe('Network switcher', () => {
[Networks.STAGNET3]: 'https://stag3.net',
[Networks.DEVNET]: 'https://dev.net',
[Networks.STAGNET1]: 'https://stag1.net',
[Networks.SANDBOX]: 'https://sandbox.net',
[Networks.MIRROR]: 'https://mirror.net',
};
// @ts-ignore Typescript doesn't know about this module being mocked
useEnvironment.mockImplementation(() => ({
@@ -179,6 +181,8 @@ describe('Network switcher', () => {
[Networks.STAGNET3]: 'https://stag3.net',
[Networks.DEVNET]: 'https://dev.net',
[Networks.STAGNET1]: 'https://stag1.net',
[Networks.SANDBOX]: 'https://sandbox.net',
[Networks.MIRROR]: 'https://mirror.net',
};
// @ts-ignore Typescript doesn't know about this module being mocked
useEnvironment.mockImplementation(() => ({
@@ -211,6 +215,8 @@ describe('Network switcher', () => {
[Networks.STAGNET3]: 'https://stag3.net',
[Networks.DEVNET]: 'https://dev.net',
[Networks.STAGNET1]: 'https://stag1.net',
[Networks.SANDBOX]: 'https://sandbox.net',
[Networks.MIRROR]: 'https://mirror.net',
};
// @ts-ignore Typescript doesn't know about this module being mocked
useEnvironment.mockImplementation(() => ({
@@ -17,10 +17,12 @@ export const envNameMapping: Record<Networks, string> = {
[Networks.VALIDATOR_TESTNET]: t('VALIDATOR_TESTNET'),
[Networks.CUSTOM]: t('Custom'),
[Networks.DEVNET]: t('Devnet'),
[Networks.SANDBOX]: t('Sandbox'),
[Networks.STAGNET1]: t('Stagnet'),
[Networks.STAGNET3]: t('Stagnet3'),
[Networks.TESTNET]: t('Fairground testnet'),
[Networks.MAINNET]: t('Mainnet'),
[Networks.MIRROR]: t('Mainnet mirror'),
};
export const envTriggerMapping: Record<Networks, string> = {
@@ -30,6 +32,7 @@ export const envTriggerMapping: Record<Networks, string> = {
export const envDescriptionMapping: Record<Networks, string> = {
[Networks.CUSTOM]: '',
[Networks.SANDBOX]: t('A playground test environment'),
[Networks.VALIDATOR_TESTNET]: t('The validator deployed testnet'),
[Networks.DEVNET]: t('The latest Vega code auto-deployed'),
[Networks.STAGNET1]: t('A release candidate for the staging environment'),
@@ -38,6 +41,9 @@ export const envDescriptionMapping: Record<Networks, string> = {
'Public testnet run by the Vega team, often used for incentives'
),
[Networks.MAINNET]: t('The vega mainnet'),
[Networks.MIRROR]: t(
'A mirror of the mainnet environment running on an Ethereum test network'
),
};
const standardNetworkKeys = [Networks.MAINNET, Networks.TESTNET];
+3 -2
View File
@@ -3,7 +3,7 @@ import { useCallback } from 'react';
import { Networks } from '../types';
import { useEnvironment } from './use-environment';
type Net = Exclude<Networks, 'CUSTOM'>;
type Net = Exclude<Networks, 'CUSTOM' | 'SANDBOX'>;
export enum DApp {
Explorer = 'Explorer',
Console = 'Console',
@@ -21,6 +21,7 @@ const EmptyLinks: DAppLinks = {
[Networks.STAGNET3]: '',
[Networks.TESTNET]: '',
[Networks.MAINNET]: '',
[Networks.MIRROR]: '',
};
const ExplorerLinks = {
@@ -60,7 +61,7 @@ export const useLinks = (dapp: DApp, network?: Net) => {
};
let net = network || VEGA_ENV;
if (net === Networks.CUSTOM) {
if (net === Networks.CUSTOM || net === Networks.SANDBOX) {
net = Networks.TESTNET;
}
+2
View File
@@ -5,11 +5,13 @@ import type { envSchema } from './utils/validate-environment';
export enum Networks {
VALIDATOR_TESTNET = 'VALIDATOR_TESTNET',
CUSTOM = 'CUSTOM',
SANDBOX = 'SANDBOX',
TESTNET = 'TESTNET',
STAGNET1 = 'STAGNET1',
STAGNET3 = 'STAGNET3',
DEVNET = 'DEVNET',
MAINNET = 'MAINNET',
MIRROR = 'MIRROR',
}
export type Environment = z.infer<typeof envSchema>;
export type Configuration = z.infer<typeof tomlConfigSchema>;
@@ -3,11 +3,13 @@ import z from 'zod';
export enum Networks {
VALIDATOR_TESTNET = 'VALIDATOR_TESTNET',
CUSTOM = 'CUSTOM',
SANDBOX = 'SANDBOX',
TESTNET = 'TESTNET',
STAGNET1 = 'STAGNET1',
STAGNET3 = 'STAGNET3',
DEVNET = 'DEVNET',
MAINNET = 'MAINNET',
MIRROR = 'MIRROR',
}
const schemaObject = {
+29 -6
View File
@@ -7,6 +7,7 @@ import type { Trade } from './fills-data-provider';
import { FillsTable, getFeesBreakdown } from './fills-table';
import { generateFill } from './test-helpers';
import { MemoryRouter } from 'react-router-dom';
describe('FillsTable', () => {
let defaultFill: PartialDeep<Trade>;
@@ -36,7 +37,11 @@ describe('FillsTable', () => {
it('correct columns are rendered', async () => {
await act(async () => {
render(<FillsTable partyId="party-id" rowData={[generateFill()]} />);
render(
<MemoryRouter>
<FillsTable partyId="party-id" rowData={[generateFill()]} />
</MemoryRouter>
);
});
const headers = screen.getAllByRole('columnheader');
@@ -67,7 +72,11 @@ describe('FillsTable', () => {
liquidityFee: '2',
},
});
render(<FillsTable partyId={partyId} rowData={[{ ...buyerFill }]} />);
render(
<MemoryRouter>
<FillsTable partyId={partyId} rowData={[{ ...buyerFill }]} />
</MemoryRouter>
);
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
buyerFill.market?.tradableInstrument.instrument.name || '',
@@ -100,7 +109,11 @@ describe('FillsTable', () => {
liquidityFee: '1',
},
});
render(<FillsTable partyId={partyId} rowData={[buyerFill]} />);
render(
<MemoryRouter>
<FillsTable partyId={partyId} rowData={[buyerFill]} />
</MemoryRouter>
);
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
@@ -129,7 +142,9 @@ describe('FillsTable', () => {
aggressor: Schema.Side.SIDE_SELL,
});
const { rerender } = render(
<FillsTable partyId={partyId} rowData={[takerFill]} />
<MemoryRouter>
<FillsTable partyId={partyId} rowData={[takerFill]} />
</MemoryRouter>
);
expect(
screen
@@ -143,7 +158,11 @@ describe('FillsTable', () => {
},
aggressor: Schema.Side.SIDE_BUY,
});
rerender(<FillsTable partyId={partyId} rowData={[makerFill]} />);
rerender(
<MemoryRouter>
<FillsTable partyId={partyId} rowData={[makerFill]} />
</MemoryRouter>
);
expect(
screen
@@ -160,7 +179,11 @@ describe('FillsTable', () => {
},
aggressor: Schema.Side.SIDE_SELL,
});
render(<FillsTable partyId={partyId} rowData={[takerFill]} />);
render(
<MemoryRouter>
<FillsTable partyId={partyId} rowData={[takerFill]} />
</MemoryRouter>
);
const feeCell = screen
.getAllByRole('gridcell')
+11 -1
View File
@@ -57,7 +57,7 @@ fragment LiquidityProvisionFields on LiquidityProvision {
query LiquidityProvisions($marketId: ID!) {
market(id: $marketId) {
liquidityProvisionsConnection(live: true) {
liquidityProvisionsConnection {
edges {
node {
...LiquidityProvisionFields
@@ -103,3 +103,13 @@ query LiquidityProviderFeeShare($marketId: ID!) {
}
}
}
subscription LiquidityProviderFeeShareUpdate($marketId: ID!) {
marketsData(marketIds: [$marketId]) {
liquidityProviderFeeShare {
partyId
equityLikeShare
averageEntryValuation
}
}
}
@@ -1,6 +1,6 @@
fragment MarketNode on Market {
id
liquidityProvisionsConnection(live: true) {
liquidityProvisionsConnection {
edges {
node {
commitmentAmount
+43 -2
View File
@@ -36,6 +36,13 @@ export type LiquidityProviderFeeShareQueryVariables = Types.Exact<{
export type LiquidityProviderFeeShareQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, data?: { __typename?: 'MarketData', market: { __typename?: 'Market', id: string }, liquidityProviderFeeShare?: Array<{ __typename?: 'LiquidityProviderFeeShare', equityLikeShare: string, averageEntryValuation: string, party: { __typename?: 'Party', id: string } }> | null } | null } | null };
export type LiquidityProviderFeeShareUpdateSubscriptionVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
}>;
export type LiquidityProviderFeeShareUpdateSubscription = { __typename?: 'Subscription', marketsData: Array<{ __typename?: 'ObservableMarketData', liquidityProviderFeeShare?: Array<{ __typename?: 'ObservableLiquidityProviderFeeShare', partyId: string, equityLikeShare: string, averageEntryValuation: string }> | null }> };
export const LiquidityProvisionFieldsFragmentDoc = gql`
fragment LiquidityProvisionFields on LiquidityProvision {
party {
@@ -131,7 +138,7 @@ export type MarketLpQueryResult = Apollo.QueryResult<MarketLpQuery, MarketLpQuer
export const LiquidityProvisionsDocument = gql`
query LiquidityProvisions($marketId: ID!) {
market(id: $marketId) {
liquidityProvisionsConnection(live: true) {
liquidityProvisionsConnection {
edges {
node {
...LiquidityProvisionFields
@@ -249,4 +256,38 @@ export function useLiquidityProviderFeeShareLazyQuery(baseOptions?: Apollo.LazyQ
}
export type LiquidityProviderFeeShareQueryHookResult = ReturnType<typeof useLiquidityProviderFeeShareQuery>;
export type LiquidityProviderFeeShareLazyQueryHookResult = ReturnType<typeof useLiquidityProviderFeeShareLazyQuery>;
export type LiquidityProviderFeeShareQueryResult = Apollo.QueryResult<LiquidityProviderFeeShareQuery, LiquidityProviderFeeShareQueryVariables>;
export type LiquidityProviderFeeShareQueryResult = Apollo.QueryResult<LiquidityProviderFeeShareQuery, LiquidityProviderFeeShareQueryVariables>;
export const LiquidityProviderFeeShareUpdateDocument = gql`
subscription LiquidityProviderFeeShareUpdate($marketId: ID!) {
marketsData(marketIds: [$marketId]) {
liquidityProviderFeeShare {
partyId
equityLikeShare
averageEntryValuation
}
}
}
`;
/**
* __useLiquidityProviderFeeShareUpdateSubscription__
*
* To run a query within a React component, call `useLiquidityProviderFeeShareUpdateSubscription` and pass it any options that fit your needs.
* When your component renders, `useLiquidityProviderFeeShareUpdateSubscription` 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 subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useLiquidityProviderFeeShareUpdateSubscription({
* variables: {
* marketId: // value for 'marketId'
* },
* });
*/
export function useLiquidityProviderFeeShareUpdateSubscription(baseOptions: Apollo.SubscriptionHookOptions<LiquidityProviderFeeShareUpdateSubscription, LiquidityProviderFeeShareUpdateSubscriptionVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useSubscription<LiquidityProviderFeeShareUpdateSubscription, LiquidityProviderFeeShareUpdateSubscriptionVariables>(LiquidityProviderFeeShareUpdateDocument, options);
}
export type LiquidityProviderFeeShareUpdateSubscriptionHookResult = ReturnType<typeof useLiquidityProviderFeeShareUpdateSubscription>;
export type LiquidityProviderFeeShareUpdateSubscriptionResult = Apollo.SubscriptionResult<LiquidityProviderFeeShareUpdateSubscription>;
+1 -1
View File
@@ -13,7 +13,7 @@ export type LiquidityProvisionMarketsQuery = { __typename?: 'Query', marketsConn
export const MarketNodeFragmentDoc = gql`
fragment MarketNode on Market {
id
liquidityProvisionsConnection(live: true) {
liquidityProvisionsConnection {
edges {
node {
commitmentAmount
@@ -6,6 +6,7 @@ import produce from 'immer';
import {
LiquidityProviderFeeShareDocument,
LiquidityProviderFeeShareUpdateDocument,
LiquidityProvisionsDocument,
LiquidityProvisionsUpdateDocument,
MarketLpDocument,
@@ -17,6 +18,7 @@ import type {
LiquidityProviderFeeShareFieldsFragment,
LiquidityProviderFeeShareQuery,
LiquidityProviderFeeShareQueryVariables,
LiquidityProviderFeeShareUpdateSubscription,
LiquidityProvisionFieldsFragment,
LiquidityProvisionsQuery,
LiquidityProvisionsQueryVariables,
@@ -63,11 +65,23 @@ export const liquidityProvisionsDataProvider = makeDataProvider<
});
},
getData: (responseData: LiquidityProvisionsQuery | null) => {
// to remove dedupe after core fix https://github.com/vegaprotocol/vega/issues/8043
const dedupeArr: string[] = [];
return (
responseData?.market?.liquidityProvisionsConnection?.edges?.map(
(e) => e?.node
) ?? []
).filter((e) => !!e) as LiquidityProvisionFieldsFragment[];
).filter((e) => {
if (e) {
const id = getId(e);
if (dedupeArr.includes(id)) {
return false;
}
dedupeArr.push(id);
return true;
}
return false;
}) as LiquidityProvisionFieldsFragment[];
},
getDelta: (
subscriptionData: LiquidityProvisionsUpdateSubscription
@@ -113,94 +127,73 @@ export const marketLiquidityDataProvider = makeDataProvider<
export const liquidityFeeShareDataProvider = makeDataProvider<
LiquidityProviderFeeShareQuery,
LiquidityProviderFeeShareFieldsFragment[],
never,
never,
LiquidityProviderFeeShareUpdateSubscription,
LiquidityProviderFeeShareUpdateSubscription['marketsData'][0]['liquidityProviderFeeShare'],
LiquidityProviderFeeShareQueryVariables
>({
query: LiquidityProviderFeeShareDocument,
subscriptionQuery: LiquidityProviderFeeShareUpdateDocument,
update: (
data: LiquidityProviderFeeShareFieldsFragment[] | null,
deltas: LiquidityProviderFeeShareUpdateSubscription['marketsData'][0]['liquidityProviderFeeShare']
) => {
return produce(data || [], (draft) => {
deltas?.forEach((delta) => {
const id = delta.partyId;
const index = draft.findIndex((a) => a.party.id === id);
if (index !== -1) {
draft[index].equityLikeShare = delta.equityLikeShare;
draft[index].averageEntryValuation = delta.averageEntryValuation;
} else {
draft.unshift({
equityLikeShare: delta.equityLikeShare,
averageEntryValuation: delta.averageEntryValuation,
party: {
id: delta.partyId,
},
// TODO add accounts connection to the subscription
});
}
});
});
},
getData: (data) => {
return data?.market?.data?.liquidityProviderFeeShare || [];
},
getDelta: (subscriptionData: LiquidityProviderFeeShareUpdateSubscription) => {
return subscriptionData.marketsData[0].liquidityProviderFeeShare;
},
});
export type Filter = { partyId?: string; active?: boolean };
export const lpAggregatedDataProvider = makeDerivedDataProvider<
LiquidityProvisionData[],
ReturnType<typeof getLiquidityProvision>,
never,
MarketLpQueryVariables & { filter?: Filter }
MarketLpQueryVariables
>(
[
(callback, client, variables) =>
liquidityProvisionsDataProvider(callback, client, {
marketId: variables.marketId,
}),
(callback, client, variables) =>
marketLiquidityDataProvider(callback, client, {
marketId: variables.marketId,
}),
(callback, client, variables) =>
liquidityFeeShareDataProvider(callback, client, {
marketId: variables.marketId,
}),
liquidityProvisionsDataProvider,
marketLiquidityDataProvider,
liquidityFeeShareDataProvider,
],
(
[liquidityProvisions, marketLiquidity, liquidityFeeShare],
{ filter }
): LiquidityProvisionData[] => {
([
liquidityProvisions,
marketLiquidity,
liquidityFeeShare,
]): LiquidityProvisionData[] => {
return getLiquidityProvision(
liquidityProvisions,
marketLiquidity,
liquidityFeeShare,
filter
liquidityFeeShare
);
}
);
export const matchFilter = (
filter: Filter,
lp: LiquidityProvisionFieldsFragment
) => {
if (filter.partyId && lp.party.id !== filter.partyId) {
return false;
}
if (
filter.active === true &&
lp.status !== Schema.LiquidityProvisionStatus.STATUS_ACTIVE
) {
return false;
}
if (
filter.active === false &&
lp.status === Schema.LiquidityProvisionStatus.STATUS_ACTIVE
) {
return false;
}
return true;
};
export const getLiquidityProvision = (
liquidityProvisions: LiquidityProvisionFieldsFragment[],
marketLiquidity: MarketLpQuery,
liquidityFeeShare: LiquidityProviderFeeShareFieldsFragment[],
filter?: Filter
liquidityFeeShare: LiquidityProviderFeeShareFieldsFragment[]
): LiquidityProvisionData[] => {
return liquidityProvisions
.filter((lp) => {
if (
![
Schema.LiquidityProvisionStatus.STATUS_ACTIVE,
Schema.LiquidityProvisionStatus.STATUS_UNDEPLOYED,
Schema.LiquidityProvisionStatus.STATUS_PENDING,
].includes(lp.status)
) {
return false;
}
if (filter && !matchFilter(filter, lp)) {
return false;
}
return true;
})
.map((lp) => {
const market = marketLiquidity?.market;
const feeShare = liquidityFeeShare.find(
@@ -229,7 +222,14 @@ export const getLiquidityProvision = (
.decimals,
balance,
};
});
})
.filter((e) =>
[
Schema.LiquidityProvisionStatus.STATUS_ACTIVE,
Schema.LiquidityProvisionStatus.STATUS_UNDEPLOYED,
Schema.LiquidityProvisionStatus.STATUS_PENDING,
].includes(e.status)
);
};
export interface LiquidityProvisionData
+15 -13
View File
@@ -48,12 +48,12 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => {
throttle(() => {
dataRef.current = {
...marketDataRef.current,
indicativePrice:
marketDataRef.current?.indicativePrice &&
getPriceLevel(
marketDataRef.current.indicativePrice,
resolutionRef.current
),
indicativePrice: marketDataRef.current?.indicativePrice
? getPriceLevel(
marketDataRef.current.indicativePrice,
resolutionRef.current
)
: undefined,
midPrice: getMidPrice(
rawDataRef.current?.depth.sell,
rawDataRef.current?.depth.buy,
@@ -148,12 +148,13 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => {
variables,
});
if (!marketDataRef.current && marketData) {
marketDataRef.current = marketData;
}
marketDataRef.current = marketData;
useEffect(() => {
const throttleRunner = updateOrderbookData.current;
if (!marketDataRef.current) {
return;
}
if (!data) {
dataRef.current = { rows: null };
setOrderbookData(dataRef.current);
@@ -161,9 +162,10 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => {
}
dataRef.current = {
...marketDataRef.current,
indicativePrice:
marketDataRef.current?.indicativePrice &&
getPriceLevel(marketDataRef.current.indicativePrice, resolution),
indicativePrice: getPriceLevel(
marketDataRef.current.indicativePrice,
resolution
),
midPrice: getMidPrice(data.depth.sell, data.depth.buy, resolution),
rows: compactRows(data.depth.sell, data.depth.buy, resolution),
};
@@ -173,7 +175,7 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => {
return () => {
throttleRunner.cancel();
};
}, [data, resolution]);
}, [data, marketData, resolution]);
useEffect(() => {
resolutionRef.current = resolution;
@@ -11,13 +11,12 @@ import {
AgGridDynamic as AgGrid,
PriceFlashCell,
MarketNameCell,
SetFilter,
} from '@vegaprotocol/datagrid';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import { AgGridColumn } from 'ag-grid-react';
import type { AgGridReact } from 'ag-grid-react';
import * as Schema from '@vegaprotocol/types';
import type { MarketMaybeWithData } from '../../';
import type { MarketMaybeWithData, MarketFieldsFragment } from '../../';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
const { MarketTradingMode, AuctionTrigger } = Schema;
@@ -34,6 +33,7 @@ export const MarketListTable = forwardRef<
return (
<AgGrid
style={{ width: '100%', height: '100%' }}
overlayNoRowsTemplate={t('No markets')}
getRowId={getRowId}
ref={ref}
defaultColDef={{
@@ -43,7 +43,7 @@ export const MarketListTable = forwardRef<
filter: true,
filterParams: { buttons: ['reset'] },
}}
suppressCellFocus
suppressCellFocus={true}
components={{ PriceFlashCell, MarketNameCell }}
{...props}
>
@@ -59,11 +59,11 @@ export const MarketListTable = forwardRef<
/>
<AgGridColumn
headerName={t('Trading mode')}
field="tradingMode"
field="data"
minWidth={170}
valueFormatter={({
valueGetter={({
data,
}: VegaValueFormatterParams<MarketMaybeWithData, 'data'>) => {
}: VegaValueGetterParams<MarketMaybeWithData, 'data'>) => {
if (!data?.data) return undefined;
const { trigger } = data.data;
const { tradingMode } = data;
@@ -75,22 +75,14 @@ export const MarketListTable = forwardRef<
- ${Schema.AuctionTriggerMapping[trigger]}`
: Schema.MarketTradingModeMapping[tradingMode];
}}
filter={SetFilter}
filterParams={{
set: Schema.MarketTradingModeMapping,
}}
/>
<AgGridColumn
headerName={t('Status')}
field="state"
valueFormatter={({
valueGetter={({
data,
}: VegaValueFormatterParams<MarketMaybeWithData, 'state'>) => {
return data?.state ? Schema.MarketStateMapping[data.state] : '-';
}}
filter={SetFilter}
filterParams={{
set: Schema.MarketStateMapping,
}: VegaValueGetterParams<MarketFieldsFragment, 'state'>) => {
return data?.state ? Schema.MarketStateMapping[data?.state] : '-';
}}
/>
<AgGridColumn
@@ -1,6 +1,4 @@
import type { MouseEvent } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import type { AgGridReact } from 'ag-grid-react';
import { t } from '@vegaprotocol/i18n';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { MarketListTable } from './market-list-table';
@@ -14,24 +12,15 @@ interface MarketsContainerProps {
}
export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const [dataCount, setDataCount] = useState(0);
const { data, error, loading, reload } = useDataProvider({
dataProvider,
skipUpdates: true,
variables: undefined,
});
useEffect(() => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, [data]);
const onFilterChanged = useCallback(() => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, []);
return (
<div className="h-full relative">
<MarketListTable
ref={gridRef}
rowData={data}
rowData={error ? [] : data}
suppressLoadingOverlay
suppressNoRowsOverlay
onCellClicked={(cellEvent: CellClickedEvent) => {
@@ -51,7 +40,6 @@ export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
);
}}
onMarketClick={onSelect}
onFilterChanged={onFilterChanged}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
@@ -59,7 +47,6 @@ export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
error={error}
data={data}
noDataMessage={t('No markets')}
noDataCondition={() => !dataCount}
reload={reload}
/>
</div>
@@ -14,8 +14,6 @@ fragment OrderFields on Order {
expiresAt
createdAt
updatedAt
postOnly
reduceOnly
liquidityProvision {
__typename
}
@@ -32,16 +30,12 @@ query OrderById($orderId: ID!) {
query Orders(
$partyId: ID!
$marketIds: [ID!]
$pagination: Pagination
$filter: OrderFilter
$filter: OrderByMarketIdsFilter
) {
party(id: $partyId) {
id
ordersConnection(
pagination: $pagination
filter: { order: $filter, marketIds: $marketIds }
) {
ordersConnection(pagination: $pagination, filter: $filter) {
edges {
node {
...OrderFields
@@ -78,8 +72,8 @@ fragment OrderUpdateFields on OrderUpdate {
}
}
subscription OrdersUpdate($partyId: ID!, $marketIds: [ID!]) {
orders(filter: { partyIds: [$partyId], marketIds: $marketIds }) {
subscription OrdersUpdate($partyId: ID!) {
orders(filter: { partyIds: [$partyId] }) {
...OrderUpdateFields
}
}
@@ -3,30 +3,28 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type OrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, postOnly?: boolean | null, reduceOnly?: boolean | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder' } | null };
export type OrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder' } | null };
export type OrderByIdQueryVariables = Types.Exact<{
orderId: Types.Scalars['ID'];
}>;
export type OrderByIdQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, postOnly?: boolean | null, reduceOnly?: boolean | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder' } | null } };
export type OrderByIdQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder' } | null } };
export type OrdersQueryVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
marketIds?: Types.InputMaybe<Array<Types.Scalars['ID']> | Types.Scalars['ID']>;
pagination?: Types.InputMaybe<Types.Pagination>;
filter?: Types.InputMaybe<Types.OrderFilter>;
filter?: Types.InputMaybe<Types.OrderByMarketIdsFilter>;
}>;
export type OrdersQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, ordersConnection?: { __typename?: 'OrderConnection', edges?: Array<{ __typename?: 'OrderEdge', cursor?: string | null, node: { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, postOnly?: boolean | null, reduceOnly?: boolean | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder' } | null } }> | null, pageInfo?: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } | null } | null } | null };
export type OrdersQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, ordersConnection?: { __typename?: 'OrderConnection', edges?: Array<{ __typename?: 'OrderEdge', cursor?: string | null, node: { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder' } | null } }> | null, pageInfo?: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } | null } | null } | null };
export type OrderUpdateFieldsFragment = { __typename?: 'OrderUpdate', id: string, marketId: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, liquidityProvisionId?: string | null, peggedOrder?: { __typename: 'PeggedOrder' } | null };
export type OrdersUpdateSubscriptionVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
marketIds?: Types.InputMaybe<Array<Types.Scalars['ID']> | Types.Scalars['ID']>;
}>;
@@ -49,8 +47,6 @@ export const OrderFieldsFragmentDoc = gql`
expiresAt
createdAt
updatedAt
postOnly
reduceOnly
liquidityProvision {
__typename
}
@@ -116,13 +112,10 @@ export type OrderByIdQueryHookResult = ReturnType<typeof useOrderByIdQuery>;
export type OrderByIdLazyQueryHookResult = ReturnType<typeof useOrderByIdLazyQuery>;
export type OrderByIdQueryResult = Apollo.QueryResult<OrderByIdQuery, OrderByIdQueryVariables>;
export const OrdersDocument = gql`
query Orders($partyId: ID!, $marketIds: [ID!], $pagination: Pagination, $filter: OrderFilter) {
query Orders($partyId: ID!, $pagination: Pagination, $filter: OrderByMarketIdsFilter) {
party(id: $partyId) {
id
ordersConnection(
pagination: $pagination
filter: {order: $filter, marketIds: $marketIds}
) {
ordersConnection(pagination: $pagination, filter: $filter) {
edges {
node {
...OrderFields
@@ -153,7 +146,6 @@ export const OrdersDocument = gql`
* const { data, loading, error } = useOrdersQuery({
* variables: {
* partyId: // value for 'partyId'
* marketIds: // value for 'marketIds'
* pagination: // value for 'pagination'
* filter: // value for 'filter'
* },
@@ -171,8 +163,8 @@ export type OrdersQueryHookResult = ReturnType<typeof useOrdersQuery>;
export type OrdersLazyQueryHookResult = ReturnType<typeof useOrdersLazyQuery>;
export type OrdersQueryResult = Apollo.QueryResult<OrdersQuery, OrdersQueryVariables>;
export const OrdersUpdateDocument = gql`
subscription OrdersUpdate($partyId: ID!, $marketIds: [ID!]) {
orders(filter: {partyIds: [$partyId], marketIds: $marketIds}) {
subscription OrdersUpdate($partyId: ID!) {
orders(filter: {partyIds: [$partyId]}) {
...OrderUpdateFields
}
}
@@ -191,7 +183,6 @@ export const OrdersUpdateDocument = gql`
* const { data, loading, error } = useOrdersUpdateSubscription({
* variables: {
* partyId: // value for 'partyId'
* marketIds: // value for 'marketIds'
* },
* });
*/
@@ -112,7 +112,9 @@ describe('order data provider', () => {
const updatedData = update(data, delta, () => null, {
partyId: '0x123',
filter: {
dateRange: { end: new Date('2022-02-01').toISOString() },
order: {
dateRange: { end: new Date('2022-02-01').toISOString() },
},
},
});
expect(
@@ -35,40 +35,45 @@ const orderMatchFilters = (
return true;
}
if (
variables?.filter?.status &&
!(order.status && variables.filter.status.includes(order.status))
variables?.filter?.order?.status &&
!(order.status && variables.filter.order.status.includes(order.status))
) {
return false;
}
if (
variables?.filter?.types &&
!(order.type && variables.filter.types.includes(order.type))
variables?.filter?.order?.types &&
!(order.type && variables.filter.order.types.includes(order.type))
) {
return false;
}
if (
variables?.filter?.timeInForce &&
!variables.filter.timeInForce.includes(order.timeInForce)
variables?.filter?.order?.timeInForce &&
!variables.filter.order.timeInForce.includes(order.timeInForce)
) {
return false;
}
if (variables?.filter?.excludeLiquidity && order.liquidityProvisionId) {
if (
variables?.filter?.order?.excludeLiquidity &&
order.liquidityProvisionId
) {
return false;
}
if (
variables?.filter?.dateRange?.start &&
variables?.filter?.order?.dateRange?.start &&
!(
(order.updatedAt || order.createdAt) &&
variables.filter.dateRange.start < (order.updatedAt || order.createdAt)
variables.filter.order.dateRange.start <
(order.updatedAt || order.createdAt)
)
) {
return false;
}
if (
variables?.filter?.dateRange?.end &&
variables?.filter?.order?.dateRange?.end &&
!(
(order.updatedAt || order.createdAt) &&
variables.filter.dateRange.end > (order.updatedAt || order.createdAt)
variables.filter.order.dateRange.end >
(order.updatedAt || order.createdAt)
)
) {
return false;
@@ -238,17 +243,18 @@ export const hasActiveOrderProvider = makeDerivedDataProvider<
{ partyId: string; marketId?: string }
>(
[
(callback, client, { partyId, marketId }) =>
(callback, client, variables) =>
hasActiveOrderProviderInternal(callback, client, {
marketIds: marketId ? [marketId] : undefined,
filter: {
status: [OrderStatus.STATUS_ACTIVE],
excludeLiquidity: true,
order: {
status: [OrderStatus.STATUS_ACTIVE],
excludeLiquidity: true,
},
},
pagination: {
first: 1,
},
partyId,
...variables,
} as OrdersQueryVariables),
],
(parts) => parts[0]
@@ -72,10 +72,12 @@ export const useOrderListData = ({
const allVars: OrdersQueryVariables & OrdersUpdateSubscriptionVariables = {
partyId,
filter: {
dateRange: filter?.updatedAt?.value,
status: filter?.status?.value,
timeInForce: filter?.timeInForce?.value,
types: filter?.type?.value,
order: {
dateRange: filter?.updatedAt?.value,
status: filter?.status?.value,
timeInForce: filter?.timeInForce?.value,
types: filter?.type?.value,
},
},
pagination: {
first: 1000,
@@ -13,6 +13,7 @@ import {
limitOrder,
marketOrder,
} from '../mocks/generate-orders';
import { MemoryRouter } from 'react-router-dom';
// Mock theme switcher to get around inconsistent mocking of zustand
// stores
@@ -36,9 +37,11 @@ const generateJsx = (
) => {
return (
<MockedProvider>
<VegaWalletContext.Provider value={context as VegaWalletContextShape}>
<OrderListTable {...defaultProps} {...props} />
</VegaWalletContext.Provider>
<MemoryRouter>
<VegaWalletContext.Provider value={context as VegaWalletContextShape}>
<OrderListTable {...defaultProps} {...props} />
</VegaWalletContext.Provider>
</MemoryRouter>
</MockedProvider>
);
};
@@ -129,9 +129,8 @@ export const OrderListTable = memo(
}: VegaValueFormatterParams<Order, 'status'>) => {
if (data?.rejectionReason && value) {
return `${Schema.OrderStatusMapping[value]}: ${
(data?.rejectionReason &&
Schema.OrderRejectionReasonMapping[data.rejectionReason]) ||
data?.rejectionReason
data?.rejectionReason &&
Schema.OrderRejectionReasonMapping[data.rejectionReason]
}`;
}
return value ? Schema.OrderStatusMapping[value] : '';
@@ -219,14 +218,7 @@ export const OrderListTable = memo(
return `${Schema.OrderTimeInForceMapping[value]}: ${expiry}`;
}
const tifLabel = value
? Schema.OrderTimeInForceMapping[value]
: '';
const label = `${tifLabel}${
data?.postOnly ? t('. Post Only') : ''
}${data?.reduceOnly ? t('. Reduce only') : ''}`;
return label;
return value ? Schema.OrderTimeInForceMapping[value] : '';
}}
minWidth={150}
/>
+3
View File
@@ -1,5 +1,8 @@
export * from './__generated__/OrdersSubscription';
export * from './use-has-active-order';
export * from './use-order-cancel';
export * from './use-order-edit';
export * from './use-order-submit';
export * from './use-order-update';
export * from './use-pending-orders-volume';
export * from './use-order-store';
@@ -0,0 +1,138 @@
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import { act, renderHook } from '@testing-library/react';
import type { ReactNode } from 'react';
import { VegaTxStatus, VegaWalletContext } from '@vegaprotocol/wallet';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { useOrderCancel } from './use-order-cancel';
import type { OrderSubSubscription } from './';
import { OrderSubDocument } from './';
import * as Schema from '@vegaprotocol/types';
const defaultWalletContext = {
pubKey: null,
pubKeys: [],
isReadOnly: false,
sendTx: jest.fn().mockReturnValue(Promise.resolve(null)),
connect: jest.fn(),
disconnect: jest.fn(),
selectPubKey: jest.fn(),
connector: null,
};
function setup(context?: Partial<VegaWalletContextShape>) {
const mocks: MockedResponse<OrderSubSubscription> = {
request: {
query: OrderSubDocument,
variables: {
partyId: context?.pubKey || '',
},
},
result: {
data: {
orders: [
{
type: Schema.OrderType.TYPE_LIMIT,
id: '9c70716f6c3698ac7bbcddc97176025b985a6bb9a0c4507ec09c9960b3216b62',
status: Schema.OrderStatus.STATUS_ACTIVE,
rejectionReason: null,
createdAt: '2022-07-05T14:25:47.815283706Z',
expiresAt: '2022-07-05T14:25:47.815283706Z',
size: '10',
price: '300000',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
side: Schema.Side.SIDE_BUY,
marketId: 'market-id',
__typename: 'OrderUpdate',
},
],
},
},
};
const filterMocks: MockedResponse<OrderSubSubscription> = {
request: {
query: OrderSubDocument,
variables: {
partyId: context?.pubKey || '',
},
},
result: {
data: {
orders: [
{
type: Schema.OrderType.TYPE_LIMIT,
id: '9c70716f6c3698ac7bbcddc97176025b985a6bb9a0c4507ec09c9960b3216b62',
status: Schema.OrderStatus.STATUS_ACTIVE,
rejectionReason: null,
createdAt: '2022-07-05T14:25:47.815283706Z',
expiresAt: '2022-07-05T14:25:47.815283706Z',
size: '10',
price: '300000',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
side: Schema.Side.SIDE_BUY,
marketId: 'market-id',
__typename: 'OrderUpdate',
},
],
},
},
};
const wrapper = ({ children }: { children: ReactNode }) => (
<MockedProvider mocks={[mocks, filterMocks]}>
<VegaWalletContext.Provider
value={{ ...defaultWalletContext, ...context }}
>
{children}
</VegaWalletContext.Provider>
</MockedProvider>
);
return renderHook(() => useOrderCancel(), { wrapper });
}
describe('useOrderCancel', () => {
it('has the correct default state', () => {
const { result } = setup();
expect(typeof result.current.cancel).toEqual('function');
expect(typeof result.current.reset).toEqual('function');
expect(result.current.transaction.status).toEqual(VegaTxStatus.Default);
expect(result.current.transaction.txHash).toEqual(null);
expect(result.current.transaction.error).toEqual(null);
});
it('should not sendTx if no keypair', () => {
const mockSendTx = jest.fn();
const { result } = setup({
sendTx: mockSendTx,
pubKeys: [],
pubKey: null,
});
act(() => {
result.current.cancel({ orderId: 'order-id', marketId: 'market-id' });
});
expect(mockSendTx).not.toHaveBeenCalled();
});
it('should cancel a correctly formatted order', async () => {
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve({}));
const pubKeyObj = { publicKey: '0x123', name: 'test key 1' };
const { result } = setup({
sendTx: mockSendTx,
pubKeys: [pubKeyObj],
pubKey: pubKeyObj.publicKey,
});
const args = {
orderId: 'order-id',
marketId: 'market-id',
};
act(() => {
result.current.cancel(args);
});
expect(mockSendTx).toHaveBeenCalledWith(pubKeyObj.publicKey, {
orderCancellation: args,
});
});
});
@@ -0,0 +1,83 @@
import { useCallback, useState } from 'react';
import {
useVegaWallet,
useVegaTransaction,
useTransactionResult,
} from '@vegaprotocol/wallet';
import type {
OrderCancellationBody,
TransactionResult,
} from '@vegaprotocol/wallet';
import type { OrderSubFieldsFragment } from './';
import * as Sentry from '@sentry/react';
import { useOrderUpdate } from './use-order-update';
export const useOrderCancel = () => {
const { pubKey } = useVegaWallet();
const [cancelledOrder, setCancelledOrder] =
useState<OrderSubFieldsFragment | null>(null);
const [transactionResult, setTransactionResult] =
useState<TransactionResult>();
const {
send,
transaction,
reset: resetTransaction,
setComplete,
Dialog,
} = useVegaTransaction();
const waitForOrderUpdate = useOrderUpdate(transaction);
const waitForTransactionResult = useTransactionResult();
const reset = useCallback(() => {
resetTransaction();
setCancelledOrder(null);
}, [resetTransaction]);
const cancel = useCallback(
async (orderCancellation: OrderCancellationBody['orderCancellation']) => {
if (!pubKey) {
return;
}
setCancelledOrder(null);
try {
const res = await send(pubKey, {
orderCancellation,
});
if (orderCancellation.orderId) {
const cancelledOrder = await waitForOrderUpdate(
orderCancellation.orderId,
pubKey
);
setCancelledOrder(cancelledOrder);
setComplete();
} else if (res) {
const txResult = await waitForTransactionResult(
res.transactionHash,
pubKey
);
setTransactionResult(txResult);
setComplete();
}
return res;
} catch (e) {
Sentry.captureException(e);
return;
}
},
[pubKey, send, setComplete, waitForOrderUpdate, waitForTransactionResult]
);
return {
transaction,
transactionResult,
cancelledOrder,
Dialog,
cancel,
reset,
};
};
@@ -0,0 +1,179 @@
import { act, renderHook } from '@testing-library/react';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { VegaTxStatus, VegaWalletContext } from '@vegaprotocol/wallet';
import type { ReactNode } from 'react';
import { useOrderEdit } from './use-order-edit';
import type { OrderSubSubscription } from './__generated__/OrdersSubscription';
import { OrderSubDocument } from './__generated__/OrdersSubscription';
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import type { Order } from '../components';
import { generateOrder } from '../components';
import * as Schema from '@vegaprotocol/types';
const defaultWalletContext = {
pubKey: null,
pubKeys: [],
isReadOnly: false,
sendTx: jest.fn().mockReturnValue(Promise.resolve(null)),
connect: jest.fn(),
disconnect: jest.fn(),
selectPubKey: jest.fn(),
connector: null,
};
function setup(order: Order, context?: Partial<VegaWalletContextShape>) {
const mocks: MockedResponse<OrderSubSubscription> = {
request: {
query: OrderSubDocument,
variables: {
partyId: context?.pubKey || '',
},
},
result: {
data: {
orders: [
{
type: Schema.OrderType.TYPE_LIMIT,
id: '9c70716f6c3698ac7bbcddc97176025b985a6bb9a0c4507ec09c9960b3216b62',
status: Schema.OrderStatus.STATUS_ACTIVE,
rejectionReason: null,
createdAt: '2022-07-05T14:25:47.815283706Z',
expiresAt: '2022-07-05T14:25:47.815283706Z',
size: '10',
price: '300000',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
side: Schema.Side.SIDE_BUY,
marketId: 'market-id',
__typename: 'OrderUpdate',
},
],
},
},
};
const filterMocks: MockedResponse<OrderSubSubscription> = {
request: {
query: OrderSubDocument,
variables: {
partyId: context?.pubKey || '',
},
},
result: {
data: {
orders: [
{
type: Schema.OrderType.TYPE_LIMIT,
id: '9c70716f6c3698ac7bbcddc97176025b985a6bb9a0c4507ec09c9960b3216b62',
status: Schema.OrderStatus.STATUS_ACTIVE,
rejectionReason: null,
createdAt: '2022-07-05T14:25:47.815283706Z',
expiresAt: '2022-07-05T14:25:47.815283706Z',
size: '10',
price: '300000',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
side: Schema.Side.SIDE_BUY,
marketId: 'market-id',
__typename: 'OrderUpdate',
},
],
},
},
};
const wrapper = ({ children }: { children: ReactNode }) => (
<MockedProvider mocks={[mocks, filterMocks]}>
<VegaWalletContext.Provider
value={{ ...defaultWalletContext, ...context }}
>
{children}
</VegaWalletContext.Provider>
</MockedProvider>
);
return renderHook(() => useOrderEdit(order), { wrapper });
}
describe('useOrderEdit', () => {
it('should edit a correctly formatted order if there is no size', async () => {
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve({}));
const pubKeyObj = { publicKey: '0x123', name: 'test key 1' };
const order = generateOrder({
price: '123456789',
market: { decimalPlaces: 2 },
});
const { result } = setup(order, {
sendTx: mockSendTx,
pubKeys: [pubKeyObj],
pubKey: pubKeyObj.publicKey,
});
act(() => {
result.current.edit({ price: '1234567.89' });
});
expect(mockSendTx).toHaveBeenCalledWith(pubKeyObj.publicKey, {
orderAmendment: {
orderId: order.id,
// eslint-disable-next-line
marketId: order.market!.id,
timeInForce: order.timeInForce,
price: '123456789', // Decimal removed
sizeDelta: 0,
expiresAt: undefined,
},
});
});
it('should edit a correctly formatted order', async () => {
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve({}));
const pubKeyObj = { publicKey: '0x123', name: 'test key 1' };
const order = generateOrder({
price: '123456789',
market: { decimalPlaces: 2 },
});
const { result } = setup(order, {
sendTx: mockSendTx,
pubKeys: [pubKeyObj],
pubKey: pubKeyObj.publicKey,
});
act(() => {
result.current.edit({ price: '1234567.89', size: '20' });
});
expect(mockSendTx).toHaveBeenCalledWith(pubKeyObj.publicKey, {
orderAmendment: {
orderId: order.id,
// eslint-disable-next-line
marketId: order.market!.id,
timeInForce: order.timeInForce,
price: '123456789', // Decimal removed
sizeDelta: 1990,
expiresAt: undefined,
},
});
});
it('has the correct default state', () => {
const order = generateOrder();
const { result } = setup(order);
expect(typeof result.current.edit).toEqual('function');
expect(typeof result.current.reset).toEqual('function');
expect(result.current.transaction.status).toEqual(VegaTxStatus.Default);
expect(result.current.transaction.txHash).toEqual(null);
expect(result.current.transaction.error).toEqual(null);
});
it('should not sendTx if no keypair', async () => {
const order = generateOrder();
const mockSendTx = jest.fn();
const { result } = setup(order, {
sendTx: mockSendTx,
pubKeys: [],
pubKey: null,
});
await act(async () => {
result.current.edit(order);
});
expect(mockSendTx).not.toHaveBeenCalled();
});
});
@@ -0,0 +1,82 @@
import { removeDecimal, toNanoSeconds } from '@vegaprotocol/utils';
import { useState, useCallback } from 'react';
import { useVegaTransaction, useVegaWallet } from '@vegaprotocol/wallet';
import type { OrderSubFieldsFragment } from './';
import * as Sentry from '@sentry/react';
import type { Order } from '../components';
import { useOrderUpdate } from './use-order-update';
import BigNumber from 'bignumber.js';
export interface EditOrderArgs {
price: string;
size?: string;
}
export const useOrderEdit = (order: Order | null) => {
const { pubKey } = useVegaWallet();
const [updatedOrder, setUpdatedOrder] =
useState<OrderSubFieldsFragment | null>(null);
const {
send,
transaction,
reset: resetTransaction,
setComplete,
Dialog,
} = useVegaTransaction();
const waitForOrderUpdate = useOrderUpdate(transaction);
const reset = useCallback(() => {
resetTransaction();
setUpdatedOrder(null);
}, [resetTransaction]);
const edit = useCallback(
async (args: EditOrderArgs) => {
if (!pubKey || !order || !order.market) {
return;
}
setUpdatedOrder(null);
try {
await send(pubKey, {
orderAmendment: {
orderId: order.id,
marketId: order.market.id,
price: removeDecimal(args.price, order.market.decimalPlaces),
timeInForce: order.timeInForce,
sizeDelta: args.size
? new BigNumber(
removeDecimal(args.size, order.market.positionDecimalPlaces)
)
.minus(order.size)
.toNumber()
: 0,
expiresAt: order.expiresAt
? toNanoSeconds(order.expiresAt) // Wallet expects timestamp in nanoseconds
: undefined,
},
});
const updatedOrder = await waitForOrderUpdate(order.id, pubKey);
setUpdatedOrder(updatedOrder);
setComplete();
} catch (e) {
Sentry.captureException(e);
return;
}
},
[pubKey, send, order, setComplete, waitForOrderUpdate]
);
return {
transaction,
updatedOrder,
Dialog,
edit,
reset,
};
};

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