Compare commits
73
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfea7eba61 | ||
|
|
ace45053c2 | ||
|
|
7cfaf02d09 | ||
|
|
c803cafd53 | ||
|
|
aa2b755b93 | ||
|
|
4a7296312a | ||
|
|
1418a2259f | ||
|
|
7e5ef0157a | ||
|
|
0cf764f11e | ||
|
|
1c626aeeb2 | ||
|
|
c4cec47b0b | ||
|
|
1a67c41bb4 | ||
|
|
fc66240390 | ||
|
|
fc0da98683 | ||
|
|
4d0dafb113 | ||
|
|
eda431be9e | ||
|
|
4ff1749e54 | ||
|
|
e7a108fd7a | ||
|
|
5d3f2ea7d8 | ||
|
|
1ab27574ca | ||
|
|
0ea3c43e78 | ||
|
|
9e183568b0 | ||
|
|
69fd5b26b6 | ||
|
|
2d5b926609 | ||
|
|
98a2ba79a8 | ||
|
|
0e6e986937 | ||
|
|
15d6ea0c05 | ||
|
|
0358ce6293 | ||
|
|
0791be4636 | ||
|
|
07442848ee | ||
|
|
d5f0bd419e | ||
|
|
2c7934a8c7 | ||
|
|
fae61c28ca | ||
|
|
fcd4541625 | ||
|
|
8e0a92fdec | ||
|
|
6e7109ed87 | ||
|
|
f2c698ce06 | ||
|
|
ad9a3a3400 | ||
|
|
efeac49d58 | ||
|
|
2d430710ab | ||
|
|
bca1a98985 | ||
|
|
8a080d3279 | ||
|
|
f121836b4e | ||
|
|
b641c82ad8 | ||
|
|
bf73559c30 | ||
|
|
653cec2592 | ||
|
|
579c884a5a | ||
|
|
b2279c7e47 | ||
|
|
a8c17b6807 | ||
|
|
b4c7dc6f59 | ||
|
|
d44392bebf | ||
|
|
bd679957e2 | ||
|
|
e30d48555e | ||
|
|
b4b2416780 | ||
|
|
dc7832ac81 | ||
|
|
91207d31ee | ||
|
|
58efb460a6 | ||
|
|
044f98777b | ||
|
|
00fdc5e81a | ||
|
|
adfad3bafb | ||
|
|
e6ae88905c | ||
|
|
3953ed9953 | ||
|
|
b48ab58e6a | ||
|
|
e44cf1ff53 | ||
|
|
bded1d32ba | ||
|
|
76ddf45f4c | ||
|
|
6a2c9004d8 | ||
|
|
77ee88b43e | ||
|
|
e4958ce3c0 | ||
|
|
ecedb879f8 | ||
|
|
89700803d6 | ||
|
|
65c9fa5df2 | ||
|
|
02399c40fc |
@@ -5,6 +5,8 @@ on:
|
||||
branches:
|
||||
- release/*
|
||||
- develop
|
||||
tags:
|
||||
- v*
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
@@ -97,15 +99,13 @@ jobs:
|
||||
- name: See affected apps
|
||||
run: |
|
||||
echo ">>>> debug"
|
||||
echo "NX Version: $nx_version"
|
||||
echo "NX_BASE: ${{ env.NX_BASE }}"
|
||||
echo "NX_HEAD: ${{ env.NX_HEAD }}"
|
||||
echo ">>>> eof debug"
|
||||
|
||||
affected="$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects)"
|
||||
echo -n "Affected projects: $affected"
|
||||
|
||||
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=""
|
||||
preview_governance="not deployed"
|
||||
preview_trading="not deployed"
|
||||
|
||||
@@ -33,5 +33,4 @@ jobs:
|
||||
config: baseUrl=${{ github.event.inputs.url }}
|
||||
env: grepTags=@live
|
||||
env:
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
######
|
||||
|
||||
- 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
|
||||
env:
|
||||
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
|
||||
|
||||
@@ -30,13 +30,21 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
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
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
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
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
@@ -54,30 +62,24 @@ jobs:
|
||||
- name: Define variables
|
||||
run: |
|
||||
envName=''
|
||||
dockerfile="dist.Dockerfile"
|
||||
if [[ "${{ github.event_name }}" = "push" ]]; then
|
||||
domain="vega.rocks"
|
||||
if [[ "${{ github.ref }}" =~ .*release/.* ]]; then
|
||||
envName="$(echo ${{ github.ref }} | rev | cut -d '/' -f 1 | rev)"
|
||||
if [[ "${{ github.ref }}" =~ .*mainnet.* ]]; then
|
||||
domain="vega.community"
|
||||
if [[ "${{ matrix.app }}" = "trading" ]]; then
|
||||
dockerfile="ipfs.Dockerfile"
|
||||
fi
|
||||
fi
|
||||
elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then
|
||||
envName="stagnet1"
|
||||
elif [[ "${{ matrix.app}}" = "trading" ]] && [[ ${{ startsWith(github.ref, 'refs/tags/v') && 'true' || 'false' }} = "true" ]]; then
|
||||
envName="mainnet"
|
||||
fi
|
||||
bucketName="${{ matrix.app }}.${envName}.${domain}"
|
||||
echo BUCKET_NAME=${bucketName} >> $GITHUB_ENV
|
||||
fi
|
||||
nodeVersion=$(cat .nvmrc | head -n 1)
|
||||
echo ENV_NAME=${envName} >> $GITHUB_ENV
|
||||
echo NODE_VERSION=${nodeVersion} >> $GITHUB_ENV
|
||||
echo DOCKERFILE=docker/${dockerfile} >> $GITHUB_ENV
|
||||
|
||||
- name: Build local dist
|
||||
if: ${{ env.DOCKERFILE != 'docker/ipfs.Dockerfile' }}
|
||||
run: |
|
||||
flags=""
|
||||
if [[ ! -z "${{ env.ENV_NAME }}" ]]; then
|
||||
@@ -98,62 +100,60 @@ jobs:
|
||||
|
||||
- name: Build and export to local Docker
|
||||
id: docker_build
|
||||
if: ${{ github.event_name == 'pull_request' || ( env.DOCKERFILE == 'docker/ipfs.Dockerfile' && github.event_name == 'push' ) }}
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ${{ env.DOCKERFILE }}
|
||||
file: docker/node-outside-docker.Dockerfile
|
||||
load: true
|
||||
build-args: |
|
||||
APP=${{ matrix.app }}
|
||||
NODE_VERSION=${{ env.NODE_VERSION }}
|
||||
ENV_NAME=${{ env.ENV_NAME }}
|
||||
tags: |
|
||||
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
|
||||
|
||||
- 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 }}
|
||||
|
||||
- name: Sanity check docker image
|
||||
if: ${{ github.event_name == 'pull_request' || ( env.DOCKERFILE == 'docker/ipfs.Dockerfile' && github.event_name == 'push' ) }}
|
||||
run: |
|
||||
echo "Check ipfs-hash"
|
||||
if [[ "${{ env.DOCKERFILE }}" = "docker/ipfs.Dockerfile" ]]; then
|
||||
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat /ipfs-hash
|
||||
fi
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
- name: Publish dist as docker image (ghcr)
|
||||
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:
|
||||
context: .
|
||||
file: ${{ env.DOCKERFILE }}
|
||||
file: docker/node-outside-docker.Dockerfile
|
||||
push: true
|
||||
build-args: |
|
||||
APP=${{ matrix.app }}
|
||||
NODE_VERSION=${{ env.NODE_VERSION }}
|
||||
ENV_NAME=${{ env.ENV_NAME }}
|
||||
tags: |
|
||||
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
|
||||
- name: Publish dist to s3
|
||||
uses: jakejarvis/s3-sync-action@master
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
if: ${{ github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/v') }}
|
||||
with:
|
||||
args: --acl private --follow-symlinks --delete
|
||||
env:
|
||||
@@ -169,3 +169,59 @@ jobs:
|
||||
with:
|
||||
labels: ${{ matrix.app }}-preview
|
||||
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"
|
||||
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 origin main
|
||||
|
||||
# branch_name=${{ github.ref_name }}-hash-update
|
||||
# git checkout -b "${branch_name}"
|
||||
# git add interstitial-allow.json netlify.toml
|
||||
# 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 --admin --auto $pr_url
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/dist-result
|
||||
/tmp
|
||||
/out-tsc
|
||||
/tools/executors/**/*.js
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
# 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.
|
||||
|
||||
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:
|
||||
In order to run a container on port 3000:
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
|
||||
@@ -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_VEGA_GOVERNANCE_URL=https://stagnet1.governance.vega.xyz
|
||||
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
|
||||
NX_EXPLORER_ASSETS=1
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
NX_TENDERMINT_URL=https://be.vega.community
|
||||
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_URL=https://api.vega.community/graphql
|
||||
NX_VEGA_ENV=MAINNET
|
||||
NX_BLOCK_EXPLORER=https://be.vega.community/rest/
|
||||
NX_ETHERSCAN_URL=https://etherscan.io
|
||||
|
||||
@@ -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"}'
|
||||
|
||||
@@ -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 { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { DEFAULT_CACHE_CONFIG } from '@vegaprotocol/apollo-client';
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
import { router } from './routes/router-config';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
const splashLoading = (
|
||||
<Splash>
|
||||
@@ -12,10 +21,23 @@ const splashLoading = (
|
||||
);
|
||||
|
||||
function App() {
|
||||
const { VEGA_URL } = useEnvironment();
|
||||
const [nodeSwitcherOpen, setNodeSwitcherOpen] = useNodeSwitcherStore(
|
||||
(store) => [store.dialogOpen, store.setDialogOpen]
|
||||
);
|
||||
return (
|
||||
<TendermintWebsocketProvider>
|
||||
<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>
|
||||
</TendermintWebsocketProvider>
|
||||
);
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import { NodeSwitcherDialog, useEnvironment } from '@vegaprotocol/environment';
|
||||
import {
|
||||
useEnvironment,
|
||||
useNodeSwitcherStore,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
import { ExternalLink, Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { ENV } from '../../config/env';
|
||||
|
||||
export const Footer = () => {
|
||||
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 showFullFeedbackLabel = useMemo(
|
||||
() => ['md', 'lg', 'xl', 'xxl', 'xxxl'].includes(screenSize),
|
||||
@@ -15,46 +21,40 @@ export const Footer = () => {
|
||||
);
|
||||
|
||||
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">
|
||||
<div className="flex justify-between gap-2 align-middle">
|
||||
{GIT_COMMIT_HASH && (
|
||||
<div className="content-center flex border-r border-neutral-700 dark:border-neutral-300 pr-4">
|
||||
<p data-testid="git-commit-hash">
|
||||
{t('Version')}:{' '}
|
||||
<Link
|
||||
href={
|
||||
GIT_ORIGIN_URL
|
||||
? `${GIT_ORIGIN_URL}/commit/${GIT_COMMIT_HASH}`
|
||||
: undefined
|
||||
}
|
||||
target={GIT_ORIGIN_URL ? '_blank' : undefined}
|
||||
>
|
||||
{GIT_COMMIT_HASH}
|
||||
</Link>
|
||||
</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>
|
||||
<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">
|
||||
{GIT_COMMIT_HASH && (
|
||||
<div className="content-center flex border-r border-neutral-700 dark:border-neutral-300 pr-4">
|
||||
<p data-testid="git-commit-hash">
|
||||
{t('Version')}:{' '}
|
||||
<Link
|
||||
href={
|
||||
GIT_ORIGIN_URL
|
||||
? `${GIT_ORIGIN_URL}/commit/${GIT_COMMIT_HASH}`
|
||||
: undefined
|
||||
}
|
||||
target={GIT_ORIGIN_URL ? '_blank' : undefined}
|
||||
>
|
||||
{GIT_COMMIT_HASH}
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex pl-2 content-center">
|
||||
<ExternalLink href={ENV.addresses.feedback}>
|
||||
{showFullFeedbackLabel ? t('Share your feedback') : t('Feedback')}
|
||||
</ExternalLink>
|
||||
</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>
|
||||
</footer>
|
||||
<NodeSwitcherDialog
|
||||
open={nodeSwitcherOpen}
|
||||
setOpen={setNodeSwitcherOpen}
|
||||
/>
|
||||
</>
|
||||
|
||||
<div className="flex pl-2 content-center">
|
||||
<ExternalLink href={ENV.addresses.feedback}>
|
||||
{showFullFeedbackLabel ? t('Share your feedback') : t('Feedback')}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+8
-2
@@ -1,3 +1,5 @@
|
||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
// https://github.com/vegaprotocol/vega/blob/develop/core/blockchain/response.go
|
||||
export const ErrorCodes = new Map([
|
||||
[51, 'Transaction failed validation'],
|
||||
@@ -28,7 +30,11 @@ export const ChainResponseCode = ({
|
||||
}: ChainResponseCodeProps) => {
|
||||
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';
|
||||
|
||||
// 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;
|
||||
|
||||
return (
|
||||
<div title={`Response code: ${code} - ${label}`} className="inline-block">
|
||||
<div title={`Response code: ${code} - ${label}`} className=" inline-block">
|
||||
<span
|
||||
className="mr-2"
|
||||
aria-label={isSuccess ? 'Success' : 'Warning'}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint
|
||||
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
import ProposalLink from '../../links/proposal-link/proposal-link';
|
||||
import { VoteIcon } from '../../vote-icon/vote-icon';
|
||||
|
||||
interface TxProposalVoteProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -30,27 +31,22 @@ export const TxProposalVote = ({
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const vote = txData.command.voteSubmission.value ? '👍' : '👎';
|
||||
const vote = txData.command.voteSubmission.value === 'VALUE_YES';
|
||||
|
||||
return (
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<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">
|
||||
<TableCell>{t('Proposal details')}</TableCell>
|
||||
<TableCell>
|
||||
<ProposalLink id={txData.command.voteSubmission.proposalId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Proposal')}</TableCell>
|
||||
<TableCell>{txData.command.voteSubmission.proposalId}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Vote')}</TableCell>
|
||||
<TableCell>{vote}</TableCell>
|
||||
<TableCell>
|
||||
<VoteIcon vote={vote} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { components } from '../../../types/explorer';
|
||||
import { VoteIcon } from '../vote-icon/vote-icon';
|
||||
|
||||
interface TxOrderTypeProps {
|
||||
orderType: string;
|
||||
@@ -137,12 +138,15 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
|
||||
let type = displayString[orderType] || orderType;
|
||||
|
||||
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
|
||||
if (type === 'Chain Event' && !!command?.chainEvent) {
|
||||
type = getLabelForChainEvent(command.chainEvent);
|
||||
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') {
|
||||
if (command && !!command.proposalSubmission) {
|
||||
type = getLabelForProposal(command.proposalSubmission);
|
||||
@@ -150,6 +154,16 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
|
||||
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') {
|
||||
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('tx-type')).toHaveTextContent('testType');
|
||||
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 (
|
||||
<div
|
||||
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
|
||||
className="text-sm col-span-10 md:col-span-3 leading-none"
|
||||
@@ -83,7 +83,7 @@ export const TxsInfiniteListItem = ({
|
||||
data-testid="tx-success"
|
||||
>
|
||||
<span className="md:hidden uppercase text-vega-dark-300">
|
||||
Success:
|
||||
Success
|
||||
</span>
|
||||
{isNumber(code) ? (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
@@ -60,3 +60,7 @@
|
||||
--ag-row-hover-color: theme(colors.neutral[800]);
|
||||
--ag-font-size: 12px;
|
||||
}
|
||||
|
||||
.voteicon svg {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
waitForSpinner,
|
||||
navigateTo,
|
||||
navigation,
|
||||
turnTelemetryOff,
|
||||
} from '../../support/common.functions';
|
||||
import {
|
||||
clickOnValidatorFromList,
|
||||
@@ -67,6 +68,7 @@ context(
|
||||
'teardown wallet & drill into a specific validator',
|
||||
function () {
|
||||
cy.clearLocalStorage();
|
||||
turnTelemetryOff();
|
||||
cy.reload();
|
||||
waitForSpinner();
|
||||
cy.connectVegaWallet();
|
||||
@@ -252,10 +254,10 @@ context(
|
||||
waitForBeginningOfEpoch();
|
||||
cy.getByTestId(stakeValidatorListStakePercentage).should(
|
||||
'have.text',
|
||||
'100%'
|
||||
'50.02%'
|
||||
);
|
||||
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(
|
||||
'have.text',
|
||||
'Approved '
|
||||
'Approved by validators '
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -136,7 +136,7 @@ context(
|
||||
);
|
||||
cy.getByTestId('protocol-upgrade-state').should(
|
||||
'have.text',
|
||||
'Approved'
|
||||
'Approved by validators'
|
||||
);
|
||||
cy.getByTestId('protocol-upgrade-release-tag').should(
|
||||
'have.text',
|
||||
|
||||
@@ -29,7 +29,7 @@ const performancePenaltyToolTip = '[data-testid="performance-penalty-tooltip"]';
|
||||
const overstakedPenaltyToolTip = '[data-testid="overstaked-penalty-tooltip"]';
|
||||
const totalPenaltyToolTip = '[data-testid="total-penalty-tooltip"]';
|
||||
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 () {
|
||||
before('navigate to validators page', function () {
|
||||
@@ -84,13 +84,13 @@ context('Validators Page - verify elements on page', function () {
|
||||
|
||||
cy.get(stakedByOperatorToolTip)
|
||||
.invoke('text')
|
||||
.should('contain', 'Staked by operator: 0.00');
|
||||
.should('contain', 'Staked by operator: 3,000.00');
|
||||
cy.get(stakedByDelegatesToolTip)
|
||||
.invoke('text')
|
||||
.should('contain', 'Staked by delegates: 0.00');
|
||||
cy.get(totalStakedToolTip)
|
||||
.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 () {
|
||||
@@ -106,10 +106,10 @@ context('Validators Page - verify elements on page', function () {
|
||||
|
||||
cy.get(unnormalisedVotingPowerToolTip)
|
||||
.invoke('text')
|
||||
.should('contain', 'Unnormalised voting power: 0.00%');
|
||||
.should('contain', 'Unnormalised voting power: 20.00%');
|
||||
cy.get(normalisedVotingPowerToolTip)
|
||||
.invoke('text')
|
||||
.should('contain', 'Normalised voting power: 0.10%');
|
||||
.should('contain', 'Normalised voting power: 50.00%');
|
||||
});
|
||||
|
||||
// 2002-SINC-018
|
||||
@@ -126,13 +126,13 @@ context('Validators Page - verify elements on page', function () {
|
||||
|
||||
cy.get(performancePenaltyToolTip)
|
||||
.invoke('text')
|
||||
.should('contain', 'Performance penalty: 100.00%');
|
||||
.should('contain', 'Performance penalty: 0.00%');
|
||||
cy.get(overstakedPenaltyToolTip)
|
||||
.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)
|
||||
.invoke('text')
|
||||
.should('contain', 'Total penalties: 0.00%');
|
||||
.should('contain', 'Total penalties: 60.00%');
|
||||
});
|
||||
|
||||
it('Should be able to see validator pending stake', function () {
|
||||
|
||||
@@ -33,4 +33,6 @@ before(() => {
|
||||
aliasGQLQuery(req, 'ChainId', chainIdQuery());
|
||||
aliasGQLQuery(req, 'Statistics', statisticsQuery());
|
||||
});
|
||||
// Self stake validators so they are displayed
|
||||
cy.validatorsSelfDelegate();
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
StakingBridge,
|
||||
Token,
|
||||
TokenVesting,
|
||||
TokenFaucetable,
|
||||
CollateralBridge,
|
||||
} from '@vegaprotocol/smart-contracts';
|
||||
import { ethers, Wallet } from 'ethers';
|
||||
@@ -41,7 +40,7 @@ export async function depositAsset(
|
||||
decimalPlaces: number
|
||||
) {
|
||||
// Approve asset
|
||||
const faucet = new TokenFaucetable(assetEthAddress, signer);
|
||||
const faucet = new Token(assetEthAddress, signer);
|
||||
cy.wrap(
|
||||
faucet.approve(Erc20BridgeAddress, amount + '0'.repeat(decimalPlaces + 1)),
|
||||
{
|
||||
@@ -62,7 +61,7 @@ export async function depositAsset(
|
||||
}
|
||||
|
||||
export async function faucetAsset(assetEthAddress: string) {
|
||||
const faucet = new TokenFaucetable(assetEthAddress, signer);
|
||||
const faucet = new Token(assetEthAddress, signer);
|
||||
await promiseWithTimeout(faucet.faucet(), 10 * 60 * 1000, 'faucet asset');
|
||||
}
|
||||
|
||||
|
||||
+77
-27
@@ -2,7 +2,6 @@ import './i18n';
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { Integrations } from '@sentry/tracing';
|
||||
import { BrowserRouter as Router, useLocation } from 'react-router-dom';
|
||||
import { AppLoader } from './app-loader';
|
||||
import { NetworkInfo } from '@vegaprotocol/network-info';
|
||||
@@ -37,6 +36,10 @@ import {
|
||||
useEnvironment,
|
||||
NetworkLoader,
|
||||
useInitializeEnv,
|
||||
NodeGuard,
|
||||
AppFailure,
|
||||
NodeSwitcherDialog,
|
||||
useNodeSwitcherStore,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { ENV } from './config';
|
||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||
@@ -48,6 +51,7 @@ import {
|
||||
TELEMETRY_ON,
|
||||
} from './components/telemetry-dialog/telemetry-dialog';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const cache: InMemoryCacheConfig = {
|
||||
typePolicies: {
|
||||
@@ -179,32 +183,70 @@ const ScrollToTop = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const removeQueryParams = (url: string) => {
|
||||
return url.split('?')[0];
|
||||
};
|
||||
|
||||
const AppContainer = () => {
|
||||
const { config, loading, error } = useEthereumConfig();
|
||||
const { VEGA_ENV, GIT_COMMIT_HASH, GIT_BRANCH, ETHEREUM_PROVIDER_URL } =
|
||||
useEnvironment();
|
||||
const {
|
||||
VEGA_ENV,
|
||||
VEGA_URL,
|
||||
GIT_COMMIT_HASH,
|
||||
GIT_BRANCH,
|
||||
ETHEREUM_PROVIDER_URL,
|
||||
} = useEnvironment();
|
||||
const [telemetryOn] = useLocalStorage(TELEMETRY_ON);
|
||||
const { t } = useTranslation();
|
||||
const [nodeSwitcherOpen, setNodeSwitcher] = useNodeSwitcherStore((store) => [
|
||||
store.dialogOpen,
|
||||
store.setDialogOpen,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ENV.dsn && telemetryOn) {
|
||||
if (ENV.dsn && telemetryOn === 'true') {
|
||||
Sentry.init({
|
||||
dsn: ENV.dsn,
|
||||
integrations: [new Integrations.BrowserTracing()],
|
||||
tracesSampleRate: 0.1,
|
||||
enabled: true,
|
||||
environment: VEGA_ENV,
|
||||
release: GIT_COMMIT_HASH,
|
||||
beforeSend(event) {
|
||||
if (event.request?.url?.includes('/claim?')) {
|
||||
return {
|
||||
...event,
|
||||
request: {
|
||||
...event.request,
|
||||
url: event.request?.url.split('?')[0],
|
||||
},
|
||||
};
|
||||
}
|
||||
return event;
|
||||
const requestUrl = event.request?.url;
|
||||
const transaction = event.transaction;
|
||||
|
||||
const updatedRequest =
|
||||
requestUrl && requestUrl.includes('/test?')
|
||||
? { ...event.request, url: removeQueryParams(requestUrl) }
|
||||
: event.request;
|
||||
|
||||
const updatedTransaction =
|
||||
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);
|
||||
@@ -219,21 +261,29 @@ const AppContainer = () => {
|
||||
<ScrollToTop />
|
||||
<AppStateProvider>
|
||||
<div className="grid min-h-full text-white">
|
||||
<AsyncRenderer<EthereumConfig | null>
|
||||
loading={loading}
|
||||
data={config}
|
||||
error={error}
|
||||
render={(cnf) =>
|
||||
cnf && (
|
||||
<Web3Container
|
||||
chainId={Number(cnf.chain_id)}
|
||||
providerUrl={ETHEREUM_PROVIDER_URL}
|
||||
/>
|
||||
)
|
||||
<NodeGuard
|
||||
skeleton={<div>{t('Loading')}</div>}
|
||||
failure={
|
||||
<AppFailure title={t('NodeUnsuitable', { url: VEGA_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>
|
||||
</AppStateProvider>
|
||||
<NodeSwitcherDialog open={nodeSwitcherOpen} setOpen={setNodeSwitcher} />
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
AppStateActionType,
|
||||
useAppState,
|
||||
} from '../../contexts/app-state/app-state-context';
|
||||
import { useWeb3ConnectStore } from '@vegaprotocol/web3';
|
||||
|
||||
export const EthConnectPrompt = () => {
|
||||
const { t } = useTranslation();
|
||||
const { appDispatch } = useAppState();
|
||||
const { open } = useWeb3ConnectStore();
|
||||
return (
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={() =>
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
|
||||
isOpen: true,
|
||||
})
|
||||
}
|
||||
onClick={open}
|
||||
fill={true}
|
||||
data-testid="connect-to-eth-btn"
|
||||
>
|
||||
|
||||
@@ -2,10 +2,7 @@ import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
AppStateActionType,
|
||||
useAppState,
|
||||
} from '../../contexts/app-state/app-state-context';
|
||||
import { useWeb3ConnectStore } from '@vegaprotocol/web3';
|
||||
|
||||
export const EthWalletContainer = ({
|
||||
children,
|
||||
@@ -14,21 +11,12 @@ export const EthWalletContainer = ({
|
||||
}) => {
|
||||
const { account } = useWeb3React();
|
||||
const { t } = useTranslation();
|
||||
const { appDispatch } = useAppState();
|
||||
const { open } = useWeb3ConnectStore();
|
||||
|
||||
if (!account) {
|
||||
return (
|
||||
<div className="w-full text-center">
|
||||
<Button
|
||||
onClick={() =>
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
|
||||
isOpen: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('connectEthWallet')}
|
||||
</Button>
|
||||
<Button onClick={open}>{t('connectEthWallet')}</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,11 @@ import {
|
||||
import { Loader } from '@vegaprotocol/ui-toolkit';
|
||||
import colors from 'tailwindcss/colors';
|
||||
import { useBalances } from '../../lib/balances/balances-store';
|
||||
import { useEthereumConfig, useWeb3Disconnect } from '@vegaprotocol/web3';
|
||||
import {
|
||||
useEthereumConfig,
|
||||
useWeb3ConnectStore,
|
||||
useWeb3Disconnect,
|
||||
} from '@vegaprotocol/web3';
|
||||
import { getChainName } from '@vegaprotocol/web3';
|
||||
|
||||
const removeLeadingAddressSymbol = (key: string) => {
|
||||
@@ -189,6 +193,7 @@ export const EthWallet = () => {
|
||||
const pendingTxs = usePendingTransactions();
|
||||
const disconnect = useWeb3Disconnect(connector);
|
||||
const { config } = useEthereumConfig();
|
||||
const { open } = useWeb3ConnectStore();
|
||||
|
||||
return (
|
||||
<WalletCard>
|
||||
@@ -233,12 +238,7 @@ export const EthWallet = () => {
|
||||
) : (
|
||||
<Button
|
||||
fill={true}
|
||||
onClick={() =>
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
|
||||
isOpen: true,
|
||||
})
|
||||
}
|
||||
onClick={open}
|
||||
data-testid="connect-to-eth-wallet-button"
|
||||
>
|
||||
{t('connectEthWalletToAssociate')}
|
||||
|
||||
@@ -25,20 +25,26 @@ export function Web3Connector({
|
||||
connectors,
|
||||
chainId,
|
||||
}: Web3ConnectorProps) {
|
||||
const { appState, appDispatch } = useAppState();
|
||||
const { open, close, isOpen } = useWeb3ConnectStore();
|
||||
|
||||
const setDialogOpen = useCallback(
|
||||
(isOpen: boolean) => {
|
||||
appDispatch({ type: AppStateActionType.SET_ETH_WALLET_OVERLAY, isOpen });
|
||||
if (isOpen) {
|
||||
open();
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
},
|
||||
[appDispatch]
|
||||
[open, close]
|
||||
);
|
||||
|
||||
const appChainId = Number(chainId);
|
||||
return (
|
||||
<>
|
||||
<Web3Content appChainId={appChainId}>{children}</Web3Content>
|
||||
<Web3ConnectDialog
|
||||
connectors={connectors}
|
||||
dialogOpen={appState.ethConnectOverlay}
|
||||
dialogOpen={isOpen}
|
||||
setDialogOpen={setDialogOpen}
|
||||
desiredChainId={appChainId}
|
||||
/>
|
||||
|
||||
@@ -34,9 +34,6 @@ export interface AppState {
|
||||
/** Whether or not the manage VEGA wallet overlay is open */
|
||||
vegaWalletManageOverlay: boolean;
|
||||
|
||||
/** Whether or not the connect to Ethereum wallet overlay is open */
|
||||
ethConnectOverlay: boolean;
|
||||
|
||||
/** Whether or not the transaction modal is open */
|
||||
transactionOverlay: boolean;
|
||||
/**
|
||||
@@ -57,7 +54,6 @@ export enum AppStateActionType {
|
||||
REFRESH_BALANCES,
|
||||
SET_VEGA_WALLET_OVERLAY,
|
||||
SET_VEGA_WALLET_MANAGE_OVERLAY,
|
||||
SET_ETH_WALLET_OVERLAY,
|
||||
SET_DRAWER,
|
||||
REFRESH_ASSOCIATED_BALANCES,
|
||||
SET_ASSOCIATION_BREAKDOWN,
|
||||
@@ -81,10 +77,6 @@ export type AppStateAction =
|
||||
type: AppStateActionType.SET_VEGA_WALLET_MANAGE_OVERLAY;
|
||||
isOpen: boolean;
|
||||
}
|
||||
| {
|
||||
type: AppStateActionType.SET_ETH_WALLET_OVERLAY;
|
||||
isOpen: boolean;
|
||||
}
|
||||
| {
|
||||
type: AppStateActionType.SET_DRAWER;
|
||||
isOpen: boolean;
|
||||
|
||||
@@ -16,7 +16,6 @@ const initialAppState: AppState = {
|
||||
totalSupply: new BigNumber(0),
|
||||
vegaWalletOverlay: false,
|
||||
vegaWalletManageOverlay: false,
|
||||
ethConnectOverlay: false,
|
||||
transactionOverlay: false,
|
||||
bannerMessage: '',
|
||||
disconnectNotice: false,
|
||||
@@ -45,12 +44,6 @@ function appStateReducer(state: AppState, action: AppStateAction): AppState {
|
||||
vegaWalletOverlay: action.isOpen ? false : state.vegaWalletOverlay,
|
||||
};
|
||||
}
|
||||
case AppStateActionType.SET_ETH_WALLET_OVERLAY: {
|
||||
return {
|
||||
...state,
|
||||
ethConnectOverlay: action.isOpen,
|
||||
};
|
||||
}
|
||||
case AppStateActionType.SET_DRAWER: {
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
"tokenVotes": "Token votes",
|
||||
"liquidityVotes": "Liquidity votes",
|
||||
"castYourVote": "Cast your vote",
|
||||
"yourVote": "Your vote",
|
||||
"for": "For",
|
||||
"against": "Against",
|
||||
"majorityRequired": "Majority Required",
|
||||
@@ -787,9 +788,9 @@
|
||||
"homeVegaTokenButtonText": "Manage tokens",
|
||||
"downloadProposalJson": "Download proposal as JSON",
|
||||
"networkUpgrade": "Network Upgrade",
|
||||
"PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED": "Approved",
|
||||
"PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING": "Pending",
|
||||
"PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED": "Rejected",
|
||||
"PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED": "Approved by validators",
|
||||
"PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING": "Waiting for validator votes",
|
||||
"PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED": "Declined by validators",
|
||||
"PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED": "Unspecified",
|
||||
"vegaRelease{release}": "Vega Release {{release}}",
|
||||
"upgradeBlockHeight": "Upgrade block height",
|
||||
@@ -811,5 +812,6 @@
|
||||
"OptOutOfTelemetry": "You can opt out any time via settings",
|
||||
"NoThanks": "No thanks",
|
||||
"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) => {
|
||||
const tranche_progress =
|
||||
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 {
|
||||
tranche_id: t.tranche_id,
|
||||
tranche_start: secondsToDate(t.cliff_start),
|
||||
@@ -60,7 +75,7 @@ export const useTranches = create<TranchesStore>()((set) => ({
|
||||
toBigNum(t.current_balance, decimals)
|
||||
),
|
||||
locked_amount: toBigNum(t.initial_balance, decimals).times(
|
||||
lockedDecimal
|
||||
clampedLockedDecimal
|
||||
),
|
||||
users: t.users,
|
||||
};
|
||||
|
||||
@@ -53,7 +53,6 @@ const mockAppState: AppState = {
|
||||
totalSupply: new BigNumber(65000000),
|
||||
vegaWalletOverlay: false,
|
||||
vegaWalletManageOverlay: false,
|
||||
ethConnectOverlay: false,
|
||||
transactionOverlay: false,
|
||||
bannerMessage: '',
|
||||
disconnectNotice: false,
|
||||
|
||||
@@ -24,7 +24,6 @@ const openProposalClosesNextMonth = generateProposal({
|
||||
},
|
||||
terms: {
|
||||
closingDatetime: nextMonth.toString(),
|
||||
enactmentDatetime: nextMonth.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -36,7 +35,6 @@ const openProposalClosesNextWeek = generateProposal({
|
||||
},
|
||||
terms: {
|
||||
closingDatetime: nextWeek.toString(),
|
||||
enactmentDatetime: nextWeek.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -45,7 +43,6 @@ const enactedProposalClosedLastWeek = generateProposal({
|
||||
state: ProposalState.STATE_ENACTED,
|
||||
terms: {
|
||||
closingDatetime: lastWeek.toString(),
|
||||
enactmentDatetime: lastWeek.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -54,7 +51,6 @@ const failedProposalClosedLastMonth = generateProposal({
|
||||
state: ProposalState.STATE_FAILED,
|
||||
terms: {
|
||||
closingDatetime: lastMonth.toString(),
|
||||
enactmentDatetime: lastMonth.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
+3
-1
@@ -32,7 +32,9 @@ describe('ProtocolUpgradeProposalDetailInfo', () => {
|
||||
|
||||
it('should render the state', () => {
|
||||
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', () => {
|
||||
|
||||
+7
-1
@@ -26,28 +26,34 @@ describe('ProtocolUpgradeProposalsListItem', () => {
|
||||
status:
|
||||
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED,
|
||||
icon: 'protocol-upgrade-proposal-status-icon-rejected',
|
||||
text: 'Declined by validators',
|
||||
},
|
||||
{
|
||||
status:
|
||||
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING,
|
||||
icon: 'protocol-upgrade-proposal-status-icon-pending',
|
||||
text: 'Waiting for validator votes',
|
||||
},
|
||||
{
|
||||
status:
|
||||
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED,
|
||||
icon: 'protocol-upgrade-proposal-status-icon-approved',
|
||||
text: 'Approved by validators',
|
||||
},
|
||||
{
|
||||
status:
|
||||
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED,
|
||||
icon: 'protocol-upgrade-proposal-status-icon-unspecified',
|
||||
text: 'Unspecified',
|
||||
},
|
||||
];
|
||||
|
||||
statuses.forEach(({ status, icon }) => {
|
||||
statuses.forEach(({ status, icon, text }) => {
|
||||
renderComponent({ ...proposal, status });
|
||||
const statusIcon = screen.getByTestId(icon);
|
||||
const textContent = screen.getByText(text);
|
||||
expect(statusIcon).toBeInTheDocument();
|
||||
expect(textContent).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -111,7 +111,9 @@ describe('Vote buttons', () => {
|
||||
</AppStateProvider>
|
||||
);
|
||||
expect(
|
||||
screen.getByText('You need some VEGA tokens to participate in governance')
|
||||
screen.getByText(
|
||||
'You need some VEGA tokens to participate in governance.'
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,12 @@ import { format } from 'date-fns';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
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 { ProposalState, VoteValue } from '@vegaprotocol/types';
|
||||
import {
|
||||
@@ -161,7 +166,12 @@ export const VoteButtons = ({
|
||||
{changeVote || (voteState === VoteState.NotCast && proposalVotable) ? (
|
||||
<>
|
||||
{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">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
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 { ProposalState } from '@vegaprotocol/types';
|
||||
import { useVoteSubmit, VoteProgress } from '@vegaprotocol/proposals';
|
||||
@@ -202,7 +202,12 @@ export const VoteDetails = ({
|
||||
)}
|
||||
|
||||
<section className="mt-10">
|
||||
<SubHeading title={t('castYourVote')} />
|
||||
{proposal?.state === ProposalState.STATE_OPEN ? (
|
||||
<SubHeading title={t('castYourVote')} />
|
||||
) : (
|
||||
<SubHeading title={t('yourVote')} />
|
||||
)}
|
||||
|
||||
{pubKey ? (
|
||||
proposal && (
|
||||
<VoteButtonsContainer
|
||||
@@ -224,6 +229,9 @@ export const VoteDetails = ({
|
||||
<Icon name={'info-sign'} />
|
||||
<div>{t('connectAVegaWalletToVote')}</div>
|
||||
</div>
|
||||
<ExternalLink href="https://blog.vega.xyz/how-to-vote-on-vega-2195d1e52ec5">
|
||||
{t('findOutMoreAboutHowToVote')}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
<ConnectToVega />
|
||||
</RoundedWrapper>
|
||||
|
||||
@@ -16,7 +16,6 @@ const mockAppState: AppState = {
|
||||
totalSupply: mockTotalSupply,
|
||||
vegaWalletOverlay: false,
|
||||
vegaWalletManageOverlay: false,
|
||||
ethConnectOverlay: false,
|
||||
transactionOverlay: false,
|
||||
bannerMessage: '',
|
||||
disconnectNotice: false,
|
||||
|
||||
@@ -20,8 +20,11 @@ import { useProtocolUpgradeProposalsQuery } from '@vegaprotocol/proposals';
|
||||
const orderByDate = (arr: ProposalFieldsFragment[]) =>
|
||||
orderBy(
|
||||
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 = (
|
||||
|
||||
@@ -54,22 +54,16 @@ export const TrancheItem = ({
|
||||
{formatNumber(total, 2)}
|
||||
</span>
|
||||
</div>
|
||||
<table className="w-full">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{t('Starts unlocking')}</td>
|
||||
<td className="text-right">
|
||||
{format(tranche.tranche_start, DATE_FORMAT_LONG)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t('Fully unlocked')}</td>
|
||||
<td className="text-right">
|
||||
{format(tranche.tranche_end, DATE_FORMAT_LONG)}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="grid grid-cols-2 my-2">
|
||||
<div>
|
||||
<span>{t('Starts unlocking')}:</span>{' '}
|
||||
<span>{format(tranche.tranche_start, DATE_FORMAT_LONG)}</span>
|
||||
</div>
|
||||
<div className="justify-self-end">
|
||||
<span>{t('Fully unlocked')}:</span>{' '}
|
||||
<span>{format(tranche.tranche_end, DATE_FORMAT_LONG)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<LockedProgress
|
||||
locked={locked}
|
||||
unlocked={unlocked}
|
||||
|
||||
+18
-1
@@ -79,7 +79,24 @@ export const generateEpochIndividualRewardsList = ({
|
||||
epoch?.rewards.push(asset);
|
||||
}
|
||||
|
||||
asset.rewardTypes[rewardType] = { amount, percentageOfTotal };
|
||||
if (!asset.rewardTypes[rewardType]) {
|
||||
asset.rewardTypes[rewardType] = { amount, percentageOfTotal };
|
||||
} else {
|
||||
const previousAmount = asset.rewardTypes[rewardType]?.amount;
|
||||
const previousPercentageOfTotal =
|
||||
asset.rewardTypes[rewardType]?.percentageOfTotal;
|
||||
|
||||
asset.rewardTypes[rewardType] = {
|
||||
amount: previousAmount
|
||||
? new BigNumber(previousAmount).plus(amount).toString()
|
||||
: amount,
|
||||
percentageOfTotal: previousPercentageOfTotal
|
||||
? new BigNumber(previousPercentageOfTotal)
|
||||
.plus(percentageOfTotal)
|
||||
.toString()
|
||||
: percentageOfTotal,
|
||||
};
|
||||
}
|
||||
|
||||
// totalAmount is the sum of all rewardTypes amounts
|
||||
asset.totalAmount = Object.values(asset.rewardTypes).reduce(
|
||||
|
||||
+8
-2
@@ -34,6 +34,7 @@ import {
|
||||
formatNumberPercentage,
|
||||
toBigNum,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { VALIDATOR_LOGO_MAP } from './logo-map';
|
||||
|
||||
interface CanonisedConsensusNodeProps {
|
||||
id: string;
|
||||
@@ -161,6 +162,11 @@ export const ConsensusValidatorsTable = ({
|
||||
pendingUserStake,
|
||||
userStakeShare,
|
||||
}) => {
|
||||
const logo = VALIDATOR_LOGO_MAP[id]
|
||||
? VALIDATOR_LOGO_MAP[id]
|
||||
: avatarUrl
|
||||
? avatarUrl
|
||||
: null;
|
||||
const {
|
||||
rawValidatorScore: previousEpochValidatorScore,
|
||||
performanceScore: previousEpochPerformanceScore,
|
||||
@@ -171,7 +177,7 @@ export const ConsensusValidatorsTable = ({
|
||||
id,
|
||||
[ValidatorFields.RANKING_INDEX]: votingPowerRanking,
|
||||
[ValidatorFields.VALIDATOR]: {
|
||||
avatarUrl,
|
||||
avatarUrl: logo,
|
||||
name,
|
||||
},
|
||||
[ValidatorFields.STAKE]: stakedTotal,
|
||||
@@ -210,7 +216,7 @@ export const ConsensusValidatorsTable = ({
|
||||
: undefined,
|
||||
[ValidatorFields.PENDING_USER_STAKE]: pendingUserStake,
|
||||
[ValidatorFields.USER_STAKE_SHARE]: userStakeShare
|
||||
? formatNumberPercentage(new BigNumber(userStakeShare))
|
||||
? formatNumberPercentage(new BigNumber(userStakeShare), 2)
|
||||
: 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 type { StakingDelegationFieldsFragment } from '../../__generated__/Staking';
|
||||
import type { ValidatorsView } from './validator-tables';
|
||||
import { VALIDATOR_LOGO_MAP } from './logo-map';
|
||||
|
||||
export enum ValidatorFields {
|
||||
RANKING_INDEX = 'rankingIndex',
|
||||
@@ -102,13 +103,18 @@ interface ValidatorRendererProps {
|
||||
export const ValidatorRenderer = ({ data }: ValidatorRendererProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { avatarUrl, name } = data.validator;
|
||||
const logo = VALIDATOR_LOGO_MAP[data.id]
|
||||
? VALIDATOR_LOGO_MAP[data.id]
|
||||
: avatarUrl
|
||||
? avatarUrl
|
||||
: null;
|
||||
return (
|
||||
<div className="w-[238px] grid grid-cols-[1fr_auto] gap-2 items-center">
|
||||
<span className="flex overflow-hidden">
|
||||
{avatarUrl && (
|
||||
{logo && (
|
||||
<img
|
||||
className="h-6 w-6 rounded-full mr-2"
|
||||
src={avatarUrl}
|
||||
src={logo}
|
||||
alt={`Avatar icon for ${name}`}
|
||||
onError={(e) => (e.currentTarget.style.display = 'none')}
|
||||
/>
|
||||
|
||||
@@ -78,37 +78,38 @@ export const ValidatorTables = ({
|
||||
const nextDelegation = delegations?.find(
|
||||
(d) => d.node.id === validatorId && d.epoch === Number(epochId) + 1
|
||||
);
|
||||
|
||||
switch (validator.rankingScore?.status) {
|
||||
case Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT:
|
||||
acc.consensusValidators.push(
|
||||
addUserDataToValidator(
|
||||
validator,
|
||||
currentDelegation,
|
||||
nextDelegation,
|
||||
currentUserStakeAvailable
|
||||
)
|
||||
);
|
||||
break;
|
||||
case Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_ERSATZ:
|
||||
acc.standbyValidators.push(
|
||||
addUserDataToValidator(
|
||||
validator,
|
||||
currentDelegation,
|
||||
nextDelegation,
|
||||
currentUserStakeAvailable
|
||||
)
|
||||
);
|
||||
break;
|
||||
case Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_PENDING:
|
||||
acc.pendingValidators.push(
|
||||
addUserDataToValidator(
|
||||
validator,
|
||||
currentDelegation,
|
||||
nextDelegation,
|
||||
currentUserStakeAvailable
|
||||
)
|
||||
);
|
||||
if (validator.stakedByOperator !== '0') {
|
||||
switch (validator.rankingScore?.status) {
|
||||
case Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT:
|
||||
acc.consensusValidators.push(
|
||||
addUserDataToValidator(
|
||||
validator,
|
||||
currentDelegation,
|
||||
nextDelegation,
|
||||
currentUserStakeAvailable
|
||||
)
|
||||
);
|
||||
break;
|
||||
case Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_ERSATZ:
|
||||
acc.standbyValidators.push(
|
||||
addUserDataToValidator(
|
||||
validator,
|
||||
currentDelegation,
|
||||
nextDelegation,
|
||||
currentUserStakeAvailable
|
||||
)
|
||||
);
|
||||
break;
|
||||
case Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_PENDING:
|
||||
acc.pendingValidators.push(
|
||||
addUserDataToValidator(
|
||||
validator,
|
||||
currentDelegation,
|
||||
nextDelegation,
|
||||
currentUserStakeAvailable
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ENV } from '../../../config';
|
||||
import { Callout, Intent, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
@@ -28,6 +29,7 @@ export const NodeContainer = ({
|
||||
const { t } = useTranslation();
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { delegationsPagination } = ENV;
|
||||
|
||||
const { data, loading, error, refetch } = useStakingQuery({
|
||||
variables: {
|
||||
partyId: pubKey || '',
|
||||
@@ -38,6 +40,20 @@ export const NodeContainer = ({
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const [isRefetching, setIsRefetching] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (error && error.message.includes('failed to get party for ID')) {
|
||||
setIsRefetching(true);
|
||||
// The API errors if there is a pubkey, but it hasn't interacted with the
|
||||
// chain before. In that case, retry the query with empty pubKey
|
||||
refetch({
|
||||
partyId: '',
|
||||
}).finally(() => setIsRefetching(false));
|
||||
}
|
||||
}, [error, refetch, delegationsPagination]);
|
||||
|
||||
const { data: previousEpochData } = usePreviousEpochQuery({
|
||||
variables: {
|
||||
epochId: (Number(data?.epoch.id) - 1).toString(),
|
||||
@@ -47,7 +63,7 @@ export const NodeContainer = ({
|
||||
|
||||
useRefreshAfterEpoch(data?.epoch.timestamps.expiry, refetch);
|
||||
|
||||
if (error) {
|
||||
if (error && !isRefetching) {
|
||||
return (
|
||||
<Callout intent={Intent.Danger} title={t('Something went wrong')}>
|
||||
<pre>
|
||||
@@ -59,7 +75,7 @@ export const NodeContainer = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
if (loading || isRefetching) {
|
||||
return (
|
||||
<Splash>
|
||||
<SplashLoader />
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@ import {
|
||||
createMarketsDataFragment,
|
||||
assetQuery,
|
||||
networkParamsQuery,
|
||||
nodeGuardQuery,
|
||||
} from '@vegaprotocol/mock';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
@@ -158,6 +159,7 @@ describe('Closed markets', { tags: '@smoke' }, () => {
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'ChainId', chainIdQuery());
|
||||
aliasGQLQuery(req, 'Statistics', statisticsQuery());
|
||||
aliasGQLQuery(req, 'NodeGuard', nodeGuardQuery());
|
||||
aliasGQLQuery(req, 'NetworkParams', networkParamsQuery());
|
||||
aliasGQLQuery(
|
||||
req,
|
||||
|
||||
@@ -6,6 +6,10 @@ const row = 'key-value-table-row';
|
||||
const marketTitle = 'accordion-title';
|
||||
const externalLink = 'external-link';
|
||||
const accordionContent = 'accordion-content';
|
||||
const providerName = 'provider-name';
|
||||
const oracleBannerStatus = 'oracle-banner-status';
|
||||
const oracleBannerDialogTrigger = 'oracle-banner-dialog-trigger';
|
||||
const oracleFullProfile = 'oracle-full-profile';
|
||||
|
||||
describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
@@ -28,7 +32,11 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
|
||||
it('show oracle banner', () => {
|
||||
cy.getByTestId(marketTitle).contains('Oracle').click();
|
||||
cy.getByTestId('oracle-status').should('contain.text', 'COMPROMISED');
|
||||
cy.getByTestId(oracleBannerStatus).should('contain.text', 'COMPROMISED');
|
||||
cy.getByTestId(oracleBannerDialogTrigger)
|
||||
.should('contain.text', 'Show more')
|
||||
.click();
|
||||
cy.getByTestId(oracleFullProfile).should('exist');
|
||||
});
|
||||
|
||||
it('current fees displayed', () => {
|
||||
@@ -181,9 +189,20 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
cy.getByTestId(marketTitle).contains('Oracle').click();
|
||||
|
||||
cy.getByTestId(accordionContent)
|
||||
.getByTestId('provider-name')
|
||||
.getByTestId(providerName)
|
||||
.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)
|
||||
.getByTestId('verified-proofs')
|
||||
.and('contain', '1');
|
||||
|
||||
@@ -16,6 +16,10 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
});
|
||||
|
||||
describe('limit order', () => {
|
||||
before(() => {
|
||||
cy.getByTestId(toggleLimit).click();
|
||||
@@ -98,7 +102,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
|
||||
'have.text',
|
||||
'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 { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import {
|
||||
accountsQuery,
|
||||
amendGeneralAccountBalance,
|
||||
estimateOrderQuery,
|
||||
} from '@vegaprotocol/mock';
|
||||
import { accountsQuery, amendGeneralAccountBalance } from '@vegaprotocol/mock';
|
||||
import type { OrderSubmission } from '@vegaprotocol/wallet';
|
||||
import { createOrder } from '../support/create-order';
|
||||
|
||||
@@ -44,13 +40,10 @@ describe(
|
||||
cy.setVegaWallet();
|
||||
cy.mockTradingPage();
|
||||
const accounts = accountsQuery();
|
||||
amendGeneralAccountBalance(accounts, 'market-0', '100000000');
|
||||
amendGeneralAccountBalance(accounts, 'market-0', '1');
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'Accounts', accounts);
|
||||
});
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'EstimateOrder', estimateOrderQuery());
|
||||
});
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
@@ -66,7 +59,7 @@ describe(
|
||||
);
|
||||
cy.getByTestId('dealticket-warning-margin').should(
|
||||
'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('dialog-content')
|
||||
|
||||
@@ -291,12 +291,58 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
|
||||
.should('have.text', '200.00');
|
||||
});
|
||||
|
||||
it('must see a pegged order - ask', () => {
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
peggedOrder: {
|
||||
__typename: 'PeggedOrder',
|
||||
reference: Schema.PeggedReference.PEGGED_REFERENCE_BEST_ASK,
|
||||
offset: '250000',
|
||||
},
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="type"]')
|
||||
.should('have.text', 'Ask - 2.50 Peg limit');
|
||||
});
|
||||
|
||||
it('must see a pegged order - bid', () => {
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
peggedOrder: {
|
||||
__typename: 'PeggedOrder',
|
||||
reference: Schema.PeggedReference.PEGGED_REFERENCE_BEST_BID,
|
||||
offset: '100',
|
||||
},
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="type"]')
|
||||
.should('have.text', 'Bid + 0.001 Peg limit');
|
||||
});
|
||||
|
||||
it('must see a pegged order - mid', () => {
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
peggedOrder: {
|
||||
__typename: 'PeggedOrder',
|
||||
reference: Schema.PeggedReference.PEGGED_REFERENCE_MID,
|
||||
offset: '0.5',
|
||||
},
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="type"]')
|
||||
.should('have.text', 'Mid + 0.00001 Peg limit');
|
||||
});
|
||||
|
||||
it('for market typy must not see a price for active or parked orders', () => {
|
||||
// 7003-MORD-005
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
type: Schema.OrderType.TYPE_MARKET,
|
||||
status: Schema.OrderStatus.STATUS_PARKED,
|
||||
peggedOrder: null,
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="price"]')
|
||||
@@ -337,6 +383,7 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
||||
peggedOrder: {},
|
||||
liquidityProvisionId: '6536',
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
|
||||
@@ -17,7 +17,6 @@ describe('ethereum wallet', { tags: '@smoke', testIsolation: true }, () => {
|
||||
it('can connect', () => {
|
||||
// 0004-EWAL-001
|
||||
|
||||
cy.wait('@NetworkParams');
|
||||
cy.getByTestId('Deposits').click();
|
||||
cy.getByTestId('deposit-button').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', () => {
|
||||
// 0004-EWAL-003
|
||||
|
||||
cy.wait('@NetworkParams');
|
||||
cy.getByTestId('Deposits').click();
|
||||
cy.getByTestId('deposit-button').click();
|
||||
cy.getByTestId('connect-eth-wallet-btn').click();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import type { CyHttpMessages } from 'cypress/types/net-stubbing';
|
||||
import type { Provider, Status } from '@vegaprotocol/oracles';
|
||||
import type { Provider, Status } from '@vegaprotocol/market-info';
|
||||
import {
|
||||
accountsQuery,
|
||||
assetQuery,
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
chainIdQuery,
|
||||
chartQuery,
|
||||
depositsQuery,
|
||||
estimateOrderQuery,
|
||||
estimateFeesQuery,
|
||||
marginsQuery,
|
||||
marketCandlesQuery,
|
||||
marketDataQuery,
|
||||
@@ -20,12 +20,16 @@ import {
|
||||
marketsDataQuery,
|
||||
marketsQuery,
|
||||
networkParamsQuery,
|
||||
nodeGuardQuery,
|
||||
ordersQuery,
|
||||
estimatePositionQuery,
|
||||
positionsQuery,
|
||||
proposalListQuery,
|
||||
statisticsQuery,
|
||||
tradesQuery,
|
||||
withdrawalsQuery,
|
||||
protocolUpgradeProposalsQuery,
|
||||
blockStatisticsQuery,
|
||||
} from '@vegaprotocol/mock';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import type { MarketDataQuery, MarketsQuery } from '@vegaprotocol/market-list';
|
||||
@@ -82,6 +86,7 @@ const mockTradingPage = (
|
||||
) => {
|
||||
aliasGQLQuery(req, 'ChainId', chainIdQuery());
|
||||
aliasGQLQuery(req, 'Statistics', statisticsQuery());
|
||||
aliasGQLQuery(req, 'NodeGuard', nodeGuardQuery());
|
||||
aliasGQLQuery(
|
||||
req,
|
||||
'Markets',
|
||||
@@ -155,9 +160,16 @@ const mockTradingPage = (
|
||||
aliasGQLQuery(req, 'Candles', candlesQuery());
|
||||
aliasGQLQuery(req, 'Withdrawals', withdrawalsQuery());
|
||||
aliasGQLQuery(req, 'NetworkParams', networkParamsQuery());
|
||||
aliasGQLQuery(req, 'EstimateOrder', estimateOrderQuery());
|
||||
aliasGQLQuery(req, 'EstimateFees', estimateFeesQuery());
|
||||
aliasGQLQuery(req, 'EstimatePosition', estimatePositionQuery());
|
||||
aliasGQLQuery(req, 'ProposalsList', proposalListQuery());
|
||||
aliasGQLQuery(req, 'Deposits', depositsQuery());
|
||||
aliasGQLQuery(
|
||||
req,
|
||||
'ProtocolUpgradeProposals',
|
||||
protocolUpgradeProposalsQuery()
|
||||
);
|
||||
aliasGQLQuery(req, 'BlockStatistics', blockStatisticsQuery());
|
||||
};
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
function ReactMarkdown({ children }) {
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
export default ReactMarkdown;
|
||||
@@ -13,7 +13,7 @@ import { memo, useState } from 'react';
|
||||
import type { ReactNode, ComponentProps } from 'react';
|
||||
import { DepthChartContainer } from '@vegaprotocol/market-depth';
|
||||
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
|
||||
import { OracleBanner } from '../../components/banner';
|
||||
import { OracleBanner } from '@vegaprotocol/market-info';
|
||||
import {
|
||||
Tab,
|
||||
LocalStoragePersistTabs as Tabs,
|
||||
|
||||
@@ -6,8 +6,8 @@ import { MarketState } from '@vegaprotocol/types';
|
||||
import { subDays } from 'date-fns';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type { OracleSpecDataConnectionQuery } from '@vegaprotocol/oracles';
|
||||
import { OracleSpecDataConnectionDocument } from '@vegaprotocol/oracles';
|
||||
import type { OracleSpecDataConnectionQuery } from '@vegaprotocol/market-info';
|
||||
import { OracleSpecDataConnectionDocument } from '@vegaprotocol/market-info';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import type {
|
||||
|
||||
@@ -2,8 +2,8 @@ import { render, screen } from '@testing-library/react';
|
||||
import type { Property } from '@vegaprotocol/types';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type { OracleSpecDataConnectionQuery } from '@vegaprotocol/oracles';
|
||||
import { OracleSpecDataConnectionDocument } from '@vegaprotocol/oracles';
|
||||
import type { OracleSpecDataConnectionQuery } from '@vegaprotocol/market-info';
|
||||
import { OracleSpecDataConnectionDocument } from '@vegaprotocol/market-info';
|
||||
import type { SettlementPriceCellProps } from './settlement-price-cell';
|
||||
import { SettlementPriceCell } from './settlement-price-cell';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DApp, EXPLORER_ORACLE, useLinks } from '@vegaprotocol/environment';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useOracleSpecBindingData } from '@vegaprotocol/oracles';
|
||||
import { useOracleSpecBindingData } from '@vegaprotocol/market-info';
|
||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Icon, NavigationLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { COG } from '@blueprintjs/icons/src/generated/iconNames';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
|
||||
export const SettingsButton = ({ withMobile }: { withMobile?: boolean }) => {
|
||||
return (
|
||||
@@ -9,7 +9,7 @@ export const SettingsButton = ({ withMobile }: { withMobile?: boolean }) => {
|
||||
{withMobile ? (
|
||||
t('Settings')
|
||||
) : (
|
||||
<Icon name={COG} className="!align-middle" />
|
||||
<Icon name={IconNames.COG} className="!align-middle" />
|
||||
)}
|
||||
</NavigationLink>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||
import {
|
||||
AppFailure,
|
||||
NetworkLoader,
|
||||
NodeGuard,
|
||||
useEnvironment,
|
||||
@@ -9,7 +10,6 @@ import { MaintenancePage } from '@vegaprotocol/ui-toolkit';
|
||||
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
||||
import dynamic from 'next/dynamic';
|
||||
import type { ReactNode } from 'react';
|
||||
import { AppFailure } from './app-failure';
|
||||
import { Web3Provider } from './web3-provider';
|
||||
|
||||
export const DynamicLoader = dynamic(() => import('../preloader/preloader'), {
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './app-failure';
|
||||
export * from './app-loader';
|
||||
export * from './web3-provider';
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export * from './announcement-banner';
|
||||
export * from './oracle-banner';
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useMarketOracle } from '@vegaprotocol/market-info';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { Intent, NotificationBanner } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
const oracleStatuses = {
|
||||
UNKNOWN: t(
|
||||
"This public key's proofs have not been verified yet, or no proofs have been provided yet."
|
||||
),
|
||||
GOOD: t("This public key's proofs have been verified."),
|
||||
SUSPICIOUS: t(
|
||||
'This public key is suspected to be acting in bad faith, pending investigation.'
|
||||
),
|
||||
MALICIOUS: t('This public key has been observed acting in bad faith.'),
|
||||
RETIRED: t('This public key is no longer in use.'),
|
||||
COMPROMISED: t(
|
||||
'This public key is no longer in the control of its original owners.'
|
||||
),
|
||||
};
|
||||
|
||||
export const OracleBanner = ({ marketId }: { marketId: string }) => {
|
||||
const oracle = useMarketOracle(marketId);
|
||||
if (!oracle || oracle.status === 'GOOD') {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<NotificationBanner intent={Intent.Danger}>
|
||||
<div>
|
||||
Oracle status for this market is{' '}
|
||||
<span data-testId="oracle-status">{oracle.status}</span>.{' '}
|
||||
{oracleStatuses[oracle.status]}
|
||||
</div>
|
||||
{oracle.status_reason ? (
|
||||
<ReactMarkdown
|
||||
className="react-markdown-container"
|
||||
skipHtml={true}
|
||||
disallowedElements={['img']}
|
||||
linkTarget="_blank"
|
||||
>
|
||||
{oracle.status_reason}
|
||||
</ReactMarkdown>
|
||||
) : null}
|
||||
</NotificationBanner>
|
||||
);
|
||||
};
|
||||
@@ -4,18 +4,15 @@ import { NodeHealth, NodeUrl, HealthIndicator } from './footer';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { Intent } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
const mockSetNodeSwitcher = jest.fn();
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
...jest.requireActual('@vegaprotocol/environment'),
|
||||
useEnvironment: jest.fn().mockImplementation(() => ({
|
||||
VEGA_URL: 'https://vega-url.wtf',
|
||||
VEGA_INCIDENT_URL: 'https://blog.vega.community',
|
||||
})),
|
||||
}));
|
||||
|
||||
const mockSetNodeSwitcher = jest.fn();
|
||||
jest.mock('../../stores', () => ({
|
||||
...jest.requireActual('../../stores'),
|
||||
useGlobalStore: () => mockSetNodeSwitcher,
|
||||
useNodeSwitcherStore: jest.fn(() => mockSetNodeSwitcher),
|
||||
}));
|
||||
|
||||
describe('NodeHealth', () => {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useEnvironment, useNodeHealth } from '@vegaprotocol/environment';
|
||||
import {
|
||||
useEnvironment,
|
||||
useNodeHealth,
|
||||
useNodeSwitcherStore,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { Indicator, ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import classNames from 'classnames';
|
||||
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
|
||||
export const Footer = () => {
|
||||
return (
|
||||
@@ -20,9 +23,7 @@ export const Footer = () => {
|
||||
|
||||
export const NodeHealth = () => {
|
||||
const { VEGA_URL, VEGA_INCIDENT_URL } = useEnvironment();
|
||||
const setNodeSwitcher = useGlobalStore(
|
||||
(store) => (open: boolean) => store.update({ nodeSwitcherDialog: open })
|
||||
);
|
||||
const setNodeSwitcher = useNodeSwitcherStore((store) => store.setDialogOpen);
|
||||
const { datanodeBlockHeight, text, intent } = useNodeHealth();
|
||||
const onClick = useCallback(() => {
|
||||
setNodeSwitcher(true);
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { Navbar } from './navbar';
|
||||
|
||||
describe('Navbar', () => {
|
||||
const pubKey = 'pubKey';
|
||||
it('should be properly rendered', () => {
|
||||
render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<VegaWalletContext.Provider
|
||||
value={{ pubKey } as VegaWalletContextShape}
|
||||
>
|
||||
<Navbar theme="dark" />
|
||||
</VegaWalletContext.Provider>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(screen.getByTestId('Markets')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('Trading')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('Portfolio')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Markets page route should not match empty market page', () => {
|
||||
render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter initialEntries={['/markets/all']}>
|
||||
<VegaWalletContext.Provider
|
||||
value={{ pubKey } as VegaWalletContextShape}
|
||||
>
|
||||
<Navbar theme="dark" />
|
||||
</VegaWalletContext.Provider>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(screen.getByTestId('Markets')).toHaveClass('active');
|
||||
expect(screen.getByTestId('Trading')).not.toHaveClass('active');
|
||||
});
|
||||
});
|
||||
@@ -75,7 +75,7 @@ export const Navbar = ({
|
||||
</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavigationLink data-testid="Trading" to={tradingPath}>
|
||||
<NavigationLink data-testid="Trading" to={tradingPath} end>
|
||||
{t('Trading')}
|
||||
</NavigationLink>
|
||||
</NavigationItem>
|
||||
|
||||
@@ -7,7 +7,13 @@ import {
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { RISK_ACCEPTED_KEY } from '../constants';
|
||||
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';
|
||||
|
||||
interface Props {
|
||||
@@ -46,8 +52,9 @@ export const RiskNoticeDialog = ({ onClose, network }: Props) => {
|
||||
};
|
||||
|
||||
const TestnetContent = ({ network }: { network: Networks }) => {
|
||||
const { VEGA_DOCS_URL, GITHUB_FEEDBACK_URL, VEGA_WALLET_URL } =
|
||||
useEnvironment();
|
||||
const { GITHUB_FEEDBACK_URL } = useEnvironment();
|
||||
const docsLink = useDocsLink();
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className="mb-4">
|
||||
@@ -62,17 +69,17 @@ const TestnetContent = ({ network }: { network: Networks }) => {
|
||||
[network]
|
||||
)}
|
||||
</p>
|
||||
{VEGA_DOCS_URL && GITHUB_FEEDBACK_URL && VEGA_WALLET_URL && (
|
||||
{GITHUB_FEEDBACK_URL && GET_VEGA_WALLET_URL && docsLink && (
|
||||
<ul className="list-disc pl-4">
|
||||
<li className="mb-1">
|
||||
<Link href={VEGA_WALLET_URL} target="_blank">
|
||||
<span className="underline">{t('Learn about Vega wallet')}</span>{' '}
|
||||
<Link href={GET_VEGA_WALLET_URL} target="_blank">
|
||||
<span className="underline">{t('Get a Vega Wallet')}</span>{' '}
|
||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
</Link>
|
||||
</li>
|
||||
<li className="mb-1">
|
||||
<Link href={VEGA_DOCS_URL} target="_blank">
|
||||
<span className="underline">{t('View documentation')}</span>{' '}
|
||||
<Link href={docsLink(DOCS_VEGA_WALLET)} target="_blank">
|
||||
<span className="underline">{t('Learn about Vega Wallet')}</span>{' '}
|
||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
@@ -21,9 +21,10 @@ import {
|
||||
NodeSwitcherDialog,
|
||||
useEnvironment,
|
||||
useInitializeEnv,
|
||||
useNodeSwitcherStore,
|
||||
} from '@vegaprotocol/environment';
|
||||
import './styles.css';
|
||||
import { useGlobalStore, usePageTitleStore } from '../stores';
|
||||
import { usePageTitleStore } from '../stores';
|
||||
import { Footer } from '../components/footer';
|
||||
import DialogsContainer from './dialogs-container';
|
||||
import ToastsManager from './toasts-manager';
|
||||
@@ -115,11 +116,10 @@ function AppBody({ Component }: AppProps) {
|
||||
|
||||
function VegaTradingApp(props: AppProps) {
|
||||
const status = useEnvironment((store) => store.status);
|
||||
const { nodeSwitcherOpen, setNodeSwitcher } = useGlobalStore((store) => ({
|
||||
nodeSwitcherOpen: store.nodeSwitcherDialog,
|
||||
setNodeSwitcher: (open: boolean) =>
|
||||
store.update({ nodeSwitcherDialog: open }),
|
||||
}));
|
||||
const [nodeSwitcherOpen, setNodeSwitcher] = useNodeSwitcherStore((store) => [
|
||||
store.dialogOpen,
|
||||
store.setDialogOpen,
|
||||
]);
|
||||
|
||||
useInitializeEnv();
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import { create } from 'zustand';
|
||||
import produce from 'immer';
|
||||
|
||||
interface GlobalStore {
|
||||
nodeSwitcherDialog: boolean;
|
||||
marketId: string | null;
|
||||
update: (store: Partial<Omit<GlobalStore, 'update'>>) => void;
|
||||
shouldDisplayWelcomeDialog: boolean;
|
||||
@@ -15,7 +14,6 @@ interface PageTitleStore {
|
||||
}
|
||||
|
||||
export const useGlobalStore = create<GlobalStore>()((set) => ({
|
||||
nodeSwitcherDialog: false,
|
||||
marketId: LocalStorage.getItem('marketId') || null,
|
||||
shouldDisplayWelcomeDialog: false,
|
||||
update: (newState) => {
|
||||
|
||||
@@ -6,10 +6,10 @@ WORKDIR /app
|
||||
ARG APP
|
||||
ARG ENV_NAME=""
|
||||
RUN apk add --update --no-cache \
|
||||
python3 \
|
||||
make \
|
||||
gcc \
|
||||
g++
|
||||
python3==3.10.11-r0 \
|
||||
make==4.3-r0 \
|
||||
gcc==11.2.1_git20220219-r2 \
|
||||
g++==11.2.1_git20220219-r2
|
||||
COPY . ./
|
||||
RUN yarn --network-timeout 100000 --pure-lockfile
|
||||
# work around for different build process in trading
|
||||
@@ -22,8 +22,10 @@ FROM --platform=amd64 nginx:1.23-alpine@sha256:6318314189b40e73145a48060bff4783a
|
||||
# configuration of system
|
||||
EXPOSE 80
|
||||
# Copy dist
|
||||
WORKDIR /usr/share/nginx/html
|
||||
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
RUN rm -rf /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
|
||||
EXPOSE 80
|
||||
WORKDIR /usr/share/nginx/html
|
||||
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
RUN rm -rf /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)"
|
||||
|
||||
Executable
+13
@@ -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(
|
||||
() => ({
|
||||
accountBalance,
|
||||
accountDecimals,
|
||||
accountBalance: pubKey ? accountBalance : '',
|
||||
accountDecimals: pubKey ? accountDecimals : null,
|
||||
}),
|
||||
[accountBalance, accountDecimals]
|
||||
[accountBalance, accountDecimals, pubKey]
|
||||
);
|
||||
};
|
||||
|
||||
@@ -32,9 +32,9 @@ export const useMarketAccountBalance = (marketId: string) => {
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
accountBalance,
|
||||
accountDecimals,
|
||||
accountBalance: pubKey ? accountBalance : '',
|
||||
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,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,11 @@
|
||||
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 { Interval as PennantInterval } from 'pennant';
|
||||
|
||||
@@ -153,7 +160,6 @@ export class VegaDataSource implements DataSource {
|
||||
},
|
||||
fetchPolicy: 'no-cache',
|
||||
});
|
||||
|
||||
if (data?.market?.candlesConnection?.edges) {
|
||||
const decimalPlaces = data.market.decimalPlaces;
|
||||
const positionDecimalPlaces = data.market.positionDecimalPlaces;
|
||||
@@ -163,8 +169,8 @@ export class VegaDataSource implements DataSource {
|
||||
.filter((node): node is CandleFieldsFragment => !!node)
|
||||
.map((node) =>
|
||||
parseCandle(node, decimalPlaces, positionDecimalPlaces)
|
||||
);
|
||||
|
||||
)
|
||||
.reduce(checkGranulationContinuity(interval), []);
|
||||
return candles;
|
||||
} else {
|
||||
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(
|
||||
candle: CandleFieldsFragment,
|
||||
decimalPlaces: number,
|
||||
|
||||
@@ -7,20 +7,24 @@ export * from '../candles-chart/src/lib/chart.mock';
|
||||
export * from '../deal-ticket/src/hooks/estimate-order.mock';
|
||||
export * from '../deposits/src/lib/deposit.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 '../proposals/src/lib/proposals-data-provider/proposals.mock';
|
||||
export * from '../ledger/src/lib/ledger-entries.mock';
|
||||
export * from '../market-depth/src/lib/market-depth.mock';
|
||||
export * from '../market-info/src/components/market-info/market-info.mock';
|
||||
export * from '../market-info/src/lib/components/market-info/market-info.mock';
|
||||
export * from '../market-list/src/lib/market-candles.mock';
|
||||
export * from '../market-list/src/lib/market-data.mock';
|
||||
export * from '../market-list/src/lib/markets-candles.mock';
|
||||
export * from '../market-list/src/lib/markets-data.mock';
|
||||
export * from '../market-list/src/lib/markets.mock';
|
||||
export * from '../oracles/src/lib/oracle-spec-data-connection.mock';
|
||||
export * from '../market-info/src/lib/oracle-spec-data-connection.mock';
|
||||
export * from '../orders/src/lib/components/order-data-provider/orders.mock';
|
||||
export * from '../positions/src/lib/positions.mock';
|
||||
export * from '../network-parameters/src/network-params.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 '../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 { requestGQL, setGraphQLEndpoint } from '../capsule/request';
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
@@ -19,112 +21,143 @@ export const addValidatorsSelfDelegate = () => {
|
||||
vegaUrl: Cypress.env('VEGA_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.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.wrap(
|
||||
obj['testnet-nodeset-validators-0-validator'].Vega.NodeWalletInfo
|
||||
.VegaWalletID
|
||||
).as('node0Id');
|
||||
cy.wrap(
|
||||
obj['testnet-nodeset-validators-1-validator'].Vega.NodeWalletInfo
|
||||
.VegaWalletID
|
||||
).as('node1Id');
|
||||
});
|
||||
|
||||
// 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(
|
||||
'./src/fixtures/wallet/node1RecoveryPhrase',
|
||||
obj['testnet-nodeset-validators-1-validator'].Vega.NodeWalletInfo
|
||||
.VegaWalletRecoveryPhrase
|
||||
cy.exec(
|
||||
'vega wallet import -w node1_wallet --recovery-phrase-file ./src/fixtures/wallet/node1RecoveryPhrase -p ./src/fixtures/wallet/passphrase --home ~/.vegacapsule/testnet/wallet'
|
||||
);
|
||||
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(
|
||||
obj['testnet-nodeset-validators-0-validator'].Vega.NodeWalletInfo
|
||||
.VegaWalletID
|
||||
).as('node0Id');
|
||||
cy.wrap(
|
||||
obj['testnet-nodeset-validators-1-validator'].Vega.NodeWalletInfo
|
||||
.VegaWalletID
|
||||
).as('node1Id');
|
||||
});
|
||||
// Initialise api token
|
||||
cy.exec(
|
||||
'vega wallet api-token init --home ~/.vegacapsule/testnet/wallet --passphrase-file ./src/fixtures/wallet/passphrase'
|
||||
);
|
||||
|
||||
// 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.exec(
|
||||
'vega wallet import -w node1_wallet --recovery-phrase-file ./src/fixtures/wallet/node1RecoveryPhrase -p ./src/fixtures/wallet/passphrase --home ~/.vegacapsule/testnet/wallet'
|
||||
);
|
||||
// Generate api tokens for wallets
|
||||
cy.exec(
|
||||
'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');
|
||||
}
|
||||
});
|
||||
|
||||
// Initialise api token
|
||||
cy.exec(
|
||||
'vega wallet api-token init --home ~/.vegacapsule/testnet/wallet --passphrase-file ./src/fixtures/wallet/passphrase'
|
||||
);
|
||||
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'
|
||||
)
|
||||
.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.exec(
|
||||
'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.updateCapsuleMultiSig();
|
||||
cy.highlight('Validators self-delegating');
|
||||
|
||||
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'
|
||||
)
|
||||
.its('stdout')
|
||||
.then((result) => {
|
||||
const apiToken = result.match('[a-zA-Z0-9]{64}');
|
||||
if (apiToken) {
|
||||
cy.wrap(apiToken[0]).as('node1ApiToken');
|
||||
}
|
||||
});
|
||||
|
||||
cy.updateCapsuleMultiSig();
|
||||
cy.highlight('Validators self-delegating');
|
||||
|
||||
// Self delegating Node 0 wallet
|
||||
cy.get('@node0PubKey').then((node0PubKey) => {
|
||||
cy.get('@node0ApiToken').then((node0ApiToken) => {
|
||||
cy.get('@node0Id').then((node0Id) => {
|
||||
cy.wrap(
|
||||
selfDelegate(
|
||||
config,
|
||||
String(node0PubKey),
|
||||
String(node0ApiToken),
|
||||
String(node0Id)
|
||||
),
|
||||
{ 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 }
|
||||
);
|
||||
// Self delegating Node 0 wallet
|
||||
cy.get('@node0PubKey').then((node0PubKey) => {
|
||||
cy.get('@node0ApiToken').then((node0ApiToken) => {
|
||||
cy.get('@node0Id').then((node0Id) => {
|
||||
cy.wrap(
|
||||
selfDelegate(
|
||||
config,
|
||||
String(node0PubKey),
|
||||
String(node0ApiToken),
|
||||
String(node0Id)
|
||||
),
|
||||
{ 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;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,12 @@ const hasOperationName = (
|
||||
operationName: string
|
||||
) => {
|
||||
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() {
|
||||
|
||||
@@ -20,8 +20,12 @@ const mockSocketServer = Cypress.env('VEGA_URL')
|
||||
: null;
|
||||
|
||||
// DO NOT REMOVE: PASSTHROUGH for walletconnect
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const relayServer = new Server('wss://relay.walletconnect.com', {
|
||||
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,
|
||||
});
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ export const useColumnSizes = ({
|
||||
const setSizes = useCallback(
|
||||
(apiEvent: GridReadyEvent | GridSizeChangedEvent) => {
|
||||
if (!storeKey || !Object.keys(sizes).length || !widthRef.current) {
|
||||
apiEvent.api.sizeColumnsToFit();
|
||||
apiEvent?.api.sizeColumnsToFit();
|
||||
} else {
|
||||
const recalculatedSizes = recalculateSizes(sizes);
|
||||
const newSizes = Object.entries(recalculatedSizes).map(
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useMemo } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
|
||||
interface OrderTypeCellProps {
|
||||
value?: Schema.OrderType;
|
||||
@@ -23,7 +24,15 @@ export const OrderTypeCell = ({
|
||||
}
|
||||
if (!value) return '-';
|
||||
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) {
|
||||
return t('Liquidity provision');
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
function ReactMarkdown({ children }) {
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
export default ReactMarkdown;
|
||||
@@ -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 classnames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import type { Market, MarketData } from '@vegaprotocol/market-list';
|
||||
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;
|
||||
}
|
||||
import { getFeeDetailsValues } from '../../hooks/use-fee-deal-ticket-details';
|
||||
import type { FeeDetails } from '../../hooks/use-fee-deal-ticket-details';
|
||||
|
||||
export interface DealTicketFeeDetailProps {
|
||||
label: string;
|
||||
@@ -45,17 +29,8 @@ export const DealTicketFeeDetail = ({
|
||||
</div>
|
||||
);
|
||||
|
||||
export const DealTicketFeeDetails = ({
|
||||
order,
|
||||
market,
|
||||
marketData,
|
||||
...args
|
||||
}: DealTicketFeeDetailsProps) => {
|
||||
const feeDetails = useFeeDealTicketDetails(order, market, marketData);
|
||||
const details = getFeeDetailsValues({
|
||||
...feeDetails,
|
||||
...args,
|
||||
});
|
||||
export const DealTicketFeeDetails = (props: FeeDetails) => {
|
||||
const details = getFeeDetailsValues(props);
|
||||
return (
|
||||
<div>
|
||||
{details.map(({ label, value, labelDescription, symbol, indent }) => (
|
||||
|
||||
@@ -25,6 +25,16 @@ import {
|
||||
TinyScroll,
|
||||
} 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 {
|
||||
validateExpiration,
|
||||
validateMarketState,
|
||||
@@ -34,7 +44,6 @@ import {
|
||||
} from '../../utils';
|
||||
import { ZeroBalanceError } from '../deal-ticket-validation/zero-balance-error';
|
||||
import { SummaryValidationType } from '../../constants';
|
||||
import { useInitialMargin } from '../../hooks/use-initial-margin';
|
||||
import type { Market, MarketData } from '@vegaprotocol/market-list';
|
||||
import { MarginWarning } from '../deal-ticket-validation/margin-warning';
|
||||
import {
|
||||
@@ -104,7 +113,67 @@ export const DealTicket = ({
|
||||
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({
|
||||
dataProvider: marketMarginDataProvider,
|
||||
@@ -401,7 +470,10 @@ export const DealTicket = ({
|
||||
asset={asset}
|
||||
marketTradingMode={marketData.marketTradingMode}
|
||||
balance={balance}
|
||||
margin={totalMargin}
|
||||
margin={
|
||||
positionEstimate?.estimatePosition?.margin.bestCase.initialLevel ||
|
||||
'0'
|
||||
}
|
||||
isReadOnly={isReadOnly}
|
||||
pubKey={pubKey}
|
||||
onClickCollateral={onClickCollateral}
|
||||
@@ -413,15 +485,15 @@ export const DealTicket = ({
|
||||
}
|
||||
/>
|
||||
<DealTicketFeeDetails
|
||||
order={normalizedOrder}
|
||||
market={market}
|
||||
marketData={marketData}
|
||||
estimatedInitialMargin={margin}
|
||||
estimatedTotalInitialMargin={totalMargin}
|
||||
currentInitialMargin={currentMargins?.initialLevel}
|
||||
currentMaintenanceMargin={currentMargins?.maintenanceLevel}
|
||||
feeEstimate={feeEstimate}
|
||||
notionalSize={notionalSize}
|
||||
assetSymbol={assetSymbol}
|
||||
marginAccountBalance={marginAccountBalance}
|
||||
generalAccountBalance={generalAccountBalance}
|
||||
positionEstimate={positionEstimate?.estimatePosition}
|
||||
market={market}
|
||||
currentInitialMargin={currentMargins?.initialLevel}
|
||||
currentMaintenanceMargin={currentMargins?.maintenanceLevel}
|
||||
/>
|
||||
</form>
|
||||
</TinyScroll>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './deal-ticket';
|
||||
export * from './deal-ticket-validation';
|
||||
export * from './trading-mode-tooltip';
|
||||
export * from './deal-ticket-estimates';
|
||||
|
||||
@@ -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.'
|
||||
);
|
||||
|
||||
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(
|
||||
'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!
|
||||
$partyId: ID!
|
||||
$price: String
|
||||
@@ -8,7 +8,7 @@ query EstimateOrder(
|
||||
$expiration: Timestamp
|
||||
$type: OrderType!
|
||||
) {
|
||||
estimateOrder(
|
||||
estimateFees(
|
||||
marketId: $marketId
|
||||
partyId: $partyId
|
||||
price: $price
|
||||
@@ -18,14 +18,11 @@ query EstimateOrder(
|
||||
expiration: $expiration
|
||||
type: $type
|
||||
) {
|
||||
fee {
|
||||
fees {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
marginLevels {
|
||||
initialLevel
|
||||
}
|
||||
totalFeeAmount
|
||||
}
|
||||
}
|
||||
|
||||
+17
-20
@@ -3,7 +3,7 @@ import * as Types from '@vegaprotocol/types';
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type EstimateOrderQueryVariables = Types.Exact<{
|
||||
export type EstimateFeesQueryVariables = Types.Exact<{
|
||||
marketId: Types.Scalars['ID'];
|
||||
partyId: Types.Scalars['ID'];
|
||||
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`
|
||||
query EstimateOrder($marketId: ID!, $partyId: ID!, $price: String, $size: String!, $side: Side!, $timeInForce: OrderTimeInForce!, $expiration: Timestamp, $type: OrderType!) {
|
||||
estimateOrder(
|
||||
export const EstimateFeesDocument = gql`
|
||||
query EstimateFees($marketId: ID!, $partyId: ID!, $price: String, $size: String!, $side: Side!, $timeInForce: OrderTimeInForce!, $expiration: Timestamp, $type: OrderType!) {
|
||||
estimateFees(
|
||||
marketId: $marketId
|
||||
partyId: $partyId
|
||||
price: $price
|
||||
@@ -30,30 +30,27 @@ export const EstimateOrderDocument = gql`
|
||||
expiration: $expiration
|
||||
type: $type
|
||||
) {
|
||||
fee {
|
||||
fees {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
marginLevels {
|
||||
initialLevel
|
||||
}
|
||||
totalFeeAmount
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useEstimateOrderQuery__
|
||||
* __useEstimateFeesQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useEstimateOrderQuery` 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
|
||||
* To run a query within a React component, call `useEstimateFeesQuery` and pass it any options that fit your needs.
|
||||
* 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.
|
||||
*
|
||||
* @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 } = useEstimateOrderQuery({
|
||||
* const { data, loading, error } = useEstimateFeesQuery({
|
||||
* variables: {
|
||||
* marketId: // value for 'marketId'
|
||||
* 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}
|
||||
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}
|
||||
return Apollo.useLazyQuery<EstimateOrderQuery, EstimateOrderQueryVariables>(EstimateOrderDocument, options);
|
||||
return Apollo.useLazyQuery<EstimateFeesQuery, EstimateFeesQueryVariables>(EstimateFeesDocument, options);
|
||||
}
|
||||
export type EstimateOrderQueryHookResult = ReturnType<typeof useEstimateOrderQuery>;
|
||||
export type EstimateOrderLazyQueryHookResult = ReturnType<typeof useEstimateOrderLazyQuery>;
|
||||
export type EstimateOrderQueryResult = Apollo.QueryResult<EstimateOrderQuery, EstimateOrderQueryVariables>;
|
||||
export type EstimateFeesQueryHookResult = ReturnType<typeof useEstimateFeesQuery>;
|
||||
export type EstimateFeesLazyQueryHookResult = ReturnType<typeof useEstimateFeesLazyQuery>;
|
||||
export type EstimateFeesQueryResult = Apollo.QueryResult<EstimateFeesQuery, EstimateFeesQueryVariables>;
|
||||
@@ -1,21 +1,20 @@
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import merge from 'lodash/merge';
|
||||
import type { EstimateOrderQuery } from './__generated__/EstimateOrder';
|
||||
import type { EstimateFeesQuery } from './__generated__/EstimateOrder';
|
||||
|
||||
export const estimateOrderQuery = (
|
||||
override?: PartialDeep<EstimateOrderQuery>
|
||||
): EstimateOrderQuery => {
|
||||
const defaultResult: EstimateOrderQuery = {
|
||||
estimateOrder: {
|
||||
__typename: 'OrderEstimate',
|
||||
export const estimateFeesQuery = (
|
||||
override?: PartialDeep<EstimateFeesQuery>
|
||||
): EstimateFeesQuery => {
|
||||
const defaultResult: EstimateFeesQuery = {
|
||||
estimateFees: {
|
||||
__typename: 'FeeEstimate',
|
||||
totalFeeAmount: '0.0006',
|
||||
fee: {
|
||||
fees: {
|
||||
__typename: 'TradeFee',
|
||||
makerFee: '100000',
|
||||
infrastructureFee: '100000',
|
||||
liquidityFee: '100000',
|
||||
},
|
||||
marginLevels: { __typename: 'MarginLevels', initialLevel: '1' },
|
||||
},
|
||||
};
|
||||
return merge(defaultResult, override);
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import { FeesBreakdown } from '@vegaprotocol/market-info';
|
||||
import {
|
||||
addDecimal,
|
||||
addDecimalsFormatNumber,
|
||||
formatNumber,
|
||||
toBigNum,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { addDecimalsFormatNumber, isNumeric } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useMemo } from 'react';
|
||||
import type { Market, MarketData } from '@vegaprotocol/market-list';
|
||||
import type { Market } from '@vegaprotocol/market-list';
|
||||
import type { EstimatePositionQuery } from '@vegaprotocol/positions';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
EST_TOTAL_MARGIN_TOOLTIP_TEXT,
|
||||
@@ -17,58 +12,30 @@ import {
|
||||
MARGIN_DIFF_TOOLTIP_TEXT,
|
||||
DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT,
|
||||
TOTAL_MARGIN_AVAILABLE,
|
||||
LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT,
|
||||
} 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 = (
|
||||
order: OrderSubmissionBody['orderSubmission'],
|
||||
market: Market,
|
||||
marketData: MarketData
|
||||
import { useEstimateFeesQuery } from './__generated__/EstimateOrder';
|
||||
import type { EstimateFeesQuery } from './__generated__/EstimateOrder';
|
||||
|
||||
export const useEstimateFees = (
|
||||
order?: OrderSubmissionBody['orderSubmission']
|
||||
) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { accountBalance } = useMarketAccountBalance(market.id);
|
||||
|
||||
const price = useMemo(() => {
|
||||
return getDerivedPrice(order, marketData);
|
||||
}, [order, marketData]);
|
||||
|
||||
const { data: estMargin } = useEstimateOrderQuery({
|
||||
variables: {
|
||||
marketId: market.id,
|
||||
const { data } = useEstimateFeesQuery({
|
||||
variables: order && {
|
||||
marketId: order.marketId,
|
||||
partyId: pubKey || '',
|
||||
price,
|
||||
price: order.price,
|
||||
size: order.size,
|
||||
side: order.side,
|
||||
timeInForce: order.timeInForce,
|
||||
type: order.type,
|
||||
},
|
||||
skip: !pubKey || !market || !order.size || !price,
|
||||
skip: !pubKey || !order?.size || !order?.price,
|
||||
});
|
||||
|
||||
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]);
|
||||
return data?.estimateFees;
|
||||
};
|
||||
|
||||
export interface FeeDetails {
|
||||
@@ -77,42 +44,54 @@ export interface FeeDetails {
|
||||
market: Market;
|
||||
assetSymbol: string;
|
||||
notionalSize: string | null;
|
||||
estimateOrder: EstimateOrderQuery['estimateOrder'] | undefined;
|
||||
estimatedInitialMargin: string;
|
||||
estimatedTotalInitialMargin: string;
|
||||
feeEstimate: EstimateFeesQuery['estimateFees'] | undefined;
|
||||
currentInitialMargin?: 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 = ({
|
||||
marginAccountBalance,
|
||||
generalAccountBalance,
|
||||
assetSymbol,
|
||||
estimateOrder,
|
||||
feeEstimate,
|
||||
market,
|
||||
notionalSize,
|
||||
estimatedTotalInitialMargin,
|
||||
currentInitialMargin,
|
||||
currentMaintenanceMargin,
|
||||
positionEstimate,
|
||||
}: FeeDetails) => {
|
||||
const liquidationEstimate = positionEstimate?.liquidation;
|
||||
const marginEstimate = positionEstimate?.margin;
|
||||
const totalBalance =
|
||||
BigInt(generalAccountBalance || '0') + BigInt(marginAccountBalance || '0');
|
||||
const assetDecimals =
|
||||
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: {
|
||||
label: string;
|
||||
value?: string | null;
|
||||
@@ -122,15 +101,15 @@ export const getFeeDetailsValues = ({
|
||||
}[] = [
|
||||
{
|
||||
label: t('Notional'),
|
||||
value: formatValueWithMarketDp(notionalSize),
|
||||
value: formatValue(notionalSize, assetDecimals),
|
||||
symbol: assetSymbol,
|
||||
labelDescription: NOTIONAL_SIZE_TOOLTIP_TEXT(assetSymbol),
|
||||
},
|
||||
{
|
||||
label: t('Fees'),
|
||||
value:
|
||||
estimateOrder?.totalFeeAmount &&
|
||||
`~${formatValueWithAssetDp(estimateOrder?.totalFeeAmount)}`,
|
||||
feeEstimate?.totalFeeAmount &&
|
||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`,
|
||||
labelDescription: (
|
||||
<>
|
||||
<span>
|
||||
@@ -139,7 +118,7 @@ export const getFeeDetailsValues = ({
|
||||
)}
|
||||
</span>
|
||||
<FeesBreakdown
|
||||
fees={estimateOrder?.fee}
|
||||
fees={feeEstimate?.fees}
|
||||
feeFactors={market.fees.factors}
|
||||
symbol={assetSymbol}
|
||||
decimals={assetDecimals}
|
||||
@@ -148,66 +127,147 @@ export const getFeeDetailsValues = ({
|
||||
),
|
||||
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) {
|
||||
const totalMarginAvailable = (
|
||||
currentMaintenanceMargin
|
||||
? totalBalance - BigInt(currentMaintenanceMargin)
|
||||
: totalBalance
|
||||
).toString();
|
||||
let marginRequiredBestCase: string | undefined = undefined;
|
||||
let marginRequiredWorstCase: string | undefined = undefined;
|
||||
if (marginEstimate) {
|
||||
if (currentInitialMargin) {
|
||||
marginRequiredBestCase = (
|
||||
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({
|
||||
indent: true,
|
||||
label: t('Total margin available'),
|
||||
value: `~${formatValueWithAssetDp(totalMarginAvailable)}`,
|
||||
symbol: assetSymbol,
|
||||
labelDescription: TOTAL_MARGIN_AVAILABLE(
|
||||
formatValueWithAssetDp(generalAccountBalance),
|
||||
formatValueWithAssetDp(marginAccountBalance),
|
||||
formatValueWithAssetDp(currentMaintenanceMargin),
|
||||
assetSymbol
|
||||
label: t('Deduction from collateral'),
|
||||
value: formatRange(
|
||||
deductionFromCollateralBestCase > 0
|
||||
? deductionFromCollateralBestCase.toString()
|
||||
: '0',
|
||||
deductionFromCollateralWorstCase > 0
|
||||
? deductionFromCollateralWorstCase.toString()
|
||||
: '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({
|
||||
label: t('Projected margin'),
|
||||
value: `~${formatValueWithAssetDp(estimatedTotalInitialMargin)}`,
|
||||
value: formatRange(
|
||||
marginEstimate?.bestCase.initialLevel,
|
||||
marginEstimate?.worstCase.initialLevel,
|
||||
assetDecimals
|
||||
),
|
||||
symbol: assetSymbol,
|
||||
labelDescription: EST_TOTAL_MARGIN_TOOLTIP_TEXT,
|
||||
});
|
||||
}
|
||||
details.push({
|
||||
label: t('Current margin allocation'),
|
||||
value: `${formatValueWithAssetDp(marginAccountBalance)}`,
|
||||
value: formatValue(marginAccountBalance, assetDecimals),
|
||||
symbol: assetSymbol,
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -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]
|
||||
);
|
||||
};
|
||||
@@ -36,10 +36,7 @@ export const DepositManager = ({
|
||||
const bridgeContract = useBridgeContract();
|
||||
const closeDepositDialog = useDepositDialog((state) => state.close);
|
||||
|
||||
const { getBalances, reset, balances } = useDepositBalances(
|
||||
asset,
|
||||
isFaucetable
|
||||
);
|
||||
const { getBalances, reset, balances } = useDepositBalances(asset);
|
||||
|
||||
// Set up approve transaction
|
||||
const approve = useSubmitApproval(asset, getBalances);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user