Compare commits

...
Author SHA1 Message Date
Matthew Russell 64bf2b3b52 feat: add check for operation name in apollo client error link 2023-05-18 15:05:32 -07:00
Matthew Russell 28f9e169e7 chore: rename nodes so its more clear what the query is used for 2023-05-18 14:54:13 -07:00
dexturr 162b167860 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-18 18:07:36 +00:00
Art a4149cc55e feat(web3): withdrawal details (#3701) 2023-05-18 16:50:15 +02:00
Mikołaj Młodzikowski 4161a74eaf feat(ci): fix release body (#3822) 2023-05-18 14:59:46 +01:00
Mikołaj Młodzikowski 5008e57a56 feat(ci): adjust variables during release pipeline to cover actual tag_name 2023-05-18 15:12:12 +02:00
Mikołaj Młodzikowski 89031bf826 feat(ci): append ipfs related information to release body (#3818) 2023-05-18 14:51:53 +02:00
dexturr 61501d4d14 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-18 12:10:20 +00:00
Madalina Raicu f8f3ce7df9 fix: no oracle for this market banner 2023-05-18 13:00:17 +01:00
Maciek f7aab5a78a feat(ui-toolkit,trading): allow toast box configuration (#3811) 2023-05-18 12:53:36 +01:00
Bartłomiej Głownia 2904975fd3 feat(trading): highlight negative oracle statuses in market lists (#3816) 2023-05-18 13:22:54 +02:00
Mikołaj Młodzikowski 4f55c946f0 feat(ci): add pipeline for rollback console deployments (#3817) 2023-05-18 10:11:41 +00:00
Matthew Russell de637fee13 chore(trading): show markets in opening auction in market selector (#3814) 2023-05-18 09:49:44 +01:00
dexturr 69c6db8ec2 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-18 06:09:07 +00:00
Matthew Russell 8dc465c1d1 feat(trading): additional row actions for tables (#3600) 2023-05-17 21:05:53 -07:00
dexturr 0be934edca chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-18 00:16:39 +00:00
Matthew Russell 653688c1dd fix(governance): clear wallet state if user changes to a non existent party (#3813) 2023-05-17 19:25:14 +00:00
dexturr 34400be18a chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-17 18:09:58 +00:00
Sam Keen d4a0a4a42c fix(governance): updated page titles (#3810) 2023-05-17 16:16:19 +00:00
Joe Tsang fabf643ce7 chore(ci): fix nightly test runner (#3804) 2023-05-17 16:21:10 +01:00
Sam Keen 45e210092c fix(governance): ensure no negative overstaked penalty (#3794) 2023-05-17 14:41:55 +00:00
Ciaran McGhie 1dc3b3fd05 fix(ui-toolkit): add keys to healthbar segments (#3797) 2023-05-17 15:14:11 +01:00
Sam Keen 0a80e3e39d fix(governance): avoid unknown pubkey causing validator page error (#3755) 2023-05-17 13:21:00 +00:00
Sam Keen 7b8d9dc94b feat(governance): validator tables refocused on stake (#3780) 2023-05-17 14:14:23 +01:00
Daniel c0320c2468 feat(ci): update DNS for mainnet console when new version is released (#3783) 2023-05-17 14:45:06 +02:00
Sam Keen 11173d6614 fix(governance): point to correct data for displayed LP rewards (#3800) 2023-05-17 12:20:51 +00:00
dexturr e3324331e2 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-17 12:11:33 +00:00
Maciek 0a6e3cb6f5 chore(trading): refactor docs links resolver (#3782) 2023-05-17 12:10:31 +02:00
Sam Keen 68ede90609 feat(3779): proposal terms in table, full proposal json, description … (#3793) 2023-05-17 08:23:25 +00:00
daro-maj 09778a5d3f test(trading): pegged orders e2e tests (#3787) 2023-05-17 09:54:19 +02:00
MaciekandMatthew Russell 59993e3ea6 chore(trading): suppress sentry logs when eth fails (#3726)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-05-16 23:31:53 -07:00
dexturr 54161a6dba chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-17 06:07:24 +00:00
Matthew Russell 26f524ea07 feat(trading): sidebar market list adjustments (#3792) 2023-05-16 18:45:17 -07:00
dexturr db13e6f21d chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-17 00:17:38 +00:00
dexturr 4a679813ba chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-16 18:07:35 +00:00
Matthew Russell a4279d5b5e feat(trading): sidebar market list (#3776) 2023-05-16 16:57:36 +00:00
m.ray 1a274a67c3 feat(trading): oracle banner check termination and settlement data oracles (#3707) 2023-05-16 15:12:22 +02:00
dexturr 1e9ff31fd7 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-16 12:08:54 +00:00
Sam Keen aa2b755b93 fix(governance): reward aggregation for validator types (#3773) 2023-05-16 08:40:36 +01:00
Matthew Russell 4a7296312a fix(smart-contracts,deposits): add different abi for tether (#3775) 2023-05-16 08:40:05 +01:00
dexturr 1418a2259f chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-16 06:07:24 +00:00
dexturr 7e5ef0157a chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-16 00:14:15 +00:00
dexturr 0cf764f11e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-15 18:09:01 +00:00
Sam Keen 1c626aeeb2 fix(governance): useWeb3ConnectStore instead of app state for eth connect d… (#3750) 2023-05-15 16:21:20 +01:00
Bartłomiej Głownia c4cec47b0b feat(trading): add oracle dialog trigger to oracle banner, display status reas… (#3727) 2023-05-15 16:20:30 +01:00
dexturr 1a67c41bb4 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-15 12:08:57 +00:00
Matthew Russell fc66240390 fix(governance): check value of telemetry local storage entry (#3762) 2023-05-15 09:48:42 +01:00
Maciek fc0da98683 fix(trading): match link as active more strictly (#3754) 2023-05-15 10:08:21 +02:00
Maciek 4d0dafb113 fix(trading): match link as active more strictly (#3754) 2023-05-15 10:08:04 +02:00
dexturr eda431be9e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-15 06:07:48 +00:00
dexturr 4ff1749e54 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-15 00:17:19 +00:00
dexturr e7a108fd7a chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-14 18:07:19 +00:00
dexturr 5d3f2ea7d8 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-14 12:08:55 +00:00
dexturr 1ab27574ca chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-14 06:09:03 +00:00
dexturr 0ea3c43e78 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-14 00:17:56 +00:00
dexturr 9e183568b0 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-13 18:08:39 +00:00
dexturr 69fd5b26b6 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-13 12:09:50 +00:00
dexturr 2d5b926609 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-13 06:07:23 +00:00
dexturr 98a2ba79a8 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-13 00:13:37 +00:00
dexturr 0e6e986937 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-12 18:07:19 +00:00
Mikołaj Młodzikowski 15d6ea0c05 feat(ci): push directly to main branch on vega.xyz 2023-05-12 17:40:52 +02:00
Mikołaj Młodzikowski 0358ce6293 feat(ci): try to merge to vega.xyz using standard PR 2023-05-12 17:13:33 +02:00
Mikołaj Młodzikowski 0791be4636 feat(ci): push directly to main branch on vega.xyz 2023-05-12 16:40:45 +02:00
Dexter Edwardsandsam-keen 07442848ee fix(governance): claim not encoding ascii correctly (#3747)
Co-authored-by: sam-keen <samuel.kleinmann@gmail.com>
2023-05-12 14:04:06 +00:00
Mikołaj Młodzikowski d5f0bd419e feat(ci): update vega landing page whenever new console is released (#3751) 2023-05-12 15:18:15 +02:00
Sam KeenandBartłomiej Głownia 2c7934a8c7 fix(governance): fix time delay notice for withdrawal of 0.00 assets (#3740)
Co-authored-by: Bartłomiej Głownia <bglownia@gmail.com>
2023-05-12 15:04:53 +02:00
Joe Tsang fae61c28ca chore(cypress): remove cypress project id (#3739) 2023-05-12 13:44:22 +01:00
dexturr fcd4541625 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-12 12:10:20 +00:00
Sam KeenandJoe 8e0a92fdec fix(governance): network upgrade proposal e2e test tweak (#3748)
Co-authored-by: Joe <joe@vega.xyz>
2023-05-12 09:58:10 +01:00
dexturr 6e7109ed87 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-12 06:09:45 +00:00
dexturr f2c698ce06 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-12 00:16:15 +00:00
dexturr ad9a3a3400 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-11 18:10:08 +00:00
Sam Keen efeac49d58 feat(governance): change status labels for network upgrade proposals (#3711) 2023-05-11 15:43:27 +00:00
Mikołaj Młodzikowski 2d430710ab feat(ci): use actual secret in the script 2023-05-11 17:31:12 +02:00
Mikołaj Młodzikowski bca1a98985 fix(ci): remove old dockerfile 2023-05-11 17:16:28 +02:00
Edd 8a080d3279 fix(explorer): fix bug where no votes displayed a yes icon (#3699) 2023-05-11 14:55:00 +00:00
Mikołaj Młodzikowski f121836b4e feat(ci): fleek updates (#3724) 2023-05-11 15:42:35 +02:00
Sam Keen b641c82ad8 feat(governance): voting blog link and button title (#3717) 2023-05-11 12:59:28 +00:00
Sam Keen bf73559c30 fix(governance): change proposal sort order (#3716) 2023-05-11 12:39:30 +00:00
dexturr 653cec2592 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-11 12:07:57 +00:00
m.ray 579c884a5a feat(trading): show pegged order offset and reference in the order table (#3675) 2023-05-11 12:43:20 +01:00
Bartłomiej Głownia b2279c7e47 feat(trading): hook up new EstimatePosition and EstimateFees api methods (#3634) 2023-05-11 12:58:12 +02:00
Sam Keen a8c17b6807 fix(governance): tranches data (#3708) 2023-05-11 10:22:52 +00:00
Sam Keen b4c7dc6f59 fix(governance): more thorough catch for url in error reporting (#3686) 2023-05-11 10:21:16 +00:00
daro-maj d44392bebf test(trading): show full oracle profile info in markets test (#3695) 2023-05-11 08:18:00 +02:00
dexturr bd679957e2 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-11 06:07:33 +00:00
dexturr e30d48555e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-11 00:16:19 +00:00
Sam Keen b4b2416780 fix(governance): my stake share 2 dp (#3685) 2023-05-10 16:23:37 +00:00
Maciek dc7832ac81 chore(candles-chart): fill up missing candles (#3664) 2023-05-10 15:26:27 +00:00
Joe Tsang 91207d31ee chore(governance): fix failing validator tests (#3703) 2023-05-10 15:03:47 +00:00
dexturr 58efb460a6 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-10 12:10:00 +00:00
Mikołaj Młodzikowski 044f98777b feat(ci): lint makefile 2023-05-10 13:43:33 +02:00
Mikołaj Młodzikowski 00fdc5e81a fix(ci): preparing dist bash syntax 2023-05-10 13:33:17 +02:00
Mikołaj Młodzikowski adfad3bafb feat(ci): fix typo 2023-05-10 13:21:53 +02:00
dexturr e6ae88905c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-10 06:07:15 +00:00
dexturr 3953ed9953 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-10 00:15:02 +00:00
Matthew Russell b48ab58e6a chore: add VEGA_URL for explorer mainnet 2023-05-09 14:06:10 -07:00
Matthew Russell e44cf1ff53 chore(environment,trading): fix unit tests for footer and node switcher (#3682) 2023-05-09 13:39:17 -07:00
Matthew Russell bded1d32ba feat(explorer,governance): always allow selecting a node, add node guard (#3678) 2023-05-09 12:58:09 -07:00
Edd 76ddf45f4c fix(explorer): add envs back to env (#3677) 2023-05-09 12:57:38 -07:00
Dexter Edwards 6a2c9004d8 fix(governance, console, explorer): show vega node in list, cache bust (#3676) 2023-05-09 12:57:11 -07:00
dexturr 77ee88b43e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-09 18:09:50 +00:00
Dexter e4958ce3c0 fix: do not show validators with 0 self stake in list 2023-05-09 18:25:20 +01:00
Mikołaj Młodzikowski ecedb879f8 fix(ci): define which app ipfs hash is set for 2023-05-09 18:47:50 +02:00
m.ray 89700803d6 fix(trading): fix release script for mainnet (#3669) 2023-05-09 16:30:05 +00:00
Mikołaj Młodzikowski 65c9fa5df2 feat(ci): establish build based on docker to produce ipfs-hashes (#3612) 2023-05-09 18:24:39 +02:00
ArtandMadalina Raicu 02399c40fc fix(trading): welcome dialog links (#3656)
Co-authored-by: Madalina Raicu <madalina@raygroup.uk>
2023-05-09 15:43:36 +00:00
508 changed files with 11346 additions and 6280 deletions
@@ -0,0 +1,74 @@
name: After Release
on:
release:
types: [published]
jobs:
after-release:
runs-on: ubuntu-22.04
timeout-minutes: 30
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- 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
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: resolve ipfs hashes for release
run: |
echo "Tag name: ${{ github.event.release.tag_name }}"
echo "Name: ${{ github.event.release.name }}"
echo "Description: ${{ github.event.release.body }}"
until docker pull vegaprotocol/trading:${{ github.event.release.tag_name }}; do
echo "Image not pushed yet, waiting 60 seconds"
sleep 60
done
docker run --rm vegaprotocol/trading:${{ github.event.release.tag_name }} cat /ipfs-hash > ipfs-hash
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
echo IPFS_V0=$(cat ipfs-hash) >> $GITHUB_ENV
echo IPFS_V1=$(ipfs cid format -v 1 -b base32 $(cat ipfs-hash)) >> $GITHUB_ENV
- name: Edit Release
uses: irongut/EditRelease@v1.2.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
id: ${{ github.event.release.id }}
body: |
---
# Deployments
* https://explorer.vega.xyz
* https://governance.vega.xyz
# IPFS releases
Tye IPFS hash of this release of the Trading app is:
CIDv0: ${{ env.IPFS_V0 }}
CIDv1: ${{ env.IPFS_V1 }}
You can always access the latest IPFS release by visiting [vega.trading](https://vega.trading).
You can also access Trading directly from an IPFS gateway.
BEWARE: The Trading interface uses [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported. You should always use an IPFS gateway that enforces [origin separation](https://ipfs.github.io/public-gateway-checker/).
Your settings are not remembered across different URLs.
IPFS gateways:
https://${{ env.IPFS_V1 }}.ipfs.dweb.link/
https://${{ env.IPFS_V1 }}.ipfs.cf-ipfs.com/
ipfs://${{ env.IPFS_V0 }}/
+3 -3
View File
@@ -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"
-1
View File
@@ -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 }}
+3 -3
View File
@@ -20,7 +20,7 @@ jobs:
project: ${{ fromJSON(inputs.projects) }}
name: ${{ matrix.project }}
runs-on: self-hosted-runner
timeout-minutes: 40
timeout-minutes: 60
steps:
# Checks if skip cache was requested
- name: Set skip-nx-cache flag
@@ -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 }}
@@ -98,4 +98,4 @@ jobs:
if: ${{ failure() }}
with:
name: test-report-${{ matrix.project }}
path: frontend-monorepo/apps/trading-e2e/cypress/reports
path: frontend-monorepo/apps/${{ matrix.project }}/cypress/reports
+83 -34
View File
@@ -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,52 @@ jobs:
with:
labels: ${{ matrix.app }}-preview
number: ${{ github.event.number }}
- 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: Update vega.trading DNS to redirect to the new console
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
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)
new_cid=$(ipfs cid format -v 1 -b base32 $new_hash)
# Generate console URL
new_console_url_type=ipfs
# new_console_url_type=ipns
new_console_url_domain=cf-ipfs.com
# new_console_url_domain=dweb.link
new_console_url="https://${new_cid}.${new_console_url_type}.${new_console_url_domain}/"
echo "new_console_url=${new_console_url}"
# Update record in DNSimple
# docs: https://developer.dnsimple.com/v2/zones/records/#updateZoneRecord
dnsimple_account_id=84895
dnsimple_zone_name=vega.trading
dnsimple_record_id=44409591
# see: https://dnsimple.com/a/84895/domains/vega.trading/records/44409591/edit
curl -H 'Authorization: Bearer ${{ secrets.DNSIMPLE_API_TOKEN }}' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-X PATCH \
-d "{
\"content\": \"${new_console_url}\"
}" \
https://api.dnsimple.com/v2/${dnsimple_account_id}/zones/${dnsimple_zone_name}/records/${dnsimple_record_id}
+77
View File
@@ -0,0 +1,77 @@
name: 'Rollback console'
on:
workflow_dispatch:
inputs:
version:
description: 'Version that should be set on rollback'
required: true
type: string
jobs:
rollback:
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry (docker hub)
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Retag mainnet
run: |
docker pull vegaprotocol/trading:${{ inputs.version }}
docker tag vegaprotocol/trading:${{ inputs.version }} vegaprotocol/trading:mainnet
docker push vegaprotocol/trading:mainnet
docker run --rm vegaprotocol/trading:mainnet cat /ipfs-hash > ipfs-hash
- name: Trigger fleek deployment
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: Update vega.trading DNS to redirect to the new console
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 ipfs-hash)
new_cid=$(ipfs cid format -v 1 -b base32 $new_hash)
# Generate console URL
new_console_url_type=ipfs
# new_console_url_type=ipns
new_console_url_domain=cf-ipfs.com
# new_console_url_domain=dweb.link
new_console_url="https://${new_cid}.${new_console_url_type}.${new_console_url_domain}/"
echo "new_console_url=${new_console_url}"
# Update record in DNSimple
# docs: https://developer.dnsimple.com/v2/zones/records/#updateZoneRecord
dnsimple_account_id=84895
dnsimple_zone_name=vega.trading
dnsimple_record_id=44409591
# see: https://dnsimple.com/a/84895/domains/vega.trading/records/44409591/edit
curl -H 'Authorization: Bearer ${{ secrets.DNSIMPLE_API_TOKEN }}' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-X PATCH \
-d "{
\"content\": \"${new_console_url}\"
}" \
https://api.dnsimple.com/v2/${dnsimple_account_id}/zones/${dnsimple_zone_name}/records/${dnsimple_record_id}
+2 -1
View File
@@ -2,6 +2,7 @@
# compiled output
/dist
/dist-result
/tmp
/out-tsc
/tools/executors/**/*.js
@@ -48,4 +49,4 @@ cypress.env.json
.next
#cypress
/apps/trading-e2e/cypress/reports/
/apps/**/cypress/reports/
+20
View File
@@ -0,0 +1,20 @@
.PHONY: latest-release
latest-release:
gh release list | head -n 1 | awk '{print $1}'
.PHONY: show-latest-release
show-latest-release:
gh release view `gh release list | head -n 1 | awk '{print $1}'`
.PHONY: recalculate-ipfs
recalculate-ipfs:
echo "ipfs hash inside the image"
docker run --rm ${TAG} cat /ipfs-hash
echo "recalculating ipfs hash"
docker run --rm ${TAG} ipfs add -r /usr/share/nginx/html
.PHONY: eject-ipfs-hash
unpack:
docker create --name=dist ${TAG}
docker cp dist:/usr/share/nginx/html dist
docker rm dist
+55 -12
View File
@@ -103,25 +103,68 @@ In CI linting, formatting and also run. These checks can be seen in the [CI work
Visit the [Nx Documentation](https://nx.dev/getting-started/intro) to learn more.
# 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
+2 -1
View File
@@ -1,10 +1,11 @@
const { defineConfig } = require('cypress');
module.exports = defineConfig({
projectId: 'et4snf',
reporter: '../../node_modules/cypress-mochawesome-reporter',
e2e: {
setupNodeEvents(on, config) {
require('cypress-mochawesome-reporter/plugin')(on);
require('@cypress/grep/src/plugin')(config);
return config;
},
+1
View File
@@ -15,5 +15,6 @@
import '@vegaprotocol/cypress';
import './common.functions.js';
import 'cypress-mochawesome-reporter/register';
import registerCypressGrep from '@cypress/grep';
registerCypressGrep();
+1
View File
@@ -13,6 +13,7 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_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
+1
View File
@@ -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
+2 -1
View File
@@ -1 +1,2 @@
# .env is stagnet1, so there are no overrides required
# .env is stagnet1, so there are no overrides required
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz","STAGNET3":"https://stagnet3.explorer.vega.xyz"}'
+24 -2
View File
@@ -1,9 +1,18 @@
import { NetworkLoader, useInitializeEnv } from '@vegaprotocol/environment';
import {
AppFailure,
NetworkLoader,
NodeGuard,
NodeSwitcherDialog,
useEnvironment,
useInitializeEnv,
useNodeSwitcherStore,
} from '@vegaprotocol/environment';
import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-websocket-provider';
import { 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>
);
};
@@ -3,6 +3,7 @@ import { Header } from './header';
import { MemoryRouter } from 'react-router-dom';
jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'),
NetworkSwitcher: () => (
<div data-testid="network-switcher">NetworkSwitcher</div>
),
@@ -1,8 +1,8 @@
import { t } from '@vegaprotocol/i18n';
import type { MarketInfoWithData } from '@vegaprotocol/market-info';
import { LiquidityInfoPanel } from '@vegaprotocol/market-info';
import { LiquidityMonitoringParametersInfoPanel } from '@vegaprotocol/market-info';
import type { MarketInfoWithData } from '@vegaprotocol/markets';
import {
LiquidityInfoPanel,
LiquidityMonitoringParametersInfoPanel,
InstrumentInfoPanel,
KeyDetailsInfoPanel,
LiquidityPriceRangeInfoPanel,
@@ -12,8 +12,8 @@ import {
RiskModelInfoPanel,
RiskParametersInfoPanel,
SettlementAssetInfoPanel,
} from '@vegaprotocol/market-info';
import { MarketInfoTable } from '@vegaprotocol/market-info';
} from '@vegaprotocol/markets';
import { MarketInfoTable } from '@vegaprotocol/markets';
import type { DataSourceDefinition } from '@vegaprotocol/types';
import isEqual from 'lodash/isEqual';
@@ -1,4 +1,4 @@
import type { MarketFieldsFragment } from '@vegaprotocol/market-list';
import type { MarketFieldsFragment } from '@vegaprotocol/markets';
import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
@@ -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:&nbsp;
Success&nbsp;
</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>
);
}
@@ -8,7 +8,7 @@ import { useScrollToLocation } from '../../hooks/scroll-to-location';
import { useDocumentTitle } from '../../hooks/use-document-title';
import compact from 'lodash/compact';
import { JsonViewerDialog } from '../../components/dialogs/json-viewer-dialog';
import { marketInfoWithDataProvider } from '@vegaprotocol/market-info';
import { marketInfoWithDataProvider } from '@vegaprotocol/markets';
import { PageTitle } from '../../components/page-helpers/page-title';
export const MarketPage = () => {
@@ -1,6 +1,6 @@
import { useScrollToLocation } from '../../hooks/scroll-to-location';
import { useDocumentTitle } from '../../hooks/use-document-title';
import { marketsProvider } from '@vegaprotocol/market-list';
import { marketsProvider } from '@vegaprotocol/markets';
import { RouteTitle } from '../../components/route-title';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
+7 -5
View File
@@ -32,9 +32,9 @@
line-height: calc(min(var(--ag-line-height, 26px), 26px) - 4px);
}
.vega-ag-grid .ag-row {
border-width: 1px 0;
border-bottom: 1px solid transparent;
.vega-ag-grid .ag-row,
.vega-ag-grid .ag-cell {
border-width: 0;
}
/* Light variables */
@@ -46,7 +46,6 @@
--ag-header-column-separator-color: theme(colors.neutral[300]);
--ag-row-border-color: theme(colors.white);
--ag-row-hover-color: theme(colors.neutral[100]);
--ag-font-size: 12px;
}
/* Dark variables */
@@ -58,5 +57,8 @@
--ag-header-column-separator-color: theme(colors.neutral[600]);
--ag-row-border-color: theme(colors.black);
--ag-row-hover-color: theme(colors.neutral[800]);
--ag-font-size: 12px;
}
.voteicon svg {
vertical-align: baseline;
}
+2 -1
View File
@@ -1,10 +1,11 @@
const { defineConfig } = require('cypress');
module.exports = defineConfig({
projectId: 'et4snf',
reporter: '../../node_modules/cypress-mochawesome-reporter',
e2e: {
setupNodeEvents(on, config) {
require('cypress-mochawesome-reporter/plugin')(on);
require('@cypress/grep/src/plugin')(config);
return config;
},
@@ -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',
@@ -63,7 +63,6 @@ context(
});
it('should have option to go to last and newest page', function () {
waitForBeginningOfEpoch();
cy.getByTestId('goto-last-page').click();
cy.getByTestId('epoch-total-rewards-table')
.last()
@@ -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 () {
+3
View File
@@ -8,6 +8,7 @@ import './wallet-eth.functions.ts';
import './wallet-teardown.functions.ts';
import './wallet-vega.functions.ts';
import './proposal.functions.ts';
import 'cypress-mochawesome-reporter/register';
import registerCypressGrep from '@cypress/grep';
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { chainIdQuery, statisticsQuery } from '@vegaprotocol/mock';
@@ -33,4 +34,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');
}
+6
View File
@@ -0,0 +1,6 @@
function ReactMarkdown({ children }) {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{children}</>;
}
export default ReactMarkdown;
+94 -29
View File
@@ -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';
@@ -17,6 +16,7 @@ import { AppStateProvider } from './contexts/app-state/app-state-provider';
import { ContractsProvider } from './contexts/contracts/contracts-provider';
import { AppRouter } from './routes';
import type { EthereumConfig } from '@vegaprotocol/web3';
import { WithdrawalApprovalDialogContainer } from '@vegaprotocol/web3';
import {
createConnectors,
useEthTransactionManager,
@@ -37,10 +37,14 @@ import {
useEnvironment,
NetworkLoader,
useInitializeEnv,
NodeGuard,
AppFailure,
NodeSwitcherDialog,
useNodeSwitcherStore,
} from '@vegaprotocol/environment';
import { ENV } from './config';
import type { InMemoryCacheConfig } from '@apollo/client';
import { WithdrawalDialog } from '@vegaprotocol/withdraws';
import { CreateWithdrawalDialog } from '@vegaprotocol/withdraws';
import { SplashLoader } from './components/splash-loader';
import { ToastsManager } from './toasts-manager';
import {
@@ -48,6 +52,8 @@ import {
TELEMETRY_ON,
} from './components/telemetry-dialog/telemetry-dialog';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { useTranslation } from 'react-i18next';
import { isPartyNotFoundError } from './lib/party';
const cache: InMemoryCacheConfig = {
typePolicies: {
@@ -153,7 +159,8 @@ const Web3Container = ({
<InitializeHandlers />
<VegaWalletDialogs />
<TransactionModal />
<WithdrawalDialog />
<CreateWithdrawalDialog />
<WithdrawalApprovalDialogContainer />
<TelemetryDialog />
</>
</BalanceManager>
@@ -179,32 +186,82 @@ 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],
},
};
beforeSend(event, hint) {
const error = hint?.originalException;
const errorIsString = typeof error === 'string';
const errorIsObject = error instanceof Error;
const requestUrl = event.request?.url;
const transaction = event.transaction;
if (
(errorIsString && isPartyNotFoundError({ message: error })) ||
(errorIsObject && isPartyNotFoundError(error))
) {
// This error is caused by a pubkey making an API request before
// it has interacted with the chain. This isn't needed in Sentry.
return null;
}
return event;
const updatedRequest =
requestUrl && requestUrl.includes('/claim?')
? { ...event.request, url: removeQueryParams(requestUrl) }
: event.request;
const updatedTransaction =
transaction && transaction.includes('/claim?')
? removeQueryParams(transaction)
: transaction;
const updatedBreadcrumbs = event.breadcrumbs?.map((breadcrumb) => {
if (
breadcrumb.type === 'navigation' &&
breadcrumb.data?.to?.includes('/claim?')
) {
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 +276,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')}
@@ -1,6 +1,6 @@
import { Link } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next';
import { ExternalLinks } from '@vegaprotocol/utils';
import { ExternalLinks } from '@vegaprotocol/environment';
export const DownloadWalletPrompt = () => {
const { t } = useTranslation();
@@ -26,6 +26,7 @@ import type {
WalletDelegationFieldsFragment,
} from './__generated__/Delegations';
import { DelegationsDocument } from './__generated__/Delegations';
import { isPartyNotFoundError } from '../../lib/party';
export const usePollForDelegations = () => {
const { token: vegaToken } = useContracts();
@@ -75,6 +76,7 @@ export const usePollForDelegations = () => {
})
.then((res) => {
if (!mounted) return;
const canonisedDelegations = removePaginationWrapper(
res.data.party?.delegationsConnection?.edges
);
@@ -203,6 +205,15 @@ export const usePollForDelegations = () => {
setDelegatedNodes(delegatedAmounts);
})
.catch((err: Error) => {
// if party isn't found, dont log to Sentry, just clear state as, user
// will not have any delagations or accounts
if (isPartyNotFoundError(err)) {
setDelegations([]);
setAccounts([]);
setDelegatedNodes([]);
setCurrentStakeAvailable(new BigNumber(0));
return;
}
Sentry.captureException(err);
// If query fails stop interval. Its almost certain that the query
// will just continue to fail
@@ -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",
@@ -593,7 +594,9 @@
"numberOfAgainstVotes": "Number of votes against",
"yesPercentage": "Yes percentage",
"noPercentage": "No percentage",
"proposalTerms": "Proposal terms",
"proposalJson": "Full proposal JSON",
"proposalDetails": "Proposal details",
"proposalDescription": "Description",
"currentlySetTo": "Currently expected to ",
"currently": "currently",
"finalOutcomeMayDiffer": "Final outcome may differ",
@@ -787,9 +790,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 +814,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"
}
+8
View File
@@ -0,0 +1,8 @@
export const PARTY_NOT_FOUND = 'failed to get party for ID';
export const isPartyNotFoundError = (error: { message: string }) => {
if (error.message.includes(PARTY_NOT_FOUND)) {
return true;
}
return false;
};
@@ -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,
};
+2 -1
View File
@@ -12,7 +12,8 @@ import { useRefreshAfterEpoch } from '../../hooks/use-refresh-after-epoch';
import { ProposalsListItem } from '../proposals/components/proposals-list-item';
import { ProtocolUpgradeProposalsListItem } from '../proposals/components/protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item';
import Routes from '../routes';
import { ExternalLinks, removePaginationWrapper } from '@vegaprotocol/utils';
import { ExternalLinks } from '@vegaprotocol/environment';
import { removePaginationWrapper } from '@vegaprotocol/utils';
import { useNodesQuery } from '../staking/home/__generated__/Nodes';
import { useProposalsQuery } from '../proposals/proposals/__generated__/Proposals';
import {
@@ -53,7 +53,6 @@ const mockAppState: AppState = {
totalSupply: new BigNumber(65000000),
vegaWalletOverlay: false,
vegaWalletManageOverlay: false,
ethConnectOverlay: false,
transactionOverlay: false,
bannerMessage: '',
disconnectNotice: false,
@@ -0,0 +1 @@
export { ProposalDescription } from './proposal-description';
@@ -0,0 +1,51 @@
import ReactMarkdown from 'react-markdown';
import classnames from 'classnames';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Icon, RoundedWrapper } from '@vegaprotocol/ui-toolkit';
import { SubHeading } from '../../../../components/heading';
export const ProposalDescription = ({
description,
}: {
description: string;
}) => {
const { t } = useTranslation();
const [showDescription, setShowDescription] = useState(false);
const showDescriptionIconClasses = classnames('mb-4', {
'rotate-180': showDescription,
});
return (
<section data-testid="proposal-description">
<button
onClick={() => setShowDescription(!showDescription)}
data-testid="proposal-description-toggle"
>
<div className="flex items-center gap-3">
<SubHeading title={t('proposalDescription')} />
<div className={showDescriptionIconClasses}>
<Icon name="chevron-down" size={8} />
</div>
</div>
</button>
{showDescription && (
<RoundedWrapper paddingBottom={true} marginBottomLarge={true}>
<div className="p-2">
<ReactMarkdown
className="react-markdown-container"
/* Prevents HTML embedded in the description from rendering */
skipHtml={true}
/* Stops users embedding images which could be used for tracking */
disallowedElements={['img']}
linkTarget="_blank"
>
{description}
</ReactMarkdown>
</div>
</RoundedWrapper>
)}
</section>
);
};
@@ -206,28 +206,6 @@ describe('Proposal header', () => {
).not.toBeInTheDocument();
});
it('Renders Freeform proposal - long rationale (105 chars) - details', () => {
renderComponent(
generateProposal({
id: 'long',
rationale: {
title: '0x0',
description:
'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean dolor.',
},
terms: {
change: {
__typename: 'NewFreeform',
},
},
}),
false
);
expect(screen.getByTestId('proposal-description')).toHaveTextContent(
/Class aptent/
);
});
// Remove once proposals have rationale and re-enable above tests
it('Renders Freeform proposal - id for title', () => {
renderComponent(
@@ -5,7 +5,6 @@ import { Heading, SubHeading } from '../../../../components/heading';
import type { ReactNode } from 'react';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import ReactMarkdown from 'react-markdown';
import { truncateMiddle } from '../../../../lib/truncate-middle';
import { CurrentProposalState } from '../current-proposal-state';
import { ProposalInfoLabel } from '../proposal-info-label';
@@ -23,12 +22,7 @@ export const ProposalHeader = ({
let details: ReactNode;
let proposalType = '';
let title = proposal?.rationale.title.trim();
let description = proposal?.rationale.description.trim();
if (title?.length === 0 && description && description.length > 0) {
title = description;
description = '';
}
const title = proposal?.rationale.title.trim();
const titleContent = shorten(title ?? '', 100);
@@ -145,23 +139,6 @@ export const ProposalHeader = ({
{details}
</div>
)}
{description && !isListItem && (
<div data-testid="proposal-description">
{/*<div className="uppercase mr-2">{t('ProposalDescription')}:</div>*/}
<SubHeading title={t('ProposalDescription')} />
<ReactMarkdown
className="react-markdown-container"
/* Prevents HTML embedded in the description from rendering */
skipHtml={true}
/* Stops users embedding images which could be used for tracking */
disallowedElements={['img']}
linkTarget="_blank"
>
{description}
</ReactMarkdown>
</div>
)}
</>
);
};
@@ -0,0 +1 @@
export { ProposalJson } from './proposal-json';
@@ -1,15 +1,15 @@
import classnames from 'classnames';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Icon, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { SubHeading } from '../../../../components/heading';
import type { PartialDeep } from 'type-fest';
import type * as Schema from '@vegaprotocol/types';
import { useState } from 'react';
import classnames from 'classnames';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
export const ProposalTermsJson = ({
terms,
export const ProposalJson = ({
proposal,
}: {
terms: PartialDeep<Schema.ProposalTerms>;
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
}) => {
const { t } = useTranslation();
const [showDetails, setShowDetails] = useState(false);
@@ -18,20 +18,20 @@ export const ProposalTermsJson = ({
});
return (
<section>
<section data-testid="proposal-json">
<button
onClick={() => setShowDetails(!showDetails)}
data-testid="proposal-terms-toggle"
data-testid="proposal-json-toggle"
>
<div className="flex items-center gap-3">
<SubHeading title={t('proposalTerms')} />
<SubHeading title={t('proposalJson')} />
<div className={showDetailsIconClasses}>
<Icon name="chevron-down" size={8} />
</div>
</div>
</button>
{showDetails && <SyntaxHighlighter data={terms} />}
{showDetails && <SyntaxHighlighter data={proposal} />}
</section>
);
};
@@ -1 +0,0 @@
export { ProposalTermsJson } from './proposal-terms-json';
@@ -0,0 +1 @@
export { ProposalTerms } from './proposal-terms';
@@ -0,0 +1,142 @@
import {
RoundedWrapper,
KeyValueTable,
KeyValueTableRow,
Icon,
} from '@vegaprotocol/ui-toolkit';
import { BigNumber } from '../../../../lib/bignumber';
import { SubHeading } from '../../../../components/heading';
import { useTranslation } from 'react-i18next';
import { useState } from 'react';
import classnames from 'classnames';
interface ProposalTermsProps {
data: Record<string, unknown>;
}
const getParsedValue = (value: unknown) => {
if (typeof value === 'string') {
try {
// Check if value is a number string - if so use bignumber to maintain precision
if (/^\d+(\.\d+)?$/.test(value)) {
return new BigNumber(value).toString();
} else {
// This would convert, for example, a JSON object ('{"key":"value"}')
// into an actual JS object ({key: "value"})
return JSON.parse(value);
}
} catch (error) {
return value;
}
} else {
return value;
}
};
const RenderKeyValue = ({
title,
value,
}: {
title: string;
value: string | number;
}) => (
<KeyValueTable>
<KeyValueTableRow>
{title}
{value}
</KeyValueTableRow>
</KeyValueTable>
);
const RenderArray = ({ title, array }: { title: string; array: unknown[] }) => {
if (array.every((item) => typeof item === 'string')) {
return <RenderKeyValue title={title} value={array.join(', ')} />;
} else {
return (
<div>
<div className="mb-2">{title}</div>
{array.map((item, index) => (
<div key={index}>
<ProposalTermsRenderer data={item as Record<string, unknown>} />
</div>
))}
</div>
);
}
};
// Working with 'unknown' type as a proposal's terms can be in many shapes
const RenderTerm = ({ title, value }: { title: string; value: unknown }) => {
const parsedValue = getParsedValue(value);
if (parsedValue === null || typeof parsedValue === 'boolean') {
return <RenderKeyValue title={title} value={String(parsedValue)} />;
} else if (
typeof parsedValue === 'string' ||
typeof parsedValue === 'number'
) {
return <RenderKeyValue title={title} value={parsedValue} />;
} else if (typeof parsedValue === 'object') {
if (Array.isArray(parsedValue)) {
return <RenderArray title={title} array={parsedValue} />;
} else {
return (
<div>
<div className="my-2">{title}</div>
<ProposalTermsRenderer
data={parsedValue as Record<string, unknown>}
/>
</div>
);
}
} else if (parsedValue === undefined) {
return <span>{'undefined'}</span>;
} else {
return <span>{String(parsedValue)}</span>;
}
};
const ProposalTermsRenderer = ({ data }: ProposalTermsProps) => {
return (
<RoundedWrapper paddingBottom={true}>
{Object.keys(data)
.filter(
(key) =>
!['__typename', 'closingDatetime', 'enactmentDatetime'].includes(
key
)
)
.map((key, index) => (
<div key={index}>
<RenderTerm title={key} value={data[key]} />
</div>
))}
</RoundedWrapper>
);
};
export const ProposalTerms = ({ data }: ProposalTermsProps) => {
const { t } = useTranslation();
const [showTerms, setShowTerms] = useState(false);
const showTermsIconClasses = classnames('mb-4', {
'rotate-180': showTerms,
});
return (
<section data-testid="proposal-terms">
<button
onClick={() => setShowTerms(!showTerms)}
data-testid="proposal-terms-toggle"
>
<div className="flex items-center gap-3">
<SubHeading title={t('proposalDetails')} />
<div className={showTermsIconClasses}>
<Icon name="chevron-down" size={8} />
</div>
</div>
</button>
{showTerms && <ProposalTermsRenderer data={data} />}
</section>
);
};
@@ -25,8 +25,11 @@ jest.mock('../proposal-detail-header/proposal-header', () => ({
jest.mock('../proposal-change-table', () => ({
ProposalChangeTable: () => <div data-testid="proposal-change-table"></div>,
}));
jest.mock('../proposal-terms-json', () => ({
ProposalTermsJson: () => <div data-testid="proposal-terms-json"></div>,
jest.mock('../proposal-json', () => ({
ProposalJson: () => <div data-testid="proposal-json"></div>,
}));
jest.mock('../proposal-terms/proposal-terms', () => ({
ProposalTerms: () => <div data-testid="proposal-terms"></div>,
}));
jest.mock('../proposal-votes-table', () => ({
ProposalVotesTable: () => <div data-testid="proposal-votes-table"></div>,
@@ -49,7 +52,8 @@ it('renders each section', async () => {
render(<Proposal proposal={proposal as ProposalQuery['proposal']} />);
expect(await screen.findByTestId('proposal-header')).toBeInTheDocument();
expect(screen.getByTestId('proposal-change-table')).toBeInTheDocument();
expect(screen.getByTestId('proposal-terms-json')).toBeInTheDocument();
expect(screen.getByTestId('proposal-json')).toBeInTheDocument();
expect(screen.getByTestId('proposal-terms')).toBeInTheDocument();
expect(screen.getByTestId('proposal-votes-table')).toBeInTheDocument();
expect(screen.getByTestId('proposal-vote-details')).toBeInTheDocument();
expect(screen.queryByTestId('proposal-list-asset')).not.toBeInTheDocument();
@@ -6,8 +6,10 @@ import { AsyncRenderer, RoundedWrapper } from '@vegaprotocol/ui-toolkit';
import { ProposalHeader } from '../proposal-detail-header/proposal-header';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { ProposalDescription } from '../proposal-description';
import { ProposalChangeTable } from '../proposal-change-table';
import { ProposalTermsJson } from '../proposal-terms-json';
import { ProposalJson } from '../proposal-json';
import { ProposalTerms } from '../proposal-terms';
import { ProposalVotesTable } from '../proposal-votes-table';
import { VoteDetails } from '../vote-details';
import { ListAsset } from '../list-asset';
@@ -20,7 +22,7 @@ export enum ProposalType {
PROPOSAL_NETWORK_PARAMETER = 'PROPOSAL_NETWORK_PARAMETER',
PROPOSAL_FREEFORM = 'PROPOSAL_FREEFORM',
}
interface ProposalProps {
export interface ProposalProps {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
}
@@ -78,9 +80,11 @@ export const Proposal = ({ proposal }: ProposalProps) => {
<AsyncRenderer data={params} loading={loading} error={error}>
<section data-testid="proposal">
<ProposalHeader proposal={proposal} isListItem={false} />
<div className="my-10">
<ProposalChangeTable proposal={proposal} />
</div>
{proposal.terms.change.__typename === 'NewAsset' &&
proposal.terms.change.source.__typename === 'ERC20' &&
proposal.id ? (
@@ -90,7 +94,20 @@ export const Proposal = ({ proposal }: ProposalProps) => {
lifetimeLimit={proposal.terms.change.source.lifetimeLimit}
/>
) : null}
<div className="mb-12">
<div className="mb-4">
<ProposalDescription description={proposal.rationale.description} />
</div>
<div className="mb-4">
<ProposalTerms data={proposal.terms} />
</div>
<div className="mb-6">
<ProposalJson proposal={proposal} />
</div>
<div className="mb-10">
<RoundedWrapper paddingBottom={true}>
<VoteDetails
proposal={proposal}
@@ -102,10 +119,10 @@ export const Proposal = ({ proposal }: ProposalProps) => {
/>
</RoundedWrapper>
</div>
<div className="mb-4">
<ProposalVotesTable proposal={proposal} proposalType={proposalType} />
</div>
<ProposalTermsJson terms={proposal.terms} />
</section>
</AsyncRenderer>
);
@@ -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(),
},
});
@@ -8,12 +8,11 @@ import { ProposalsListFilter } from '../proposals-list-filter';
import Routes from '../../../routes';
import { Button, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { Link } from 'react-router-dom';
import { createDocsLinks, ExternalLinks } from '@vegaprotocol/utils';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { useEnvironment } from '@vegaprotocol/environment';
import { DocsLinks, ExternalLinks } from '@vegaprotocol/environment';
interface ProposalsListProps {
proposals: Array<ProposalFieldsFragment | ProposalQuery['proposal']>;
@@ -36,7 +35,6 @@ export const ProposalsList = ({
protocolUpgradeProposals,
lastBlockHeight,
}: ProposalsListProps) => {
const { VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation();
const [filterString, setFilterString] = useState('');
const sortedProposals = proposals.reduce(
@@ -83,9 +81,9 @@ export const ProposalsList = ({
marginBottom={false}
title={t('pageTitleProposals')}
/>
{VEGA_DOCS_URL && (
{DocsLinks && (
<div className="xs:justify-self-end" data-testid="new-proposal-link">
<ExternalLink href={createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}>
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE}>
<Button variant="primary" size="sm">
<div className="flex items-center gap-1">
{t('NewProposal')}
@@ -2,9 +2,10 @@ import { render, screen } from '@testing-library/react';
import { ProposalFormTerms } from './proposal-form-terms';
jest.mock('@vegaprotocol/environment', () => ({
useEnvironment: () => ({
VEGA_DOCS_URL: 'https://docs.vega.xyz',
}),
...jest.requireActual('@vegaprotocol/environment'),
DocsLinks: {
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals',
},
}));
const renderComponent = () => {
@@ -5,8 +5,7 @@ import {
ExternalLink,
TextArea,
} from '@vegaprotocol/ui-toolkit';
import { createDocsLinks } from '@vegaprotocol/utils';
import { useEnvironment } from '@vegaprotocol/environment';
import { DocsLinks } from '@vegaprotocol/environment';
import type { UseFormRegisterReturn } from 'react-hook-form';
interface ProposalFormTermsProps {
@@ -22,23 +21,20 @@ export const ProposalFormTerms = ({
labelOverride,
docsLink,
}: ProposalFormTermsProps) => {
const { VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation();
return (
<FormGroup
label={labelOverride || t('ProposalTerms')}
labelFor="proposal-terms"
>
{docsLink && VEGA_DOCS_URL && (
{docsLink && DocsLinks && (
<div className="mt-[-4px] mb-2 text-sm font-light">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${docsLink}`}
href={`${DocsLinks.PROPOSALS_GUIDE}${docsLink}`}
target="_blank"
>
{`${createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}${docsLink}`}
{`${DocsLinks.PROPOSALS_GUIDE}${docsLink}`}
</ExternalLink>
</div>
)}
@@ -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', () => {
@@ -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 = (
@@ -10,9 +10,10 @@ import type { MockedResponse } from '@apollo/client/testing';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
jest.mock('@vegaprotocol/environment', () => ({
useEnvironment: () => ({
VEGA_DOCS_URL: 'https://docs.vega.xyz',
}),
...jest.requireActual('@vegaprotocol/environment'),
DocsLinks: {
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals',
},
}));
const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
@@ -5,7 +5,7 @@ import {
useProposalSubmit,
deadlineToRoundedHours,
} from '@vegaprotocol/proposals';
import { useEnvironment } from '@vegaprotocol/environment';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
import {
ProposalFormDescription,
ProposalFormSubheader,
@@ -17,7 +17,6 @@ import {
import { ProposalMinRequirements } from '../../components/shared';
import { AsyncRenderer, ExternalLink } from '@vegaprotocol/ui-toolkit';
import { Heading } from '../../../../components/heading';
import { createDocsLinks } from '@vegaprotocol/utils';
import {
NetworkParams,
useNetworkParams,
@@ -42,7 +41,7 @@ export const ProposeFreeform = () => {
NetworkParams.governance_proposal_freeform_minProposerBalance,
NetworkParams.spam_protection_proposal_min_tokens,
]);
const { VEGA_DOCS_URL, VEGA_EXPLORER_URL } = useEnvironment();
const { VEGA_EXPLORER_URL } = useEnvironment();
const { t } = useTranslation();
const {
register,
@@ -111,17 +110,13 @@ export const ProposeFreeform = () => {
userAction={ProposalUserAction.CREATE}
/>
{VEGA_DOCS_URL && (
{DocsLinks && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}
target="_blank"
>{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink>
</p>
)}
@@ -10,9 +10,10 @@ import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import type { MockedResponse } from '@apollo/client/testing';
jest.mock('@vegaprotocol/environment', () => ({
useEnvironment: () => ({
VEGA_DOCS_URL: 'https://docs.vega.xyz',
}),
...jest.requireActual('@vegaprotocol/environment'),
DocsLinks: {
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals',
},
}));
const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
@@ -1,10 +1,7 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useForm } from 'react-hook-form';
import {
createDocsLinks,
suitableForSyntaxHighlighter,
} from '@vegaprotocol/utils';
import { suitableForSyntaxHighlighter } from '@vegaprotocol/utils';
import { useNetworkParams } from '@vegaprotocol/network-parameters';
import {
getClosingTimestamp,
@@ -12,7 +9,7 @@ import {
useProposalSubmit,
doesValueEquateToParam,
} from '@vegaprotocol/proposals';
import { useEnvironment } from '@vegaprotocol/environment';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
import {
ProposalFormDescription,
ProposalFormSubheader,
@@ -84,7 +81,7 @@ export const ProposeNetworkParameter = () => {
loading: networkParamsLoading,
error: networkParamsError,
} = useNetworkParams();
const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { VEGA_EXPLORER_URL } = useEnvironment();
const { t } = useTranslation();
const {
register,
@@ -175,17 +172,13 @@ export const ProposeNetworkParameter = () => {
userAction={ProposalUserAction.CREATE}
/>
{VEGA_DOCS_URL && (
{DocsLinks && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}
target="_blank"
>{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink>
</p>
)}
@@ -10,9 +10,10 @@ import type { MockedResponse } from '@apollo/client/testing';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
jest.mock('@vegaprotocol/environment', () => ({
useEnvironment: () => ({
VEGA_DOCS_URL: 'https://docs.vega.xyz',
}),
...jest.requireActual('@vegaprotocol/environment'),
DocsLinks: {
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals',
},
}));
const newAssetNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
@@ -7,8 +7,8 @@ import {
useProposalSubmit,
doesValueEquateToParam,
} from '@vegaprotocol/proposals';
import { useEnvironment } from '@vegaprotocol/environment';
import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
import { validateJson } from '@vegaprotocol/utils';
import {
NetworkParams,
useNetworkParams,
@@ -53,7 +53,7 @@ export const ProposeNewAsset = () => {
NetworkParams.governance_proposal_asset_minProposerBalance,
NetworkParams.spam_protection_proposal_min_tokens,
]);
const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { VEGA_EXPLORER_URL } = useEnvironment();
const { t } = useTranslation();
const {
register,
@@ -142,17 +142,13 @@ export const ProposeNewAsset = () => {
userAction={ProposalUserAction.CREATE}
/>
{VEGA_DOCS_URL && (
{DocsLinks && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}
target="_blank"
>{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink>
</p>
)}
@@ -10,9 +10,10 @@ import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
jest.mock('@vegaprotocol/environment', () => ({
useEnvironment: () => ({
VEGA_DOCS_URL: 'https://docs.vega.xyz',
}),
...jest.requireActual('@vegaprotocol/environment'),
DocsLinks: {
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals',
},
}));
const newMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
@@ -6,8 +6,8 @@ import {
useProposalSubmit,
doesValueEquateToParam,
} from '@vegaprotocol/proposals';
import { useEnvironment } from '@vegaprotocol/environment';
import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
import { validateJson } from '@vegaprotocol/utils';
import {
NetworkParams,
useNetworkParams,
@@ -51,7 +51,7 @@ export const ProposeNewMarket = () => {
NetworkParams.governance_proposal_market_minProposerBalance,
NetworkParams.spam_protection_proposal_min_tokens,
]);
const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { VEGA_EXPLORER_URL } = useEnvironment();
const { t } = useTranslation();
const {
register,
@@ -132,17 +132,13 @@ export const ProposeNewMarket = () => {
userAction={ProposalUserAction.CREATE}
/>
{VEGA_DOCS_URL && (
{DocsLinks && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}
target="_blank"
>{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink>
</p>
)}
@@ -2,12 +2,11 @@ import Routes from '../../routes';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/environment';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
import { Heading } from '../../../components/heading';
import { createDocsLinks } from '@vegaprotocol/utils';
export const Propose = () => {
const { VEGA_DOCS_URL, VEGA_EXPLORER_URL } = useEnvironment();
const { VEGA_EXPLORER_URL } = useEnvironment();
const { t } = useTranslation();
return (
@@ -15,14 +14,11 @@ export const Propose = () => {
<section className="pb-6">
<Heading title={t('NewProposal')} />
<div className="text-sm">
{VEGA_DOCS_URL && (
{DocsLinks && (
<p>
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}
target="_blank"
>
{createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE} target="_blank">
{DocsLinks.PROPOSALS_GUIDE}
</ExternalLink>
</p>
)}
@@ -1,6 +1,6 @@
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useEnvironment } from '@vegaprotocol/environment';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
import { Heading } from '../../../../components/heading';
import {
AsyncRenderer,
@@ -9,7 +9,7 @@ import {
InputError,
TextArea,
} from '@vegaprotocol/ui-toolkit';
import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
import { validateJson } from '@vegaprotocol/utils';
import {
NetworkParams,
useNetworkParams,
@@ -39,7 +39,7 @@ export const ProposeRaw = () => {
NetworkParams.governance_proposal_freeform_minProposerBalance,
NetworkParams.spam_protection_proposal_min_tokens,
]);
const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { VEGA_EXPLORER_URL } = useEnvironment();
const { t } = useTranslation();
const {
register,
@@ -79,14 +79,11 @@ export const ProposeRaw = () => {
spamProtectionMin={params.spam_protection_proposal_min_tokens}
/>
{VEGA_DOCS_URL && (
{DocsLinks && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}
target="_blank"
>
{createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE} target="_blank">
{DocsLinks.PROPOSALS_GUIDE}
</ExternalLink>
</p>
)}
@@ -10,9 +10,10 @@ import type { MockedResponse } from '@apollo/client/testing';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
jest.mock('@vegaprotocol/environment', () => ({
useEnvironment: () => ({
VEGA_DOCS_URL: 'https://docs.vega.xyz',
}),
...jest.requireActual('@vegaprotocol/environment'),
DocsLinks: {
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals',
},
}));
const updateAssetNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
@@ -6,8 +6,8 @@ import {
useProposalSubmit,
doesValueEquateToParam,
} from '@vegaprotocol/proposals';
import { useEnvironment } from '@vegaprotocol/environment';
import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
import { validateJson } from '@vegaprotocol/utils';
import {
NetworkParams,
useNetworkParams,
@@ -51,7 +51,7 @@ export const ProposeUpdateAsset = () => {
NetworkParams.governance_proposal_updateAsset_minProposerBalance,
NetworkParams.spam_protection_proposal_min_tokens,
]);
const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { VEGA_EXPLORER_URL } = useEnvironment();
const { t } = useTranslation();
const {
register,
@@ -132,17 +132,13 @@ export const ProposeUpdateAsset = () => {
userAction={ProposalUserAction.CREATE}
/>
{VEGA_DOCS_URL && (
{DocsLinks && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}
target="_blank"
>{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink>
</p>
)}
@@ -7,8 +7,8 @@ import {
getEnactmentTimestamp,
useProposalSubmit,
} from '@vegaprotocol/proposals';
import { useEnvironment } from '@vegaprotocol/environment';
import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
import { validateJson } from '@vegaprotocol/utils';
import {
NetworkParams,
useNetworkParams,
@@ -98,7 +98,7 @@ export const ProposeUpdateMarket = () => {
const [selectedMarket, setSelectedMarket] = useState<string | undefined>(
undefined
);
const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { VEGA_EXPLORER_URL } = useEnvironment();
const { t } = useTranslation();
const {
register,
@@ -181,17 +181,13 @@ export const ProposeUpdateMarket = () => {
userAction={ProposalUserAction.CREATE}
/>
{VEGA_DOCS_URL && (
{DocsLinks && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink
href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}
target="_blank"
>{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink>
</p>
)}
@@ -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}
@@ -25,7 +25,7 @@ const mockData = {
amount: '0',
percentageOfTotal: '0',
},
ACCOUNT_TYPE_FEES_LIQUIDITY: {
ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES: {
amount: '0',
percentageOfTotal: '0',
},
@@ -55,7 +55,9 @@ describe('EpochIndividualRewardsTable', () => {
expect(
getByTestId('ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES')
).toBeInTheDocument();
expect(getByTestId('ACCOUNT_TYPE_FEES_LIQUIDITY')).toBeInTheDocument();
expect(
getByTestId('ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES')
).toBeInTheDocument();
expect(
getByTestId('ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS')
).toBeInTheDocument();
@@ -24,7 +24,7 @@ describe('generateEpochIndividualRewardsList', () => {
};
const reward3: RewardFieldsFragment = {
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '200',
percentageOfTotal: '0.2',
receivedAt: new Date(),
@@ -34,7 +34,7 @@ describe('generateEpochIndividualRewardsList', () => {
};
const reward4: RewardFieldsFragment = {
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '100',
percentageOfTotal: '0.1',
receivedAt: new Date(),
@@ -44,7 +44,7 @@ describe('generateEpochIndividualRewardsList', () => {
};
const reward5: RewardFieldsFragment = {
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '150',
percentageOfTotal: '0.15',
receivedAt: new Date(),
@@ -105,7 +105,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
@@ -172,7 +172,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
amount: '200',
percentageOfTotal: '0.2',
},
@@ -202,7 +202,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
@@ -237,7 +237,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
amount: '100',
percentageOfTotal: '0.1',
},
@@ -285,7 +285,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
amount: '150',
percentageOfTotal: '0.15',
},
@@ -320,7 +320,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
amount: '200',
percentageOfTotal: '0.2',
},
@@ -350,7 +350,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
@@ -395,7 +395,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
amount: '100',
percentageOfTotal: '0.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(
@@ -29,7 +29,7 @@ const rewardsList = [
amount: '0',
},
{
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '0',
},
{
@@ -78,7 +78,9 @@ describe('EpochTotalRewardsTable', () => {
expect(
getByTestId('ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES')
).toBeInTheDocument();
expect(getByTestId('ACCOUNT_TYPE_FEES_LIQUIDITY')).toBeInTheDocument();
expect(
getByTestId('ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES')
).toBeInTheDocument();
expect(
getByTestId('ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS')
).toBeInTheDocument();
@@ -162,9 +162,10 @@ describe('generateEpochAssetRewardsList', () => {
},
],
[
AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
{
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '0',
},
],
@@ -227,7 +228,7 @@ describe('generateEpochAssetRewardsList', () => {
node: {
epoch: 2,
assetId: '1',
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '5',
},
},
@@ -287,9 +288,10 @@ describe('generateEpochAssetRewardsList', () => {
},
],
[
AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
{
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '0',
},
],
@@ -351,9 +353,10 @@ describe('generateEpochAssetRewardsList', () => {
},
],
[
AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
{
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '5',
},
],
@@ -416,7 +419,7 @@ describe('generateEpochAssetRewardsList', () => {
node: {
epoch: 2,
assetId: '1',
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '6',
},
},
@@ -424,7 +427,7 @@ describe('generateEpochAssetRewardsList', () => {
node: {
epoch: 2,
assetId: '1',
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '27',
},
},
@@ -497,9 +500,10 @@ describe('generateEpochAssetRewardsList', () => {
},
],
[
AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
{
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '33',
},
],
@@ -561,9 +565,10 @@ describe('generateEpochAssetRewardsList', () => {
},
],
[
AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
{
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '0',
},
],
@@ -637,9 +642,10 @@ describe('generateEpochAssetRewardsList', () => {
},
],
[
AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
{
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '0',
},
],
@@ -11,7 +11,6 @@ import {
ExternalLink,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { createDocsLinks } from '@vegaprotocol/utils';
import {
useNetworkParams,
NetworkParams,
@@ -21,7 +20,7 @@ import { EpochCountdown } from '../../../components/epoch-countdown';
import { Heading, SubHeading } from '../../../components/heading';
import { EpochIndividualRewards } from '../epoch-individual-rewards/epoch-individual-rewards';
import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch';
import { useEnvironment } from '@vegaprotocol/environment';
import { DocsLinks } from '@vegaprotocol/environment';
import { ConnectToSeeRewards } from '../connect-to-see-rewards';
import { EpochTotalRewards } from '../epoch-total-rewards/epoch-total-rewards';
@@ -29,7 +28,6 @@ type RewardsView = 'total' | 'individual';
export const RewardsPage = () => {
const { t } = useTranslation();
const { VEGA_DOCS_URL } = useEnvironment();
const { pubKey, pubKeys } = useVegaWallet();
const [toggleRewardsView, setToggleRewardsView] =
useState<RewardsView>('total');
@@ -68,9 +66,9 @@ export const RewardsPage = () => {
<Heading title={t('pageTitleRewards')} />
<p className="mb-12">
{t('rewardsIntro')}{' '}
{VEGA_DOCS_URL && (
{DocsLinks && (
<ExternalLink
href={createDocsLinks(VEGA_DOCS_URL).REWARDS_GUIDE}
href={DocsLinks.REWARDS_GUIDE}
target="_blank"
data-testid="rewards-guide-link"
className="text-white"
@@ -26,7 +26,7 @@ export const RowAccountTypes = {
columnTitle: 'rewardsColPriceMakingHeader',
description: 'rewardsColPriceMakingTooltip',
},
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
columnTitle: 'rewardsColLiquidityProvisionHeader',
description: 'rewardsColLiquidityProvisionTooltip',
},
+7 -4
View File
@@ -229,7 +229,7 @@ const redirects = [
const routerConfig = [
{
path: Routes.HOME,
element: <LazyHome name="Token" />,
element: <LazyHome name="Home" />,
},
{
path: Routes.PROPOSALS,
@@ -291,7 +291,7 @@ const routerConfig = [
// Not lazy as loaded when a user first hits the site
children: [
{
element: <Home name="Home" />,
element: <Home name="Token" />,
index: true,
},
{
@@ -325,8 +325,11 @@ const routerConfig = [
},
],
},
{ path: 'associate', element: <LazyStakingAssociate /> },
{ path: 'disassociate', element: <LazyStakingDisassociate /> },
{ path: 'associate', element: <LazyStakingAssociate name="Associate" /> },
{
path: 'disassociate',
element: <LazyStakingDisassociate name="Disassociate" />,
},
],
},
{
@@ -1,12 +1,14 @@
import { useEthereumConfig } from '@vegaprotocol/web3';
import { StakingWalletsContainer } from './components/staking-wallets-container/staking-wallets-container';
import { StakingWalletsContainer } from './components/staking-wallets-container';
import { AssociatePage } from './associate-page';
import { AssociatePageNoVega } from './associate-page-no-vega';
import { Heading } from '../../../components/heading';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useDocumentTitle } from '../../../hooks/use-document-title';
import type { RouteChildProps } from '../../index';
export const AssociateContainer = () => {
export const AssociateContainer = ({ name }: RouteChildProps) => {
useDocumentTitle(name);
const { t } = useTranslation();
const { config } = useEthereumConfig();
@@ -39,7 +39,7 @@ jest.mock('@vegaprotocol/wallet', () => ({
}));
const renderComponent = () => {
return render(<Disassociate />);
return render(<Disassociate name="Disassociate" />);
};
describe('Disassociate', () => {
@@ -3,10 +3,12 @@ import { useWeb3React } from '@web3-react/core';
import { EthConnectPrompt } from '../../../components/eth-connect-prompt';
import { DisassociatePage } from './components/disassociate-page';
import { Heading } from '../../../components/heading';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useDocumentTitle } from '../../../hooks/use-document-title';
import type { RouteChildProps } from '../../index';
export const DisassociateContainer = () => {
export const DisassociateContainer = ({ name }: RouteChildProps) => {
useDocumentTitle(name);
const { t } = useTranslation();
const { account } = useWeb3React();
const { pubKey } = useVegaWallet();
@@ -1,14 +1,12 @@
import { useTranslation } from 'react-i18next';
import { EpochData } from './epoch-data';
import { useEnvironment } from '@vegaprotocol/environment';
import { DocsLinks } from '@vegaprotocol/environment';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { createDocsLinks } from '@vegaprotocol/utils';
import { Heading } from '../../../components/heading';
import React from 'react';
export const Staking = () => {
const { t } = useTranslation();
const { VEGA_DOCS_URL } = useEnvironment();
return (
<>
@@ -16,9 +14,9 @@ export const Staking = () => {
<section>
<p className="mb-12">
{t('stakingIntro')}{' '}
{VEGA_DOCS_URL && (
{DocsLinks && (
<ExternalLink
href={createDocsLinks(VEGA_DOCS_URL).STAKING_GUIDE}
href={DocsLinks.STAKING_GUIDE}
target="_blank"
data-testid="staking-guide-link"
className="text-white"
@@ -34,6 +34,7 @@ import {
formatNumberPercentage,
toBigNum,
} from '@vegaprotocol/utils';
import { VALIDATOR_LOGO_MAP } from './logo-map';
interface CanonisedConsensusNodeProps {
id: string;
@@ -130,20 +131,25 @@ export const ConsensusValidatorsTable = ({
const gridRef = useRef<AgGridReact | null>(null);
const thirdOfTotalStake = useMemo(
() => new BigNumber(totalStake).dividedBy(3),
[totalStake]
);
const nodes = useMemo(() => {
if (!data) return [];
let canonisedNodes = data
.sort((a, b) => {
const aVotingPower = new BigNumber(a.rankingScore.votingPower);
const bVotingPower = new BigNumber(b.rankingScore.votingPower);
return bVotingPower.minus(aVotingPower).toNumber();
const aStakedTotal = new BigNumber(a.stakedTotal);
const bStakedTotal = new BigNumber(b.stakedTotal);
return bStakedTotal.minus(aStakedTotal).toNumber();
})
.map((node, index) => {
const votingPowerRanking = index + 1;
const stakedTotalRanking = index + 1;
return {
...node,
votingPowerRanking,
stakedTotalRanking,
};
})
.map(
@@ -156,11 +162,16 @@ export const ConsensusValidatorsTable = ({
stakedTotal,
rankingScore: { stakeScore, votingPower },
pendingStake,
votingPowerRanking,
stakedTotalRanking,
stakedByUser,
pendingUserStake,
userStakeShare,
}) => {
const logo = VALIDATOR_LOGO_MAP[id]
? VALIDATOR_LOGO_MAP[id]
: avatarUrl
? avatarUrl
: null;
const {
rawValidatorScore: previousEpochValidatorScore,
performanceScore: previousEpochPerformanceScore,
@@ -169,9 +180,9 @@ export const ConsensusValidatorsTable = ({
return {
id,
[ValidatorFields.RANKING_INDEX]: votingPowerRanking,
[ValidatorFields.RANKING_INDEX]: stakedTotalRanking,
[ValidatorFields.VALIDATOR]: {
avatarUrl,
avatarUrl: logo,
name,
},
[ValidatorFields.STAKE]: stakedTotal,
@@ -210,7 +221,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,
};
}
@@ -230,16 +241,12 @@ export const ConsensusValidatorsTable = ({
return canonisedNodes;
}
// The point of identifying and hiding the group that could halt the network
// is that we assume the top 1/3 of stake is held by considerably less than
// 1/3 of the validators and we really want people not to stake any more to
// that group, because we want to make it require as many difference
// validators to collude as possible to halt the network, so we hide them.
const { topThird, remaining } = canonisedNodes.reduce(
(acc, node) => {
if (acc.cumulativeScore < 100 / 3) {
acc.cumulativeScore += parseFloat(
node[ValidatorFields.NORMALISED_VOTING_POWER]
if (acc.cumulativeScore.isLessThan(thirdOfTotalStake)) {
const prev = acc.cumulativeScore;
acc.cumulativeScore = prev.plus(
new BigNumber(node[ValidatorFields.STAKE])
);
acc.topThird.push(node);
return acc;
@@ -247,61 +254,70 @@ export const ConsensusValidatorsTable = ({
acc.remaining.push(node);
return acc;
},
{ topThird: [], remaining: [], cumulativeScore: 0 } as {
{ topThird: [], remaining: [], cumulativeScore: new BigNumber(0) } as {
topThird: CanonisedConsensusNodeProps[];
remaining: CanonisedConsensusNodeProps[];
cumulativeScore: number;
cumulativeScore: BigNumber;
}
);
// We need to combine the top third of validators into a single node, this
// way the combined values can be passed to AG grid so that the combined cell's
// values are correct for ordering.
const combinedTopThird = topThird.reduce((acc, node) => {
const {
[ValidatorFields.STAKE]: stake,
[ValidatorFields.STAKE_SHARE]: stakeShare,
[ValidatorFields.PENDING_STAKE]: pendingStake,
[ValidatorFields.NORMALISED_VOTING_POWER]: normalisedVotingPower,
[ValidatorFields.TOTAL_PENALTIES]: totalPenalties,
} = node;
const combinedTopThird = topThird.reduce(
(acc, node) => {
const {
[ValidatorFields.STAKE]: stake,
[ValidatorFields.STAKE_SHARE]: stakeShare,
[ValidatorFields.PENDING_STAKE]: pendingStake,
[ValidatorFields.NORMALISED_VOTING_POWER]: normalisedVotingPower,
[ValidatorFields.TOTAL_PENALTIES]: totalPenalties,
} = node;
const {
[ValidatorFields.STAKE]: accStake,
[ValidatorFields.STAKE_SHARE]: accStakeShare,
[ValidatorFields.PENDING_STAKE]: accPendingStake,
[ValidatorFields.NORMALISED_VOTING_POWER]: accNormalisedVotingPower,
[ValidatorFields.TOTAL_PENALTIES]: accTotalPenalties,
} = acc;
const {
[ValidatorFields.STAKE]: accStake,
[ValidatorFields.STAKE_SHARE]: accStakeShare,
[ValidatorFields.PENDING_STAKE]: accPendingStake,
[ValidatorFields.NORMALISED_VOTING_POWER]: accNormalisedVotingPower,
[ValidatorFields.TOTAL_PENALTIES]: accTotalPenalties,
} = acc;
return {
...acc,
[ValidatorFields.STAKE]: toBigNum(accStake, decimals)
.plus(toBigNum(stake, decimals))
.toString(),
[ValidatorFields.STAKE_SHARE]: formatNumberPercentage(
new BigNumber(parseFloat(accStakeShare)).plus(
new BigNumber(parseFloat(stakeShare))
return {
...acc,
[ValidatorFields.STAKE]: new BigNumber(accStake)
.plus(new BigNumber(stake))
.toString(),
[ValidatorFields.STAKE_SHARE]: formatNumberPercentage(
new BigNumber(parseFloat(accStakeShare)).plus(
new BigNumber(parseFloat(stakeShare))
),
2
),
2
),
[ValidatorFields.PENDING_STAKE]: toBigNum(accPendingStake, decimals)
.plus(toBigNum(pendingStake, decimals))
.toString(),
[ValidatorFields.NORMALISED_VOTING_POWER]: formatNumberPercentage(
new BigNumber(parseFloat(accNormalisedVotingPower)).plus(
new BigNumber(parseFloat(normalisedVotingPower))
[ValidatorFields.PENDING_STAKE]: toBigNum(accPendingStake, decimals)
.plus(toBigNum(pendingStake, decimals))
.toString(),
[ValidatorFields.NORMALISED_VOTING_POWER]: formatNumberPercentage(
new BigNumber(parseFloat(accNormalisedVotingPower)).plus(
new BigNumber(parseFloat(normalisedVotingPower))
),
2
),
2
),
[ValidatorFields.TOTAL_PENALTIES]: formatNumberPercentage(
new BigNumber(parseFloat(accTotalPenalties)).plus(
new BigNumber(parseFloat(totalPenalties))
[ValidatorFields.TOTAL_PENALTIES]: formatNumberPercentage(
new BigNumber(parseFloat(accTotalPenalties)).plus(
new BigNumber(parseFloat(totalPenalties))
),
2
),
2
),
};
});
};
},
{
[ValidatorFields.STAKE]: '0',
[ValidatorFields.STAKE_SHARE]: '0',
[ValidatorFields.PENDING_STAKE]: '0',
[ValidatorFields.NORMALISED_VOTING_POWER]: '0',
[ValidatorFields.TOTAL_PENALTIES]: '0',
}
);
return [
{
@@ -316,6 +332,7 @@ export const ConsensusValidatorsTable = ({
decimals,
hideTopThird,
previousEpochData,
thirdOfTotalStake,
totalStake,
validatorsView,
]);
@@ -346,6 +363,7 @@ export const ConsensusValidatorsTable = ({
headerTooltip: t('StakeDescription').toString(),
cellRenderer: TotalStakeRenderer,
width: 120,
sort: 'desc',
},
{
field: ValidatorFields.PENDING_STAKE,
@@ -367,7 +385,6 @@ export const ConsensusValidatorsTable = ({
headerTooltip: t('NormalisedVotingPowerDescription').toString(),
cellRenderer: VotingPowerRenderer,
width: 200,
sort: 'desc',
},
{
field: ValidatorFields.TOTAL_PENALTIES,

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