Compare commits

..
Author SHA1 Message Date
sam-keen 1a22d11447 Merge remote-tracking branch 'origin/develop' into fix/claim 2023-05-12 14:29:43 +01:00
Mikołaj Młodzikowski d5f0bd419e feat(ci): update vega landing page whenever new console is released (#3751) 2023-05-12 15:18:15 +02:00
Sam KeenandBartłomiej Głownia 2c7934a8c7 fix(governance): fix time delay notice for withdrawal of 0.00 assets (#3740)
Co-authored-by: Bartłomiej Głownia <bglownia@gmail.com>
2023-05-12 15:04:53 +02:00
Joe Tsang fae61c28ca chore(cypress): remove cypress project id (#3739) 2023-05-12 13:44:22 +01:00
dexturr fcd4541625 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-12 12:10:20 +00:00
Sam KeenandJoe 8e0a92fdec fix(governance): network upgrade proposal e2e test tweak (#3748)
Co-authored-by: Joe <joe@vega.xyz>
2023-05-12 09:58:10 +01:00
Dexter 2cb4aa1029 fix: claim not encoding ascii correctly 2023-05-12 08:59:15 +01:00
dexturr 6e7109ed87 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-12 06:09:45 +00:00
dexturr f2c698ce06 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-12 00:16:15 +00:00
dexturr ad9a3a3400 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-11 18:10:08 +00:00
Sam Keen efeac49d58 feat(governance): change status labels for network upgrade proposals (#3711) 2023-05-11 15:43:27 +00:00
Mikołaj Młodzikowski 2d430710ab feat(ci): use actual secret in the script 2023-05-11 17:31:12 +02:00
Mikołaj Młodzikowski bca1a98985 fix(ci): remove old dockerfile 2023-05-11 17:16:28 +02:00
Edd 8a080d3279 fix(explorer): fix bug where no votes displayed a yes icon (#3699) 2023-05-11 14:55:00 +00:00
Mikołaj Młodzikowski f121836b4e feat(ci): fleek updates (#3724) 2023-05-11 15:42:35 +02:00
Sam Keen b641c82ad8 feat(governance): voting blog link and button title (#3717) 2023-05-11 12:59:28 +00:00
Sam Keen bf73559c30 fix(governance): change proposal sort order (#3716) 2023-05-11 12:39:30 +00:00
dexturr 653cec2592 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-11 12:07:57 +00:00
m.ray 579c884a5a feat(trading): show pegged order offset and reference in the order table (#3675) 2023-05-11 12:43:20 +01:00
Bartłomiej Głownia b2279c7e47 feat(trading): hook up new EstimatePosition and EstimateFees api methods (#3634) 2023-05-11 12:58:12 +02:00
Sam Keen a8c17b6807 fix(governance): tranches data (#3708) 2023-05-11 10:22:52 +00:00
Sam Keen b4c7dc6f59 fix(governance): more thorough catch for url in error reporting (#3686) 2023-05-11 10:21:16 +00:00
daro-maj d44392bebf test(trading): show full oracle profile info in markets test (#3695) 2023-05-11 08:18:00 +02:00
dexturr bd679957e2 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-11 06:07:33 +00:00
dexturr e30d48555e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-11 00:16:19 +00:00
Sam Keen b4b2416780 fix(governance): my stake share 2 dp (#3685) 2023-05-10 16:23:37 +00:00
Maciek dc7832ac81 chore(candles-chart): fill up missing candles (#3664) 2023-05-10 15:26:27 +00:00
Joe Tsang 91207d31ee chore(governance): fix failing validator tests (#3703) 2023-05-10 15:03:47 +00:00
dexturr 58efb460a6 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-10 12:10:00 +00:00
Mikołaj Młodzikowski 044f98777b feat(ci): lint makefile 2023-05-10 13:43:33 +02:00
Mikołaj Młodzikowski 00fdc5e81a fix(ci): preparing dist bash syntax 2023-05-10 13:33:17 +02:00
Mikołaj Młodzikowski adfad3bafb feat(ci): fix typo 2023-05-10 13:21:53 +02:00
dexturr e6ae88905c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-10 06:07:15 +00:00
dexturr 3953ed9953 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-10 00:15:02 +00:00
Matthew Russell b48ab58e6a chore: add VEGA_URL for explorer mainnet 2023-05-09 14:06:10 -07:00
Matthew Russell e44cf1ff53 chore(environment,trading): fix unit tests for footer and node switcher (#3682) 2023-05-09 13:39:17 -07:00
Matthew Russell bded1d32ba feat(explorer,governance): always allow selecting a node, add node guard (#3678) 2023-05-09 12:58:09 -07:00
Edd 76ddf45f4c fix(explorer): add envs back to env (#3677) 2023-05-09 12:57:38 -07:00
Dexter Edwards 6a2c9004d8 fix(governance, console, explorer): show vega node in list, cache bust (#3676) 2023-05-09 12:57:11 -07:00
dexturr 77ee88b43e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-09 18:09:50 +00:00
Dexter e4958ce3c0 fix: do not show validators with 0 self stake in list 2023-05-09 18:25:20 +01:00
Mikołaj Młodzikowski ecedb879f8 fix(ci): define which app ipfs hash is set for 2023-05-09 18:47:50 +02:00
m.ray 89700803d6 fix(trading): fix release script for mainnet (#3669) 2023-05-09 16:30:05 +00:00
Mikołaj Młodzikowski 65c9fa5df2 feat(ci): establish build based on docker to produce ipfs-hashes (#3612) 2023-05-09 18:24:39 +02:00
ArtandMadalina Raicu 02399c40fc fix(trading): welcome dialog links (#3656)
Co-authored-by: Madalina Raicu <madalina@raygroup.uk>
2023-05-09 15:43:36 +00:00
110 changed files with 2363 additions and 1204 deletions
+3 -3
View File
@@ -5,6 +5,8 @@ on:
branches: branches:
- release/* - release/*
- develop - develop
tags:
- v*
pull_request: pull_request:
types: types:
- opened - opened
@@ -97,15 +99,13 @@ jobs:
- name: See affected apps - name: See affected apps
run: | run: |
echo ">>>> debug" echo ">>>> debug"
echo "NX Version: $nx_version"
echo "NX_BASE: ${{ env.NX_BASE }}" echo "NX_BASE: ${{ env.NX_BASE }}"
echo "NX_HEAD: ${{ env.NX_HEAD }}" echo "NX_HEAD: ${{ env.NX_HEAD }}"
echo ">>>> eof debug" echo ">>>> eof debug"
affected="$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects)" affected="$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects)"
echo -n "Affected projects: $affected"
branch_slug="$(echo ${{ github.head_ref || github.ref_name }} | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | cut -c 1-50 )" branch_slug="$(echo '${{ github.head_ref || github.ref_name }}' | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | cut -c 1-50 )"
projects_e2e="" projects_e2e=""
preview_governance="not deployed" preview_governance="not deployed"
preview_trading="not deployed" preview_trading="not deployed"
-1
View File
@@ -33,5 +33,4 @@ jobs:
config: baseUrl=${{ github.event.inputs.url }} config: baseUrl=${{ github.event.inputs.url }}
env: grepTags=@live env: grepTags=@live
env: env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -66,7 +66,7 @@ jobs:
###### ######
- name: Run Cypress tests - name: Run Cypress tests
run: yarn nx run ${{ matrix.project }}:e2e ${{ env.SKIP_CACHE }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome --env.grepTags="${{ inputs.tags }}" run: yarn nx run ${{ matrix.project }}:e2e ${{ env.SKIP_CACHE }} --browser chrome --env.grepTags="${{ inputs.tags }}"
working-directory: frontend-monorepo working-directory: frontend-monorepo
env: env:
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }} CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
+87 -34
View File
@@ -30,13 +30,21 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry - name: Log in to the Container registry (ghcr)
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to the Container registry (docker hub)
uses: docker/login-action@v2
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
with:
# registry: registry.hub.docker.com
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Setup node - name: Setup node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
@@ -54,30 +62,24 @@ jobs:
- name: Define variables - name: Define variables
run: | run: |
envName='' envName=''
dockerfile="dist.Dockerfile"
if [[ "${{ github.event_name }}" = "push" ]]; then if [[ "${{ github.event_name }}" = "push" ]]; then
domain="vega.rocks" domain="vega.rocks"
if [[ "${{ github.ref }}" =~ .*release/.* ]]; then if [[ "${{ github.ref }}" =~ .*release/.* ]]; then
envName="$(echo ${{ github.ref }} | rev | cut -d '/' -f 1 | rev)" envName="$(echo ${{ github.ref }} | rev | cut -d '/' -f 1 | rev)"
if [[ "${{ github.ref }}" =~ .*mainnet.* ]]; then if [[ "${{ github.ref }}" =~ .*mainnet.* ]]; then
domain="vega.community" domain="vega.community"
if [[ "${{ matrix.app }}" = "trading" ]]; then
dockerfile="ipfs.Dockerfile"
fi
fi fi
elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then
envName="stagnet1" envName="stagnet1"
elif [[ "${{ matrix.app}}" = "trading" ]] && [[ ${{ startsWith(github.ref, 'refs/tags/v') && 'true' || 'false' }} = "true" ]]; then
envName="mainnet"
fi fi
bucketName="${{ matrix.app }}.${envName}.${domain}" bucketName="${{ matrix.app }}.${envName}.${domain}"
echo BUCKET_NAME=${bucketName} >> $GITHUB_ENV echo BUCKET_NAME=${bucketName} >> $GITHUB_ENV
fi fi
nodeVersion=$(cat .nvmrc | head -n 1)
echo ENV_NAME=${envName} >> $GITHUB_ENV echo ENV_NAME=${envName} >> $GITHUB_ENV
echo NODE_VERSION=${nodeVersion} >> $GITHUB_ENV
echo DOCKERFILE=docker/${dockerfile} >> $GITHUB_ENV
- name: Build local dist - name: Build local dist
if: ${{ env.DOCKERFILE != 'docker/ipfs.Dockerfile' }}
run: | run: |
flags="" flags=""
if [[ ! -z "${{ env.ENV_NAME }}" ]]; then if [[ ! -z "${{ env.ENV_NAME }}" ]]; then
@@ -98,62 +100,60 @@ jobs:
- name: Build and export to local Docker - name: Build and export to local Docker
id: docker_build id: docker_build
if: ${{ github.event_name == 'pull_request' || ( env.DOCKERFILE == 'docker/ipfs.Dockerfile' && github.event_name == 'push' ) }}
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
with: with:
context: . context: .
file: ${{ env.DOCKERFILE }} file: docker/node-outside-docker.Dockerfile
load: true load: true
build-args: | build-args: |
APP=${{ matrix.app }} APP=${{ matrix.app }}
NODE_VERSION=${{ env.NODE_VERSION }}
ENV_NAME=${{ env.ENV_NAME }} ENV_NAME=${{ env.ENV_NAME }}
tags: | tags: |
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
- name: Image digest - name: Image digest
if: ${{ github.event_name == 'pull_request' || ( env.DOCKERFILE == 'docker/ipfs.Dockerfile' && github.event_name == 'push' ) }} if: ${{ github.event_name == 'pull_request' }}
run: echo ${{ steps.docker_build.outputs.digest }} run: echo ${{ steps.docker_build.outputs.digest }}
- name: Sanity check docker image - name: Sanity check docker image
if: ${{ github.event_name == 'pull_request' || ( env.DOCKERFILE == 'docker/ipfs.Dockerfile' && github.event_name == 'push' ) }}
run: | run: |
echo "Check ipfs-hash" echo "Check ipfs-hash"
if [[ "${{ env.DOCKERFILE }}" = "docker/ipfs.Dockerfile" ]]; then docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat /ipfs-hash
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat /ipfs-hash docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat /ipfs-hash > ${{ matrix.app }}-ipfs-hash
fi
echo "List html directory" echo "List html directory"
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local sh -c 'apk add --update tree; tree .' docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local sh -c 'apk add --update tree; tree /usr/share/nginx/html'
- name: Copy dist to local filesystem - name: Publish dist as docker image (ghcr)
if: ${{ env.DOCKERFILE == 'docker/ipfs.Dockerfile' && github.event_name == 'push' }}
run: |
docker create --name=dist ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
docker cp dist:/usr/share/nginx/html dist
echo "check local dist files"
tree dist/html
mv dist/html dist-result
- name: Publish dist as docker image
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
if: ${{ github.event_name == 'pull_request' }} if: ${{ github.event_name == 'pull_request' || (matrix.app == 'trading' && github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/v') ) }}
with: with:
context: . context: .
file: ${{ env.DOCKERFILE }} file: docker/node-outside-docker.Dockerfile
push: true push: true
build-args: | build-args: |
APP=${{ matrix.app }} APP=${{ matrix.app }}
NODE_VERSION=${{ env.NODE_VERSION }}
ENV_NAME=${{ env.ENV_NAME }} ENV_NAME=${{ env.ENV_NAME }}
tags: | tags: |
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:${{ github.event.pull_request.head.sha || github.sha }} ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:${{ github.event.pull_request.head.sha || github.sha }}
- name: Publish dist as docker image (docker hub)
uses: docker/build-push-action@v3
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
with:
context: .
file: docker/node-outside-docker.Dockerfile
push: true
build-args: |
APP=${{ matrix.app }}
ENV_NAME=${{ env.ENV_NAME }}
tags: |
vegaprotocol/${{ matrix.app }}:${{ github.ref_name }}
vegaprotocol/${{ matrix.app }}:mainnet
# bucket creation in github.com/vegaprotocol/terraform//frontend # bucket creation in github.com/vegaprotocol/terraform//frontend
- name: Publish dist to s3 - name: Publish dist to s3
uses: jakejarvis/s3-sync-action@master uses: jakejarvis/s3-sync-action@master
if: ${{ github.event_name == 'push' }} if: ${{ github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/v') }}
with: with:
args: --acl private --follow-symlinks --delete args: --acl private --follow-symlinks --delete
env: env:
@@ -169,3 +169,56 @@ jobs:
with: with:
labels: ${{ matrix.app }}-preview labels: ${{ matrix.app }}-preview
number: ${{ github.event.number }} number: ${{ github.event.number }}
- name: Add ipfs hash to release
uses: softprops/action-gh-release@v1
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
with:
files: ${{ matrix.app }}-ipfs-hash
- name: Trigger fleek deployment
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
run: |
# display info about app
curl -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"query": "query{getSiteById(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id latestDeploy{id status}}}"}' \
https://api.fleek.co/graphql
# trigger new deployment as base image is always set to vegaprotocol/trading:mainnet
curl -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"query": "mutation{triggerDeploy(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id status}}"}' \
https://api.fleek.co/graphql
- name: Checkout vega.xyz
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
uses: actions/checkout@v3
with:
repository: vegaprotocol/vega.xyz
path: './vega-xyz'
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
- name: Update hash on interstitial page
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
env:
GH_TOKEN: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
run: |
curl -L https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz -o kubo.tgz
tar -xzf kubo.tgz
export PATH="$PATH:$PWD/kubo"
which ipfs
new_hash=$(cat ${{ matrix.app }}-ipfs-hash)
cd vega-xyz
./interstital-allow-update.sh ${new_hash}
git config --global user.email "vega-ci-bot@vega.xyz"
git config --global user.name "vega-ci-bot"
branch_name=${{ github.ref_name }}-hash-update
git checkout -b "${branch_name}"
git add interstitial-allow.json netlify.toml
commit_msg="feat(ci): Update CID for console release @ ${{ github.ref }}"
git commit -m "${commit_msg}"
git push -u origin "${branch_name}"
pr_url="$(gh pr create --title "${commit_msg}" --body 'update ipfs hash for console @ ${{ github.ref }}')"
echo $pr_url
gh pr merge --auto $pr_url
+1
View File
@@ -2,6 +2,7 @@
# compiled output # compiled output
/dist /dist
/dist-result
/tmp /tmp
/out-tsc /out-tsc
/tools/executors/**/*.js /tools/executors/**/*.js
+20
View File
@@ -0,0 +1,20 @@
.PHONY: latest-release
latest-release:
gh release list | head -n 1 | awk '{print $1}'
.PHONY: show-latest-release
show-latest-release:
gh release view `gh release list | head -n 1 | awk '{print $1}'`
.PHONY: recalculate-ipfs
recalculate-ipfs:
echo "ipfs hash inside the image"
docker run --rm ${TAG} cat /ipfs-hash
echo "recalculating ipfs hash"
docker run --rm ${TAG} ipfs add -r /usr/share/nginx/html
.PHONY: eject-ipfs-hash
unpack:
docker create --name=dist ${TAG}
docker cp dist:/usr/share/nginx/html dist
docker rm dist
+55 -12
View File
@@ -103,25 +103,68 @@ In CI linting, formatting and also run. These checks can be seen in the [CI work
Visit the [Nx Documentation](https://nx.dev/getting-started/intro) to learn more. Visit the [Nx Documentation](https://nx.dev/getting-started/intro) to learn more.
# Docker & Vegacapsule # 🐋 Hosting a console
## Docker To host a console there are two possible build scenarios for running the frontends: nx performed **outside** or **inside** docker build. For specific build instructions follow [build instructions](#build-instructions).
The [Dockerfile](./dockerfiles) for running the frontends is pretty basic, merely building the application with the APP arg that is passed in and serving the application from [nginx](./nginx/nginx.conf). The only complexity that exists is that there is a script which allows the passing of run time environment variables to the containers. See configuration below for how to do this. In order to run a container on port 3000:
You can build any of the containers locally with the following command:
```bash
docker build --dockerfile dockerfiles/Dockerfile.cra . --build-arg APP=[YOUR APP] --tag=[TAG]
```
In order to run a container:
```bash ```bash
docker run -p 3000:80 [TAG] docker run -p 3000:80 [TAG]
``` ```
Images ending with `.dist` are to pack locally created transpiled HTML files into nginx container for non-compatible with yarn architectures like M1 Mac ## Build instructions
The [`docker`](./docker) subfolder has some docker configurations for easily setting up your own hosted version of console either for the web, or ready for pinning on IPFS
### nx build outside the docker
Packaging prepared dist into [`nginx`](https://hub.docker.com/_/nginx)([server configuration](./nginx/nginx.conf)) docker image involves building the application on docker host machine from source.
As a prerequisite you need to perform build of `dist` directory and move its content for specific application to `dist-result` directory. Use following script to do it with a single command:
```bash
./docker/prepare-dist.sh
```
You can build any of the containers locally with the following command:
```bash
docker build --dockerfile docker/node-outside-docker.Dockerfile . --tag=[TAG]
```
### nx build inside the docker
Using multistage dockerfile dist is compiled using [node](https://hub.docker.com/_/node) image and later packed to nginx as in [dist build](#dist-build) example.
```bash
docker build --build-arg APP=[YOUR APP] --build-arg NODE_VERSION=$(cat .nvmrc) --build-arg ENV_NAME=mainnet -t [TAG] -f docker/node-inside-docker.Dockerfile .
```
### Computing ipfs-hash of the build
At the moment this feature is important only for `trading` (console) releases.
Each docker build finishes with hash calculation for dist directory. Resulting hash is added to file named as `/ipfs-hash`. Once docker image is produced you can run following commad to display ipfs-hash:
```bash
make recalculate-ipfs TAG=vegaprotocol/trading:{YOUR_VERSION}
```
**updating hash:** recompiling dist directory (even if there are no changed to source code) results in different hash computed by ipfs command.
### Verifying ipfs-hash of existing current application version
An IPFS CID will be attached to every [release](https://github.com/vegaprotocol/frontend-monorepo/releases). If you are intending to pin an application on IPFS, you can check that your build matches by running the following steps:
1. Show latest release by runnning: `make latest-release`. You need to configure [`gh`](https://cli.github.com/) for this step to work, otherwise please provide release manually from [github](https://github.com/vegaprotocol/frontend-monorepo/releases) or [dockerhub](https://hub.docker.com/r/vegaprotocol/trading)
2. Set RELEASE environment variable to value that you want to validate: `export RELEASE=$(make latest-release)` or `export RELEASE=vXX.XX.XX`
3. Set TAG environment variable to image that you want to validate: `export TAG=vegaprotocol/trading:$RELEASE`
4. Download docker image with the desired release `docker pull $TAG`.
5. Recalculate hash: `make recalculate-ipfs`
6. You should see exactly same hash produced by ipfs command as one placed with the release notes: `make show-latest-release`
7. If you want to extract dist from docker image to your local filesystem you can run following command: `make unpack`
8. Now `dist` directory contains valid application build. **it is not possible to calculate same ipfs hash on files that are result of copy operation**
## Config ## Config
+1
View File
@@ -13,6 +13,7 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_VEGA_GOVERNANCE_URL=https://stagnet1.governance.vega.xyz NX_VEGA_GOVERNANCE_URL=https://stagnet1.governance.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.jsoo NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.jsoo
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
# App flags # App flags
NX_EXPLORER_ASSETS=1 NX_EXPLORER_ASSETS=1
+1
View File
@@ -2,6 +2,7 @@
NX_TENDERMINT_URL=https://be.vega.community NX_TENDERMINT_URL=https://be.vega.community
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_ENV=MAINNET NX_VEGA_ENV=MAINNET
NX_BLOCK_EXPLORER=https://be.vega.community/rest/ NX_BLOCK_EXPLORER=https://be.vega.community/rest/
NX_ETHERSCAN_URL=https://etherscan.io NX_ETHERSCAN_URL=https://etherscan.io
+2 -1
View File
@@ -1 +1,2 @@
# .env is stagnet1, so there are no overrides required # .env is stagnet1, so there are no overrides required
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz","STAGNET3":"https://stagnet3.explorer.vega.xyz"}'
+24 -2
View File
@@ -1,9 +1,18 @@
import { NetworkLoader, useInitializeEnv } from '@vegaprotocol/environment'; import {
AppFailure,
NetworkLoader,
NodeGuard,
NodeSwitcherDialog,
useEnvironment,
useInitializeEnv,
useNodeSwitcherStore,
} from '@vegaprotocol/environment';
import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-websocket-provider'; import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-websocket-provider';
import { Loader, Splash } from '@vegaprotocol/ui-toolkit'; import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
import { DEFAULT_CACHE_CONFIG } from '@vegaprotocol/apollo-client'; import { DEFAULT_CACHE_CONFIG } from '@vegaprotocol/apollo-client';
import { RouterProvider } from 'react-router-dom'; import { RouterProvider } from 'react-router-dom';
import { router } from './routes/router-config'; import { router } from './routes/router-config';
import { t } from '@vegaprotocol/i18n';
const splashLoading = ( const splashLoading = (
<Splash> <Splash>
@@ -12,10 +21,23 @@ const splashLoading = (
); );
function App() { function App() {
const { VEGA_URL } = useEnvironment();
const [nodeSwitcherOpen, setNodeSwitcherOpen] = useNodeSwitcherStore(
(store) => [store.dialogOpen, store.setDialogOpen]
);
return ( return (
<TendermintWebsocketProvider> <TendermintWebsocketProvider>
<NetworkLoader cache={DEFAULT_CACHE_CONFIG}> <NetworkLoader cache={DEFAULT_CACHE_CONFIG}>
<RouterProvider router={router} fallbackElement={splashLoading} /> <NodeGuard
skeleton={<div>{t('Loading')}</div>}
failure={<AppFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />}
>
<RouterProvider router={router} fallbackElement={splashLoading} />
</NodeGuard>
<NodeSwitcherDialog
open={nodeSwitcherOpen}
setOpen={setNodeSwitcherOpen}
/>
</NetworkLoader> </NetworkLoader>
</TendermintWebsocketProvider> </TendermintWebsocketProvider>
); );
@@ -1,13 +1,19 @@
import { NodeSwitcherDialog, useEnvironment } from '@vegaprotocol/environment'; import {
useEnvironment,
useNodeSwitcherStore,
} from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { useScreenDimensions } from '@vegaprotocol/react-helpers'; import { useScreenDimensions } from '@vegaprotocol/react-helpers';
import { ExternalLink, Link } from '@vegaprotocol/ui-toolkit'; import { ExternalLink, Link } from '@vegaprotocol/ui-toolkit';
import { useMemo, useState } from 'react'; import { useMemo } from 'react';
import { ENV } from '../../config/env'; import { ENV } from '../../config/env';
export const Footer = () => { export const Footer = () => {
const { VEGA_URL, GIT_COMMIT_HASH, GIT_ORIGIN_URL } = useEnvironment(); const { VEGA_URL, GIT_COMMIT_HASH, GIT_ORIGIN_URL } = useEnvironment();
const [nodeSwitcherOpen, setNodeSwitcherOpen] = useState(false); const setNodeSwitcherOpen = useNodeSwitcherStore(
(store) => store.setDialogOpen
);
const { screenSize } = useScreenDimensions(); const { screenSize } = useScreenDimensions();
const showFullFeedbackLabel = useMemo( const showFullFeedbackLabel = useMemo(
() => ['md', 'lg', 'xl', 'xxl', 'xxxl'].includes(screenSize), () => ['md', 'lg', 'xl', 'xxl', 'xxxl'].includes(screenSize),
@@ -15,46 +21,40 @@ export const Footer = () => {
); );
return ( return (
<> <footer className="grid grid-rows-2 grid-cols-[1fr_auto] text-xs md:text-md md:flex md:col-span-2 px-4 py-2 gap-4 border-t border-vega-light-200 dark:border-vega-dark-200">
<footer className="grid grid-rows-2 grid-cols-[1fr_auto] text-xs md:text-md md:flex md:col-span-2 px-4 py-2 gap-4 border-t border-vega-light-200 dark:border-vega-dark-200"> <div className="flex justify-between gap-2 align-middle">
<div className="flex justify-between gap-2 align-middle"> {GIT_COMMIT_HASH && (
{GIT_COMMIT_HASH && ( <div className="content-center flex border-r border-neutral-700 dark:border-neutral-300 pr-4">
<div className="content-center flex border-r border-neutral-700 dark:border-neutral-300 pr-4"> <p data-testid="git-commit-hash">
<p data-testid="git-commit-hash"> {t('Version')}:{' '}
{t('Version')}:{' '} <Link
<Link href={
href={ GIT_ORIGIN_URL
GIT_ORIGIN_URL ? `${GIT_ORIGIN_URL}/commit/${GIT_COMMIT_HASH}`
? `${GIT_ORIGIN_URL}/commit/${GIT_COMMIT_HASH}` : undefined
: undefined }
} target={GIT_ORIGIN_URL ? '_blank' : undefined}
target={GIT_ORIGIN_URL ? '_blank' : undefined} >
> {GIT_COMMIT_HASH}
{GIT_COMMIT_HASH} </Link>
</Link> </p>
</p>
</div>
)}
<div className="content-center flex pl-2 md:border-r border-neutral-700 dark:border-neutral-300 pr-4">
{VEGA_URL && <NodeUrl url={VEGA_URL} />}
<Link className="ml-2" onClick={() => setNodeSwitcherOpen(true)}>
{t('Change')}
</Link>
</div> </div>
)}
<div className="flex pl-2 content-center"> <div className="content-center flex pl-2 md:border-r border-neutral-700 dark:border-neutral-300 pr-4">
<ExternalLink href={ENV.addresses.feedback}> {VEGA_URL && <NodeUrl url={VEGA_URL} />}
{showFullFeedbackLabel ? t('Share your feedback') : t('Feedback')} <Link className="ml-2" onClick={() => setNodeSwitcherOpen(true)}>
</ExternalLink> {t('Change')}
</div> </Link>
</div> </div>
</footer>
<NodeSwitcherDialog <div className="flex pl-2 content-center">
open={nodeSwitcherOpen} <ExternalLink href={ENV.addresses.feedback}>
setOpen={setNodeSwitcherOpen} {showFullFeedbackLabel ? t('Share your feedback') : t('Feedback')}
/> </ExternalLink>
</> </div>
</div>
</footer>
); );
}; };
@@ -1,3 +1,5 @@
import { Icon } from '@vegaprotocol/ui-toolkit';
// https://github.com/vegaprotocol/vega/blob/develop/core/blockchain/response.go // https://github.com/vegaprotocol/vega/blob/develop/core/blockchain/response.go
export const ErrorCodes = new Map([ export const ErrorCodes = new Map([
[51, 'Transaction failed validation'], [51, 'Transaction failed validation'],
@@ -28,7 +30,11 @@ export const ChainResponseCode = ({
}: ChainResponseCodeProps) => { }: ChainResponseCodeProps) => {
const isSuccess = successCodes.has(code); const isSuccess = successCodes.has(code);
const icon = isSuccess ? '✅' : '❌'; const icon = isSuccess ? (
<Icon name="tick-circle" className="fill-vega-green-550" />
) : (
<Icon name="cross" className="fill-vega-pink-550" />
);
const label = ErrorCodes.get(code) || 'Unknown response code'; const label = ErrorCodes.get(code) || 'Unknown response code';
// Hack for batches with many errors - see https://github.com/vegaprotocol/vega/issues/7245 // Hack for batches with many errors - see https://github.com/vegaprotocol/vega/issues/7245
@@ -36,7 +42,7 @@ export const ChainResponseCode = ({
error && error.length > 100 ? error.replace(/,/g, ',\r\n') : error; error && error.length > 100 ? error.replace(/,/g, ',\r\n') : error;
return ( return (
<div title={`Response code: ${code} - ${label}`} className="inline-block"> <div title={`Response code: ${code} - ${label}`} className=" inline-block">
<span <span
className="mr-2" className="mr-2"
aria-label={isSuccess ? 'Success' : 'Warning'} aria-label={isSuccess ? 'Success' : 'Warning'}
@@ -4,6 +4,7 @@ import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint
import { TxDetailsShared } from './shared/tx-details-shared'; import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table'; import { TableCell, TableRow, TableWithTbody } from '../../table';
import ProposalLink from '../../links/proposal-link/proposal-link'; import ProposalLink from '../../links/proposal-link/proposal-link';
import { VoteIcon } from '../../vote-icon/vote-icon';
interface TxProposalVoteProps { interface TxProposalVoteProps {
txData: BlockExplorerTransactionResult | undefined; txData: BlockExplorerTransactionResult | undefined;
@@ -30,27 +31,22 @@ export const TxProposalVote = ({
return <>{t('Awaiting Block Explorer transaction details')}</>; return <>{t('Awaiting Block Explorer transaction details')}</>;
} }
const vote = txData.command.voteSubmission.value ? '👍' : '👎'; const vote = txData.command.voteSubmission.value === 'VALUE_YES';
return ( return (
<TableWithTbody className="mb-8" allowWrap={true}> <TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} /> <TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TableRow modifier="bordered">
<TableCell>{t('Proposal ID')}</TableCell>
<TableCell>{txData.command.voteSubmission.proposalId}</TableCell>
</TableRow>
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Proposal details')}</TableCell> <TableCell>{t('Proposal details')}</TableCell>
<TableCell> <TableCell>
<ProposalLink id={txData.command.voteSubmission.proposalId} /> <ProposalLink id={txData.command.voteSubmission.proposalId} />
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Proposal')}</TableCell>
<TableCell>{txData.command.voteSubmission.proposalId}</TableCell>
</TableRow>
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Vote')}</TableCell> <TableCell>{t('Vote')}</TableCell>
<TableCell>{vote}</TableCell> <TableCell>
<VoteIcon vote={vote} />
</TableCell>
</TableRow> </TableRow>
</TableWithTbody> </TableWithTbody>
); );
@@ -1,5 +1,6 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { components } from '../../../types/explorer'; import type { components } from '../../../types/explorer';
import { VoteIcon } from '../vote-icon/vote-icon';
interface TxOrderTypeProps { interface TxOrderTypeProps {
orderType: string; orderType: string;
@@ -137,12 +138,15 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
let type = displayString[orderType] || orderType; let type = displayString[orderType] || orderType;
let colours = let colours =
'text-white dark:text-white bg-vega-dark-150 dark:bg-vega-dark-150'; 'text-white dark:text-white bg-vega-dark-150 dark:bg-vega-dark-250';
// This will get unwieldy and should probably produce a different colour of tag // This will get unwieldy and should probably produce a different colour of tag
if (type === 'Chain Event' && !!command?.chainEvent) { if (type === 'Chain Event' && !!command?.chainEvent) {
type = getLabelForChainEvent(command.chainEvent); type = getLabelForChainEvent(command.chainEvent);
colours = 'text-white dark-text-white bg-vega-pink dark:bg-vega-pink'; colours = 'text-white dark-text-white bg-vega-pink dark:bg-vega-pink';
} else if (type === 'Validator Heartbeat') {
colours =
'text-white dark-text-white bg-vega-light-200 dark:bg-vega-dark-100';
} else if (type === 'Proposal' || type === 'Governance Proposal') { } else if (type === 'Proposal' || type === 'Governance Proposal') {
if (command && !!command.proposalSubmission) { if (command && !!command.proposalSubmission) {
type = getLabelForProposal(command.proposalSubmission); type = getLabelForProposal(command.proposalSubmission);
@@ -150,6 +154,16 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
colours = 'text-black bg-vega-yellow'; colours = 'text-black bg-vega-yellow';
} }
if (type === 'Vote on Proposal') {
return (
<VoteIcon
vote={command?.voteSubmission?.value === 'VALUE_YES'}
yesText="Proposal vote"
noText="Proposal vote"
/>
);
}
if (type === 'Vote on Proposal' || type === 'Vote Submission') { if (type === 'Vote on Proposal' || type === 'Vote Submission') {
colours = 'text-black bg-vega-yellow'; colours = 'text-black bg-vega-yellow';
} }
@@ -98,6 +98,6 @@ describe('Txs infinite list item', () => {
expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey'); expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey');
expect(screen.getByTestId('tx-type')).toHaveTextContent('testType'); expect(screen.getByTestId('tx-type')).toHaveTextContent('testType');
expect(screen.getByTestId('tx-block')).toHaveTextContent('1'); expect(screen.getByTestId('tx-block')).toHaveTextContent('1');
expect(screen.getByTestId('tx-success')).toHaveTextContent('Success: ✅'); expect(screen.getByTestId('tx-success')).toHaveTextContent('Success');
}); });
}); });
@@ -31,7 +31,7 @@ export const TxsInfiniteListItem = ({
return ( return (
<div <div
data-testid="transaction-row" data-testid="transaction-row"
className="flex items-center h-full border-t border-neutral-600 dark:border-neutral-800 txs-infinite-list-item grid grid-cols-10 py-2" className="flex items-center h-full border-t border-neutral-600 dark:border-neutral-800 txs-infinite-list-item grid grid-cols-10"
> >
<div <div
className="text-sm col-span-10 md:col-span-3 leading-none" className="text-sm col-span-10 md:col-span-3 leading-none"
@@ -83,7 +83,7 @@ export const TxsInfiniteListItem = ({
data-testid="tx-success" data-testid="tx-success"
> >
<span className="md:hidden uppercase text-vega-dark-300"> <span className="md:hidden uppercase text-vega-dark-300">
Success:&nbsp; Success&nbsp;
</span> </span>
{isNumber(code) ? ( {isNumber(code) ? (
<ChainResponseCode code={code} hideLabel={true} /> <ChainResponseCode code={code} hideLabel={true} />
@@ -0,0 +1,37 @@
import { render } from '@testing-library/react';
import { VoteIcon } from './vote-icon';
describe('Vote TX icon', () => {
it('should use the text For by default for yes votes', () => {
const yes = render(<VoteIcon vote={true} />);
expect(yes.getByTestId('label')).toHaveTextContent('For');
});
it('should use the yesText for yes votes if specified', () => {
const yes = render(<VoteIcon vote={true} yesText="Test" />);
expect(yes.getByTestId('label')).toHaveTextContent('Test');
});
it('should display the tick icon for yes votes', () => {
const no = render(<VoteIcon vote={true} />);
expect(no.getByRole('img')).toHaveAttribute(
'aria-label',
'tick-circle icon'
);
});
it('should use the text Against by default for no votes', () => {
const no = render(<VoteIcon vote={false} />);
expect(no.getByTestId('label')).toHaveTextContent('Against');
});
it('should use the noText for no votes if specified', () => {
const no = render(<VoteIcon vote={false} noText="Test" />);
expect(no.getByTestId('label')).toHaveTextContent('Test');
});
it('should display the delete icon for no votes', () => {
const no = render(<VoteIcon vote={false} />);
expect(no.getByRole('img')).toHaveAttribute('aria-label', 'delete icon');
});
});
@@ -0,0 +1,40 @@
import { Icon } from '@vegaprotocol/ui-toolkit';
import type { IconName } from '@vegaprotocol/ui-toolkit';
export interface VoteIconProps {
// True is a yes vote, false is undefined or no vorte
vote: boolean;
// Defaults to 'For', but can be any text
yesText?: string;
// Defaults to 'Against', but can be any text
noText?: string;
}
/**
* Displays a lozenge with an icon representing the way a user voted for a proposal.
* The yes and no text can be overridden
*
* @returns
*/
export function VoteIcon({
vote,
yesText = 'For',
noText = 'Against',
}: VoteIconProps) {
const label = vote ? yesText : noText;
const bg = vote ? 'bg-vega-green-550' : 'bg-vega-pink-550';
const icon: IconName = vote ? 'tick-circle' : 'delete';
const fill = vote ? 'vega-green-300' : 'vega-pink-300';
const text = vote ? 'vega-green-200' : 'vega-pink-200';
return (
<div
className={`voteicon inline-block my-1 py-1 px-2 py rounded-md text-white leading-one sm align-top ${bg}`}
>
<Icon name={icon} size={3} className={`mr-2 p-0 fill-${fill}`} />
<span className={`text-base text-${text}`} data-testid="label">
{label}
</span>
</div>
);
}
+4
View File
@@ -60,3 +60,7 @@
--ag-row-hover-color: theme(colors.neutral[800]); --ag-row-hover-color: theme(colors.neutral[800]);
--ag-font-size: 12px; --ag-font-size: 12px;
} }
.voteicon svg {
vertical-align: baseline;
}
@@ -7,6 +7,7 @@ import {
waitForSpinner, waitForSpinner,
navigateTo, navigateTo,
navigation, navigation,
turnTelemetryOff,
} from '../../support/common.functions'; } from '../../support/common.functions';
import { import {
clickOnValidatorFromList, clickOnValidatorFromList,
@@ -67,6 +68,7 @@ context(
'teardown wallet & drill into a specific validator', 'teardown wallet & drill into a specific validator',
function () { function () {
cy.clearLocalStorage(); cy.clearLocalStorage();
turnTelemetryOff();
cy.reload(); cy.reload();
waitForSpinner(); waitForSpinner();
cy.connectVegaWallet(); cy.connectVegaWallet();
@@ -252,10 +254,10 @@ context(
waitForBeginningOfEpoch(); waitForBeginningOfEpoch();
cy.getByTestId(stakeValidatorListStakePercentage).should( cy.getByTestId(stakeValidatorListStakePercentage).should(
'have.text', 'have.text',
'100%' '50.02%'
); );
navigateTo(navigation.validators); navigateTo(navigation.validators);
validateValidatorListTotalStakeAndShare('0', '2.00', '100.00%'); validateValidatorListTotalStakeAndShare('0', '3,002.00', '50.02%');
} }
); );
@@ -109,7 +109,7 @@ context(
); );
cy.getByTestId('protocol-upgrade-proposal-status').should( cy.getByTestId('protocol-upgrade-proposal-status').should(
'have.text', 'have.text',
'Approved ' 'Approved by validators '
); );
}); });
}); });
@@ -136,7 +136,7 @@ context(
); );
cy.getByTestId('protocol-upgrade-state').should( cy.getByTestId('protocol-upgrade-state').should(
'have.text', 'have.text',
'Approved' 'Approved by validators'
); );
cy.getByTestId('protocol-upgrade-release-tag').should( cy.getByTestId('protocol-upgrade-release-tag').should(
'have.text', 'have.text',
@@ -29,7 +29,7 @@ const performancePenaltyToolTip = '[data-testid="performance-penalty-tooltip"]';
const overstakedPenaltyToolTip = '[data-testid="overstaked-penalty-tooltip"]'; const overstakedPenaltyToolTip = '[data-testid="overstaked-penalty-tooltip"]';
const totalPenaltyToolTip = '[data-testid="total-penalty-tooltip"]'; const totalPenaltyToolTip = '[data-testid="total-penalty-tooltip"]';
const epochCountDown = '[data-testid="epoch-countdown"]'; const epochCountDown = '[data-testid="epoch-countdown"]';
const stakeNumberRegex = /^\d*\.?\d*$/; const stakeNumberRegex = /^\d{1,3}(,\d{3})*(\.\d+)?$/;
context('Validators Page - verify elements on page', function () { context('Validators Page - verify elements on page', function () {
before('navigate to validators page', function () { before('navigate to validators page', function () {
@@ -84,13 +84,13 @@ context('Validators Page - verify elements on page', function () {
cy.get(stakedByOperatorToolTip) cy.get(stakedByOperatorToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Staked by operator: 0.00'); .should('contain', 'Staked by operator: 3,000.00');
cy.get(stakedByDelegatesToolTip) cy.get(stakedByDelegatesToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Staked by delegates: 0.00'); .should('contain', 'Staked by delegates: 0.00');
cy.get(totalStakedToolTip) cy.get(totalStakedToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Total stake: 0.00'); .should('contain', 'Total stake: 3,000.00');
}); });
it('Should be able to see validator normalised voting power', function () { it('Should be able to see validator normalised voting power', function () {
@@ -106,10 +106,10 @@ context('Validators Page - verify elements on page', function () {
cy.get(unnormalisedVotingPowerToolTip) cy.get(unnormalisedVotingPowerToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Unnormalised voting power: 0.00%'); .should('contain', 'Unnormalised voting power: 20.00%');
cy.get(normalisedVotingPowerToolTip) cy.get(normalisedVotingPowerToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Normalised voting power: 0.10%'); .should('contain', 'Normalised voting power: 50.00%');
}); });
// 2002-SINC-018 // 2002-SINC-018
@@ -126,13 +126,13 @@ context('Validators Page - verify elements on page', function () {
cy.get(performancePenaltyToolTip) cy.get(performancePenaltyToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Performance penalty: 100.00%'); .should('contain', 'Performance penalty: 0.00%');
cy.get(overstakedPenaltyToolTip) cy.get(overstakedPenaltyToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Overstaked penalty:'); // value not asserted due to #2886 .should('contain', 'Overstaked penalty: 60.00%'); // value not asserted due to #2886
cy.get(totalPenaltyToolTip) cy.get(totalPenaltyToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Total penalties: 0.00%'); .should('contain', 'Total penalties: 60.00%');
}); });
it('Should be able to see validator pending stake', function () { it('Should be able to see validator pending stake', function () {
+2
View File
@@ -33,4 +33,6 @@ before(() => {
aliasGQLQuery(req, 'ChainId', chainIdQuery()); aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery()); aliasGQLQuery(req, 'Statistics', statisticsQuery());
}); });
// Self stake validators so they are displayed
cy.validatorsSelfDelegate();
}); });
+76 -26
View File
@@ -2,7 +2,6 @@ import './i18n';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import * as Sentry from '@sentry/react'; import * as Sentry from '@sentry/react';
import { Integrations } from '@sentry/tracing';
import { BrowserRouter as Router, useLocation } from 'react-router-dom'; import { BrowserRouter as Router, useLocation } from 'react-router-dom';
import { AppLoader } from './app-loader'; import { AppLoader } from './app-loader';
import { NetworkInfo } from '@vegaprotocol/network-info'; import { NetworkInfo } from '@vegaprotocol/network-info';
@@ -37,6 +36,10 @@ import {
useEnvironment, useEnvironment,
NetworkLoader, NetworkLoader,
useInitializeEnv, useInitializeEnv,
NodeGuard,
AppFailure,
NodeSwitcherDialog,
useNodeSwitcherStore,
} from '@vegaprotocol/environment'; } from '@vegaprotocol/environment';
import { ENV } from './config'; import { ENV } from './config';
import type { InMemoryCacheConfig } from '@apollo/client'; import type { InMemoryCacheConfig } from '@apollo/client';
@@ -48,6 +51,7 @@ import {
TELEMETRY_ON, TELEMETRY_ON,
} from './components/telemetry-dialog/telemetry-dialog'; } from './components/telemetry-dialog/telemetry-dialog';
import { useLocalStorage } from '@vegaprotocol/react-helpers'; import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { useTranslation } from 'react-i18next';
const cache: InMemoryCacheConfig = { const cache: InMemoryCacheConfig = {
typePolicies: { typePolicies: {
@@ -179,32 +183,70 @@ const ScrollToTop = () => {
return null; return null;
}; };
const removeQueryParams = (url: string) => {
return url.split('?')[0];
};
const AppContainer = () => { const AppContainer = () => {
const { config, loading, error } = useEthereumConfig(); const { config, loading, error } = useEthereumConfig();
const { VEGA_ENV, GIT_COMMIT_HASH, GIT_BRANCH, ETHEREUM_PROVIDER_URL } = const {
useEnvironment(); VEGA_ENV,
VEGA_URL,
GIT_COMMIT_HASH,
GIT_BRANCH,
ETHEREUM_PROVIDER_URL,
} = useEnvironment();
const [telemetryOn] = useLocalStorage(TELEMETRY_ON); const [telemetryOn] = useLocalStorage(TELEMETRY_ON);
const { t } = useTranslation();
const [nodeSwitcherOpen, setNodeSwitcher] = useNodeSwitcherStore((store) => [
store.dialogOpen,
store.setDialogOpen,
]);
useEffect(() => { useEffect(() => {
if (ENV.dsn && telemetryOn) { if (ENV.dsn && telemetryOn) {
Sentry.init({ Sentry.init({
dsn: ENV.dsn, dsn: ENV.dsn,
integrations: [new Integrations.BrowserTracing()],
tracesSampleRate: 0.1, tracesSampleRate: 0.1,
enabled: true, enabled: true,
environment: VEGA_ENV, environment: VEGA_ENV,
release: GIT_COMMIT_HASH, release: GIT_COMMIT_HASH,
beforeSend(event) { beforeSend(event) {
if (event.request?.url?.includes('/claim?')) { const requestUrl = event.request?.url;
return { const transaction = event.transaction;
...event,
request: { const updatedRequest =
...event.request, requestUrl && requestUrl.includes('/test?')
url: event.request?.url.split('?')[0], ? { ...event.request, url: removeQueryParams(requestUrl) }
}, : event.request;
};
} const updatedTransaction =
return event; transaction && transaction.includes('/test?')
? removeQueryParams(transaction)
: transaction;
const updatedBreadcrumbs = event.breadcrumbs?.map((breadcrumb) => {
if (
breadcrumb.type === 'navigation' &&
breadcrumb.data?.to?.includes('/test?')
) {
return {
...breadcrumb,
data: {
...breadcrumb.data,
to: removeQueryParams(breadcrumb.data.to),
},
};
}
return breadcrumb;
});
return {
...event,
request: updatedRequest,
transaction: updatedTransaction,
breadcrumbs: updatedBreadcrumbs ?? event.breadcrumbs,
};
}, },
}); });
Sentry.setTag('branch', GIT_BRANCH); Sentry.setTag('branch', GIT_BRANCH);
@@ -219,21 +261,29 @@ const AppContainer = () => {
<ScrollToTop /> <ScrollToTop />
<AppStateProvider> <AppStateProvider>
<div className="grid min-h-full text-white"> <div className="grid min-h-full text-white">
<AsyncRenderer<EthereumConfig | null> <NodeGuard
loading={loading} skeleton={<div>{t('Loading')}</div>}
data={config} failure={
error={error} <AppFailure title={t('NodeUnsuitable', { url: VEGA_URL })} />
render={(cnf) =>
cnf && (
<Web3Container
chainId={Number(cnf.chain_id)}
providerUrl={ETHEREUM_PROVIDER_URL}
/>
)
} }
/> >
<AsyncRenderer<EthereumConfig | null>
loading={loading}
data={config}
error={error}
render={(cnf) =>
cnf && (
<Web3Container
chainId={Number(cnf.chain_id)}
providerUrl={ETHEREUM_PROVIDER_URL}
/>
)
}
/>
</NodeGuard>
</div> </div>
</AppStateProvider> </AppStateProvider>
<NodeSwitcherDialog open={nodeSwitcherOpen} setOpen={setNodeSwitcher} />
</Router> </Router>
); );
}; };
@@ -202,6 +202,7 @@
"tokenVotes": "Token votes", "tokenVotes": "Token votes",
"liquidityVotes": "Liquidity votes", "liquidityVotes": "Liquidity votes",
"castYourVote": "Cast your vote", "castYourVote": "Cast your vote",
"yourVote": "Your vote",
"for": "For", "for": "For",
"against": "Against", "against": "Against",
"majorityRequired": "Majority Required", "majorityRequired": "Majority Required",
@@ -787,9 +788,9 @@
"homeVegaTokenButtonText": "Manage tokens", "homeVegaTokenButtonText": "Manage tokens",
"downloadProposalJson": "Download proposal as JSON", "downloadProposalJson": "Download proposal as JSON",
"networkUpgrade": "Network Upgrade", "networkUpgrade": "Network Upgrade",
"PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED": "Approved", "PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED": "Approved by validators",
"PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING": "Pending", "PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING": "Waiting for validator votes",
"PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED": "Rejected", "PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED": "Declined by validators",
"PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED": "Unspecified", "PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED": "Unspecified",
"vegaRelease{release}": "Vega Release {{release}}", "vegaRelease{release}": "Vega Release {{release}}",
"upgradeBlockHeight": "Upgrade block height", "upgradeBlockHeight": "Upgrade block height",
@@ -811,5 +812,6 @@
"OptOutOfTelemetry": "You can opt out any time via settings", "OptOutOfTelemetry": "You can opt out any time via settings",
"NoThanks": "No thanks", "NoThanks": "No thanks",
"ShareData": "Share data", "ShareData": "Share data",
"ContinueSharingData": "Continue sharing data" "ContinueSharingData": "Continue sharing data",
"NodeUnsuitable": "Node: {{url}} is unsuitable"
} }
@@ -50,7 +50,22 @@ export const useTranches = create<TranchesStore>()((set) => ({
?.map((t) => { ?.map((t) => {
const tranche_progress = const tranche_progress =
t.duration !== 0 ? (now - t.cliff_start) / t.duration : 0; t.duration !== 0 ? (now - t.cliff_start) / t.duration : 0;
const lockedDecimal = tranche_progress < 0 ? 1 : 1 - tranche_progress; let lockedDecimal;
if (t.duration !== 0) {
if (tranche_progress < 0) {
lockedDecimal = 1;
} else {
lockedDecimal = 1 - tranche_progress;
}
} else {
if (now < t.cliff_start) {
lockedDecimal = 1;
} else {
lockedDecimal = 0;
}
}
const clampedLockedDecimal = Math.max(0, Math.min(1, lockedDecimal));
return { return {
tranche_id: t.tranche_id, tranche_id: t.tranche_id,
tranche_start: secondsToDate(t.cliff_start), tranche_start: secondsToDate(t.cliff_start),
@@ -60,7 +75,7 @@ export const useTranches = create<TranchesStore>()((set) => ({
toBigNum(t.current_balance, decimals) toBigNum(t.current_balance, decimals)
), ),
locked_amount: toBigNum(t.initial_balance, decimals).times( locked_amount: toBigNum(t.initial_balance, decimals).times(
lockedDecimal clampedLockedDecimal
), ),
users: t.users, users: t.users,
}; };
@@ -24,7 +24,6 @@ const openProposalClosesNextMonth = generateProposal({
}, },
terms: { terms: {
closingDatetime: nextMonth.toString(), closingDatetime: nextMonth.toString(),
enactmentDatetime: nextMonth.toString(),
}, },
}); });
@@ -36,7 +35,6 @@ const openProposalClosesNextWeek = generateProposal({
}, },
terms: { terms: {
closingDatetime: nextWeek.toString(), closingDatetime: nextWeek.toString(),
enactmentDatetime: nextWeek.toString(),
}, },
}); });
@@ -45,7 +43,6 @@ const enactedProposalClosedLastWeek = generateProposal({
state: ProposalState.STATE_ENACTED, state: ProposalState.STATE_ENACTED,
terms: { terms: {
closingDatetime: lastWeek.toString(), closingDatetime: lastWeek.toString(),
enactmentDatetime: lastWeek.toString(),
}, },
}); });
@@ -54,7 +51,6 @@ const failedProposalClosedLastMonth = generateProposal({
state: ProposalState.STATE_FAILED, state: ProposalState.STATE_FAILED,
terms: { terms: {
closingDatetime: lastMonth.toString(), closingDatetime: lastMonth.toString(),
enactmentDatetime: lastMonth.toString(),
}, },
}); });
@@ -32,7 +32,9 @@ describe('ProtocolUpgradeProposalDetailInfo', () => {
it('should render the state', () => { it('should render the state', () => {
const { getByTestId } = renderComponent(); const { getByTestId } = renderComponent();
expect(getByTestId('protocol-upgrade-state')).toHaveTextContent('Pending'); expect(getByTestId('protocol-upgrade-state')).toHaveTextContent(
'Waiting for validator votes'
);
}); });
it('should render the vega release tag', () => { it('should render the vega release tag', () => {
@@ -26,28 +26,34 @@ describe('ProtocolUpgradeProposalsListItem', () => {
status: status:
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED, ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED,
icon: 'protocol-upgrade-proposal-status-icon-rejected', icon: 'protocol-upgrade-proposal-status-icon-rejected',
text: 'Declined by validators',
}, },
{ {
status: status:
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING, ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING,
icon: 'protocol-upgrade-proposal-status-icon-pending', icon: 'protocol-upgrade-proposal-status-icon-pending',
text: 'Waiting for validator votes',
}, },
{ {
status: status:
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED, ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED,
icon: 'protocol-upgrade-proposal-status-icon-approved', icon: 'protocol-upgrade-proposal-status-icon-approved',
text: 'Approved by validators',
}, },
{ {
status: status:
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED, ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED,
icon: 'protocol-upgrade-proposal-status-icon-unspecified', icon: 'protocol-upgrade-proposal-status-icon-unspecified',
text: 'Unspecified',
}, },
]; ];
statuses.forEach(({ status, icon }) => { statuses.forEach(({ status, icon, text }) => {
renderComponent({ ...proposal, status }); renderComponent({ ...proposal, status });
const statusIcon = screen.getByTestId(icon); const statusIcon = screen.getByTestId(icon);
const textContent = screen.getByText(text);
expect(statusIcon).toBeInTheDocument(); expect(statusIcon).toBeInTheDocument();
expect(textContent).toBeInTheDocument();
}); });
}); });
@@ -111,7 +111,9 @@ describe('Vote buttons', () => {
</AppStateProvider> </AppStateProvider>
); );
expect( expect(
screen.getByText('You need some VEGA tokens to participate in governance') screen.getByText(
'You need some VEGA tokens to participate in governance.'
)
).toBeTruthy(); ).toBeTruthy();
}); });
@@ -2,7 +2,12 @@ import { format } from 'date-fns';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet'; import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { AsyncRenderer, Button, ButtonLink } from '@vegaprotocol/ui-toolkit'; import {
AsyncRenderer,
Button,
ButtonLink,
ExternalLink,
} from '@vegaprotocol/ui-toolkit';
import { addDecimal, toBigNum } from '@vegaprotocol/utils'; import { addDecimal, toBigNum } from '@vegaprotocol/utils';
import { ProposalState, VoteValue } from '@vegaprotocol/types'; import { ProposalState, VoteValue } from '@vegaprotocol/types';
import { import {
@@ -161,7 +166,12 @@ export const VoteButtons = ({
{changeVote || (voteState === VoteState.NotCast && proposalVotable) ? ( {changeVote || (voteState === VoteState.NotCast && proposalVotable) ? (
<> <>
{currentStakeAvailable.isLessThanOrEqualTo(0) && ( {currentStakeAvailable.isLessThanOrEqualTo(0) && (
<p data-testid="no-stake-available">{t('noGovernanceTokens')}</p> <>
<p data-testid="no-stake-available">{t('noGovernanceTokens')}.</p>
<ExternalLink href="https://blog.vega.xyz/how-to-vote-on-vega-2195d1e52ec5">
{t('findOutMoreAboutHowToVote')}
</ExternalLink>
</>
)} )}
<div className="flex gap-4" data-testid="vote-buttons"> <div className="flex gap-4" data-testid="vote-buttons">
@@ -1,6 +1,6 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from 'date-fns';
import { RoundedWrapper, Icon } from '@vegaprotocol/ui-toolkit'; import { RoundedWrapper, Icon, ExternalLink } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import { ProposalState } from '@vegaprotocol/types'; import { ProposalState } from '@vegaprotocol/types';
import { useVoteSubmit, VoteProgress } from '@vegaprotocol/proposals'; import { useVoteSubmit, VoteProgress } from '@vegaprotocol/proposals';
@@ -202,7 +202,12 @@ export const VoteDetails = ({
)} )}
<section className="mt-10"> <section className="mt-10">
<SubHeading title={t('castYourVote')} /> {proposal?.state === ProposalState.STATE_OPEN ? (
<SubHeading title={t('castYourVote')} />
) : (
<SubHeading title={t('yourVote')} />
)}
{pubKey ? ( {pubKey ? (
proposal && ( proposal && (
<VoteButtonsContainer <VoteButtonsContainer
@@ -224,6 +229,9 @@ export const VoteDetails = ({
<Icon name={'info-sign'} /> <Icon name={'info-sign'} />
<div>{t('connectAVegaWalletToVote')}</div> <div>{t('connectAVegaWalletToVote')}</div>
</div> </div>
<ExternalLink href="https://blog.vega.xyz/how-to-vote-on-vega-2195d1e52ec5">
{t('findOutMoreAboutHowToVote')}
</ExternalLink>
</div> </div>
<ConnectToVega /> <ConnectToVega />
</RoundedWrapper> </RoundedWrapper>
@@ -20,8 +20,11 @@ import { useProtocolUpgradeProposalsQuery } from '@vegaprotocol/proposals';
const orderByDate = (arr: ProposalFieldsFragment[]) => const orderByDate = (arr: ProposalFieldsFragment[]) =>
orderBy( orderBy(
arr, arr,
[(p) => new Date(p?.terms?.closingDatetime).getTime(), (p) => p.id], [
['desc', 'desc'] (p) => new Date(p?.terms?.closingDatetime).getTime(),
(p) => new Date(p?.datetime).getTime(),
],
['asc', 'asc']
); );
const orderByUpgradeBlockHeight = ( const orderByUpgradeBlockHeight = (
@@ -54,22 +54,16 @@ export const TrancheItem = ({
{formatNumber(total, 2)} {formatNumber(total, 2)}
</span> </span>
</div> </div>
<table className="w-full"> <div className="grid grid-cols-2 my-2">
<tbody> <div>
<tr> <span>{t('Starts unlocking')}:</span>{' '}
<td>{t('Starts unlocking')}</td> <span>{format(tranche.tranche_start, DATE_FORMAT_LONG)}</span>
<td className="text-right"> </div>
{format(tranche.tranche_start, DATE_FORMAT_LONG)} <div className="justify-self-end">
</td> <span>{t('Fully unlocked')}:</span>{' '}
</tr> <span>{format(tranche.tranche_end, DATE_FORMAT_LONG)}</span>
<tr> </div>
<td>{t('Fully unlocked')}</td> </div>
<td className="text-right">
{format(tranche.tranche_end, DATE_FORMAT_LONG)}
</td>
</tr>
</tbody>
</table>
<LockedProgress <LockedProgress
locked={locked} locked={locked}
unlocked={unlocked} unlocked={unlocked}
@@ -34,6 +34,7 @@ import {
formatNumberPercentage, formatNumberPercentage,
toBigNum, toBigNum,
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
import { VALIDATOR_LOGO_MAP } from './logo-map';
interface CanonisedConsensusNodeProps { interface CanonisedConsensusNodeProps {
id: string; id: string;
@@ -161,6 +162,11 @@ export const ConsensusValidatorsTable = ({
pendingUserStake, pendingUserStake,
userStakeShare, userStakeShare,
}) => { }) => {
const logo = VALIDATOR_LOGO_MAP[id]
? VALIDATOR_LOGO_MAP[id]
: avatarUrl
? avatarUrl
: null;
const { const {
rawValidatorScore: previousEpochValidatorScore, rawValidatorScore: previousEpochValidatorScore,
performanceScore: previousEpochPerformanceScore, performanceScore: previousEpochPerformanceScore,
@@ -171,7 +177,7 @@ export const ConsensusValidatorsTable = ({
id, id,
[ValidatorFields.RANKING_INDEX]: votingPowerRanking, [ValidatorFields.RANKING_INDEX]: votingPowerRanking,
[ValidatorFields.VALIDATOR]: { [ValidatorFields.VALIDATOR]: {
avatarUrl, avatarUrl: logo,
name, name,
}, },
[ValidatorFields.STAKE]: stakedTotal, [ValidatorFields.STAKE]: stakedTotal,
@@ -210,7 +216,7 @@ export const ConsensusValidatorsTable = ({
: undefined, : undefined,
[ValidatorFields.PENDING_USER_STAKE]: pendingUserStake, [ValidatorFields.PENDING_USER_STAKE]: pendingUserStake,
[ValidatorFields.USER_STAKE_SHARE]: userStakeShare [ValidatorFields.USER_STAKE_SHARE]: userStakeShare
? formatNumberPercentage(new BigNumber(userStakeShare)) ? formatNumberPercentage(new BigNumber(userStakeShare), 2)
: undefined, : undefined,
}; };
} }
@@ -0,0 +1,14 @@
export const VALIDATOR_LOGO_MAP: { [key: string]: string } = {
'55504e9bfd914a7bbefa342c82f59a2f4dee344e5b6863a14c02a812f4fbde32':
'https://pbs.twimg.com/profile_images/1586047492629712897/ZVMWBE94_400x400.jpg',
efbdf943443bd7595e83b0d7e88f37b7932d487d1b94aab3d004997273bb43fc:
'https://pbs.twimg.com/profile_images/1026823609979949057/3e-LCHHm_400x400.jpg',
'126751c5830b50d39eb85412fb2964f46338cce6946ff455b73f1b1be3f5e8cc':
'https://pbs.twimg.com/profile_images/1228627868542029824/9aoaLiIx_400x400.jpg',
'43697a3e911d8b70c0ce672adde17a5c38ca8f6a0486bf85ed0546e1b9a82887':
'https://pbs.twimg.com/profile_images/1352167987478843392/XzX82gIb_400x400.jpg',
ac735acc9ab11cf1d8c59c2df2107e00092b4ac96451cb137a1629af5b66242a:
'https://pbs.twimg.com/profile_images/1159254945885016064/vhhp1wL4_400x400.jpg',
'74023df02b8afc9eaf3e3e2e8b07eab1d2122ac3e74b1b0222daf4af565ad3dd':
'https://cdn4.telegram-cdn.org/file/n_OJ6upnglNG95E7u5FyfLmqqBUhf25LqtyZD0UM27gMM_WWi8rywrp9JaAA83quP7vx-WU9Q86quvIRdk9wuUrC2tdD08cYxah4dXHa4OgphFg7Vm5UYFjuGxuHn916OIlzIuVOjJWK_nricGdlPcZQK_T4kBJ_5v4pp94j76_DQ1eSy8DjUxnchZxqpGFv-lGN8U4SjRMK0RC_6DusNaRYEn9Ni2ZQHig7nMiqud-nkK5RgUrClfXFwyhoMFVfhHpGUzwpSv1pSEVLXVh7E1mVmWYrW9hzdQGGrouWCGRqtf_hRa9enS2fv0P9mAB23BcURWuLOVtMdJTF4wfc-w.jpg',
};
@@ -16,6 +16,7 @@ import type { PreviousEpochQuery } from '../../__generated__/PreviousEpoch';
import { useAppState } from '../../../../contexts/app-state/app-state-context'; import { useAppState } from '../../../../contexts/app-state/app-state-context';
import type { StakingDelegationFieldsFragment } from '../../__generated__/Staking'; import type { StakingDelegationFieldsFragment } from '../../__generated__/Staking';
import type { ValidatorsView } from './validator-tables'; import type { ValidatorsView } from './validator-tables';
import { VALIDATOR_LOGO_MAP } from './logo-map';
export enum ValidatorFields { export enum ValidatorFields {
RANKING_INDEX = 'rankingIndex', RANKING_INDEX = 'rankingIndex',
@@ -102,13 +103,18 @@ interface ValidatorRendererProps {
export const ValidatorRenderer = ({ data }: ValidatorRendererProps) => { export const ValidatorRenderer = ({ data }: ValidatorRendererProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { avatarUrl, name } = data.validator; const { avatarUrl, name } = data.validator;
const logo = VALIDATOR_LOGO_MAP[data.id]
? VALIDATOR_LOGO_MAP[data.id]
: avatarUrl
? avatarUrl
: null;
return ( return (
<div className="w-[238px] grid grid-cols-[1fr_auto] gap-2 items-center"> <div className="w-[238px] grid grid-cols-[1fr_auto] gap-2 items-center">
<span className="flex overflow-hidden"> <span className="flex overflow-hidden">
{avatarUrl && ( {logo && (
<img <img
className="h-6 w-6 rounded-full mr-2" className="h-6 w-6 rounded-full mr-2"
src={avatarUrl} src={logo}
alt={`Avatar icon for ${name}`} alt={`Avatar icon for ${name}`}
onError={(e) => (e.currentTarget.style.display = 'none')} onError={(e) => (e.currentTarget.style.display = 'none')}
/> />
@@ -78,37 +78,38 @@ export const ValidatorTables = ({
const nextDelegation = delegations?.find( const nextDelegation = delegations?.find(
(d) => d.node.id === validatorId && d.epoch === Number(epochId) + 1 (d) => d.node.id === validatorId && d.epoch === Number(epochId) + 1
); );
if (validator.stakedByOperator !== '0') {
switch (validator.rankingScore?.status) { switch (validator.rankingScore?.status) {
case Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT: case Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT:
acc.consensusValidators.push( acc.consensusValidators.push(
addUserDataToValidator( addUserDataToValidator(
validator, validator,
currentDelegation, currentDelegation,
nextDelegation, nextDelegation,
currentUserStakeAvailable currentUserStakeAvailable
) )
); );
break; break;
case Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_ERSATZ: case Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_ERSATZ:
acc.standbyValidators.push( acc.standbyValidators.push(
addUserDataToValidator( addUserDataToValidator(
validator, validator,
currentDelegation, currentDelegation,
nextDelegation, nextDelegation,
currentUserStakeAvailable currentUserStakeAvailable
) )
); );
break; break;
case Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_PENDING: case Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_PENDING:
acc.pendingValidators.push( acc.pendingValidators.push(
addUserDataToValidator( addUserDataToValidator(
validator, validator,
currentDelegation, currentDelegation,
nextDelegation, nextDelegation,
currentUserStakeAvailable currentUserStakeAvailable
) )
); );
}
} }
return acc; return acc;
}, },
+382 -76
View File
@@ -1,4 +1,37 @@
[ [
{
"tranche_id": 59,
"tranche_start": "2024-05-01T00:00:00.000Z",
"tranche_end": "2024-11-01T00:00:00.000Z",
"total_added": "15000",
"total_removed": "0",
"locked_amount": "15000",
"deposits": [
{
"amount": "15000",
"user": "0xA530ac2B576eF27C09B7b55C83b3E163D167e9AE",
"tx": "0x90af73d7321833fc32445850655a1210f0298845b9222e695e1229801f7a95b0"
}
],
"withdrawals": [],
"users": [
{
"address": "0xA530ac2B576eF27C09B7b55C83b3E163D167e9AE",
"deposits": [
{
"amount": "15000",
"user": "0xA530ac2B576eF27C09B7b55C83b3E163D167e9AE",
"tranche_id": 59,
"tx": "0x90af73d7321833fc32445850655a1210f0298845b9222e695e1229801f7a95b0"
}
],
"withdrawals": [],
"total_tokens": "15000",
"withdrawn_tokens": "0",
"remaining_tokens": "15000"
}
]
},
{ {
"tranche_id": 58, "tranche_id": 58,
"tranche_start": "2023-05-11T00:00:00.000Z", "tranche_start": "2023-05-11T00:00:00.000Z",
@@ -48,8 +81,8 @@
"tranche_start": "2023-04-20T00:00:00.000Z", "tranche_start": "2023-04-20T00:00:00.000Z",
"tranche_end": "2023-05-20T00:00:00.000Z", "tranche_end": "2023-05-20T00:00:00.000Z",
"total_added": "19242.125", "total_added": "19242.125",
"total_removed": "1523.8177488329475", "total_removed": "2249.511113406525",
"locked_amount": "6891.9471601080244395475", "locked_amount": "4805.928581211419705575",
"deposits": [ "deposits": [
{ {
"amount": "188", "amount": "188",
@@ -228,6 +261,21 @@
"user": "0xDd7a98557586ce21f770662319C2047C5a3bD605", "user": "0xDd7a98557586ce21f770662319C2047C5a3bD605",
"tx": "0x2b3571c143ecebddf91fb62f402d516d51110edfe37b13200a6e5cf682dc5bb0" "tx": "0x2b3571c143ecebddf91fb62f402d516d51110edfe37b13200a6e5cf682dc5bb0"
}, },
{
"amount": "336.4580555509875",
"user": "0xf915Da10e5136352Ba049acB0545Deb119054256",
"tx": "0x7af5942634e236f5f9c580f4ed042794ed309e83885f652a55ab37793ea2e85c"
},
{
"amount": "119.364649300815",
"user": "0xDd7a98557586ce21f770662319C2047C5a3bD605",
"tx": "0x47f5bf2c758c5270dd1b6519ac649ddabef8199f8a0bae319e36f8ac5c9c142e"
},
{
"amount": "269.870659721775",
"user": "0x1447Efc62d5077d7AbB20492dF831Dd6c9Eb1756",
"tx": "0x156ef84c345adecdf7b1c55756b1f4326716d1b0191ce1a208f614fc807ce033"
},
{ {
"amount": "202.093666077975", "amount": "202.093666077975",
"user": "0x1447Efc62d5077d7AbB20492dF831Dd6c9Eb1756", "user": "0x1447Efc62d5077d7AbB20492dF831Dd6c9Eb1756",
@@ -301,6 +349,12 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "269.870659721775",
"user": "0x1447Efc62d5077d7AbB20492dF831Dd6c9Eb1756",
"tranche_id": 56,
"tx": "0x156ef84c345adecdf7b1c55756b1f4326716d1b0191ce1a208f614fc807ce033"
},
{ {
"amount": "202.093666077975", "amount": "202.093666077975",
"user": "0x1447Efc62d5077d7AbB20492dF831Dd6c9Eb1756", "user": "0x1447Efc62d5077d7AbB20492dF831Dd6c9Eb1756",
@@ -327,8 +381,8 @@
} }
], ],
"total_tokens": "1207.5", "total_tokens": "1207.5",
"withdrawn_tokens": "597.002068860225", "withdrawn_tokens": "866.872728582",
"remaining_tokens": "610.497931139775" "remaining_tokens": "340.627271418"
}, },
{ {
"address": "0x33Ce1D9E53AFb7367E34749517C086405a651a95", "address": "0x33Ce1D9E53AFb7367E34749517C086405a651a95",
@@ -362,6 +416,12 @@
"tranche_id": 56, "tranche_id": 56,
"tx": "0x2b3571c143ecebddf91fb62f402d516d51110edfe37b13200a6e5cf682dc5bb0" "tx": "0x2b3571c143ecebddf91fb62f402d516d51110edfe37b13200a6e5cf682dc5bb0"
}, },
{
"amount": "119.364649300815",
"user": "0xDd7a98557586ce21f770662319C2047C5a3bD605",
"tranche_id": 56,
"tx": "0x47f5bf2c758c5270dd1b6519ac649ddabef8199f8a0bae319e36f8ac5c9c142e"
},
{ {
"amount": "195.89040769089", "amount": "195.89040769089",
"user": "0xDd7a98557586ce21f770662319C2047C5a3bD605", "user": "0xDd7a98557586ce21f770662319C2047C5a3bD605",
@@ -370,8 +430,8 @@
} }
], ],
"total_tokens": "914.25", "total_tokens": "914.25",
"withdrawn_tokens": "504.71220630471", "withdrawn_tokens": "624.076855605525",
"remaining_tokens": "409.53779369529" "remaining_tokens": "290.173144394475"
}, },
{ {
"address": "0x9573BDF7FfC5519912d293e4D1f750eab2E471E7", "address": "0x9573BDF7FfC5519912d293e4D1f750eab2E471E7",
@@ -705,6 +765,12 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "336.4580555509875",
"user": "0xf915Da10e5136352Ba049acB0545Deb119054256",
"tranche_id": 56,
"tx": "0x7af5942634e236f5f9c580f4ed042794ed309e83885f652a55ab37793ea2e85c"
},
{ {
"amount": "422.1034736680125", "amount": "422.1034736680125",
"user": "0xf915Da10e5136352Ba049acB0545Deb119054256", "user": "0xf915Da10e5136352Ba049acB0545Deb119054256",
@@ -713,8 +779,8 @@
} }
], ],
"total_tokens": "1121.25", "total_tokens": "1121.25",
"withdrawn_tokens": "422.1034736680125", "withdrawn_tokens": "758.561529219",
"remaining_tokens": "699.1465263319875" "remaining_tokens": "362.688470781"
}, },
{ {
"address": "0x237D23FcA6d7B2530C7614a9cB921CF27924911E", "address": "0x237D23FcA6d7B2530C7614a9cB921CF27924911E",
@@ -877,7 +943,7 @@
"tranche_start": "2023-04-06T00:00:00.000Z", "tranche_start": "2023-04-06T00:00:00.000Z",
"tranche_end": "2023-05-06T00:00:00.000Z", "tranche_end": "2023-05-06T00:00:00.000Z",
"total_added": "14610", "total_added": "14610",
"total_removed": "6141.45090157707", "total_removed": "6675.45090157707",
"locked_amount": "0", "locked_amount": "0",
"deposits": [ "deposits": [
{ {
@@ -1077,6 +1143,11 @@
"user": "0x9E53F72210479BF46aE7ABF77c48B0d611b3c3dB", "user": "0x9E53F72210479BF46aE7ABF77c48B0d611b3c3dB",
"tx": "0x1dbcf713b48965a82aa2e17cb3e7db9a491668d859d408a39c8a74b0ea860b6b" "tx": "0x1dbcf713b48965a82aa2e17cb3e7db9a491668d859d408a39c8a74b0ea860b6b"
}, },
{
"amount": "534",
"user": "0x2586bA83696a92b5467Aaa0CF9EEC052F28F2c02",
"tx": "0x5aa922056cad64f97a7dfa750da57d63abfdaea4499192d4d57958bfb4fba2ea"
},
{ {
"amount": "106.53500000286", "amount": "106.53500000286",
"user": "0x6F32AA5A6198329c16e438512F992a0548C856f9", "user": "0x6F32AA5A6198329c16e438512F992a0548C856f9",
@@ -1200,10 +1271,17 @@
"tx": "0xf970ea0ce3e36fa0014d24bb830dd2ea0dbea06f6e53af486de5ee7e1c63e540" "tx": "0xf970ea0ce3e36fa0014d24bb830dd2ea0dbea06f6e53af486de5ee7e1c63e540"
} }
], ],
"withdrawals": [], "withdrawals": [
{
"amount": "534",
"user": "0x2586bA83696a92b5467Aaa0CF9EEC052F28F2c02",
"tranche_id": 54,
"tx": "0x5aa922056cad64f97a7dfa750da57d63abfdaea4499192d4d57958bfb4fba2ea"
}
],
"total_tokens": "534", "total_tokens": "534",
"withdrawn_tokens": "0", "withdrawn_tokens": "534",
"remaining_tokens": "534" "remaining_tokens": "0"
}, },
{ {
"address": "0xBf1AaB792D729fA125e6D7122D4b916a1E1C44B1", "address": "0xBf1AaB792D729fA125e6D7122D4b916a1E1C44B1",
@@ -1826,7 +1904,7 @@
"tranche_start": "2023-03-06T00:00:00.000Z", "tranche_start": "2023-03-06T00:00:00.000Z",
"tranche_end": "2023-04-06T00:00:00.000Z", "tranche_end": "2023-04-06T00:00:00.000Z",
"total_added": "14099", "total_added": "14099",
"total_removed": "3722.49002352036", "total_removed": "4343.49002352036",
"locked_amount": "0", "locked_amount": "0",
"deposits": [ "deposits": [
{ {
@@ -2591,6 +2669,16 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "63",
"user": "0x2a65Ae527C6Ff4665e048B0E0883c486A7BA4DBc",
"tx": "0xb0edcc25e422bc3db3ad8027dfdfc0928abd7a8af8957a1699e4cf2dc8cee8f7"
},
{
"amount": "558",
"user": "0xA7f89FF809549F4577993de3d1B3017241a53F40",
"tx": "0x84e5ff587c312b578fa946e2dce386cdf19c7638602b12eceaaab7de563f8394"
},
{ {
"amount": "30", "amount": "30",
"user": "0xBe9F912Ad481C61B653463E8F1D2b2b310D49861", "user": "0xBe9F912Ad481C61B653463E8F1D2b2b310D49861",
@@ -3629,10 +3717,17 @@
"tx": "0xd4a269b070cbaaff7e29a99f6b3997117d765f69512bb760e28bade799fcbba4" "tx": "0xd4a269b070cbaaff7e29a99f6b3997117d765f69512bb760e28bade799fcbba4"
} }
], ],
"withdrawals": [], "withdrawals": [
{
"amount": "63",
"user": "0x2a65Ae527C6Ff4665e048B0E0883c486A7BA4DBc",
"tranche_id": 53,
"tx": "0xb0edcc25e422bc3db3ad8027dfdfc0928abd7a8af8957a1699e4cf2dc8cee8f7"
}
],
"total_tokens": "63", "total_tokens": "63",
"withdrawn_tokens": "0", "withdrawn_tokens": "63",
"remaining_tokens": "63" "remaining_tokens": "0"
}, },
{ {
"address": "0xc3B1eB0feE837Db0A3ded5bf16A050726955195B", "address": "0xc3B1eB0feE837Db0A3ded5bf16A050726955195B",
@@ -4400,10 +4495,17 @@
"tx": "0x46d80a145d2f49ef3152cbfaa6d2bb054deabe21396ab43553a3f076e2db62e1" "tx": "0x46d80a145d2f49ef3152cbfaa6d2bb054deabe21396ab43553a3f076e2db62e1"
} }
], ],
"withdrawals": [], "withdrawals": [
{
"amount": "558",
"user": "0xA7f89FF809549F4577993de3d1B3017241a53F40",
"tranche_id": 53,
"tx": "0x84e5ff587c312b578fa946e2dce386cdf19c7638602b12eceaaab7de563f8394"
}
],
"total_tokens": "558", "total_tokens": "558",
"withdrawn_tokens": "0", "withdrawn_tokens": "558",
"remaining_tokens": "558" "remaining_tokens": "0"
}, },
{ {
"address": "0x363D3d06d70761c34D6EfEB25E409A9549E6970D", "address": "0x363D3d06d70761c34D6EfEB25E409A9549E6970D",
@@ -4861,7 +4963,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z", "tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "86666.297", "total_added": "86666.297",
"total_removed": "0", "total_removed": "0",
"locked_amount": "49802.2753121154213651374", "locked_amount": "49030.0504645820725222068",
"deposits": [ "deposits": [
{ {
"amount": "86666.297", "amount": "86666.297",
@@ -4927,7 +5029,7 @@
"tranche_end": "2023-06-01T00:00:00.000Z", "tranche_end": "2023-06-01T00:00:00.000Z",
"total_added": "2500", "total_added": "2500",
"total_removed": "0", "total_removed": "0",
"locked_amount": "312.4325905575906", "locked_amount": "267.758572446072375",
"deposits": [ "deposits": [
{ {
"amount": "2500", "amount": "2500",
@@ -4960,7 +5062,7 @@
"tranche_end": "2023-11-01T00:00:00.000Z", "tranche_end": "2023-11-01T00:00:00.000Z",
"total_added": "15000.000000000000015", "total_added": "15000.000000000000015",
"total_removed": "0", "total_removed": "0",
"locked_amount": "14327.0455917874395143270455917874395", "locked_amount": "14061.9150060386475140619150060386475",
"deposits": [ "deposits": [
{ {
"amount": "1.5e-14", "amount": "1.5e-14",
@@ -5002,10 +5104,15 @@
"tranche_id": 46, "tranche_id": 46,
"tranche_start": "2023-11-01T00:00:00.000Z", "tranche_start": "2023-11-01T00:00:00.000Z",
"tranche_end": "2024-05-01T00:00:00.000Z", "tranche_end": "2024-05-01T00:00:00.000Z",
"total_added": "7500", "total_added": "22500",
"total_removed": "0", "total_removed": "0",
"locked_amount": "7500", "locked_amount": "22500",
"deposits": [ "deposits": [
{
"amount": "15000",
"user": "0x2539b51EbDE65a75672aBcfE9439a706a99D18D1",
"tx": "0x959f5eed214f5376e834c2c20ed2a23a75c4465240c2776452ade80afa11ae2e"
},
{ {
"amount": "7500", "amount": "7500",
"user": "0xA530ac2B576eF27C09B7b55C83b3E163D167e9AE", "user": "0xA530ac2B576eF27C09B7b55C83b3E163D167e9AE",
@@ -5014,6 +5121,21 @@
], ],
"withdrawals": [], "withdrawals": [],
"users": [ "users": [
{
"address": "0x2539b51EbDE65a75672aBcfE9439a706a99D18D1",
"deposits": [
{
"amount": "15000",
"user": "0x2539b51EbDE65a75672aBcfE9439a706a99D18D1",
"tranche_id": 46,
"tx": "0x959f5eed214f5376e834c2c20ed2a23a75c4465240c2776452ade80afa11ae2e"
}
],
"withdrawals": [],
"total_tokens": "15000",
"withdrawn_tokens": "0",
"remaining_tokens": "15000"
},
{ {
"address": "0xA530ac2B576eF27C09B7b55C83b3E163D167e9AE", "address": "0xA530ac2B576eF27C09B7b55C83b3E163D167e9AE",
"deposits": [ "deposits": [
@@ -5048,7 +5170,7 @@
"tranche_end": "2023-09-01T00:00:00.000Z", "tranche_end": "2023-09-01T00:00:00.000Z",
"total_added": "17500", "total_added": "17500",
"total_removed": "0", "total_removed": "0",
"locked_amount": "10913.25608896940375", "locked_amount": "10603.93707226247975",
"deposits": [ "deposits": [
{ {
"amount": "12500", "amount": "12500",
@@ -5314,8 +5436,8 @@
"tranche_start": "2023-02-01T00:00:00.000Z", "tranche_start": "2023-02-01T00:00:00.000Z",
"tranche_end": "2023-08-01T00:00:00.000Z", "tranche_end": "2023-08-01T00:00:00.000Z",
"total_added": "37500", "total_added": "37500",
"total_removed": "18077.0118744", "total_removed": "18302.01762945",
"locked_amount": "17350.5026089625535", "locked_amount": "16676.690070595455",
"deposits": [ "deposits": [
{ {
"amount": "7500", "amount": "7500",
@@ -5329,6 +5451,11 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "225.00575505",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0x0a46fa3cc0154727ae4c7abca97bd7e26e6835018f96cf3a3860a9fcc791b9a4"
},
{ {
"amount": "164.727209925", "amount": "164.727209925",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -5507,6 +5634,12 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "225.00575505",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 34,
"tx": "0x0a46fa3cc0154727ae4c7abca97bd7e26e6835018f96cf3a3860a9fcc791b9a4"
},
{ {
"amount": "164.727209925", "amount": "164.727209925",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -5689,8 +5822,8 @@
} }
], ],
"total_tokens": "7500", "total_tokens": "7500",
"withdrawn_tokens": "3867.2532036", "withdrawn_tokens": "4092.25895865",
"remaining_tokens": "3632.7467964" "remaining_tokens": "3407.74104135"
}, },
{ {
"address": "0x0B4e6fcE839B01ef43DA6F890FAC7B1Afb004600", "address": "0x0B4e6fcE839B01ef43DA6F890FAC7B1Afb004600",
@@ -5734,7 +5867,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z", "tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "129999.45", "total_added": "129999.45",
"total_removed": "0", "total_removed": "0",
"locked_amount": "49756.837002255673748325", "locked_amount": "48985.316712730632200127",
"deposits": [ "deposits": [
{ {
"amount": "129999.45", "amount": "129999.45",
@@ -5767,7 +5900,7 @@
"tranche_end": "2024-04-01T00:00:00.000Z", "tranche_end": "2024-04-01T00:00:00.000Z",
"total_added": "54144.7663", "total_added": "54144.7663",
"total_removed": "0", "total_removed": "0",
"locked_amount": "48485.46842731635829207292", "locked_amount": "48004.33914075889629566061",
"deposits": [ "deposits": [
{ {
"amount": "54144.7663", "amount": "54144.7663",
@@ -5800,7 +5933,7 @@
"tranche_end": "2023-09-03T00:00:00.000Z", "tranche_end": "2023-09-03T00:00:00.000Z",
"total_added": "62600", "total_added": "62600",
"total_removed": "0", "total_removed": "0",
"locked_amount": "20022.5830035514954", "locked_amount": "19464.79667681379966",
"deposits": [ "deposits": [
{ {
"amount": "10000", "amount": "10000",
@@ -5993,7 +6126,7 @@
"tranche_end": "2023-09-17T00:00:00.000Z", "tranche_end": "2023-09-17T00:00:00.000Z",
"total_added": "5000", "total_added": "5000",
"total_removed": "0", "total_removed": "0",
"locked_amount": "1791.028665651953", "locked_amount": "1746.4770421106035",
"deposits": [ "deposits": [
{ {
"amount": "5000", "amount": "5000",
@@ -7062,7 +7195,7 @@
"tranche_end": "2023-06-02T00:00:00.000Z", "tranche_end": "2023-06-02T00:00:00.000Z",
"total_added": "1939928.38", "total_added": "1939928.38",
"total_removed": "1709370.7872515768348", "total_removed": "1709370.7872515768348",
"locked_amount": "126202.1342632826843379468", "locked_amount": "108916.74248669465746918748",
"deposits": [ "deposits": [
{ {
"amount": "1852091.69", "amount": "1852091.69",
@@ -40934,7 +41067,7 @@
"tranche_end": "2023-06-05T00:00:00.000Z", "tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "3732368.4671", "total_added": "3732368.4671",
"total_removed": "715655.108029600523393", "total_removed": "715655.108029600523393",
"locked_amount": "218430.066175628697078272948", "locked_amount": "191868.437149237997353062585",
"deposits": [ "deposits": [
{ {
"amount": "1998.95815", "amount": "1998.95815",
@@ -42326,8 +42459,8 @@
"tranche_start": "2022-06-05T00:00:00.000Z", "tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-12-05T00:00:00.000Z", "tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "15870102.715470999700000001", "total_added": "15870102.715470999700000001",
"total_removed": "871680.07831804700259352", "total_removed": "875570.57907812695176852",
"locked_amount": "6074226.5757489411253544394273819693295485", "locked_amount": "5980040.74464094540876136852099906476290886",
"deposits": [ "deposits": [
{ {
"amount": "16249.93", "amount": "16249.93",
@@ -42836,6 +42969,21 @@
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0x625e0c8fb8d5e2dd7fef0359bb396571f7d8aed05a601ddf0f6e6577edb26a13" "tx": "0x625e0c8fb8d5e2dd7fef0359bb396571f7d8aed05a601ddf0f6e6577edb26a13"
}, },
{
"amount": "955.820117175268125",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0x4301fcb38d16c1f6aa4a93a18a277a4e77eb3b4565bf98ca6218f6598b78b90b"
},
{
"amount": "739.2861718946715",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0x989d22d5ba66353cb132b5643100a99e78133e5d40aef1ac91ed6ab0a2290299"
},
{
"amount": "2195.39447101000955",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
"tx": "0xa515dafd399b366ba7f510ea6314587eb19e83fa92dbd5c2a212f3541a0cc4ef"
},
{ {
"amount": "10150.87581603206683", "amount": "10150.87581603206683",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D", "user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
@@ -45010,6 +45158,18 @@
"tranche_id": 2, "tranche_id": 2,
"tx": "0x625e0c8fb8d5e2dd7fef0359bb396571f7d8aed05a601ddf0f6e6577edb26a13" "tx": "0x625e0c8fb8d5e2dd7fef0359bb396571f7d8aed05a601ddf0f6e6577edb26a13"
}, },
{
"amount": "955.820117175268125",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 2,
"tx": "0x4301fcb38d16c1f6aa4a93a18a277a4e77eb3b4565bf98ca6218f6598b78b90b"
},
{
"amount": "739.2861718946715",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 2,
"tx": "0x989d22d5ba66353cb132b5643100a99e78133e5d40aef1ac91ed6ab0a2290299"
},
{ {
"amount": "913.910324501590625", "amount": "913.910324501590625",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -46566,8 +46726,8 @@
} }
], ],
"total_tokens": "259998.8875", "total_tokens": "259998.8875",
"withdrawn_tokens": "159590.522104386423875", "withdrawn_tokens": "161285.6283934563635",
"remaining_tokens": "100408.365395613576125" "remaining_tokens": "98713.2591065436365"
}, },
{ {
"address": "0x89051CAb67Bc7F8CC44F7e270c6EDaf1EC57676c", "address": "0x89051CAb67Bc7F8CC44F7e270c6EDaf1EC57676c",
@@ -46794,6 +46954,12 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "2195.39447101000955",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
"tranche_id": 2,
"tx": "0xa515dafd399b366ba7f510ea6314587eb19e83fa92dbd5c2a212f3541a0cc4ef"
},
{ {
"amount": "10150.87581603206683", "amount": "10150.87581603206683",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D", "user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
@@ -47084,8 +47250,8 @@
} }
], ],
"total_tokens": "150551.801", "total_tokens": "150551.801",
"withdrawn_tokens": "91577.204932811647", "withdrawn_tokens": "93772.59940382165655",
"remaining_tokens": "58974.596067188353" "remaining_tokens": "56779.20159617834345"
}, },
{ {
"address": "0x4d982Ab0823fD2f48e934a7be2bb0a5374a26148", "address": "0x4d982Ab0823fD2f48e934a7be2bb0a5374a26148",
@@ -48948,7 +49114,7 @@
"tranche_start": "2021-11-05T00:00:00.000Z", "tranche_start": "2021-11-05T00:00:00.000Z",
"tranche_end": "2023-05-05T00:00:00.000Z", "tranche_end": "2023-05-05T00:00:00.000Z",
"total_added": "14597706.0446472999", "total_added": "14597706.0446472999",
"total_removed": "6170228.182342542771392682", "total_removed": "6246751.954791069035514289",
"locked_amount": "0", "locked_amount": "0",
"deposits": [ "deposits": [
{ {
@@ -49158,6 +49324,11 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "76523.772448526264121607",
"user": "0xfc3b2D0b548d3edBb512CeE0Bb79Fb7FaD50AaF3",
"tx": "0x5c4a7a8fbab9c7c5899835a06397ea9f9dfbc434376dd0515482b8ba83944d67"
},
{ {
"amount": "1257.7360413991436375", "amount": "1257.7360413991436375",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b", "user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
@@ -56357,6 +56528,12 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "76523.772448526264121607",
"user": "0xfc3b2D0b548d3edBb512CeE0Bb79Fb7FaD50AaF3",
"tranche_id": 3,
"tx": "0x5c4a7a8fbab9c7c5899835a06397ea9f9dfbc434376dd0515482b8ba83944d67"
},
{ {
"amount": "466184.354416277260082874", "amount": "466184.354416277260082874",
"user": "0xfc3b2D0b548d3edBb512CeE0Bb79Fb7FaD50AaF3", "user": "0xfc3b2D0b548d3edBb512CeE0Bb79Fb7FaD50AaF3",
@@ -56371,8 +56548,8 @@
} }
], ],
"total_tokens": "575536.79731735", "total_tokens": "575536.79731735",
"withdrawn_tokens": "499013.024868823735878393", "withdrawn_tokens": "575536.79731735",
"remaining_tokens": "76523.772448526264121607" "remaining_tokens": "0"
}, },
{ {
"address": "0xaAeD573103e3f981867C4cd31c6674DE1c8a13c3", "address": "0xaAeD573103e3f981867C4cd31c6674DE1c8a13c3",
@@ -56864,7 +57041,7 @@
"tranche_start": "2021-10-05T00:00:00.000Z", "tranche_start": "2021-10-05T00:00:00.000Z",
"tranche_end": "2023-04-05T00:00:00.000Z", "tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "5778205.3912159303", "total_added": "5778205.3912159303",
"total_removed": "3480901.398845846498418769", "total_removed": "3492469.400132244009296769",
"locked_amount": "0", "locked_amount": "0",
"deposits": [ "deposits": [
{ {
@@ -57009,6 +57186,11 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "11568.001286397510878",
"user": "0x759C9ABABA492500c4c730bEB568B5b851Dec2c7",
"tx": "0xd4191eb657ad26e08faddde326514247ae4a88e8aefcc7592590f368010c4046"
},
{ {
"amount": "58898.87608135458795", "amount": "58898.87608135458795",
"user": "0x83b1a48376E045D26420200345414e6b93066396", "user": "0x83b1a48376E045D26420200345414e6b93066396",
@@ -57979,6 +58161,12 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "11568.001286397510878",
"user": "0x759C9ABABA492500c4c730bEB568B5b851Dec2c7",
"tranche_id": 4,
"tx": "0xd4191eb657ad26e08faddde326514247ae4a88e8aefcc7592590f368010c4046"
},
{ {
"amount": "209431.056913602489122", "amount": "209431.056913602489122",
"user": "0x759C9ABABA492500c4c730bEB568B5b851Dec2c7", "user": "0x759C9ABABA492500c4c730bEB568B5b851Dec2c7",
@@ -57987,8 +58175,8 @@
} }
], ],
"total_tokens": "220999.0582", "total_tokens": "220999.0582",
"withdrawn_tokens": "209431.056913602489122", "withdrawn_tokens": "220999.0582",
"remaining_tokens": "11568.001286397510878" "remaining_tokens": "0"
}, },
{ {
"address": "0x9058e12e2F32cB1cD4D3123359963D77786477FC", "address": "0x9058e12e2F32cB1cD4D3123359963D77786477FC",
@@ -59069,8 +59257,8 @@
"tranche_start": "2022-06-05T00:00:00.000Z", "tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-06-05T00:00:00.000Z", "tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "472355.6199999996", "total_added": "472355.6199999996",
"total_removed": "44479.0535455583416", "total_removed": "44730.9909821383416",
"locked_amount": "34611.49258501772999518148756976", "locked_amount": "30402.650633041581093917170877724",
"deposits": [ "deposits": [
{ {
"amount": "3000", "amount": "3000",
@@ -65694,6 +65882,16 @@
"user": "0x0436E2E4EA467f9958403dc56635404EE1bb1E94", "user": "0x0436E2E4EA467f9958403dc56635404EE1bb1E94",
"tx": "0x97f5b5ec5ac839a8d182b9267b5d052cb70f9a18b648874337e3d5360b21a5fb" "tx": "0x97f5b5ec5ac839a8d182b9267b5d052cb70f9a18b648874337e3d5360b21a5fb"
}, },
{
"amount": "65.120243532",
"user": "0x3F9E884B459a6AaC3f66b72555611AdE68a7F472",
"tx": "0x4697229c7f272bd3fc06ecdc1b15143d28e20083e15f1b3cadb3fc80e80af180"
},
{
"amount": "186.817193048",
"user": "0x0003423D0A1858B6Eaf9a67914aBc67cDF989c2F",
"tx": "0x7f120bd664d54bdabddad1ddb7ffdd84fbaa2630f1657289e3a7b11503ec4018"
},
{ {
"amount": "182.662252664", "amount": "182.662252664",
"user": "0xb1169C6daAc76bAcaf0D8f87641Fc38fbabe569F", "user": "0xb1169C6daAc76bAcaf0D8f87641Fc38fbabe569F",
@@ -70949,10 +71147,17 @@
"tx": "0x1c2c084e3efafb2ffa09c0b259bd229cce0d6da04dd092d911c7e6136a7ac8a9" "tx": "0x1c2c084e3efafb2ffa09c0b259bd229cce0d6da04dd092d911c7e6136a7ac8a9"
} }
], ],
"withdrawals": [], "withdrawals": [
{
"amount": "186.817193048",
"user": "0x0003423D0A1858B6Eaf9a67914aBc67cDF989c2F",
"tranche_id": 5,
"tx": "0x7f120bd664d54bdabddad1ddb7ffdd84fbaa2630f1657289e3a7b11503ec4018"
}
],
"total_tokens": "200", "total_tokens": "200",
"withdrawn_tokens": "0", "withdrawn_tokens": "186.817193048",
"remaining_tokens": "200" "remaining_tokens": "13.182806952"
}, },
{ {
"address": "0x659ce8E49DA0c872AAC44c70496122044CbcA911", "address": "0x659ce8E49DA0c872AAC44c70496122044CbcA911",
@@ -86287,6 +86492,12 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "65.120243532",
"user": "0x3F9E884B459a6AaC3f66b72555611AdE68a7F472",
"tranche_id": 5,
"tx": "0x4697229c7f272bd3fc06ecdc1b15143d28e20083e15f1b3cadb3fc80e80af180"
},
{ {
"amount": "120.24984145", "amount": "120.24984145",
"user": "0x3F9E884B459a6AaC3f66b72555611AdE68a7F472", "user": "0x3F9E884B459a6AaC3f66b72555611AdE68a7F472",
@@ -86295,8 +86506,8 @@
} }
], ],
"total_tokens": "200", "total_tokens": "200",
"withdrawn_tokens": "120.24984145", "withdrawn_tokens": "185.370084982",
"remaining_tokens": "79.75015855" "remaining_tokens": "14.629915018"
}, },
{ {
"address": "0x8c951C54F9cd08Cf81F770248e835D7A4F491a46", "address": "0x8c951C54F9cd08Cf81F770248e835D7A4F491a46",
@@ -88859,7 +89070,7 @@
"tranche_start": "2021-12-05T00:00:00.000Z", "tranche_start": "2021-12-05T00:00:00.000Z",
"tranche_end": "2022-06-05T00:00:00.000Z", "tranche_end": "2022-06-05T00:00:00.000Z",
"total_added": "171288.42", "total_added": "171288.42",
"total_removed": "70140.5995794947989", "total_removed": "71890.5995794947989",
"locked_amount": "0", "locked_amount": "0",
"deposits": [ "deposits": [
{ {
@@ -93084,6 +93295,41 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "250",
"user": "0xcAEbd70D80D5aB92Ae5A2E1c92F479298826548C",
"tx": "0x7ffbc82e96bb7c796feb548a7ca48a7430a47dae3f2e9de859e6d45ac31a4e6c"
},
{
"amount": "250",
"user": "0xac494D6eC2CcA7C9BD1caD0c1D0516F3706c6b7c",
"tx": "0x85a5d2a4faa6490fc25e080f09dc4d9b5fb3857d762042ba2dcbc0f04de41e0c"
},
{
"amount": "250",
"user": "0x4eE1C5ED78143f298ae4E5E17e538a99E8512db7",
"tx": "0xcb7b99dc5240cdcaaed2fae06e9fd8172db0387179fe09b5f1e1cefff16c6025"
},
{
"amount": "250",
"user": "0x01E41ffFBcBC9DDA6E2e9e7291cA8AcCE1AF2091",
"tx": "0x0af2b3703c268eed4532409b52b6fae066207adf80f9dd2fc17b7aacc40f2879"
},
{
"amount": "250",
"user": "0xa9C1CA1E66CB3063352433843521861844A2Fd33",
"tx": "0x8fad99b8976fff57a099e59351ae96379ec3041e32ccce144727eea29cfffe59"
},
{
"amount": "250",
"user": "0x13A01db15250975cE4f7AddF5C20CC1f5056Fc47",
"tx": "0xc4bcffd71245b9f2af79e04fd58740f7b4cce50da80a405403c959563d132f2c"
},
{
"amount": "250",
"user": "0x2A6Ea2aBEf3D7d7A76CBeF3c297eE0eF9a86BBA8",
"tx": "0x163b699e8a25073ad1ad45c8a8f9b1348eb0b648d3f4bc6f3f64648e0ad726bf"
},
{ {
"amount": "250", "amount": "250",
"user": "0x0E3407C9A94effa675471afe7A9D37e687C32141", "user": "0x0E3407C9A94effa675471afe7A9D37e687C32141",
@@ -95426,10 +95672,17 @@
"tx": "0x87b7e454992157e175f824cbd5cbfd068ce890b8603edcb70174bae9b5c4afc8" "tx": "0x87b7e454992157e175f824cbd5cbfd068ce890b8603edcb70174bae9b5c4afc8"
} }
], ],
"withdrawals": [], "withdrawals": [
{
"amount": "250",
"user": "0x13A01db15250975cE4f7AddF5C20CC1f5056Fc47",
"tranche_id": 6,
"tx": "0xc4bcffd71245b9f2af79e04fd58740f7b4cce50da80a405403c959563d132f2c"
}
],
"total_tokens": "250", "total_tokens": "250",
"withdrawn_tokens": "0", "withdrawn_tokens": "250",
"remaining_tokens": "250" "remaining_tokens": "0"
}, },
{ {
"address": "0x7b4127D262acC0Ff3dB2D0521d1F1f3b602243C2", "address": "0x7b4127D262acC0Ff3dB2D0521d1F1f3b602243C2",
@@ -97942,10 +98195,17 @@
"tx": "0x1c2c084e3efafb2ffa09c0b259bd229cce0d6da04dd092d911c7e6136a7ac8a9" "tx": "0x1c2c084e3efafb2ffa09c0b259bd229cce0d6da04dd092d911c7e6136a7ac8a9"
} }
], ],
"withdrawals": [], "withdrawals": [
{
"amount": "250",
"user": "0x4eE1C5ED78143f298ae4E5E17e538a99E8512db7",
"tranche_id": 6,
"tx": "0xcb7b99dc5240cdcaaed2fae06e9fd8172db0387179fe09b5f1e1cefff16c6025"
}
],
"total_tokens": "250", "total_tokens": "250",
"withdrawn_tokens": "0", "withdrawn_tokens": "250",
"remaining_tokens": "250" "remaining_tokens": "0"
}, },
{ {
"address": "0x1dC24e9601e40013a12CB9976a519860c4eDF359", "address": "0x1dC24e9601e40013a12CB9976a519860c4eDF359",
@@ -97987,10 +98247,17 @@
"tx": "0x1c2c084e3efafb2ffa09c0b259bd229cce0d6da04dd092d911c7e6136a7ac8a9" "tx": "0x1c2c084e3efafb2ffa09c0b259bd229cce0d6da04dd092d911c7e6136a7ac8a9"
} }
], ],
"withdrawals": [], "withdrawals": [
{
"amount": "250",
"user": "0x01E41ffFBcBC9DDA6E2e9e7291cA8AcCE1AF2091",
"tranche_id": 6,
"tx": "0x0af2b3703c268eed4532409b52b6fae066207adf80f9dd2fc17b7aacc40f2879"
}
],
"total_tokens": "250", "total_tokens": "250",
"withdrawn_tokens": "0", "withdrawn_tokens": "250",
"remaining_tokens": "250" "remaining_tokens": "0"
}, },
{ {
"address": "0x6EE016F4512C910b401B18087BBAC15412dD97cA", "address": "0x6EE016F4512C910b401B18087BBAC15412dD97cA",
@@ -98324,10 +98591,17 @@
"tx": "0x1c2c084e3efafb2ffa09c0b259bd229cce0d6da04dd092d911c7e6136a7ac8a9" "tx": "0x1c2c084e3efafb2ffa09c0b259bd229cce0d6da04dd092d911c7e6136a7ac8a9"
} }
], ],
"withdrawals": [], "withdrawals": [
{
"amount": "250",
"user": "0xac494D6eC2CcA7C9BD1caD0c1D0516F3706c6b7c",
"tranche_id": 6,
"tx": "0x85a5d2a4faa6490fc25e080f09dc4d9b5fb3857d762042ba2dcbc0f04de41e0c"
}
],
"total_tokens": "250", "total_tokens": "250",
"withdrawn_tokens": "0", "withdrawn_tokens": "250",
"remaining_tokens": "250" "remaining_tokens": "0"
}, },
{ {
"address": "0xa7137DA63B53138A6DCdC9D9a010F42F951F1113", "address": "0xa7137DA63B53138A6DCdC9D9a010F42F951F1113",
@@ -98369,10 +98643,17 @@
"tx": "0x1c2c084e3efafb2ffa09c0b259bd229cce0d6da04dd092d911c7e6136a7ac8a9" "tx": "0x1c2c084e3efafb2ffa09c0b259bd229cce0d6da04dd092d911c7e6136a7ac8a9"
} }
], ],
"withdrawals": [], "withdrawals": [
{
"amount": "250",
"user": "0xa9C1CA1E66CB3063352433843521861844A2Fd33",
"tranche_id": 6,
"tx": "0x8fad99b8976fff57a099e59351ae96379ec3041e32ccce144727eea29cfffe59"
}
],
"total_tokens": "250", "total_tokens": "250",
"withdrawn_tokens": "0", "withdrawn_tokens": "250",
"remaining_tokens": "250" "remaining_tokens": "0"
}, },
{ {
"address": "0xbCd93996629B0fFA1DfaE74Bb6e107ddB1CE7934", "address": "0xbCd93996629B0fFA1DfaE74Bb6e107ddB1CE7934",
@@ -98399,10 +98680,17 @@
"tx": "0x1c2c084e3efafb2ffa09c0b259bd229cce0d6da04dd092d911c7e6136a7ac8a9" "tx": "0x1c2c084e3efafb2ffa09c0b259bd229cce0d6da04dd092d911c7e6136a7ac8a9"
} }
], ],
"withdrawals": [], "withdrawals": [
{
"amount": "250",
"user": "0xcAEbd70D80D5aB92Ae5A2E1c92F479298826548C",
"tranche_id": 6,
"tx": "0x7ffbc82e96bb7c796feb548a7ca48a7430a47dae3f2e9de859e6d45ac31a4e6c"
}
],
"total_tokens": "250", "total_tokens": "250",
"withdrawn_tokens": "0", "withdrawn_tokens": "250",
"remaining_tokens": "250" "remaining_tokens": "0"
}, },
{ {
"address": "0x64Aac7Cb63317DE77C331686E0b2dAA41303Adf0", "address": "0x64Aac7Cb63317DE77C331686E0b2dAA41303Adf0",
@@ -98481,10 +98769,17 @@
"tx": "0x1c2c084e3efafb2ffa09c0b259bd229cce0d6da04dd092d911c7e6136a7ac8a9" "tx": "0x1c2c084e3efafb2ffa09c0b259bd229cce0d6da04dd092d911c7e6136a7ac8a9"
} }
], ],
"withdrawals": [], "withdrawals": [
{
"amount": "250",
"user": "0x2A6Ea2aBEf3D7d7A76CBeF3c297eE0eF9a86BBA8",
"tranche_id": 6,
"tx": "0x163b699e8a25073ad1ad45c8a8f9b1348eb0b648d3f4bc6f3f64648e0ad726bf"
}
],
"total_tokens": "250", "total_tokens": "250",
"withdrawn_tokens": "0", "withdrawn_tokens": "250",
"remaining_tokens": "250" "remaining_tokens": "0"
}, },
{ {
"address": "0x5D84C52FEf44a84298422f0F87147F80145e2043", "address": "0x5D84C52FEf44a84298422f0F87147F80145e2043",
@@ -144401,6 +144696,11 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "0",
"user": "0x759C9ABABA492500c4c730bEB568B5b851Dec2c7",
"tx": "0x7b20f640f6947ca553bb2ffb572e316f155781b051b198911b234c6c40e79eaa"
},
{ {
"amount": "0", "amount": "0",
"user": "0x759C9ABABA492500c4c730bEB568B5b851Dec2c7", "user": "0x759C9ABABA492500c4c730bEB568B5b851Dec2c7",
@@ -144696,6 +144996,12 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "0",
"user": "0x759C9ABABA492500c4c730bEB568B5b851Dec2c7",
"tranche_id": 9,
"tx": "0x7b20f640f6947ca553bb2ffb572e316f155781b051b198911b234c6c40e79eaa"
},
{ {
"amount": "0", "amount": "0",
"user": "0x759C9ABABA492500c4c730bEB568B5b851Dec2c7", "user": "0x759C9ABABA492500c4c730bEB568B5b851Dec2c7",
@@ -12,6 +12,7 @@ import {
createMarketsDataFragment, createMarketsDataFragment,
assetQuery, assetQuery,
networkParamsQuery, networkParamsQuery,
nodeGuardQuery,
} from '@vegaprotocol/mock'; } from '@vegaprotocol/mock';
import { import {
addDecimalsFormatNumber, addDecimalsFormatNumber,
@@ -158,6 +159,7 @@ describe('Closed markets', { tags: '@smoke' }, () => {
cy.mockGQL((req) => { cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery()); aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery()); aliasGQLQuery(req, 'Statistics', statisticsQuery());
aliasGQLQuery(req, 'NodeGuard', nodeGuardQuery());
aliasGQLQuery(req, 'NetworkParams', networkParamsQuery()); aliasGQLQuery(req, 'NetworkParams', networkParamsQuery());
aliasGQLQuery( aliasGQLQuery(
req, req,
@@ -6,6 +6,7 @@ const row = 'key-value-table-row';
const marketTitle = 'accordion-title'; const marketTitle = 'accordion-title';
const externalLink = 'external-link'; const externalLink = 'external-link';
const accordionContent = 'accordion-content'; const accordionContent = 'accordion-content';
const providerName = 'provider-name';
describe('market info is displayed', { tags: '@smoke' }, () => { describe('market info is displayed', { tags: '@smoke' }, () => {
beforeEach(() => { beforeEach(() => {
@@ -181,9 +182,20 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
cy.getByTestId(marketTitle).contains('Oracle').click(); cy.getByTestId(marketTitle).contains('Oracle').click();
cy.getByTestId(accordionContent) cy.getByTestId(accordionContent)
.getByTestId('provider-name') .getByTestId(providerName)
.and('contain', 'Another oracle'); .and('contain', 'Another oracle');
cy.getByTestId(providerName).should('be.visible').click();
cy.getByTestId('dialog-content')
.eq(1)
.within(() => {
cy.getByTestId('block-explorer-link').contains('Block explorer');
cy.getByTestId('github-link').contains('Oracle repository');
cy.getByTestId('verified-accounts').contains('0 proofs of ownership');
});
cy.getByTestId('dialog-close').click();
cy.getByTestId(accordionContent) cy.getByTestId(accordionContent)
.getByTestId('verified-proofs') .getByTestId('verified-proofs')
.and('contain', '1'); .and('contain', '1');
@@ -16,6 +16,10 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
cy.wait('@Markets'); cy.wait('@Markets');
}); });
beforeEach(() => {
cy.mockTradingPage();
});
describe('limit order', () => { describe('limit order', () => {
before(() => { before(() => {
cy.getByTestId(toggleLimit).click(); cy.getByTestId(toggleLimit).click();
@@ -98,7 +102,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
'have.text', 'have.text',
'Total margin available' 'Total margin available'
); );
cy.get('.text-neutral-500').should('have.text', '~100,000.01 tDAI'); cy.get('.text-neutral-500').should('have.text', '100,000.01 tDAI');
}); });
}); });
}); });
@@ -1,10 +1,6 @@
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
import { aliasGQLQuery } from '@vegaprotocol/cypress'; import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { import { accountsQuery, amendGeneralAccountBalance } from '@vegaprotocol/mock';
accountsQuery,
amendGeneralAccountBalance,
estimateOrderQuery,
} from '@vegaprotocol/mock';
import type { OrderSubmission } from '@vegaprotocol/wallet'; import type { OrderSubmission } from '@vegaprotocol/wallet';
import { createOrder } from '../support/create-order'; import { createOrder } from '../support/create-order';
@@ -44,13 +40,10 @@ describe(
cy.setVegaWallet(); cy.setVegaWallet();
cy.mockTradingPage(); cy.mockTradingPage();
const accounts = accountsQuery(); const accounts = accountsQuery();
amendGeneralAccountBalance(accounts, 'market-0', '100000000'); amendGeneralAccountBalance(accounts, 'market-0', '1');
cy.mockGQL((req) => { cy.mockGQL((req) => {
aliasGQLQuery(req, 'Accounts', accounts); aliasGQLQuery(req, 'Accounts', accounts);
}); });
cy.mockGQL((req) => {
aliasGQLQuery(req, 'EstimateOrder', estimateOrderQuery());
});
cy.mockSubscription(); cy.mockSubscription();
cy.visit('/#/markets/market-0'); cy.visit('/#/markets/market-0');
cy.wait('@Markets'); cy.wait('@Markets');
@@ -66,7 +59,7 @@ describe(
); );
cy.getByTestId('dealticket-warning-margin').should( cy.getByTestId('dealticket-warning-margin').should(
'contain.text', 'contain.text',
'You may not have enough margin available to open this position. 2,354.72283 tDAI is currently required. You have only 1,000.01 tDAI available.' 'You may not have enough margin available to open this position. 5.00 tDAI is currently required. You have only 0.01001 tDAI available.'
); );
cy.getByTestId('deal-ticket-deposit-dialog-button').click(); cy.getByTestId('deal-ticket-deposit-dialog-button').click();
cy.getByTestId('dialog-content') cy.getByTestId('dialog-content')
@@ -17,7 +17,6 @@ describe('ethereum wallet', { tags: '@smoke', testIsolation: true }, () => {
it('can connect', () => { it('can connect', () => {
// 0004-EWAL-001 // 0004-EWAL-001
cy.wait('@NetworkParams');
cy.getByTestId('Deposits').click(); cy.getByTestId('Deposits').click();
cy.getByTestId('deposit-button').click(); cy.getByTestId('deposit-button').click();
cy.getByTestId('connect-eth-wallet-btn').click(); cy.getByTestId('connect-eth-wallet-btn').click();
@@ -30,7 +29,6 @@ describe('ethereum wallet', { tags: '@smoke', testIsolation: true }, () => {
it('should see QR code modal for WalletConnect', () => { it('should see QR code modal for WalletConnect', () => {
// 0004-EWAL-003 // 0004-EWAL-003
cy.wait('@NetworkParams');
cy.getByTestId('Deposits').click(); cy.getByTestId('Deposits').click();
cy.getByTestId('deposit-button').click(); cy.getByTestId('deposit-button').click();
cy.getByTestId('connect-eth-wallet-btn').click(); cy.getByTestId('connect-eth-wallet-btn').click();
+14 -2
View File
@@ -10,7 +10,7 @@ import {
chainIdQuery, chainIdQuery,
chartQuery, chartQuery,
depositsQuery, depositsQuery,
estimateOrderQuery, estimateFeesQuery,
marginsQuery, marginsQuery,
marketCandlesQuery, marketCandlesQuery,
marketDataQuery, marketDataQuery,
@@ -20,12 +20,16 @@ import {
marketsDataQuery, marketsDataQuery,
marketsQuery, marketsQuery,
networkParamsQuery, networkParamsQuery,
nodeGuardQuery,
ordersQuery, ordersQuery,
estimatePositionQuery,
positionsQuery, positionsQuery,
proposalListQuery, proposalListQuery,
statisticsQuery, statisticsQuery,
tradesQuery, tradesQuery,
withdrawalsQuery, withdrawalsQuery,
protocolUpgradeProposalsQuery,
blockStatisticsQuery,
} from '@vegaprotocol/mock'; } from '@vegaprotocol/mock';
import type { PartialDeep } from 'type-fest'; import type { PartialDeep } from 'type-fest';
import type { MarketDataQuery, MarketsQuery } from '@vegaprotocol/market-list'; import type { MarketDataQuery, MarketsQuery } from '@vegaprotocol/market-list';
@@ -82,6 +86,7 @@ const mockTradingPage = (
) => { ) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery()); aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery()); aliasGQLQuery(req, 'Statistics', statisticsQuery());
aliasGQLQuery(req, 'NodeGuard', nodeGuardQuery());
aliasGQLQuery( aliasGQLQuery(
req, req,
'Markets', 'Markets',
@@ -155,9 +160,16 @@ const mockTradingPage = (
aliasGQLQuery(req, 'Candles', candlesQuery()); aliasGQLQuery(req, 'Candles', candlesQuery());
aliasGQLQuery(req, 'Withdrawals', withdrawalsQuery()); aliasGQLQuery(req, 'Withdrawals', withdrawalsQuery());
aliasGQLQuery(req, 'NetworkParams', networkParamsQuery()); aliasGQLQuery(req, 'NetworkParams', networkParamsQuery());
aliasGQLQuery(req, 'EstimateOrder', estimateOrderQuery()); aliasGQLQuery(req, 'EstimateFees', estimateFeesQuery());
aliasGQLQuery(req, 'EstimatePosition', estimatePositionQuery());
aliasGQLQuery(req, 'ProposalsList', proposalListQuery()); aliasGQLQuery(req, 'ProposalsList', proposalListQuery());
aliasGQLQuery(req, 'Deposits', depositsQuery()); aliasGQLQuery(req, 'Deposits', depositsQuery());
aliasGQLQuery(
req,
'ProtocolUpgradeProposals',
protocolUpgradeProposalsQuery()
);
aliasGQLQuery(req, 'BlockStatistics', blockStatisticsQuery());
}; };
declare global { declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace // eslint-disable-next-line @typescript-eslint/no-namespace
@@ -1,5 +1,6 @@
import type { InMemoryCacheConfig } from '@apollo/client'; import type { InMemoryCacheConfig } from '@apollo/client';
import { import {
AppFailure,
NetworkLoader, NetworkLoader,
NodeGuard, NodeGuard,
useEnvironment, useEnvironment,
@@ -9,7 +10,6 @@ import { MaintenancePage } from '@vegaprotocol/ui-toolkit';
import { VegaWalletProvider } from '@vegaprotocol/wallet'; import { VegaWalletProvider } from '@vegaprotocol/wallet';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { AppFailure } from './app-failure';
import { Web3Provider } from './web3-provider'; import { Web3Provider } from './web3-provider';
export const DynamicLoader = dynamic(() => import('../preloader/preloader'), { export const DynamicLoader = dynamic(() => import('../preloader/preloader'), {
@@ -1,3 +1,2 @@
export * from './app-failure';
export * from './app-loader'; export * from './app-loader';
export * from './web3-provider'; export * from './web3-provider';
@@ -4,18 +4,15 @@ import { NodeHealth, NodeUrl, HealthIndicator } from './footer';
import { MockedProvider } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing';
import { Intent } from '@vegaprotocol/ui-toolkit'; import { Intent } from '@vegaprotocol/ui-toolkit';
const mockSetNodeSwitcher = jest.fn();
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'), ...jest.requireActual('@vegaprotocol/environment'),
useEnvironment: jest.fn().mockImplementation(() => ({ useEnvironment: jest.fn().mockImplementation(() => ({
VEGA_URL: 'https://vega-url.wtf', VEGA_URL: 'https://vega-url.wtf',
VEGA_INCIDENT_URL: 'https://blog.vega.community', VEGA_INCIDENT_URL: 'https://blog.vega.community',
})), })),
})); useNodeSwitcherStore: jest.fn(() => mockSetNodeSwitcher),
const mockSetNodeSwitcher = jest.fn();
jest.mock('../../stores', () => ({
...jest.requireActual('../../stores'),
useGlobalStore: () => mockSetNodeSwitcher,
})); }));
describe('NodeHealth', () => { describe('NodeHealth', () => {
+6 -5
View File
@@ -1,11 +1,14 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useEnvironment, useNodeHealth } from '@vegaprotocol/environment'; import {
useEnvironment,
useNodeHealth,
useNodeSwitcherStore,
} from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { Intent } from '@vegaprotocol/ui-toolkit'; import type { Intent } from '@vegaprotocol/ui-toolkit';
import { Indicator, ExternalLink } from '@vegaprotocol/ui-toolkit'; import { Indicator, ExternalLink } from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames'; import classNames from 'classnames';
import type { ButtonHTMLAttributes, ReactNode } from 'react'; import type { ButtonHTMLAttributes, ReactNode } from 'react';
import { useGlobalStore } from '../../stores';
export const Footer = () => { export const Footer = () => {
return ( return (
@@ -20,9 +23,7 @@ export const Footer = () => {
export const NodeHealth = () => { export const NodeHealth = () => {
const { VEGA_URL, VEGA_INCIDENT_URL } = useEnvironment(); const { VEGA_URL, VEGA_INCIDENT_URL } = useEnvironment();
const setNodeSwitcher = useGlobalStore( const setNodeSwitcher = useNodeSwitcherStore((store) => store.setDialogOpen);
(store) => (open: boolean) => store.update({ nodeSwitcherDialog: open })
);
const { datanodeBlockHeight, text, intent } = useNodeHealth(); const { datanodeBlockHeight, text, intent } = useNodeHealth();
const onClick = useCallback(() => { const onClick = useCallback(() => {
setNodeSwitcher(true); setNodeSwitcher(true);
@@ -7,7 +7,13 @@ import {
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { RISK_ACCEPTED_KEY } from '../constants'; import { RISK_ACCEPTED_KEY } from '../constants';
import { TelemetryApproval } from './telemetry-approval'; import { TelemetryApproval } from './telemetry-approval';
import { Networks, useEnvironment } from '@vegaprotocol/environment'; import {
DOCS_VEGA_WALLET,
GET_VEGA_WALLET_URL,
Networks,
useDocsLink,
useEnvironment,
} from '@vegaprotocol/environment';
import { useLocalStorage } from '@vegaprotocol/react-helpers'; import { useLocalStorage } from '@vegaprotocol/react-helpers';
interface Props { interface Props {
@@ -46,8 +52,9 @@ export const RiskNoticeDialog = ({ onClose, network }: Props) => {
}; };
const TestnetContent = ({ network }: { network: Networks }) => { const TestnetContent = ({ network }: { network: Networks }) => {
const { VEGA_DOCS_URL, GITHUB_FEEDBACK_URL, VEGA_WALLET_URL } = const { GITHUB_FEEDBACK_URL } = useEnvironment();
useEnvironment(); const docsLink = useDocsLink();
return ( return (
<> <>
<p className="mb-4"> <p className="mb-4">
@@ -62,17 +69,17 @@ const TestnetContent = ({ network }: { network: Networks }) => {
[network] [network]
)} )}
</p> </p>
{VEGA_DOCS_URL && GITHUB_FEEDBACK_URL && VEGA_WALLET_URL && ( {GITHUB_FEEDBACK_URL && GET_VEGA_WALLET_URL && docsLink && (
<ul className="list-disc pl-4"> <ul className="list-disc pl-4">
<li className="mb-1"> <li className="mb-1">
<Link href={VEGA_WALLET_URL} target="_blank"> <Link href={GET_VEGA_WALLET_URL} target="_blank">
<span className="underline">{t('Learn about Vega wallet')}</span>{' '} <span className="underline">{t('Get a Vega Wallet')}</span>{' '}
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} /> <VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
</Link> </Link>
</li> </li>
<li className="mb-1"> <li className="mb-1">
<Link href={VEGA_DOCS_URL} target="_blank"> <Link href={docsLink(DOCS_VEGA_WALLET)} target="_blank">
<span className="underline">{t('View documentation')}</span>{' '} <span className="underline">{t('Learn about Vega Wallet')}</span>{' '}
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} /> <VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
</Link> </Link>
</li> </li>
+6 -6
View File
@@ -21,9 +21,10 @@ import {
NodeSwitcherDialog, NodeSwitcherDialog,
useEnvironment, useEnvironment,
useInitializeEnv, useInitializeEnv,
useNodeSwitcherStore,
} from '@vegaprotocol/environment'; } from '@vegaprotocol/environment';
import './styles.css'; import './styles.css';
import { useGlobalStore, usePageTitleStore } from '../stores'; import { usePageTitleStore } from '../stores';
import { Footer } from '../components/footer'; import { Footer } from '../components/footer';
import DialogsContainer from './dialogs-container'; import DialogsContainer from './dialogs-container';
import ToastsManager from './toasts-manager'; import ToastsManager from './toasts-manager';
@@ -115,11 +116,10 @@ function AppBody({ Component }: AppProps) {
function VegaTradingApp(props: AppProps) { function VegaTradingApp(props: AppProps) {
const status = useEnvironment((store) => store.status); const status = useEnvironment((store) => store.status);
const { nodeSwitcherOpen, setNodeSwitcher } = useGlobalStore((store) => ({ const [nodeSwitcherOpen, setNodeSwitcher] = useNodeSwitcherStore((store) => [
nodeSwitcherOpen: store.nodeSwitcherDialog, store.dialogOpen,
setNodeSwitcher: (open: boolean) => store.setDialogOpen,
store.update({ nodeSwitcherDialog: open }), ]);
}));
useInitializeEnv(); useInitializeEnv();
-2
View File
@@ -3,7 +3,6 @@ import { create } from 'zustand';
import produce from 'immer'; import produce from 'immer';
interface GlobalStore { interface GlobalStore {
nodeSwitcherDialog: boolean;
marketId: string | null; marketId: string | null;
update: (store: Partial<Omit<GlobalStore, 'update'>>) => void; update: (store: Partial<Omit<GlobalStore, 'update'>>) => void;
shouldDisplayWelcomeDialog: boolean; shouldDisplayWelcomeDialog: boolean;
@@ -15,7 +14,6 @@ interface PageTitleStore {
} }
export const useGlobalStore = create<GlobalStore>()((set) => ({ export const useGlobalStore = create<GlobalStore>()((set) => ({
nodeSwitcherDialog: false,
marketId: LocalStorage.getItem('marketId') || null, marketId: LocalStorage.getItem('marketId') || null,
shouldDisplayWelcomeDialog: false, shouldDisplayWelcomeDialog: false,
update: (newState) => { update: (newState) => {
@@ -6,10 +6,10 @@ WORKDIR /app
ARG APP ARG APP
ARG ENV_NAME="" ARG ENV_NAME=""
RUN apk add --update --no-cache \ RUN apk add --update --no-cache \
python3 \ python3==3.10.11-r0 \
make \ make==4.3-r0 \
gcc \ gcc==11.2.1_git20220219-r2 \
g++ g++==11.2.1_git20220219-r2
COPY . ./ COPY . ./
RUN yarn --network-timeout 100000 --pure-lockfile RUN yarn --network-timeout 100000 --pure-lockfile
# work around for different build process in trading # work around for different build process in trading
@@ -22,8 +22,10 @@ FROM --platform=amd64 nginx:1.23-alpine@sha256:6318314189b40e73145a48060bff4783a
# configuration of system # configuration of system
EXPOSE 80 EXPOSE 80
# Copy dist # Copy dist
WORKDIR /usr/share/nginx/html
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -rf /usr/share/nginx/html/* RUN rm -rf /usr/share/nginx/html/*
COPY --from=build /app/dist/apps/${APP}/* /usr/share/nginx/html 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 RUN apk add --no-cache go-ipfs==0.16.0-r6 \
&& ipfs init \
&& echo "$(ipfs add -rQ /usr/share/nginx/html)" > /ipfs-hash \
&& echo "ipfs hash of this build: $(cat /ipfs-hash)"
@@ -1,6 +1,10 @@
FROM --platform=amd64 nginx:1.23-alpine@sha256:6318314189b40e73145a48060bff4783a116c34cc7241532d0d94198fb2c9629 FROM --platform=amd64 nginx:1.23-alpine@sha256:6318314189b40e73145a48060bff4783a116c34cc7241532d0d94198fb2c9629
EXPOSE 80 EXPOSE 80
WORKDIR /usr/share/nginx/html
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -rf /usr/share/nginx/html/* RUN rm -rf /usr/share/nginx/html/*
COPY ./dist-result/ /usr/share/nginx/html/ COPY ./dist-result/ /usr/share/nginx/html/
RUN apk add --no-cache go-ipfs==0.16.0-r6 \
&& ipfs init \
&& echo "$(ipfs add -rQ /usr/share/nginx/html)" > /ipfs-hash \
&& echo "ipfs hash of this build: $(cat /ipfs-hash)"
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash -e
yarn --pure-lockfile
app=${1:-trading}
flags="--env=${2:-mainnet}"
yarn install
if [ "${app}" = "trading" ]; then
yarn nx export trading $flags
DIST_LOCATION=dist/apps/trading/exported
else
yarn nx build ${app} $flags
DIST_LOCATION=dist/apps/${app}
fi
cp -r $DIST_LOCATION dist-result
@@ -33,9 +33,9 @@ export const useAccountBalance = (assetId?: string) => {
return useMemo( return useMemo(
() => ({ () => ({
accountBalance, accountBalance: pubKey ? accountBalance : '',
accountDecimals, accountDecimals: pubKey ? accountDecimals : null,
}), }),
[accountBalance, accountDecimals] [accountBalance, accountDecimals, pubKey]
); );
}; };
@@ -32,9 +32,9 @@ export const useMarketAccountBalance = (marketId: string) => {
return useMemo( return useMemo(
() => ({ () => ({
accountBalance, accountBalance: pubKey ? accountBalance : '',
accountDecimals, accountDecimals: pubKey ? accountDecimals : null,
}), }),
[accountBalance, accountDecimals] [accountBalance, accountDecimals, pubKey]
); );
}; };
@@ -0,0 +1,280 @@
import { VegaDataSource } from './data-source';
import type { ApolloClient } from '@apollo/client';
import { Interval } from 'pennant';
import type {
CandleFieldsFragment,
CandlesQuery,
} from './__generated__/Candles';
import * as Schema from '@vegaprotocol/types';
const returnDataMocks = (nodes: CandleFieldsFragment[]): CandlesQuery => {
return {
data: {
market: {
decimalPlaces: 1,
positionDecimalPlaces: 1,
candlesConnection: {
edges: nodes.map((node) => ({ node })),
},
},
},
} as CandlesQuery;
};
const dataMocks: { [key in Schema.Interval]: Partial<CandleFieldsFragment>[] } =
{
[Schema.Interval.INTERVAL_I1M]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T12:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-10T12:05:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_I5M]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T12:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-10T12:25:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_I15M]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T12:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-10T13:15:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_I1H]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T12:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-10T17:00:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_I6H]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T12:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-11T18:00:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_I1D]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T00:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-15T00:00:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_BLOCK]: [],
};
describe('VegaDataSource', () => {
const marketId = 'marketId';
const partyId = 'partyId';
const client = {
query: jest.fn().mockImplementation(({ variables: { interval } }) => {
return returnDataMocks(
dataMocks[interval as Schema.Interval] as CandleFieldsFragment[]
);
}),
} as unknown as ApolloClient<object>;
it('should be properly initialized', () => {
const dataSource = new VegaDataSource(client, marketId, partyId);
expect(dataSource).toBeInstanceOf(VegaDataSource);
expect(dataSource.onReady).toBeDefined();
expect(dataSource.query).toBeDefined();
expect(dataSource.subscribeData).toBeDefined();
expect(dataSource.unsubscribeData).toBeDefined();
expect(dataSource.decimalPlaces).toBeDefined();
expect(dataSource.positionDecimalPlaces).toBeDefined();
});
describe('query should return continuous data', () => {
it('when interval is I1M', async () => {
const dataSource = new VegaDataSource(client, marketId, partyId);
const data = await dataSource.query(Interval.I1M, '');
expect(data).toHaveLength(6);
expect(data[1]).toStrictEqual({
date: new Date('2023-05-10T12:01:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
expect(data[2]).toStrictEqual({
date: new Date('2023-05-10T12:02:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
});
it('when interval is I5M', async () => {
const dataSource = new VegaDataSource(client, marketId, partyId);
const data = await dataSource.query(Interval.I5M, '');
expect(data).toHaveLength(6);
expect(data[1]).toStrictEqual({
date: new Date('2023-05-10T12:05:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
expect(data[2]).toStrictEqual({
date: new Date('2023-05-10T12:10:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
});
it('when interval is I15M', async () => {
const dataSource = new VegaDataSource(client, marketId, partyId);
const data = await dataSource.query(Interval.I15M, '');
expect(data).toHaveLength(6);
expect(data[1]).toStrictEqual({
date: new Date('2023-05-10T12:15:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
expect(data[2]).toStrictEqual({
date: new Date('2023-05-10T12:30:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
});
it('when interval is I1H', async () => {
const dataSource = new VegaDataSource(client, marketId, partyId);
const data = await dataSource.query(Interval.I1H, '');
expect(data).toHaveLength(6);
expect(data[1]).toStrictEqual({
date: new Date('2023-05-10T13:00:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
expect(data[2]).toStrictEqual({
date: new Date('2023-05-10T14:00:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
});
it('when interval is I6H', async () => {
const dataSource = new VegaDataSource(client, marketId, partyId);
const data = await dataSource.query(Interval.I6H, '');
expect(data).toHaveLength(6);
expect(data[1]).toStrictEqual({
date: new Date('2023-05-10T18:00:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
expect(data[2]).toStrictEqual({
date: new Date('2023-05-11T00:00:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
});
it('when interval is I1D', async () => {
const dataSource = new VegaDataSource(client, marketId, partyId);
const data = await dataSource.query(Interval.I1D, '');
expect(data).toHaveLength(6);
expect(data[1]).toStrictEqual({
date: new Date('2023-05-11T00:00:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
expect(data[2]).toStrictEqual({
date: new Date('2023-05-12T00:00:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
});
});
});
+88 -3
View File
@@ -1,4 +1,11 @@
import type { ApolloClient } from '@apollo/client'; import type { ApolloClient } from '@apollo/client';
import type { Duration } from 'date-fns';
import {
add,
differenceInDays,
differenceInHours,
differenceInMinutes,
} from 'date-fns';
import type { Candle, DataSource } from 'pennant'; import type { Candle, DataSource } from 'pennant';
import { Interval as PennantInterval } from 'pennant'; import { Interval as PennantInterval } from 'pennant';
@@ -153,7 +160,6 @@ export class VegaDataSource implements DataSource {
}, },
fetchPolicy: 'no-cache', fetchPolicy: 'no-cache',
}); });
if (data?.market?.candlesConnection?.edges) { if (data?.market?.candlesConnection?.edges) {
const decimalPlaces = data.market.decimalPlaces; const decimalPlaces = data.market.decimalPlaces;
const positionDecimalPlaces = data.market.positionDecimalPlaces; const positionDecimalPlaces = data.market.positionDecimalPlaces;
@@ -163,8 +169,8 @@ export class VegaDataSource implements DataSource {
.filter((node): node is CandleFieldsFragment => !!node) .filter((node): node is CandleFieldsFragment => !!node)
.map((node) => .map((node) =>
parseCandle(node, decimalPlaces, positionDecimalPlaces) parseCandle(node, decimalPlaces, positionDecimalPlaces)
); )
.reduce(checkGranulationContinuity(interval), []);
return candles; return candles;
} else { } else {
return []; return [];
@@ -213,6 +219,85 @@ export class VegaDataSource implements DataSource {
} }
} }
const getDuration = (
interval: PennantInterval,
multiplier: number
): Duration => {
switch (interval) {
case 'I1D':
return {
days: 1 * multiplier,
};
case 'I1H':
return {
hours: 1 * multiplier,
};
case 'I1M':
return {
minutes: 1 * multiplier,
};
case 'I5M':
return {
minutes: 5 * multiplier,
};
case 'I6H':
return {
hours: 6 * multiplier,
};
case 'I15M':
return {
minutes: 15 * multiplier,
};
}
};
const getDifference = (
interval: PennantInterval,
dateLeft: Date,
dateRight: Date
): number => {
switch (interval) {
case 'I1D':
return differenceInDays(dateRight, dateLeft);
case 'I6H':
return differenceInHours(dateRight, dateLeft) / 6;
case 'I1H':
return differenceInHours(dateRight, dateLeft);
case 'I15M':
return differenceInMinutes(dateRight, dateLeft) / 15;
case 'I5M':
return differenceInMinutes(dateRight, dateLeft) / 5;
case 'I1M':
return differenceInMinutes(dateRight, dateLeft);
}
};
const checkGranulationContinuity =
(interval: PennantInterval) =>
(agg: Candle[], candle: Candle, i: number): Candle[] => {
if (agg.length && i) {
const previous = agg[agg.length - 1];
const difference = getDifference(interval, previous.date, candle.date);
if (difference > 1) {
for (let j = 1; j < difference; j++) {
const duration = getDuration(interval, j);
const newStartDate = add(previous.date, duration);
const newParsedCandle: Candle = {
date: newStartDate,
high: previous.close,
low: previous.close,
open: previous.close,
close: previous.close,
volume: 0,
};
agg.push(newParsedCandle);
}
}
}
agg.push(candle);
return agg;
};
function parseCandle( function parseCandle(
candle: CandleFieldsFragment, candle: CandleFieldsFragment,
decimalPlaces: number, decimalPlaces: number,
+4
View File
@@ -7,6 +7,7 @@ export * from '../candles-chart/src/lib/chart.mock';
export * from '../deal-ticket/src/hooks/estimate-order.mock'; export * from '../deal-ticket/src/hooks/estimate-order.mock';
export * from '../deposits/src/lib/deposit.mock'; export * from '../deposits/src/lib/deposit.mock';
export * from '../environment/src/utils/node.mock'; export * from '../environment/src/utils/node.mock';
export * from '../environment/src/components/node-guard/node-guard.mock';
export * from '../fills/src/lib/fills.mock'; export * from '../fills/src/lib/fills.mock';
export * from '../proposals/src/lib/proposals-data-provider/proposals.mock'; export * from '../proposals/src/lib/proposals-data-provider/proposals.mock';
export * from '../ledger/src/lib/ledger-entries.mock'; export * from '../ledger/src/lib/ledger-entries.mock';
@@ -22,5 +23,8 @@ export * from '../orders/src/lib/components/order-data-provider/orders.mock';
export * from '../positions/src/lib/positions.mock'; export * from '../positions/src/lib/positions.mock';
export * from '../network-parameters/src/network-params.mock'; export * from '../network-parameters/src/network-params.mock';
export * from '../wallet/src/connect-dialog/chain-id.mock'; export * from '../wallet/src/connect-dialog/chain-id.mock';
export * from '../positions/src/lib/estimate-position.mock';
export * from '../trades/src/lib/trades.mock'; export * from '../trades/src/lib/trades.mock';
export * from '../withdraws/src/lib/withdrawal.mock'; export * from '../withdraws/src/lib/withdrawal.mock';
export * from '../proposals/src/lib/protocol-upgrade-proposals/protocol-statistics-proposals.mock';
export * from '../proposals/src/lib/protocol-upgrade-proposals/block-statistics.mock';
@@ -1,4 +1,6 @@
import { gql } from 'graphql-request';
import { selfDelegate } from '../capsule/self-delegate'; import { selfDelegate } from '../capsule/self-delegate';
import { requestGQL, setGraphQLEndpoint } from '../capsule/request';
declare global { declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace // eslint-disable-next-line @typescript-eslint/no-namespace
@@ -19,112 +21,143 @@ export const addValidatorsSelfDelegate = () => {
vegaUrl: Cypress.env('VEGA_URL'), vegaUrl: Cypress.env('VEGA_URL'),
faucetUrl: Cypress.env('FAUCET_URL'), faucetUrl: Cypress.env('FAUCET_URL'),
}; };
setGraphQLEndpoint(config.vegaUrl);
cy.wrap(getStakedByOperator()).as('selfStakeAmount');
cy.get('@selfStakeAmount').then((selfStakeAmount) => {
if (String(selfStakeAmount) == '0') {
// Get node wallet recovery phrases
cy.exec('vegacapsule nodes ls --home-path ~/.vegacapsule/testnet/')
.its('stdout')
.then((result) => {
const obj = JSON.parse(result);
console.log(obj);
cy.writeFile(
'./src/fixtures/wallet/node0RecoveryPhrase',
obj['testnet-nodeset-validators-0-validator'].Vega.NodeWalletInfo
.VegaWalletRecoveryPhrase
);
cy.writeFile(
'./src/fixtures/wallet/node1RecoveryPhrase',
obj['testnet-nodeset-validators-1-validator'].Vega.NodeWalletInfo
.VegaWalletRecoveryPhrase
);
cy.wrap(
obj['testnet-nodeset-validators-0-validator'].Vega.NodeWalletInfo
.VegaWalletPublicKey
).as('node0PubKey');
cy.wrap(
obj['testnet-nodeset-validators-1-validator'].Vega.NodeWalletInfo
.VegaWalletPublicKey
).as('node1PubKey');
// Get node wallet recovery phrases cy.wrap(
cy.exec('vegacapsule nodes ls --home-path ~/.vegacapsule/testnet/') obj['testnet-nodeset-validators-0-validator'].Vega.NodeWalletInfo
.its('stdout') .VegaWalletID
.then((result) => { ).as('node0Id');
const obj = JSON.parse(result); cy.wrap(
console.log(obj); obj['testnet-nodeset-validators-1-validator'].Vega.NodeWalletInfo
cy.writeFile( .VegaWalletID
'./src/fixtures/wallet/node0RecoveryPhrase', ).as('node1Id');
obj['testnet-nodeset-validators-0-validator'].Vega.NodeWalletInfo });
.VegaWalletRecoveryPhrase
// Import node wallets
cy.exec(
'vega wallet import -w node0_wallet --recovery-phrase-file ./src/fixtures/wallet/node0RecoveryPhrase -p ./src/fixtures/wallet/passphrase --home ~/.vegacapsule/testnet/wallet'
); );
cy.writeFile( cy.exec(
'./src/fixtures/wallet/node1RecoveryPhrase', 'vega wallet import -w node1_wallet --recovery-phrase-file ./src/fixtures/wallet/node1RecoveryPhrase -p ./src/fixtures/wallet/passphrase --home ~/.vegacapsule/testnet/wallet'
obj['testnet-nodeset-validators-1-validator'].Vega.NodeWalletInfo
.VegaWalletRecoveryPhrase
); );
cy.wrap(
obj['testnet-nodeset-validators-0-validator'].Vega.NodeWalletInfo
.VegaWalletPublicKey
).as('node0PubKey');
cy.wrap(
obj['testnet-nodeset-validators-1-validator'].Vega.NodeWalletInfo
.VegaWalletPublicKey
).as('node1PubKey');
cy.wrap( // Initialise api token
obj['testnet-nodeset-validators-0-validator'].Vega.NodeWalletInfo cy.exec(
.VegaWalletID 'vega wallet api-token init --home ~/.vegacapsule/testnet/wallet --passphrase-file ./src/fixtures/wallet/passphrase'
).as('node0Id'); );
cy.wrap(
obj['testnet-nodeset-validators-1-validator'].Vega.NodeWalletInfo
.VegaWalletID
).as('node1Id');
});
// Import node wallets // Generate api tokens for wallets
cy.exec( cy.exec(
'vega wallet import -w node0_wallet --recovery-phrase-file ./src/fixtures/wallet/node0RecoveryPhrase -p ./src/fixtures/wallet/passphrase --home ~/.vegacapsule/testnet/wallet' 'vega wallet api-token generate --wallet-name node0_wallet --tokens-passphrase-file ./src/fixtures/wallet/passphrase --wallet-passphrase-file ./src/fixtures/wallet/passphrase --home ~/.vegacapsule/testnet/wallet'
); )
cy.exec( .its('stdout')
'vega wallet import -w node1_wallet --recovery-phrase-file ./src/fixtures/wallet/node1RecoveryPhrase -p ./src/fixtures/wallet/passphrase --home ~/.vegacapsule/testnet/wallet' .then((result) => {
); const apiToken = result.match('[a-zA-Z0-9]{64}');
if (apiToken) {
cy.wrap(apiToken[0]).as('node0ApiToken');
}
});
// Initialise api token cy.exec(
cy.exec( 'vega wallet api-token generate --wallet-name node1_wallet --tokens-passphrase-file ./src/fixtures/wallet/passphrase --wallet-passphrase-file ./src/fixtures/wallet/passphrase --home ~/.vegacapsule/testnet/wallet'
'vega wallet api-token init --home ~/.vegacapsule/testnet/wallet --passphrase-file ./src/fixtures/wallet/passphrase' )
); .its('stdout')
.then((result) => {
const apiToken = result.match('[a-zA-Z0-9]{64}');
if (apiToken) {
cy.wrap(apiToken[0]).as('node1ApiToken');
}
});
// Generate api tokens for wallets cy.updateCapsuleMultiSig();
cy.exec( cy.highlight('Validators self-delegating');
'vega wallet api-token generate --wallet-name node0_wallet --tokens-passphrase-file ./src/fixtures/wallet/passphrase --wallet-passphrase-file ./src/fixtures/wallet/passphrase --home ~/.vegacapsule/testnet/wallet'
)
.its('stdout')
.then((result) => {
const apiToken = result.match('[a-zA-Z0-9]{64}');
if (apiToken) {
cy.wrap(apiToken[0]).as('node0ApiToken');
}
});
cy.exec( // Self delegating Node 0 wallet
'vega wallet api-token generate --wallet-name node1_wallet --tokens-passphrase-file ./src/fixtures/wallet/passphrase --wallet-passphrase-file ./src/fixtures/wallet/passphrase --home ~/.vegacapsule/testnet/wallet' cy.get('@node0PubKey').then((node0PubKey) => {
) cy.get('@node0ApiToken').then((node0ApiToken) => {
.its('stdout') cy.get('@node0Id').then((node0Id) => {
.then((result) => { cy.wrap(
const apiToken = result.match('[a-zA-Z0-9]{64}'); selfDelegate(
if (apiToken) { config,
cy.wrap(apiToken[0]).as('node1ApiToken'); String(node0PubKey),
} String(node0ApiToken),
}); String(node0Id)
),
cy.updateCapsuleMultiSig(); { timeout: 60000 }
cy.highlight('Validators self-delegating'); );
// Self delegating Node 1 wallet
// Self delegating Node 0 wallet cy.get('@node1PubKey').then((node1PubKey) => {
cy.get('@node0PubKey').then((node0PubKey) => { cy.get('@node1ApiToken').then((node1ApiToken) => {
cy.get('@node0ApiToken').then((node0ApiToken) => { cy.get('@node1Id').then((node1Id) => {
cy.get('@node0Id').then((node0Id) => { cy.wrap(
cy.wrap( selfDelegate(
selfDelegate( config,
config, String(node1PubKey),
String(node0PubKey), String(node1ApiToken),
String(node0ApiToken), String(node1Id)
String(node0Id) ),
), { timeout: 60000 }
{ timeout: 60000 } );
); });
// Self delegating Node 1 wallet });
cy.get('@node1PubKey').then((node1PubKey) => {
cy.get('@node1ApiToken').then((node1ApiToken) => {
cy.get('@node1Id').then((node1Id) => {
cy.wrap(
selfDelegate(
config,
String(node1PubKey),
String(node1ApiToken),
String(node1Id)
),
{ timeout: 60000 }
);
}); });
}); });
}); });
}); });
}); }
}); });
}); });
}; };
async function getStakedByOperator() {
const query = gql`
query ExplorerNodes {
nodesConnection {
edges {
node {
stakedByOperator
}
}
}
}
`;
const res = await requestGQL<{
nodesConnection: {
edges: Array<{
node: {
stakedByOperator: string;
};
}>;
};
}>(query);
return res.nodesConnection.edges[0].node.stakedByOperator;
}
+6 -1
View File
@@ -17,7 +17,12 @@ const hasOperationName = (
operationName: string operationName: string
) => { ) => {
const { body } = req; const { body } = req;
return 'operationName' in body && body.operationName === operationName; return (
typeof body === 'object' &&
body !== null &&
'operationName' in body &&
body.operationName === operationName
);
}; };
export function addMockGQLCommand() { export function addMockGQLCommand() {
+6 -2
View File
@@ -20,8 +20,12 @@ const mockSocketServer = Cypress.env('VEGA_URL')
: null; : null;
// DO NOT REMOVE: PASSTHROUGH for walletconnect // DO NOT REMOVE: PASSTHROUGH for walletconnect
// eslint-disable-next-line @typescript-eslint/no-unused-vars new Server('wss://relay.walletconnect.com', {
const relayServer = new Server('wss://relay.walletconnect.com', { mock: false,
});
// DO NOT REMOVE: PASSTHROUGH for hot module reload
new Server('ws://localhost:4200/_next/webpack-hmr', {
mock: false, mock: false,
}); });
@@ -97,7 +97,7 @@ export const useColumnSizes = ({
const setSizes = useCallback( const setSizes = useCallback(
(apiEvent: GridReadyEvent | GridSizeChangedEvent) => { (apiEvent: GridReadyEvent | GridSizeChangedEvent) => {
if (!storeKey || !Object.keys(sizes).length || !widthRef.current) { if (!storeKey || !Object.keys(sizes).length || !widthRef.current) {
apiEvent.api.sizeColumnsToFit(); apiEvent?.api.sizeColumnsToFit();
} else { } else {
const recalculatedSizes = recalculateSizes(sizes); const recalculatedSizes = recalculateSizes(sizes);
const newSizes = Object.entries(recalculatedSizes).map( const newSizes = Object.entries(recalculatedSizes).map(
@@ -3,6 +3,7 @@ import { useMemo } from 'react';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
interface OrderTypeCellProps { interface OrderTypeCellProps {
value?: Schema.OrderType; value?: Schema.OrderType;
@@ -23,7 +24,15 @@ export const OrderTypeCell = ({
} }
if (!value) return '-'; if (!value) return '-';
if (order?.peggedOrder) { if (order?.peggedOrder) {
return t('Pegged'); const reference =
Schema.PeggedReferenceMapping[order.peggedOrder?.reference];
// the offset (e.g. + 0.001 for a Sell, or -1231.023 for a Buy)
const side = order.side === Schema.Side.SIDE_BUY ? '-' : '+';
const offset = addDecimalsFormatNumber(
order.peggedOrder?.offset,
order.market.decimalPlaces
);
return t('%s %s %s Peg limit', [reference, side, offset]);
} }
if (order?.liquidityProvision) { if (order?.liquidityProvision) {
return t('Liquidity provision'); return t('Liquidity provision');
@@ -1,132 +0,0 @@
import React from 'react';
import type { ReactNode } from 'react';
import { t } from '@vegaprotocol/i18n';
import { Icon, Tooltip, TrafficLight } from '@vegaprotocol/ui-toolkit';
import { IconNames } from '@blueprintjs/icons';
import * as constants from '../constants';
interface DealTicketEstimatesProps {
quoteName?: string;
price?: string;
estCloseOut?: string;
estMargin?: string;
fees?: string;
notionalSize?: string;
size?: string;
slippage?: string;
}
export const DealTicketEstimates = ({
price,
quoteName,
estCloseOut,
estMargin,
fees,
notionalSize,
size,
slippage,
}: DealTicketEstimatesProps) => (
<dl className="text-black dark:text-white">
{size && (
<div className="flex justify-between mb-2">
<DataTitle>{t('Contracts')}</DataTitle>
<ValueTooltipRow
value={size}
description={constants.CONTRACTS_MARGIN_TOOLTIP_TEXT}
id="contracts_tooltip_trigger"
/>
</div>
)}
{price && (
<div className="flex justify-between mb-2">
<DataTitle>{t('Est. Price')}</DataTitle>
<dd>{price}</dd>
</div>
)}
{notionalSize && (
<div className="flex justify-between mb-2">
<DataTitle quoteName={quoteName}>{t('Est. Position Size')}</DataTitle>
<ValueTooltipRow
value={notionalSize}
description={constants.NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName || '')}
/>
</div>
)}
{fees && (
<div className="flex justify-between mb-2">
<DataTitle quoteName={quoteName}>{t('Est. Fees')}</DataTitle>
<ValueTooltipRow
value={fees}
description={constants.EST_FEES_TOOLTIP_TEXT}
/>
</div>
)}
{estMargin && (
<div className="flex justify-between mb-2">
<DataTitle quoteName={quoteName}>{t('Est. Margin')}</DataTitle>
<ValueTooltipRow
value={estMargin}
description={constants.EST_MARGIN_TOOLTIP_TEXT(quoteName || '')}
/>
</div>
)}
{estCloseOut && (
<div className="flex justify-between mb-2">
<DataTitle quoteName={quoteName}>{t('Est. Close out')}</DataTitle>
<ValueTooltipRow
value={estCloseOut}
description={constants.EST_CLOSEOUT_TOOLTIP_TEXT(quoteName || '')}
/>
</div>
)}
{slippage && (
<div className="flex justify-between mb-2">
<DataTitle>{t('Est. Price Impact / Slippage')}</DataTitle>
<ValueTooltipRow description={constants.EST_SLIPPAGE}>
<TrafficLight value={parseFloat(slippage)} q1={1} q2={5}>
{slippage}%
</TrafficLight>
</ValueTooltipRow>
</div>
)}
</dl>
);
interface DataTitleProps {
children: ReactNode;
quoteName?: string;
}
export const DataTitle = ({ children, quoteName = '' }: DataTitleProps) => (
<dt>
{children}
{quoteName && <small> ({quoteName})</small>}
</dt>
);
interface ValueTooltipProps {
value?: string;
children?: ReactNode;
description: string;
id?: string;
}
export const ValueTooltipRow = ({
value,
children,
description,
id,
}: ValueTooltipProps) => (
<dd className="flex gap-x-2 items-center">
{value || children}
<Tooltip align="center" description={description}>
<div className="cursor-help" id={id || ''} tabIndex={-1}>
<Icon
name={IconNames.ISSUE}
className="block rotate-180"
ariaLabel={description}
/>
</div>
</Tooltip>
</dd>
);
@@ -1,24 +1,8 @@
import { Tooltip } from '@vegaprotocol/ui-toolkit'; import { Tooltip } from '@vegaprotocol/ui-toolkit';
import classnames from 'classnames'; import classnames from 'classnames';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; import { getFeeDetailsValues } from '../../hooks/use-fee-deal-ticket-details';
import type { Market, MarketData } from '@vegaprotocol/market-list'; import type { FeeDetails } from '../../hooks/use-fee-deal-ticket-details';
import {
getFeeDetailsValues,
useFeeDealTicketDetails,
} from '../../hooks/use-fee-deal-ticket-details';
interface DealTicketFeeDetailsProps {
order: OrderSubmissionBody['orderSubmission'];
market: Market;
marketData: MarketData;
currentInitialMargin?: string;
currentMaintenanceMargin?: string;
estimatedInitialMargin: string;
estimatedTotalInitialMargin: string;
marginAccountBalance: string;
generalAccountBalance: string;
}
export interface DealTicketFeeDetailProps { export interface DealTicketFeeDetailProps {
label: string; label: string;
@@ -45,17 +29,8 @@ export const DealTicketFeeDetail = ({
</div> </div>
); );
export const DealTicketFeeDetails = ({ export const DealTicketFeeDetails = (props: FeeDetails) => {
order, const details = getFeeDetailsValues(props);
market,
marketData,
...args
}: DealTicketFeeDetailsProps) => {
const feeDetails = useFeeDealTicketDetails(order, market, marketData);
const details = getFeeDetailsValues({
...feeDetails,
...args,
});
return ( return (
<div> <div>
{details.map(({ label, value, labelDescription, symbol, indent }) => ( {details.map(({ label, value, labelDescription, symbol, indent }) => (
@@ -25,6 +25,16 @@ import {
TinyScroll, TinyScroll,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import {
useEstimatePositionQuery,
useOpenVolume,
} from '@vegaprotocol/positions';
import { toBigNum, removeDecimal } from '@vegaprotocol/utils';
import { activeOrdersProvider } from '@vegaprotocol/orders';
import { useEstimateFees } from '../../hooks/use-fee-deal-ticket-details';
import { getDerivedPrice } from '../../utils/get-price';
import type { OrderInfo } from '@vegaprotocol/types';
import { import {
validateExpiration, validateExpiration,
validateMarketState, validateMarketState,
@@ -34,7 +44,6 @@ import {
} from '../../utils'; } from '../../utils';
import { ZeroBalanceError } from '../deal-ticket-validation/zero-balance-error'; import { ZeroBalanceError } from '../deal-ticket-validation/zero-balance-error';
import { SummaryValidationType } from '../../constants'; import { SummaryValidationType } from '../../constants';
import { useInitialMargin } from '../../hooks/use-initial-margin';
import type { Market, MarketData } from '@vegaprotocol/market-list'; import type { Market, MarketData } from '@vegaprotocol/market-list';
import { MarginWarning } from '../deal-ticket-validation/margin-warning'; import { MarginWarning } from '../deal-ticket-validation/margin-warning';
import { import {
@@ -104,7 +113,67 @@ export const DealTicket = ({
market.positionDecimalPlaces market.positionDecimalPlaces
); );
const { margin, totalMargin } = useInitialMargin(market.id, normalizedOrder); const price = useMemo(() => {
return normalizedOrder && getDerivedPrice(normalizedOrder, marketData);
}, [normalizedOrder, marketData]);
const notionalSize = useMemo(() => {
if (price && normalizedOrder?.size) {
return removeDecimal(
toBigNum(
normalizedOrder.size,
market.positionDecimalPlaces
).multipliedBy(toBigNum(price, market.decimalPlaces)),
asset.decimals
);
}
return null;
}, [
price,
normalizedOrder?.size,
market.decimalPlaces,
market.positionDecimalPlaces,
asset.decimals,
]);
const feeEstimate = useEstimateFees(
normalizedOrder && { ...normalizedOrder, price }
);
const { data: activeOrders } = useDataProvider({
dataProvider: activeOrdersProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
const openVolume = useOpenVolume(pubKey, market.id) ?? '0';
const orders = activeOrders
? activeOrders.map<OrderInfo>(({ node: order }) => ({
isMarketOrder: order.type === OrderType.TYPE_MARKET,
price: order.price,
remaining: order.remaining,
side: order.side,
}))
: [];
if (normalizedOrder) {
orders.push({
isMarketOrder: normalizedOrder.type === OrderType.TYPE_MARKET,
price: normalizedOrder.price ?? '0',
remaining: normalizedOrder.size,
side: normalizedOrder.side,
});
}
const { data: positionEstimate } = useEstimatePositionQuery({
variables: {
marketId: market.id,
openVolume,
orders,
collateralAvailable:
marginAccountBalance || generalAccountBalance ? balance : undefined,
},
skip: !normalizedOrder,
});
const assetSymbol =
market.tradableInstrument.instrument.product.settlementAsset.symbol;
const { data: currentMargins } = useDataProvider({ const { data: currentMargins } = useDataProvider({
dataProvider: marketMarginDataProvider, dataProvider: marketMarginDataProvider,
@@ -401,7 +470,10 @@ export const DealTicket = ({
asset={asset} asset={asset}
marketTradingMode={marketData.marketTradingMode} marketTradingMode={marketData.marketTradingMode}
balance={balance} balance={balance}
margin={totalMargin} margin={
positionEstimate?.estimatePosition?.margin.bestCase.initialLevel ||
'0'
}
isReadOnly={isReadOnly} isReadOnly={isReadOnly}
pubKey={pubKey} pubKey={pubKey}
onClickCollateral={onClickCollateral} onClickCollateral={onClickCollateral}
@@ -413,15 +485,15 @@ export const DealTicket = ({
} }
/> />
<DealTicketFeeDetails <DealTicketFeeDetails
order={normalizedOrder} feeEstimate={feeEstimate}
market={market} notionalSize={notionalSize}
marketData={marketData} assetSymbol={assetSymbol}
estimatedInitialMargin={margin}
estimatedTotalInitialMargin={totalMargin}
currentInitialMargin={currentMargins?.initialLevel}
currentMaintenanceMargin={currentMargins?.maintenanceLevel}
marginAccountBalance={marginAccountBalance} marginAccountBalance={marginAccountBalance}
generalAccountBalance={generalAccountBalance} generalAccountBalance={generalAccountBalance}
positionEstimate={positionEstimate?.estimatePosition}
market={market}
currentInitialMargin={currentMargins?.initialLevel}
currentMaintenanceMargin={currentMargins?.maintenanceLevel}
/> />
</form> </form>
</TinyScroll> </TinyScroll>
-1
View File
@@ -1,4 +1,3 @@
export * from './deal-ticket'; export * from './deal-ticket';
export * from './deal-ticket-validation'; export * from './deal-ticket-validation';
export * from './trading-mode-tooltip'; export * from './trading-mode-tooltip';
export * from './deal-ticket-estimates';
+4
View File
@@ -59,6 +59,10 @@ export const EST_FEES_TOOLTIP_TEXT = t(
'When you execute a new buy or sell order, you must pay a small amount of commission to the network for doing so. This fee is used to provide income to the node operates of the network and market makers who make prices on the futures market you are trading.' 'When you execute a new buy or sell order, you must pay a small amount of commission to the network for doing so. This fee is used to provide income to the node operates of the network and market makers who make prices on the futures market you are trading.'
); );
export const LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT = t(
'This is a approximation to the liquidation price for that particular contract position, assuming nothing else changes, which may affect your margin and collateral balances.'
);
export const EST_SLIPPAGE = t( export const EST_SLIPPAGE = t(
'When you execute a trade on Vega, the price obtained in the market may differ from the best available price displayed at the time of placing the trade. The estimated slippage shows the difference between the best available price and the estimated execution price, determined by market liquidity and your chosen order size.' 'When you execute a trade on Vega, the price obtained in the market may differ from the best available price displayed at the time of placing the trade. The estimated slippage shows the difference between the best available price and the estimated execution price, determined by market liquidity and your chosen order size.'
); );
@@ -1,4 +1,4 @@
query EstimateOrder( query EstimateFees(
$marketId: ID! $marketId: ID!
$partyId: ID! $partyId: ID!
$price: String $price: String
@@ -8,7 +8,7 @@ query EstimateOrder(
$expiration: Timestamp $expiration: Timestamp
$type: OrderType! $type: OrderType!
) { ) {
estimateOrder( estimateFees(
marketId: $marketId marketId: $marketId
partyId: $partyId partyId: $partyId
price: $price price: $price
@@ -18,14 +18,11 @@ query EstimateOrder(
expiration: $expiration expiration: $expiration
type: $type type: $type
) { ) {
fee { fees {
makerFee makerFee
infrastructureFee infrastructureFee
liquidityFee liquidityFee
} }
marginLevels {
initialLevel
}
totalFeeAmount totalFeeAmount
} }
} }
+17 -20
View File
@@ -3,7 +3,7 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client'; import * as Apollo from '@apollo/client';
const defaultOptions = {} as const; const defaultOptions = {} as const;
export type EstimateOrderQueryVariables = Types.Exact<{ export type EstimateFeesQueryVariables = Types.Exact<{
marketId: Types.Scalars['ID']; marketId: Types.Scalars['ID'];
partyId: Types.Scalars['ID']; partyId: Types.Scalars['ID'];
price?: Types.InputMaybe<Types.Scalars['String']>; price?: Types.InputMaybe<Types.Scalars['String']>;
@@ -15,12 +15,12 @@ export type EstimateOrderQueryVariables = Types.Exact<{
}>; }>;
export type EstimateOrderQuery = { __typename?: 'Query', estimateOrder: { __typename?: 'OrderEstimate', totalFeeAmount: string, fee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string }, marginLevels: { __typename?: 'MarginLevels', initialLevel: string } } }; export type EstimateFeesQuery = { __typename?: 'Query', estimateFees: { __typename?: 'FeeEstimate', totalFeeAmount: string, fees: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string } } };
export const EstimateOrderDocument = gql` export const EstimateFeesDocument = gql`
query EstimateOrder($marketId: ID!, $partyId: ID!, $price: String, $size: String!, $side: Side!, $timeInForce: OrderTimeInForce!, $expiration: Timestamp, $type: OrderType!) { query EstimateFees($marketId: ID!, $partyId: ID!, $price: String, $size: String!, $side: Side!, $timeInForce: OrderTimeInForce!, $expiration: Timestamp, $type: OrderType!) {
estimateOrder( estimateFees(
marketId: $marketId marketId: $marketId
partyId: $partyId partyId: $partyId
price: $price price: $price
@@ -30,30 +30,27 @@ export const EstimateOrderDocument = gql`
expiration: $expiration expiration: $expiration
type: $type type: $type
) { ) {
fee { fees {
makerFee makerFee
infrastructureFee infrastructureFee
liquidityFee liquidityFee
} }
marginLevels {
initialLevel
}
totalFeeAmount totalFeeAmount
} }
} }
`; `;
/** /**
* __useEstimateOrderQuery__ * __useEstimateFeesQuery__
* *
* To run a query within a React component, call `useEstimateOrderQuery` and pass it any options that fit your needs. * To run a query within a React component, call `useEstimateFeesQuery` and pass it any options that fit your needs.
* When your component renders, `useEstimateOrderQuery` returns an object from Apollo Client that contains loading, error, and data properties * When your component renders, `useEstimateFeesQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI. * you can use to render your UI.
* *
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
* *
* @example * @example
* const { data, loading, error } = useEstimateOrderQuery({ * const { data, loading, error } = useEstimateFeesQuery({
* variables: { * variables: {
* marketId: // value for 'marketId' * marketId: // value for 'marketId'
* partyId: // value for 'partyId' * partyId: // value for 'partyId'
@@ -66,14 +63,14 @@ export const EstimateOrderDocument = gql`
* }, * },
* }); * });
*/ */
export function useEstimateOrderQuery(baseOptions: Apollo.QueryHookOptions<EstimateOrderQuery, EstimateOrderQueryVariables>) { export function useEstimateFeesQuery(baseOptions: Apollo.QueryHookOptions<EstimateFeesQuery, EstimateFeesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions} const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<EstimateOrderQuery, EstimateOrderQueryVariables>(EstimateOrderDocument, options); return Apollo.useQuery<EstimateFeesQuery, EstimateFeesQueryVariables>(EstimateFeesDocument, options);
} }
export function useEstimateOrderLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<EstimateOrderQuery, EstimateOrderQueryVariables>) { export function useEstimateFeesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<EstimateFeesQuery, EstimateFeesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions} const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<EstimateOrderQuery, EstimateOrderQueryVariables>(EstimateOrderDocument, options); return Apollo.useLazyQuery<EstimateFeesQuery, EstimateFeesQueryVariables>(EstimateFeesDocument, options);
} }
export type EstimateOrderQueryHookResult = ReturnType<typeof useEstimateOrderQuery>; export type EstimateFeesQueryHookResult = ReturnType<typeof useEstimateFeesQuery>;
export type EstimateOrderLazyQueryHookResult = ReturnType<typeof useEstimateOrderLazyQuery>; export type EstimateFeesLazyQueryHookResult = ReturnType<typeof useEstimateFeesLazyQuery>;
export type EstimateOrderQueryResult = Apollo.QueryResult<EstimateOrderQuery, EstimateOrderQueryVariables>; export type EstimateFeesQueryResult = Apollo.QueryResult<EstimateFeesQuery, EstimateFeesQueryVariables>;
@@ -1,21 +1,20 @@
import type { PartialDeep } from 'type-fest'; import type { PartialDeep } from 'type-fest';
import merge from 'lodash/merge'; import merge from 'lodash/merge';
import type { EstimateOrderQuery } from './__generated__/EstimateOrder'; import type { EstimateFeesQuery } from './__generated__/EstimateOrder';
export const estimateOrderQuery = ( export const estimateFeesQuery = (
override?: PartialDeep<EstimateOrderQuery> override?: PartialDeep<EstimateFeesQuery>
): EstimateOrderQuery => { ): EstimateFeesQuery => {
const defaultResult: EstimateOrderQuery = { const defaultResult: EstimateFeesQuery = {
estimateOrder: { estimateFees: {
__typename: 'OrderEstimate', __typename: 'FeeEstimate',
totalFeeAmount: '0.0006', totalFeeAmount: '0.0006',
fee: { fees: {
__typename: 'TradeFee', __typename: 'TradeFee',
makerFee: '100000', makerFee: '100000',
infrastructureFee: '100000', infrastructureFee: '100000',
liquidityFee: '100000', liquidityFee: '100000',
}, },
marginLevels: { __typename: 'MarginLevels', initialLevel: '1' },
}, },
}; };
return merge(defaultResult, override); return merge(defaultResult, override);
@@ -1,14 +1,9 @@
import { FeesBreakdown } from '@vegaprotocol/market-info'; import { FeesBreakdown } from '@vegaprotocol/market-info';
import { import { addDecimalsFormatNumber, isNumeric } from '@vegaprotocol/utils';
addDecimal,
addDecimalsFormatNumber,
formatNumber,
toBigNum,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import { useMemo } from 'react'; import type { Market } from '@vegaprotocol/market-list';
import type { Market, MarketData } from '@vegaprotocol/market-list'; import type { EstimatePositionQuery } from '@vegaprotocol/positions';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { import {
EST_TOTAL_MARGIN_TOOLTIP_TEXT, EST_TOTAL_MARGIN_TOOLTIP_TEXT,
@@ -17,58 +12,30 @@ import {
MARGIN_DIFF_TOOLTIP_TEXT, MARGIN_DIFF_TOOLTIP_TEXT,
DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT, DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT,
TOTAL_MARGIN_AVAILABLE, TOTAL_MARGIN_AVAILABLE,
LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT,
} from '../constants'; } from '../constants';
import { useMarketAccountBalance } from '@vegaprotocol/accounts';
import { getDerivedPrice } from '../utils/get-price';
import { useEstimateOrderQuery } from './__generated__/EstimateOrder';
import type { EstimateOrderQuery } from './__generated__/EstimateOrder';
export const useFeeDealTicketDetails = ( import { useEstimateFeesQuery } from './__generated__/EstimateOrder';
order: OrderSubmissionBody['orderSubmission'], import type { EstimateFeesQuery } from './__generated__/EstimateOrder';
market: Market,
marketData: MarketData export const useEstimateFees = (
order?: OrderSubmissionBody['orderSubmission']
) => { ) => {
const { pubKey } = useVegaWallet(); const { pubKey } = useVegaWallet();
const { accountBalance } = useMarketAccountBalance(market.id);
const price = useMemo(() => { const { data } = useEstimateFeesQuery({
return getDerivedPrice(order, marketData); variables: order && {
}, [order, marketData]); marketId: order.marketId,
const { data: estMargin } = useEstimateOrderQuery({
variables: {
marketId: market.id,
partyId: pubKey || '', partyId: pubKey || '',
price, price: order.price,
size: order.size, size: order.size,
side: order.side, side: order.side,
timeInForce: order.timeInForce, timeInForce: order.timeInForce,
type: order.type, type: order.type,
}, },
skip: !pubKey || !market || !order.size || !price, skip: !pubKey || !order?.size || !order?.price,
}); });
return data?.estimateFees;
const notionalSize = useMemo(() => {
if (price && order.size) {
return toBigNum(order.size, market.positionDecimalPlaces)
.multipliedBy(addDecimal(price, market.decimalPlaces))
.toString();
}
return null;
}, [price, order.size, market.decimalPlaces, market.positionDecimalPlaces]);
const assetSymbol =
market.tradableInstrument.instrument.product.settlementAsset.symbol;
return useMemo(() => {
return {
market,
assetSymbol,
notionalSize,
accountBalance,
estimateOrder: estMargin?.estimateOrder,
};
}, [market, assetSymbol, notionalSize, accountBalance, estMargin]);
}; };
export interface FeeDetails { export interface FeeDetails {
@@ -77,42 +44,54 @@ export interface FeeDetails {
market: Market; market: Market;
assetSymbol: string; assetSymbol: string;
notionalSize: string | null; notionalSize: string | null;
estimateOrder: EstimateOrderQuery['estimateOrder'] | undefined; feeEstimate: EstimateFeesQuery['estimateFees'] | undefined;
estimatedInitialMargin: string;
estimatedTotalInitialMargin: string;
currentInitialMargin?: string; currentInitialMargin?: string;
currentMaintenanceMargin?: string; currentMaintenanceMargin?: string;
positionEstimate: EstimatePositionQuery['estimatePosition'];
} }
const emptyValue = '-';
const formatValue = (
value: string | number | null | undefined,
formatDecimals: number
): string => {
return isNumeric(value)
? addDecimalsFormatNumber(value, formatDecimals)
: emptyValue;
};
const formatRange = (
min: string | number | null | undefined,
max: string | number | null | undefined,
formatDecimals: number
) => {
const minFormatted = formatValue(min, formatDecimals);
const maxFormatted = formatValue(max, formatDecimals);
if (minFormatted !== maxFormatted) {
return `${minFormatted} - ${maxFormatted}`;
}
if (minFormatted !== emptyValue) {
return minFormatted;
}
return maxFormatted;
};
export const getFeeDetailsValues = ({ export const getFeeDetailsValues = ({
marginAccountBalance, marginAccountBalance,
generalAccountBalance, generalAccountBalance,
assetSymbol, assetSymbol,
estimateOrder, feeEstimate,
market, market,
notionalSize, notionalSize,
estimatedTotalInitialMargin,
currentInitialMargin, currentInitialMargin,
currentMaintenanceMargin, currentMaintenanceMargin,
positionEstimate,
}: FeeDetails) => { }: FeeDetails) => {
const liquidationEstimate = positionEstimate?.liquidation;
const marginEstimate = positionEstimate?.margin;
const totalBalance = const totalBalance =
BigInt(generalAccountBalance || '0') + BigInt(marginAccountBalance || '0'); BigInt(generalAccountBalance || '0') + BigInt(marginAccountBalance || '0');
const assetDecimals = const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals; market.tradableInstrument.instrument.product.settlementAsset.decimals;
const formatValueWithMarketDp = (
value: string | number | null | undefined
): string => {
return value && !isNaN(Number(value))
? formatNumber(value, market.decimalPlaces)
: '-';
};
const formatValueWithAssetDp = (
value: string | number | null | undefined
): string => {
return value && !isNaN(Number(value))
? addDecimalsFormatNumber(value, assetDecimals)
: '-';
};
const details: { const details: {
label: string; label: string;
value?: string | null; value?: string | null;
@@ -122,15 +101,15 @@ export const getFeeDetailsValues = ({
}[] = [ }[] = [
{ {
label: t('Notional'), label: t('Notional'),
value: formatValueWithMarketDp(notionalSize), value: formatValue(notionalSize, assetDecimals),
symbol: assetSymbol, symbol: assetSymbol,
labelDescription: NOTIONAL_SIZE_TOOLTIP_TEXT(assetSymbol), labelDescription: NOTIONAL_SIZE_TOOLTIP_TEXT(assetSymbol),
}, },
{ {
label: t('Fees'), label: t('Fees'),
value: value:
estimateOrder?.totalFeeAmount && feeEstimate?.totalFeeAmount &&
`~${formatValueWithAssetDp(estimateOrder?.totalFeeAmount)}`, `~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`,
labelDescription: ( labelDescription: (
<> <>
<span> <span>
@@ -139,7 +118,7 @@ export const getFeeDetailsValues = ({
)} )}
</span> </span>
<FeesBreakdown <FeesBreakdown
fees={estimateOrder?.fee} fees={feeEstimate?.fees}
feeFactors={market.fees.factors} feeFactors={market.fees.factors}
symbol={assetSymbol} symbol={assetSymbol}
decimals={assetDecimals} decimals={assetDecimals}
@@ -148,66 +127,147 @@ export const getFeeDetailsValues = ({
), ),
symbol: assetSymbol, symbol: assetSymbol,
}, },
{
label: t('Margin required'),
value: `~${formatValueWithAssetDp(
currentInitialMargin
? (
BigInt(estimatedTotalInitialMargin) - BigInt(currentInitialMargin)
).toString()
: estimatedTotalInitialMargin
)}`,
symbol: assetSymbol,
labelDescription: MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol),
},
]; ];
if (totalBalance) { let marginRequiredBestCase: string | undefined = undefined;
const totalMarginAvailable = ( let marginRequiredWorstCase: string | undefined = undefined;
currentMaintenanceMargin if (marginEstimate) {
? totalBalance - BigInt(currentMaintenanceMargin) if (currentInitialMargin) {
: totalBalance marginRequiredBestCase = (
).toString(); BigInt(marginEstimate.bestCase.initialLevel) -
BigInt(currentInitialMargin)
).toString();
if (marginRequiredBestCase.startsWith('-')) {
marginRequiredBestCase = '0';
}
marginRequiredWorstCase = (
BigInt(marginEstimate.worstCase.initialLevel) -
BigInt(currentInitialMargin)
).toString();
if (marginRequiredWorstCase.startsWith('-')) {
marginRequiredWorstCase = '0';
}
} else {
marginRequiredBestCase = marginEstimate.bestCase.initialLevel;
marginRequiredWorstCase = marginEstimate.worstCase.initialLevel;
}
}
details.push({
label: t('Margin required'),
value: formatRange(
marginRequiredBestCase,
marginRequiredWorstCase,
assetDecimals
),
symbol: assetSymbol,
labelDescription: MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol),
});
const totalMarginAvailable = (
currentMaintenanceMargin
? totalBalance - BigInt(currentMaintenanceMargin)
: totalBalance
).toString();
details.push({
indent: true,
label: t('Total margin available'),
value: formatValue(totalMarginAvailable, assetDecimals),
symbol: assetSymbol,
labelDescription: TOTAL_MARGIN_AVAILABLE(
formatValue(generalAccountBalance, assetDecimals),
formatValue(marginAccountBalance, assetDecimals),
formatValue(currentMaintenanceMargin, assetDecimals),
assetSymbol
),
});
if (marginAccountBalance) {
const deductionFromCollateralBestCase =
BigInt(marginEstimate?.bestCase.initialLevel ?? 0) -
BigInt(marginAccountBalance);
const deductionFromCollateralWorstCase =
BigInt(marginEstimate?.worstCase.initialLevel ?? 0) -
BigInt(marginAccountBalance);
details.push({ details.push({
indent: true, indent: true,
label: t('Total margin available'), label: t('Deduction from collateral'),
value: `~${formatValueWithAssetDp(totalMarginAvailable)}`, value: formatRange(
symbol: assetSymbol, deductionFromCollateralBestCase > 0
labelDescription: TOTAL_MARGIN_AVAILABLE( ? deductionFromCollateralBestCase.toString()
formatValueWithAssetDp(generalAccountBalance), : '0',
formatValueWithAssetDp(marginAccountBalance), deductionFromCollateralWorstCase > 0
formatValueWithAssetDp(currentMaintenanceMargin), ? deductionFromCollateralWorstCase.toString()
assetSymbol : '0',
assetDecimals
), ),
symbol: assetSymbol,
labelDescription: DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT(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),
});
}
details.push({ details.push({
label: t('Projected margin'), label: t('Projected margin'),
value: `~${formatValueWithAssetDp(estimatedTotalInitialMargin)}`, value: formatRange(
marginEstimate?.bestCase.initialLevel,
marginEstimate?.worstCase.initialLevel,
assetDecimals
),
symbol: assetSymbol, symbol: assetSymbol,
labelDescription: EST_TOTAL_MARGIN_TOOLTIP_TEXT, labelDescription: EST_TOTAL_MARGIN_TOOLTIP_TEXT,
}); });
} }
details.push({ details.push({
label: t('Current margin allocation'), label: t('Current margin allocation'),
value: `${formatValueWithAssetDp(marginAccountBalance)}`, value: formatValue(marginAccountBalance, assetDecimals),
symbol: assetSymbol, symbol: assetSymbol,
labelDescription: MARGIN_ACCOUNT_TOOLTIP_TEXT, labelDescription: MARGIN_ACCOUNT_TOOLTIP_TEXT,
}); });
let liquidationPriceEstimate = emptyValue;
if (liquidationEstimate) {
const liquidationEstimateBestCaseIncludingBuyOrders = BigInt(
liquidationEstimate.bestCase.including_buy_orders.replace(/\..*/, '')
);
const liquidationEstimateBestCaseIncludingSellOrders = BigInt(
liquidationEstimate.bestCase.including_sell_orders.replace(/\..*/, '')
);
const liquidationEstimateBestCase =
liquidationEstimateBestCaseIncludingBuyOrders >
liquidationEstimateBestCaseIncludingSellOrders
? liquidationEstimateBestCaseIncludingBuyOrders
: liquidationEstimateBestCaseIncludingSellOrders;
const liquidationEstimateWorstCaseIncludingBuyOrders = BigInt(
liquidationEstimate.worstCase.including_buy_orders.replace(/\..*/, '')
);
const liquidationEstimateWorstCaseIncludingSellOrders = BigInt(
liquidationEstimate.worstCase.including_sell_orders.replace(/\..*/, '')
);
const liquidationEstimateWorstCase =
liquidationEstimateWorstCaseIncludingBuyOrders >
liquidationEstimateWorstCaseIncludingSellOrders
? liquidationEstimateWorstCaseIncludingBuyOrders
: liquidationEstimateWorstCaseIncludingSellOrders;
liquidationPriceEstimate = formatRange(
(liquidationEstimateBestCase < liquidationEstimateWorstCase
? liquidationEstimateBestCase
: liquidationEstimateWorstCase
).toString(),
(liquidationEstimateBestCase > liquidationEstimateWorstCase
? liquidationEstimateBestCase
: liquidationEstimateWorstCase
).toString(),
assetDecimals
);
}
details.push({
label: t('Liquidation price estimate'),
value: liquidationPriceEstimate,
symbol: assetSymbol,
labelDescription: LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT,
});
return details; return details;
}; };
@@ -1,74 +0,0 @@
import { useMemo } from 'react';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { marketDataProvider } from '@vegaprotocol/market-list';
import {
calculateMargins,
// getDerivedPrice,
volumeAndMarginProvider,
} from '@vegaprotocol/positions';
import { Side } from '@vegaprotocol/types';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { marketInfoProvider } from '@vegaprotocol/market-info';
export const useInitialMargin = (
marketId: OrderSubmissionBody['orderSubmission']['marketId'],
order?: OrderSubmissionBody['orderSubmission']
) => {
const { pubKey } = useVegaWallet();
const { data: marketData } = useDataProvider({
dataProvider: marketDataProvider,
variables: { marketId },
});
const { data: activeVolumeAndMargin } = useDataProvider({
dataProvider: volumeAndMarginProvider,
variables: { marketId, partyId: pubKey || '' },
skip: !pubKey,
});
const { data: marketInfo } = useDataProvider({
dataProvider: marketInfoProvider,
variables: { marketId },
});
let totalMargin = '0';
let margin = '0';
if (marketInfo?.riskFactors && marketData && order) {
const {
positionDecimalPlaces,
decimalPlaces,
tradableInstrument,
riskFactors,
} = marketInfo;
const { marginCalculator, instrument } = tradableInstrument;
const { decimals } = instrument.product.settlementAsset;
margin = totalMargin = calculateMargins({
side: order.side,
size: order.size,
price: marketData.markPrice, // getDerivedPrice(order, marketData), same in positions-data-providers
positionDecimalPlaces,
decimalPlaces,
decimals,
scalingFactors: marginCalculator?.scalingFactors,
riskFactors,
}).initialMargin;
}
if (activeVolumeAndMargin) {
let sellMargin = BigInt(activeVolumeAndMargin.sellInitialMargin);
let buyMargin = BigInt(activeVolumeAndMargin.buyInitialMargin);
if (order?.side === Side.SIDE_SELL) {
sellMargin += BigInt(totalMargin);
} else {
buyMargin += BigInt(totalMargin);
}
totalMargin =
sellMargin > buyMargin ? sellMargin.toString() : buyMargin.toString();
}
return useMemo(
() => ({
totalMargin,
margin,
}),
[totalMargin, margin]
);
};
@@ -1,6 +1,6 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { Button } from '@vegaprotocol/ui-toolkit'; import { Button } from '@vegaprotocol/ui-toolkit';
import { useGlobalStore } from '../../stores'; import { useNodeSwitcherStore } from '../../hooks/use-node-switcher-store';
export const AppFailure = ({ export const AppFailure = ({
title, title,
@@ -9,17 +9,13 @@ export const AppFailure = ({
title: string; title: string;
error?: string | null; error?: string | null;
}) => { }) => {
const { setNodeSwitcher } = useGlobalStore((store) => ({ const setNodeSwitcher = useNodeSwitcherStore((store) => store.setDialogOpen);
nodeSwitcherOpen: store.nodeSwitcherDialog,
setNodeSwitcher: (open: boolean) =>
store.update({ nodeSwitcherDialog: open }),
}));
const nonIdealWrapperClasses = const nonIdealWrapperClasses =
'h-full min-h-screen flex items-center justify-center'; 'h-full min-h-screen flex items-center justify-center';
return ( return (
<div className={nonIdealWrapperClasses}> <div className={nonIdealWrapperClasses}>
<div className="text-center"> <div className="text-center">
<h1 className="text-xl mb-4">{title}</h1> <p className="text-xl mb-4">{title}</p>
{error && <p className="text-sm mb-8">{error}</p>} {error && <p className="text-sm mb-8">{error}</p>}
<Button onClick={() => setNodeSwitcher(true)}> <Button onClick={() => setNodeSwitcher(true)}>
{t('Change node')} {t('Change node')}
@@ -0,0 +1 @@
export * from './app-failure';
+1
View File
@@ -1,3 +1,4 @@
export * from './app-failure';
export * from './network-loader'; export * from './network-loader';
export * from './network-switcher'; export * from './network-switcher';
export * from './node-guard'; export * from './node-guard';
@@ -0,0 +1,11 @@
query NodeGuard {
lastBlockHeight
networkParametersConnection {
edges {
node {
key
value
}
}
}
}
@@ -0,0 +1,51 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type NodeGuardQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type NodeGuardQuery = { __typename?: 'Query', lastBlockHeight: string, networkParametersConnection: { __typename?: 'NetworkParametersConnection', edges?: Array<{ __typename?: 'NetworkParameterEdge', node: { __typename?: 'NetworkParameter', key: string, value: string } } | null> | null } };
export const NodeGuardDocument = gql`
query NodeGuard {
lastBlockHeight
networkParametersConnection {
edges {
node {
key
value
}
}
}
}
`;
/**
* __useNodeGuardQuery__
*
* To run a query within a React component, call `useNodeGuardQuery` and pass it any options that fit your needs.
* When your component renders, `useNodeGuardQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useNodeGuardQuery({
* variables: {
* },
* });
*/
export function useNodeGuardQuery(baseOptions?: Apollo.QueryHookOptions<NodeGuardQuery, NodeGuardQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<NodeGuardQuery, NodeGuardQueryVariables>(NodeGuardDocument, options);
}
export function useNodeGuardLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<NodeGuardQuery, NodeGuardQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<NodeGuardQuery, NodeGuardQueryVariables>(NodeGuardDocument, options);
}
export type NodeGuardQueryHookResult = ReturnType<typeof useNodeGuardQuery>;
export type NodeGuardLazyQueryHookResult = ReturnType<typeof useNodeGuardLazyQuery>;
export type NodeGuardQueryResult = Apollo.QueryResult<NodeGuardQuery, NodeGuardQueryVariables>;
@@ -0,0 +1,53 @@
import type { NodeGuardQuery } from './__generated__/NodeGuard';
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
export const nodeGuardQuery = (
override?: PartialDeep<NodeGuardQuery>
): NodeGuardQuery => {
const defaultResult: NodeGuardQuery = {
lastBlockHeight: '11',
networkParametersConnection: {
__typename: 'NetworkParametersConnection',
edges: [
{
__typename: 'NetworkParameterEdge',
node: {
__typename: 'NetworkParameter' as const,
key: 'governance.proposal.market.requiredMajority',
value: '0.66',
},
},
{
__typename: 'NetworkParameterEdge',
node: {
__typename: 'NetworkParameter' as const,
key: 'blockchains.ethereumConfig',
value: JSON.stringify({
network_id: '3',
chain_id: '3',
collateral_bridge_contract: {
address: '0x7fe27d970bc8Afc3B11Cc8d9737bfB66B1efd799',
},
multisig_control_contract: {
address: '0x6eBc32d66277D94DB8FF2ccF86E36f37F29a52D3',
deployment_block_height: 12341882,
},
staking_bridge_contract: {
address: '0xFFb0A0d4806502ceF491aF1141f66669A1Bd0D03',
deployment_block_height: 11177313,
},
token_vesting_contract: {
address: '0x680fF88252FA7071CAce7398e77872d54D781d0B',
deployment_block_height: 11177353,
},
confirmations: 3,
}),
},
},
],
},
};
return merge(defaultResult, override);
};
@@ -1,5 +1,5 @@
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { useStatisticsQuery } from '../../utils/__generated__/Node'; import { useNodeGuardQuery } from './__generated__/NodeGuard';
export const NodeGuard = ({ export const NodeGuard = ({
children, children,
@@ -10,14 +10,19 @@ export const NodeGuard = ({
failure: ReactNode; failure: ReactNode;
skeleton: ReactNode; skeleton: ReactNode;
}) => { }) => {
const { error, loading } = useStatisticsQuery(); const { data, error, loading } = useNodeGuardQuery();
const wrapperClasses = 'h-full min-h-screen flex items-center justify-center'; const wrapperClasses =
'h-full min-h-screen flex items-center justify-center text-black dark:text-white';
if (loading) { if (loading) {
return <div className={wrapperClasses}>{skeleton}</div>; return <div className={wrapperClasses}>{skeleton}</div>;
} }
if (error) { // It is possible for nodes to have a functioning datanode, but not return
// any net params. The app cannot safely function without net params
const netParamEdges = data?.networkParametersConnection.edges;
if (error || !netParamEdges || !netParamEdges.length) {
return <div className={wrapperClasses}>{failure}</div>; return <div className={wrapperClasses}>{failure}</div>;
} }
@@ -96,13 +96,13 @@ describe('RowData', () => {
mockHeaders(props.url); mockHeaders(props.url);
render(renderComponent(props, statsQueryMock, subMock)); render(renderComponent(props, statsQueryMock, subMock));
// radio should be disabled until query resolves // radio should be enabled until query resolves
expect( expect(
screen.getByRole('radio', { screen.getByRole('radio', {
checked: false, checked: false,
name: props.url, name: props.url,
}) })
).toBeDisabled(); ).toBeEnabled();
expect(screen.getByTestId('response-time-cell')).toHaveTextContent( expect(screen.getByTestId('response-time-cell')).toHaveTextContent(
'Checking' 'Checking'
); );
@@ -124,7 +124,7 @@ describe('RowData', () => {
}); });
}); });
it('radio button disabled if query fails', async () => { it('radio button still enabled if query fails', async () => {
mockHeaders(props.url, {}); mockHeaders(props.url, {});
const failedQueryMock: MockedResponse<StatisticsQuery> = { const failedQueryMock: MockedResponse<StatisticsQuery> = {
@@ -149,7 +149,7 @@ describe('RowData', () => {
checked: false, checked: false,
name: props.url, name: props.url,
}) })
).toBeDisabled(); ).toBeEnabled();
expect(screen.getByTestId('response-time-cell')).toHaveTextContent( expect(screen.getByTestId('response-time-cell')).toHaveTextContent(
'Checking' 'Checking'
); );
@@ -170,7 +170,7 @@ describe('RowData', () => {
checked: false, checked: false,
name: props.url, name: props.url,
}) })
).toBeDisabled(); ).toBeEnabled();
}); });
}); });
@@ -207,19 +207,6 @@ describe('RowData', () => {
expect(screen.getByTestId('block-height-cell')).toHaveClass('text-danger'); expect(screen.getByTestId('block-height-cell')).toHaveClass('text-danger');
}); });
it('disables radio button if url is invalid', () => {
mockHeaders(props.url, { blockHeight: 100 });
render(renderComponent(props, statsQueryMock, subMock));
expect(
screen.getByRole('radio', {
checked: false,
name: props.url,
})
).toBeDisabled();
});
it('doesnt render the radio if its the custom row', () => { it('doesnt render the radio if its the custom row', () => {
render( render(
renderComponent( renderComponent(
@@ -124,29 +124,6 @@ export const RowData = ({
return false; return false;
}; };
const getIsNodeDisabled = () => {
if (!isValidUrl(url)) {
return true;
}
// if still waiting or query errored disable node
if (loading || error) {
return true;
}
if (subLoading || subError) {
return true;
}
// if we are still waiting for a header entry for this
// url disable the node
if (!headers) {
return true;
}
return false;
};
const getSubFailed = ( const getSubFailed = (
subError: ApolloError | undefined, subError: ApolloError | undefined,
subFailed: boolean subFailed: boolean
@@ -160,12 +137,7 @@ export const RowData = ({
<> <>
{id !== CUSTOM_NODE_KEY && ( {id !== CUSTOM_NODE_KEY && (
<div className="break-all" data-testid="node"> <div className="break-all" data-testid="node">
<Radio <Radio id={`node-url-${id}`} value={url} label={url} />
id={`node-url-${id}`}
value={url}
label={url}
disabled={getIsNodeDisabled()}
/>
</div> </div>
)} )}
<LayoutCell <LayoutCell
+1
View File
@@ -1,5 +1,6 @@
export * from './use-environment'; export * from './use-environment';
export * from './use-links'; export * from './use-links';
export * from './use-node-health'; export * from './use-node-health';
export * from './use-node-switcher-store';
export * from './use-vega-releases'; export * from './use-vega-releases';
export * from './use-vega-release'; export * from './use-vega-release';
@@ -15,6 +15,7 @@ import { Networks } from '../types';
import { compileErrors } from '../utils/compile-errors'; import { compileErrors } from '../utils/compile-errors';
import { envSchema } from '../utils/validate-environment'; import { envSchema } from '../utils/validate-environment';
import { tomlConfigSchema } from '../utils/validate-configuration'; import { tomlConfigSchema } from '../utils/validate-configuration';
import uniq from 'lodash/uniq';
type Client = ReturnType<typeof createClient>; type Client = ReturnType<typeof createClient>;
type ClientCollection = { type ClientCollection = {
@@ -32,7 +33,8 @@ type Actions = {
export type Env = Environment & EnvState; export type Env = Environment & EnvState;
export type EnvStore = Env & Actions; export type EnvStore = Env & Actions;
export const STORAGE_KEY = 'vega_url'; const VERSION = 1;
export const STORAGE_KEY = `vega_url_${VERSION}`;
const SUBSCRIPTION_TIMEOUT = 3000; const SUBSCRIPTION_TIMEOUT = 3000;
export const useEnvironment = create<EnvStore>()((set, get) => ({ export const useEnvironment = create<EnvStore>()((set, get) => ({
@@ -69,7 +71,10 @@ export const useEnvironment = create<EnvStore>()((set, get) => ({
let nodes: string[] | undefined; let nodes: string[] | undefined;
try { try {
nodes = await fetchConfig(state.VEGA_CONFIG_URL); nodes = await fetchConfig(state.VEGA_CONFIG_URL);
set({ nodes }); const enrichedNodes = uniq(
[...nodes, state.VEGA_URL, storedUrl].filter(Boolean) as string[]
);
set({ nodes: enrichedNodes });
} catch (err) { } catch (err) {
console.warn(`Could not fetch node config from ${state.VEGA_CONFIG_URL}`); console.warn(`Could not fetch node config from ${state.VEGA_CONFIG_URL}`);
} }
+16
View File
@@ -84,6 +84,16 @@ export const useEtherscanLink = () => {
return link; return link;
}; };
export const useDocsLink = () => {
const { VEGA_DOCS_URL } = useEnvironment();
const baseUrl = trim(VEGA_DOCS_URL, '/');
const link = useCallback(
(url?: string) => `${baseUrl}/${trim(url, '/') || ''}`,
[baseUrl]
);
return link;
};
// Vega blog // Vega blog
export const BLOG = 'https://blog.vega.xyz/'; export const BLOG = 'https://blog.vega.xyz/';
@@ -125,3 +135,9 @@ export const CONSOLE_MARKET = '/markets/:marketId';
export const CONSOLE_MARKETS = '/markets/all'; export const CONSOLE_MARKETS = '/markets/all';
export const CONSOLE_PORTFOLIO = '/portfolio'; export const CONSOLE_PORTFOLIO = '/portfolio';
export const CONSOLE_LIQUIDITY = 'liquidity/:marketId'; export const CONSOLE_LIQUIDITY = 'liquidity/:marketId';
// Vega XYZ
export const GET_VEGA_WALLET_URL = 'https://vega.xyz/wallet/';
// Docs
export const DOCS_VEGA_WALLET = '/tools/vega-wallet';
@@ -38,7 +38,7 @@ export const useNodeHealth = () => {
return; return;
} }
if (!('Cypress' in window)) { if (!('Cypress' in window) && window.location.hostname !== 'localhost') {
startPolling(POLL_INTERVAL); startPolling(POLL_INTERVAL);
} }
}, [error, startPolling, stopPolling]); }, [error, startPolling, stopPolling]);
@@ -0,0 +1,11 @@
import { create } from 'zustand';
export const useNodeSwitcherStore = create<{
dialogOpen: boolean;
setDialogOpen: (isOpen: boolean) => void;
}>()((set) => ({
dialogOpen: false,
setDialogOpen: (isOpen) => {
set({ dialogOpen: isOpen });
},
}));
+54 -56
View File
@@ -1,7 +1,10 @@
import { Fragment, useState } from 'react'; import { Fragment } from 'react';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { Link, Lozenge } from '@vegaprotocol/ui-toolkit'; import { Link, Lozenge } from '@vegaprotocol/ui-toolkit';
import { NodeSwitcherDialog, useEnvironment } from '@vegaprotocol/environment'; import {
useEnvironment,
useNodeSwitcherStore,
} from '@vegaprotocol/environment';
const getFeedbackLinks = (gitOriginUrl?: string) => const getFeedbackLinks = (gitOriginUrl?: string) =>
[ [
@@ -19,64 +22,59 @@ export const NetworkInfo = () => {
GITHUB_FEEDBACK_URL, GITHUB_FEEDBACK_URL,
ETHEREUM_PROVIDER_URL, ETHEREUM_PROVIDER_URL,
} = useEnvironment(); } = useEnvironment();
const [nodeSwitcherOpen, setNodeSwitcherOpen] = useState(false);
const setNodeSwitcher = useNodeSwitcherStore((store) => store.setDialogOpen);
const feedbackLinks = getFeedbackLinks(GITHUB_FEEDBACK_URL); const feedbackLinks = getFeedbackLinks(GITHUB_FEEDBACK_URL);
return ( return (
<> <div data-testid="git-info">
<div data-testid="git-info"> <p data-testid="git-network-data" className="mb-2">
<p data-testid="git-network-data" className="mb-2"> {t('Reading network data from')}{' '}
{t('Reading network data from')}{' '} <Lozenge className="bg-neutral-300 dark:bg-neutral-700">
<Lozenge className="bg-neutral-300 dark:bg-neutral-700"> {VEGA_URL}
{VEGA_URL} </Lozenge>
</Lozenge> . <Link onClick={() => setNodeSwitcher(true)}>{t('Edit')}</Link>
. <Link onClick={() => setNodeSwitcherOpen(true)}>{t('Edit')}</Link> </p>
<p data-testid="git-eth-data" className="mb-2 break-all">
{t('Reading Ethereum data from')}{' '}
<Lozenge className="bg-neutral-300 dark:bg-neutral-700">
{ETHEREUM_PROVIDER_URL}
</Lozenge>
.{' '}
</p>
{GIT_COMMIT_HASH && (
<p data-testid="git-commit-hash" className="mb-2">
{t('Version/commit hash')}:{' '}
<Link
href={
GIT_ORIGIN_URL
? `${GIT_ORIGIN_URL}/commit/${GIT_COMMIT_HASH}`
: undefined
}
target={GIT_ORIGIN_URL ? '_blank' : undefined}
>
{GIT_COMMIT_HASH}
</Link>
</p> </p>
<p data-testid="git-eth-data" className="mb-2 break-all"> )}
{t('Reading Ethereum data from')}{' '} {feedbackLinks.length > 0 && (
<Lozenge className="bg-neutral-300 dark:bg-neutral-700"> <p>
{ETHEREUM_PROVIDER_URL} {t('Known issues and feedback on')}{' '}
</Lozenge> {feedbackLinks.map(({ name, url }, index) => (
.{' '} <Fragment key={index}>
<Link key={index} href={url}>
{name}
</Link>
{feedbackLinks.length > 1 &&
index < feedbackLinks.length - 2 &&
','}
{feedbackLinks.length > 1 &&
index === feedbackLinks.length - 1 &&
`, ${t('and')} `}
</Fragment>
))}
</p> </p>
{GIT_COMMIT_HASH && ( )}
<p data-testid="git-commit-hash" className="mb-2"> </div>
{t('Version/commit hash')}:{' '}
<Link
href={
GIT_ORIGIN_URL
? `${GIT_ORIGIN_URL}/commit/${GIT_COMMIT_HASH}`
: undefined
}
target={GIT_ORIGIN_URL ? '_blank' : undefined}
>
{GIT_COMMIT_HASH}
</Link>
</p>
)}
{feedbackLinks.length > 0 && (
<p>
{t('Known issues and feedback on')}{' '}
{feedbackLinks.map(({ name, url }, index) => (
<Fragment key={index}>
<Link key={index} href={url}>
{name}
</Link>
{feedbackLinks.length > 1 &&
index < feedbackLinks.length - 2 &&
','}
{feedbackLinks.length > 1 &&
index === feedbackLinks.length - 1 &&
`, ${t('and')} `}
</Fragment>
))}
</p>
)}
</div>
<NodeSwitcherDialog
open={nodeSwitcherOpen}
setOpen={setNodeSwitcherOpen}
/>
</>
); );
}; };
@@ -21,6 +21,8 @@ fragment OrderFields on Order {
} }
peggedOrder { peggedOrder {
__typename __typename
reference
offset
} }
} }
@@ -64,6 +66,7 @@ fragment OrderUpdateFields on OrderUpdate {
type type
side side
size size
remaining
status status
rejectionReason rejectionReason
price price
@@ -75,6 +78,8 @@ fragment OrderUpdateFields on OrderUpdate {
liquidityProvisionId liquidityProvisionId
peggedOrder { peggedOrder {
__typename __typename
reference
offset
} }
} }
@@ -3,14 +3,14 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client'; import * as Apollo from '@apollo/client';
const defaultOptions = {} as const; 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, postOnly?: boolean | null, reduceOnly?: boolean | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null };
export type OrderByIdQueryVariables = Types.Exact<{ export type OrderByIdQueryVariables = Types.Exact<{
orderId: Types.Scalars['ID']; 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, postOnly?: boolean | null, reduceOnly?: boolean | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null } };
export type OrdersQueryVariables = Types.Exact<{ export type OrdersQueryVariables = Types.Exact<{
partyId: Types.Scalars['ID']; partyId: Types.Scalars['ID'];
@@ -20,9 +20,9 @@ export type OrdersQueryVariables = Types.Exact<{
}>; }>;
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, postOnly?: boolean | null, reduceOnly?: boolean | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | 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 OrderUpdateFieldsFragment = { __typename?: 'OrderUpdate', id: string, marketId: string, type?: Types.OrderType | null, side: Types.Side, size: string, remaining: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, liquidityProvisionId?: string | null, peggedOrder?: { __typename: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null };
export type OrdersUpdateSubscriptionVariables = Types.Exact<{ export type OrdersUpdateSubscriptionVariables = Types.Exact<{
partyId: Types.Scalars['ID']; partyId: Types.Scalars['ID'];
@@ -30,7 +30,7 @@ export type OrdersUpdateSubscriptionVariables = Types.Exact<{
}>; }>;
export type OrdersUpdateSubscription = { __typename?: 'Subscription', orders?: Array<{ __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 }> | null }; export type OrdersUpdateSubscription = { __typename?: 'Subscription', orders?: Array<{ __typename?: 'OrderUpdate', id: string, marketId: string, type?: Types.OrderType | null, side: Types.Side, size: string, remaining: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, liquidityProvisionId?: string | null, peggedOrder?: { __typename: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null }> | null };
export const OrderFieldsFragmentDoc = gql` export const OrderFieldsFragmentDoc = gql`
fragment OrderFields on Order { fragment OrderFields on Order {
@@ -56,6 +56,8 @@ export const OrderFieldsFragmentDoc = gql`
} }
peggedOrder { peggedOrder {
__typename __typename
reference
offset
} }
} }
`; `;
@@ -66,6 +68,7 @@ export const OrderUpdateFieldsFragmentDoc = gql`
type type
side side
size size
remaining
status status
rejectionReason rejectionReason
price price
@@ -77,6 +80,8 @@ export const OrderUpdateFieldsFragmentDoc = gql`
liquidityProvisionId liquidityProvisionId
peggedOrder { peggedOrder {
__typename __typename
reference
offset
} }
} }
`; `;
@@ -213,6 +213,8 @@ describe('OrderListTable', () => {
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC, timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
peggedOrder: { peggedOrder: {
__typename: 'PeggedOrder', __typename: 'PeggedOrder',
reference: Schema.PeggedReference.PEGGED_REFERENCE_MID,
offset: '100',
}, },
}); });
@@ -222,7 +224,7 @@ describe('OrderListTable', () => {
const amendCell = getAmendCell(); const amendCell = getAmendCell();
const typeCell = screen.getAllByRole('gridcell')[2]; const typeCell = screen.getAllByRole('gridcell')[2];
expect(typeCell).toHaveTextContent('Pegged'); expect(typeCell).toHaveTextContent('Mid - 10.0 Peg limit');
expect(amendCell.queryAllByRole('button')).toHaveLength(0); expect(amendCell.queryAllByRole('button')).toHaveLength(0);
}); });
-1
View File
@@ -2,7 +2,6 @@ export * from './lib/__generated__/Positions';
export * from './lib/positions-container'; export * from './lib/positions-container';
export * from './lib/positions-data-providers'; export * from './lib/positions-data-providers';
export * from './lib/margin-data-provider'; export * from './lib/margin-data-provider';
export * from './lib/margin-calculator';
export * from './lib/positions-table'; export * from './lib/positions-table';
export * from './lib/use-market-margin'; export * from './lib/use-market-margin';
export * from './lib/use-open-volume'; export * from './lib/use-open-volume';
+41
View File
@@ -75,3 +75,44 @@ subscription MarginsSubscription($partyId: ID!) {
timestamp timestamp
} }
} }
query EstimatePosition(
$marketId: ID!
$openVolume: String!
$orders: [OrderInfo!]
$collateralAvailable: String
) {
estimatePosition(
marketId: $marketId
openVolume: $openVolume
orders: $orders
collateralAvailable: $collateralAvailable
) {
margin {
worstCase {
maintenanceLevel
searchLevel
initialLevel
collateralReleaseLevel
}
bestCase {
maintenanceLevel
searchLevel
initialLevel
collateralReleaseLevel
}
}
liquidation {
worstCase {
open_volume_only
including_buy_orders
including_sell_orders
}
bestCase {
open_volume_only
including_buy_orders
including_sell_orders
}
}
}
}
+79 -1
View File
@@ -35,6 +35,16 @@ export type MarginsSubscriptionSubscriptionVariables = Types.Exact<{
export type MarginsSubscriptionSubscription = { __typename?: 'Subscription', margins: { __typename?: 'MarginLevelsUpdate', marketId: string, asset: string, partyId: string, maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, timestamp: any } }; export type MarginsSubscriptionSubscription = { __typename?: 'Subscription', margins: { __typename?: 'MarginLevelsUpdate', marketId: string, asset: string, partyId: string, maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, timestamp: any } };
export type EstimatePositionQueryVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
openVolume: Types.Scalars['String'];
orders?: Types.InputMaybe<Array<Types.OrderInfo> | Types.OrderInfo>;
collateralAvailable?: Types.InputMaybe<Types.Scalars['String']>;
}>;
export type EstimatePositionQuery = { __typename?: 'Query', estimatePosition?: { __typename?: 'PositionEstimate', margin: { __typename?: 'MarginEstimate', worstCase: { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string }, bestCase: { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string } }, liquidation?: { __typename?: 'LiquidationEstimate', worstCase: { __typename?: 'LiquidationPrice', open_volume_only: string, including_buy_orders: string, including_sell_orders: string }, bestCase: { __typename?: 'LiquidationPrice', open_volume_only: string, including_buy_orders: string, including_sell_orders: string } } | null } | null };
export const PositionFieldsFragmentDoc = gql` export const PositionFieldsFragmentDoc = gql`
fragment PositionFields on Position { fragment PositionFields on Position {
realisedPNL realisedPNL
@@ -220,4 +230,72 @@ export function useMarginsSubscriptionSubscription(baseOptions: Apollo.Subscript
return Apollo.useSubscription<MarginsSubscriptionSubscription, MarginsSubscriptionSubscriptionVariables>(MarginsSubscriptionDocument, options); return Apollo.useSubscription<MarginsSubscriptionSubscription, MarginsSubscriptionSubscriptionVariables>(MarginsSubscriptionDocument, options);
} }
export type MarginsSubscriptionSubscriptionHookResult = ReturnType<typeof useMarginsSubscriptionSubscription>; export type MarginsSubscriptionSubscriptionHookResult = ReturnType<typeof useMarginsSubscriptionSubscription>;
export type MarginsSubscriptionSubscriptionResult = Apollo.SubscriptionResult<MarginsSubscriptionSubscription>; export type MarginsSubscriptionSubscriptionResult = Apollo.SubscriptionResult<MarginsSubscriptionSubscription>;
export const EstimatePositionDocument = gql`
query EstimatePosition($marketId: ID!, $openVolume: String!, $orders: [OrderInfo!], $collateralAvailable: String) {
estimatePosition(
marketId: $marketId
openVolume: $openVolume
orders: $orders
collateralAvailable: $collateralAvailable
) {
margin {
worstCase {
maintenanceLevel
searchLevel
initialLevel
collateralReleaseLevel
}
bestCase {
maintenanceLevel
searchLevel
initialLevel
collateralReleaseLevel
}
}
liquidation {
worstCase {
open_volume_only
including_buy_orders
including_sell_orders
}
bestCase {
open_volume_only
including_buy_orders
including_sell_orders
}
}
}
}
`;
/**
* __useEstimatePositionQuery__
*
* To run a query within a React component, call `useEstimatePositionQuery` and pass it any options that fit your needs.
* When your component renders, `useEstimatePositionQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useEstimatePositionQuery({
* variables: {
* marketId: // value for 'marketId'
* openVolume: // value for 'openVolume'
* orders: // value for 'orders'
* collateralAvailable: // value for 'collateralAvailable'
* },
* });
*/
export function useEstimatePositionQuery(baseOptions: Apollo.QueryHookOptions<EstimatePositionQuery, EstimatePositionQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<EstimatePositionQuery, EstimatePositionQueryVariables>(EstimatePositionDocument, options);
}
export function useEstimatePositionLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<EstimatePositionQuery, EstimatePositionQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<EstimatePositionQuery, EstimatePositionQueryVariables>(EstimatePositionDocument, options);
}
export type EstimatePositionQueryHookResult = ReturnType<typeof useEstimatePositionQuery>;
export type EstimatePositionLazyQueryHookResult = ReturnType<typeof useEstimatePositionLazyQuery>;
export type EstimatePositionQueryResult = Apollo.QueryResult<EstimatePositionQuery, EstimatePositionQueryVariables>;
@@ -0,0 +1,40 @@
import type { PartialDeep } from 'type-fest';
import merge from 'lodash/merge';
import type { EstimatePositionQuery } from './__generated__/Positions';
export const estimatePositionQuery = (
override?: PartialDeep<EstimatePositionQuery>
): EstimatePositionQuery => {
const defaultResult: EstimatePositionQuery = {
estimatePosition: {
__typename: 'PositionEstimate',
margin: {
bestCase: {
collateralReleaseLevel: '1000000',
initialLevel: '500000',
maintenanceLevel: '200000',
searchLevel: '300000',
},
worstCase: {
collateralReleaseLevel: '1100000',
initialLevel: '600000',
maintenanceLevel: '300000',
searchLevel: '400000',
},
},
liquidation: {
bestCase: {
including_buy_orders: '1',
including_sell_orders: '1',
open_volume_only: '1',
},
worstCase: {
including_buy_orders: '1',
including_sell_orders: '1',
open_volume_only: '1',
},
},
},
};
return merge(defaultResult, override);
};
@@ -1,95 +0,0 @@
import { toBigNum } from '@vegaprotocol/utils';
import { Side, MarketTradingMode, OrderType } from '@vegaprotocol/types';
import type { ScalingFactors, RiskFactor } from '@vegaprotocol/types';
import type { MarketData } from '@vegaprotocol/market-list';
export const isMarketInAuction = (marketTradingMode: MarketTradingMode) => {
return [
MarketTradingMode.TRADING_MODE_BATCH_AUCTION,
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
MarketTradingMode.TRADING_MODE_OPENING_AUCTION,
].includes(marketTradingMode);
};
/**
* Get the market price based on market mode (auction or not auction)
*/
export const getMarketPrice = ({
marketTradingMode,
indicativePrice,
markPrice,
}: Pick<MarketData, 'marketTradingMode' | 'indicativePrice' | 'markPrice'>) => {
if (isMarketInAuction(marketTradingMode)) {
// 0 can never be a valid uncrossing price
// as it would require there being orders on the book at that price.
if (
indicativePrice &&
indicativePrice !== '0' &&
BigInt(indicativePrice) !== BigInt(0)
) {
return indicativePrice;
}
}
return markPrice;
};
/**
* Gets the price for an order, order limit this is the user
* entered value, for market this will be the mark price or
* if in auction the indicative uncrossing price
*/
export const getDerivedPrice = (
order: {
type?: OrderType | null;
price?: string;
},
marketData: Pick<
MarketData,
'marketTradingMode' | 'indicativePrice' | 'markPrice'
>
) => {
// If order type is market we should use either the mark price
// or the uncrossing price. If order type is limit use the price
// the user has input
// Use the market price if order is a market order
if (order.type === OrderType.TYPE_LIMIT && order.price) {
return order.price;
}
return getMarketPrice(marketData);
};
export const calculateMargins = ({
size,
side,
price,
decimals,
positionDecimalPlaces,
decimalPlaces,
scalingFactors,
riskFactors,
}: {
size: string;
side: Side;
positionDecimalPlaces: number;
decimalPlaces: number;
decimals: number;
price: string;
scalingFactors?: ScalingFactors;
riskFactors: RiskFactor;
}) => {
const maintenanceMargin = toBigNum(size, positionDecimalPlaces)
.multipliedBy(
side === Side.SIDE_SELL ? riskFactors.short : riskFactors.long
)
.multipliedBy(toBigNum(price, decimalPlaces));
return {
maintenanceMargin: maintenanceMargin
.multipliedBy(Math.pow(10, decimals))
.toFixed(0),
initialMargin: maintenanceMargin
.multipliedBy(scalingFactors?.initialMargin ?? 1)
.multipliedBy(Math.pow(10, decimals))
.toFixed(0),
};
};
@@ -5,7 +5,6 @@ import sortBy from 'lodash/sortBy';
import type { Account } from '@vegaprotocol/accounts'; import type { Account } from '@vegaprotocol/accounts';
import { accountsDataProvider } from '@vegaprotocol/accounts'; import { accountsDataProvider } from '@vegaprotocol/accounts';
import { toBigNum, removePaginationWrapper } from '@vegaprotocol/utils'; import { toBigNum, removePaginationWrapper } from '@vegaprotocol/utils';
import type { Edge } from '@vegaprotocol/data-provider';
import { import {
makeDataProvider, makeDataProvider,
makeDerivedDataProvider, makeDerivedDataProvider,
@@ -28,14 +27,6 @@ import {
PositionsSubscriptionDocument, PositionsSubscriptionDocument,
} from './__generated__/Positions'; } from './__generated__/Positions';
import { marginsDataProvider } from './margin-data-provider'; import { marginsDataProvider } from './margin-data-provider';
import { calculateMargins } from './margin-calculator';
import { Side } from '@vegaprotocol/types';
import { marketInfoProvider } from '@vegaprotocol/market-info';
import type { MarketInfoQuery } from '@vegaprotocol/market-info';
import { marketDataProvider } from '@vegaprotocol/market-list';
import type { MarketData } from '@vegaprotocol/market-list';
import { activeOrdersProvider } from '@vegaprotocol/orders';
import type { OrderFieldsFragment } from '@vegaprotocol/orders';
import type { PositionStatus } from '@vegaprotocol/types'; import type { PositionStatus } from '@vegaprotocol/types';
type PositionMarginLevel = Pick< type PositionMarginLevel = Pick<
@@ -336,98 +327,3 @@ export const positionsMetricsProvider = makeDerivedDataProvider<
return !(previousRow && isEqual(previousRow, row)); return !(previousRow && isEqual(previousRow, row));
}) })
); );
export const volumeAndMarginProvider = makeDerivedDataProvider<
{
buyVolume: string;
sellVolume: string;
buyInitialMargin: string;
sellInitialMargin: string;
},
never,
PositionsQueryVariables & MarketDataQueryVariables
>(
[
(callback, client, { partyId, marketId }) =>
activeOrdersProvider(callback, client, {
partyId,
marketId,
}),
(callback, client, { marketId }) =>
marketDataProvider(callback, client, { marketId }),
(callback, client, { marketId }) =>
marketInfoProvider(callback, client, { marketId }),
openVolumeDataProvider,
],
(data) => {
const orders = data[0] as (Edge<OrderFieldsFragment> | null)[] | null;
const marketData = data[1] as MarketData | null;
const marketInfo = data[2] as MarketInfoQuery['market'];
let openVolume = (data[3] as string | null) || '0';
const shortPosition = openVolume?.startsWith('-');
if (shortPosition) {
openVolume = openVolume.substring(1);
}
let buyVolume = BigInt(shortPosition ? 0 : openVolume);
let sellVolume = BigInt(shortPosition ? openVolume : 0);
let buyInitialMargin = BigInt(0);
let sellInitialMargin = BigInt(0);
if (marketInfo?.riskFactors && marketData) {
const {
positionDecimalPlaces,
decimalPlaces,
tradableInstrument,
riskFactors,
} = marketInfo;
const { marginCalculator, instrument } = tradableInstrument;
const { decimals } = instrument.product.settlementAsset;
const calculatorParams = {
positionDecimalPlaces,
decimalPlaces,
decimals,
scalingFactors: marginCalculator?.scalingFactors,
riskFactors,
};
if (openVolume !== '0') {
const { initialMargin } = calculateMargins({
side: shortPosition ? Side.SIDE_SELL : Side.SIDE_BUY,
size: openVolume,
price: marketData.markPrice,
...calculatorParams,
});
if (shortPosition) {
sellInitialMargin += BigInt(initialMargin);
} else {
buyInitialMargin += BigInt(initialMargin);
}
}
orders?.forEach((order) => {
if (!order) {
return;
}
const { side, remaining: size } = order.node;
const initialMargin = BigInt(
calculateMargins({
side,
size,
price: marketData.markPrice, //getDerivedPrice(order.node, marketData), same use-initial-margin
...calculatorParams,
}).initialMargin
);
if (order.node.side === Side.SIDE_BUY) {
buyVolume += BigInt(size);
buyInitialMargin += initialMargin;
} else {
sellVolume += BigInt(size);
sellInitialMargin += initialMargin;
}
});
}
return {
buyVolume: buyVolume.toString(),
sellVolume: sellVolume.toString(),
buyInitialMargin: buyInitialMargin.toString(),
sellInitialMargin: sellInitialMargin.toString(),
};
}
);

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