Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77ae0408e0 | ||
|
|
51f076bca8 | ||
|
|
22eefb5d0d | ||
|
|
145bb13848 | ||
|
|
87000a33d5 | ||
|
|
6371199537 | ||
|
|
75cb48a4b9 | ||
|
|
933878acf5 | ||
|
|
5995df8e68 | ||
|
|
4d5b7cf5e7 | ||
|
|
e1f60446f0 | ||
|
|
7f271069ab | ||
|
|
1ae7af5d59 | ||
|
|
b9c5d9ee96 | ||
|
|
ae1768a669 | ||
|
|
a1bfe7f1a7 | ||
|
|
b0f1aeedae | ||
|
|
da59e83b19 | ||
|
|
360624282d | ||
|
|
9e054c7c85 | ||
|
|
911e927ab0 | ||
|
|
e4a51061a3 | ||
|
|
c15051d457 | ||
|
|
460ccdb3a2 | ||
|
|
959fb9cf62 | ||
|
|
2c7170d0e9 | ||
|
|
8a28a4afd4 | ||
|
|
48072eb4c3 | ||
|
|
ae92ff2580 | ||
|
|
c99b7fbb02 | ||
|
|
a9b1767a42 | ||
|
|
3cd60ad3e6 | ||
|
|
2f4bc9484b | ||
|
|
d4f5643799 | ||
|
|
72c49e62a8 | ||
|
|
0988edc129 | ||
|
|
4d266963ae | ||
|
|
ec12c7ecfd | ||
|
|
045dace274 | ||
|
|
2346b2f1a0 | ||
|
|
31859cb779 | ||
|
|
20507ecdd1 | ||
|
|
13ba661ea0 | ||
|
|
03f5b9fc7b | ||
|
|
b9fed65615 | ||
|
|
d671e51edf | ||
|
|
4f3295d3fc | ||
|
|
7be7bab1ca | ||
|
|
61380f06b9 | ||
|
|
c113856d63 | ||
|
|
90884beee8 | ||
|
|
d9d2e428f4 | ||
|
|
ba8d54b761 | ||
|
|
dce4edb943 | ||
|
|
b468a0fd84 | ||
|
|
c98fbe7453 | ||
|
|
d20928ec06 | ||
|
|
289559fe4f | ||
|
|
071fd6128c | ||
|
|
554d07a31b | ||
|
|
48b62380b0 | ||
|
|
a9f4604bad | ||
|
|
54f2519a62 | ||
|
|
260e7bf9cf | ||
|
|
83db796cdb | ||
|
|
2f6800212e | ||
|
|
77e16ad1b7 | ||
|
|
dcce983842 | ||
|
|
53ce9bfaa2 | ||
|
|
6ae34d296f | ||
|
|
ee2aafb99c | ||
|
|
e8fdb63323 | ||
|
|
3df9270b57 | ||
|
|
ab26667265 | ||
|
|
9fc6b255c8 | ||
|
|
91f61d8bb5 | ||
|
|
bbfda65bcc | ||
|
|
45f2e926c3 | ||
|
|
ccadbc2cb6 | ||
|
|
7128acb7b8 | ||
|
|
357bdaa4f3 | ||
|
|
f3fe43724e | ||
|
|
03f5ca3096 | ||
|
|
03d19349af | ||
|
|
1f53b2cbb5 | ||
|
|
b010c98346 | ||
|
|
29d57af2ba | ||
|
|
fd7940c4cf | ||
|
|
8ab7ac8414 | ||
|
|
ce7cd03c3e | ||
|
|
dfa3355a68 | ||
|
|
8553904d81 | ||
|
|
10afa3c1d4 | ||
|
|
ecd362615e | ||
|
|
0d9bd67465 | ||
|
|
b5aa92f007 | ||
|
|
2955790be1 | ||
|
|
89ff9309e9 | ||
|
|
e1df3489f6 | ||
|
|
f0fa9ca5e1 | ||
|
|
9d92477c2a | ||
|
|
b1a198be85 | ||
|
|
5fb385d43c | ||
|
|
8014b279fe | ||
|
|
f4bde57294 | ||
|
|
2e5ff07824 | ||
|
|
7accb42793 | ||
|
|
9229e7f686 | ||
|
|
b570b9c4c1 | ||
|
|
ed78261aad | ||
|
|
363ca9c6e1 | ||
|
|
bafd3c384c | ||
|
|
3b830abe07 | ||
|
|
1787e22b69 | ||
|
|
f105133959 | ||
|
|
aff0e46d23 | ||
|
|
7e4aafcb77 | ||
|
|
643d5408cd | ||
|
|
2a2af3ba9c | ||
|
|
8d4e4a1228 | ||
|
|
84068c8081 | ||
|
|
e1185b9a96 | ||
|
|
f390448c24 | ||
|
|
70943c523c | ||
|
|
dcb79e70d3 | ||
|
|
cc72cdbe16 | ||
|
|
3fd1817e1e | ||
|
|
b483c13f81 | ||
|
|
bde7a9fbf9 | ||
|
|
5fd93ee9c8 | ||
|
|
9ac07fd98a | ||
|
|
9c975ed822 | ||
|
|
138bf7da00 | ||
|
|
3e7fe517f3 | ||
|
|
4f960d09b3 | ||
|
|
351a5aaf96 | ||
|
|
381d9011a0 | ||
|
|
861760b4f9 | ||
|
|
18f1c0014c | ||
|
|
474543d91b | ||
|
|
662753c74b | ||
|
|
71aa8882bc | ||
|
|
ecfbccf8ed | ||
|
|
2aad6b1a14 | ||
|
|
8463d371ad | ||
|
|
0914e7ce4b | ||
|
|
7c82144f37 | ||
|
|
17ad0cc975 | ||
|
|
eb58dcd350 | ||
|
|
c8c0bf2cc0 | ||
|
|
00579fcb1f | ||
|
|
5e13173efb | ||
|
|
3a6c0554cc | ||
|
|
d4b237a6ba | ||
|
|
5e765f3172 | ||
|
|
5ef8c11e6e | ||
|
|
84bae12def | ||
|
|
c5b22f5bfb | ||
|
|
071dcc61a8 | ||
|
|
ce0ccdfebc | ||
|
|
f1524d3fcd | ||
|
|
82e5128ba1 | ||
|
|
2bfc3abd15 | ||
|
|
bd3b557f3d | ||
|
|
c899da52c2 | ||
|
|
b9c4057ce5 | ||
|
|
d02feee5c6 | ||
|
|
6d1ab36a72 | ||
|
|
5690fe91a8 | ||
|
|
1daae6a233 | ||
|
|
cd35a34246 | ||
|
|
77ada964b5 | ||
|
|
d7404bcd76 | ||
|
|
91862701a7 | ||
|
|
0136efd78f | ||
|
|
c3a4954d0d | ||
|
|
528fd96721 | ||
|
|
155923ff8e | ||
|
|
5f7e60d22f | ||
|
|
351583ce8f | ||
|
|
88bf58b974 | ||
|
|
fcda4d7fcd | ||
|
|
b1b0039b33 | ||
|
|
6854980b32 | ||
|
|
d8aac83f2d | ||
|
|
5c17045752 | ||
|
|
8863209342 | ||
|
|
462bf6b8b5 | ||
|
|
e0b701e05a | ||
|
|
eca212eee0 | ||
|
|
c25858037a | ||
|
|
081f5b48e2 | ||
|
|
e306d1c9fa | ||
|
|
737dec4f5f | ||
|
|
6780b2f618 | ||
|
|
08dc60086d | ||
|
|
4595db07b0 | ||
|
|
c3da31146f | ||
|
|
b6a9faaa7c | ||
|
|
477d523582 | ||
|
|
a2b112d898 | ||
|
|
621ba497e8 | ||
|
|
a6964bc3cf | ||
|
|
48a9998441 | ||
|
|
bd1bd94ee7 | ||
|
|
2fdebbd694 | ||
|
|
96af8035d0 | ||
|
|
051e3da366 | ||
|
|
af5e666d5e | ||
|
|
333a7d6206 | ||
|
|
cb6b988466 | ||
|
|
806ea74f7e | ||
|
|
8693f3bfd8 | ||
|
|
58c6652e29 | ||
|
|
e8adff25c4 | ||
|
|
3e594a783a | ||
|
|
1f7117075b | ||
|
|
4c850ecead | ||
|
|
19f8fa909e | ||
|
|
8214858685 | ||
|
|
868f8e21dc | ||
|
|
6ead84b57c | ||
|
|
3975477985 | ||
|
|
8a0c15ac11 | ||
|
|
a4f512f946 |
@@ -3,3 +3,7 @@ apps/**/node_modules/*
|
||||
tmp/*
|
||||
.dockerignore
|
||||
dockerfiles
|
||||
node_modules
|
||||
.git
|
||||
.github
|
||||
.vscode
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: Release
|
||||
about: A template to outline the steps needed to for a successful release of our frontend apps
|
||||
title: 'Release [add dapp version]-core-[add core version]'
|
||||
labels:
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
### Tasks
|
||||
|
||||
- [ ] Review [link to core release](xxx)
|
||||
- [ ] Tag frontend-monorepo
|
||||
- [ ] Create release and generate release notes
|
||||
- [ ] Run `@smoke` tests
|
||||
- [ ] Run `@regression` tests
|
||||
- [ ] Run `@slow` tests
|
||||
- [ ] Explorative testing of key flows
|
||||
- [ ] Set `release/[network]` to tagged commit
|
||||
- [ ] Verify builds (on Netlify and Fleek) are successful
|
||||
- [ ] Verify build has been deployed
|
||||
- [ ] Smoke testing on deployed app
|
||||
@@ -13,7 +13,7 @@ env:
|
||||
|
||||
jobs:
|
||||
add_issue:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: 'Add issue to project board'
|
||||
run: |
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
name: CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- release/*
|
||||
- develop
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- ready_for_review
|
||||
- reopened
|
||||
- edited
|
||||
- synchronize
|
||||
jobs:
|
||||
node-modules:
|
||||
runs-on: ubuntu-22.04
|
||||
name: 'Cache yarn modules'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Cache node modules
|
||||
id: cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
|
||||
# comment out "restore-keys" if you need to rebuild yarn from 0
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cache-node-modules-
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||
cache: yarn
|
||||
|
||||
- name: yarn install
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --pure-lockfile
|
||||
|
||||
lint-pr-title:
|
||||
needs: node-modules
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
name: Verify PR title
|
||||
uses: ./.github/workflows/lint-pr.yml
|
||||
secrets: inherit
|
||||
|
||||
lint-test-build:
|
||||
timeout-minutes: 35
|
||||
needs: node-modules
|
||||
runs-on: ubuntu-22.04
|
||||
name: '(CI) lint + unit test + build'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||
cache: yarn
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
|
||||
|
||||
- name: Derive appropriate SHAs for base and head for `nx affected` commands
|
||||
uses: nrwl/nx-set-shas@v3
|
||||
with:
|
||||
main-branch-name: develop
|
||||
|
||||
- name: Check formatting
|
||||
run: yarn nx format:check
|
||||
|
||||
- name: Lint affected
|
||||
run: yarn nx affected:lint --max-warnings=0
|
||||
|
||||
- name: Build affected spec
|
||||
run: yarn nx affected --target=build-spec
|
||||
|
||||
- name: Test affected
|
||||
run: yarn nx affected:test
|
||||
|
||||
- name: Build affected
|
||||
run: yarn nx affected:build || (yarn install && yarn nx affected:build)
|
||||
|
||||
# See affected apps
|
||||
- name: See affected apps
|
||||
run: |
|
||||
echo ">>>> debug"
|
||||
echo "NX Version: $nx_version"
|
||||
echo "NX_BASE: ${{ env.NX_BASE }}"
|
||||
echo "NX_HEAD: ${{ env.NX_HEAD }}"
|
||||
echo ">>>> eof debug"
|
||||
|
||||
affected="$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects)"
|
||||
echo -n "Affected projects: $affected"
|
||||
|
||||
projects_e2e=""
|
||||
if [[ $affected == *"governance"* ]]; then projects_e2e+='"governance-e2e" '; fi
|
||||
if [[ $affected == *"trading"* ]]; then projects_e2e+='"trading-e2e" '; fi
|
||||
if [[ $affected == *"explorer"* ]]; then projects_e2e+='"explorer-e2e" '; fi
|
||||
if [[ -z "$projects_e2e" ]]; then projects_e2e+='"governance-e2e" "trading-e2e" "explorer-e2e" '; fi
|
||||
projects_e2e=${projects_e2e%?}
|
||||
projects_e2e=[${projects_e2e// /,}]
|
||||
echo PROJECTS_E2E=$projects_e2e >> $GITHUB_ENV
|
||||
echo PROJECTS=$(echo $projects_e2e | sed 's|-e2e||g') >> $GITHUB_ENV
|
||||
|
||||
outputs:
|
||||
projects: ${{ env.PROJECTS }}
|
||||
projects-e2e: ${{ env.PROJECTS_E2E }}
|
||||
|
||||
cypress:
|
||||
needs: lint-test-build
|
||||
name: '(CI) cypress'
|
||||
if: ${{ needs.lint-test-build.outputs.projects != '[]' }}
|
||||
uses: ./.github/workflows/cypress-run.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
projects: ${{ needs.lint-test-build.outputs.projects-e2e }}
|
||||
tags: '@smoke @regression'
|
||||
|
||||
publish-dist:
|
||||
needs: lint-test-build
|
||||
name: '(CD) publish dist'
|
||||
if: ${{ needs.lint-test-build.outputs.projects != '[]' }}
|
||||
uses: ./.github/workflows/publish-dist.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
projects: ${{ needs.lint-test-build.outputs.projects }}
|
||||
|
||||
dist-check:
|
||||
runs-on: ubuntu-latest
|
||||
needs: publish-dist
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
name: '(CD) comment preview links'
|
||||
steps:
|
||||
- name: Find Comment
|
||||
uses: peter-evans/find-comment@v2
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body-includes: Previews
|
||||
|
||||
- name: Inject slug/short variables
|
||||
if: ${{ steps.fc.outputs.comment-id == 0 }}
|
||||
uses: rlespinasse/github-slug-action@v4
|
||||
with:
|
||||
prefix: CI_
|
||||
|
||||
- name: Create comment
|
||||
if: ${{ steps.fc.outputs.comment-id == 0 }}
|
||||
uses: peter-evans/create-or-update-comment@v3
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
Previews
|
||||
- explorer https://explorer.${{ env.CI_GITHUB_REF_NAME }}.vega.rocks
|
||||
- trading https://trading.${{ env.CI_GITHUB_REF_NAME }}.vega.rocks
|
||||
- governance https://governance.${{ env.CI_GITHUB_REF_NAME }}.vega.rocks
|
||||
|
||||
cypress-check:
|
||||
name: '(CI) cypress - check'
|
||||
runs-on: ubuntu-latest
|
||||
needs: cypress
|
||||
steps:
|
||||
- run: echo Done!
|
||||
|
||||
# Report single result at the end, to avoid mess with required checks in PR
|
||||
cypress-result:
|
||||
if: ${{ always() }}
|
||||
needs: cypress
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- run: |
|
||||
result="${{ needs.cypress.result }}"
|
||||
if [[ $result == "success" || $result == "skipped" ]]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
@@ -13,7 +13,7 @@ on:
|
||||
jobs:
|
||||
cypress-run:
|
||||
name: Run Cypress Trading tests -- live environment
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
name: Cypress tests - PR
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
- main
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
jobs:
|
||||
pr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout frontend mono repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
|
||||
- name: Remove package.json & yarn.lock to avoid installing everything
|
||||
run: rm package.json yarn.lock
|
||||
|
||||
- name: Install nx
|
||||
run: yarn add nx
|
||||
|
||||
# Check SHAs
|
||||
- name: Derive appropriate SHAs for base and head for `nx affected` commands
|
||||
uses: nrwl/nx-set-shas@v2
|
||||
with:
|
||||
main-branch-name: ${{ github.base_ref || github.ref_name }}
|
||||
set-environment-variables-for-job: true
|
||||
|
||||
# See affected apps
|
||||
- name: See affected apps
|
||||
run: |
|
||||
affected=$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects)
|
||||
echo -n "Affected projects: $affected"
|
||||
projects=""
|
||||
if [[ $affected == *"governance"* ]]; then projects+='"governance-e2e" '; fi
|
||||
if [[ $affected == *"trading"* ]]; then projects+='"trading-e2e" '; fi
|
||||
if [[ $affected == *"explorer"* ]]; then projects+='"explorer-e2e" '; fi
|
||||
if [[ -z "$projects" ]]; then projects+='"governance-e2e" "trading-e2e" "explorer-e2e" '; fi
|
||||
projects=${projects%?}
|
||||
projects=[${projects// /,}]
|
||||
echo PROJECTS=$projects >> $GITHUB_ENV
|
||||
|
||||
outputs:
|
||||
projects: ${{ env.PROJECTS }}
|
||||
|
||||
run:
|
||||
needs: pr
|
||||
if: ${{ needs.pr.outputs.projects != '[]' }}
|
||||
uses: ./.github/workflows/cypress-run.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
projects: ${{ needs.pr.outputs.projects }}
|
||||
tags: '@smoke @regression'
|
||||
|
||||
# Report single result at the end, to avoid mess with required checks in PR
|
||||
result:
|
||||
if: ${{ always() }}
|
||||
needs: run
|
||||
runs-on: ubuntu-latest
|
||||
name: Cypress result
|
||||
steps:
|
||||
- run: |
|
||||
result="${{ needs.run.result }}"
|
||||
if [[ $result == "success" || $result == "skipped" ]]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,3 +1,4 @@
|
||||
name: (CI) Cypress Run
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
@@ -17,8 +18,9 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
project: ${{ fromJSON(inputs.projects) }}
|
||||
name: ${{ matrix.project }}
|
||||
runs-on: self-hosted-runner
|
||||
timeout-minutes: 30
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
# Checks if skip cache was requested
|
||||
- name: Set skip-nx-cache flag
|
||||
@@ -91,3 +93,9 @@ jobs:
|
||||
with:
|
||||
name: logs-${{ matrix.project }}
|
||||
path: /home/runner/.vegacapsule/testnet/logs
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: test-report-${{ matrix.project }}
|
||||
path: frontend-monorepo/apps/trading-e2e/cypress/reports
|
||||
|
||||
@@ -8,22 +8,25 @@ on:
|
||||
jobs:
|
||||
master:
|
||||
name: Generate Queries
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Use Node.js 16
|
||||
id: Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16.15.1
|
||||
node-version-file: '.nvmrc'
|
||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||
cache: yarn
|
||||
|
||||
- name: Install root dependencies
|
||||
run: yarn install
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Generate queries
|
||||
run: node ./scripts/get-queries.js
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: queries
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: Verify PR title
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
lint_pr:
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||
cache: yarn
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
|
||||
|
||||
- name: Check PR title
|
||||
run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
name: Verify PR title
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, ready_for_review, reopened, edited, synchronize]
|
||||
jobs:
|
||||
lint_pr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Use Node.js 16
|
||||
id: Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16.15.1
|
||||
- name: Install root dependencies
|
||||
run: yarn install
|
||||
- name: Check PR title
|
||||
run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
master:
|
||||
name: Generate Queries
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
name: (CD) Publish docker + s3
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
projects:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
publish-dist:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
app: ${{ fromJSON(inputs.projects) }}
|
||||
name: ${{ matrix.app }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
id: quemu
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Available platforms
|
||||
run: echo ${{ steps.qemu.outputs.platforms }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||
cache: yarn
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
|
||||
|
||||
# https://docs.github.com/en/actions/learn-github-actions/contexts
|
||||
- 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="stagnet3"
|
||||
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
|
||||
if [[ "${{ env.ENV_NAME }}" != "ops-vega" ]]; then
|
||||
flags="--env=${{ env.ENV_NAME }}"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${{ matrix.app }}" = "trading" ]; then
|
||||
yarn nx export trading $flags || (yarn install && yarn nx export trading $flags)
|
||||
DIST_LOCATION=dist/apps/trading/exported
|
||||
else
|
||||
yarn nx build ${{ matrix.app }} $flags || (yarn install && yarn nx build ${{ matrix.app }} $flags)
|
||||
DIST_LOCATION=dist/apps/${{ matrix.app }}
|
||||
fi
|
||||
mv $DIST_LOCATION dist-result
|
||||
tree dist-result
|
||||
|
||||
- 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 }}
|
||||
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' ) }}
|
||||
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
|
||||
|
||||
echo "List html directory"
|
||||
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local sh -c 'apk add --update tree; tree .'
|
||||
|
||||
- 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
|
||||
uses: docker/build-push-action@v3
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
with:
|
||||
context: .
|
||||
file: ${{ env.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 }}
|
||||
|
||||
# bucket creation in github.com/vegaprotocol/terraform//frontend
|
||||
- name: Publish dist to s3
|
||||
uses: jakejarvis/s3-sync-action@master
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
with:
|
||||
args: --acl private --follow-symlinks --delete
|
||||
env:
|
||||
AWS_S3_BUCKET: ${{ env.BUCKET_NAME }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_REGION: 'eu-west-1'
|
||||
SOURCE_DIR: 'dist-result'
|
||||
|
||||
- name: Add preview label
|
||||
uses: actions-ecosystem/action-add-labels@v1
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
with:
|
||||
labels: ${{ matrix.app }}-preview
|
||||
number: ${{ github.event.number }}
|
||||
@@ -1,89 +0,0 @@
|
||||
name: Publish docker containers
|
||||
|
||||
'on':
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
master:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
# to be replaced by nix: https://github.com/vegaprotocol/frontend-monorepo/blob/develop/tools/ipfs-deploy.js#L106-L108
|
||||
matrix:
|
||||
app: ${{ fromJson('["explorer"]') }}
|
||||
name: Build the ${{ matrix.app }} image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
id: quemu
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Available platforms
|
||||
run: echo ${{ steps.qemu.outputs.platforms }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
# https://docs.github.com/en/actions/learn-github-actions/contexts
|
||||
# https://github.com/actions/checkout#Checkout-pull-request-HEAD-commit-instead-of-merge-commit
|
||||
- name: Determine Docker Image tag
|
||||
id: tags
|
||||
run: |
|
||||
npmVersion=$(cat .nvmrc | head -n 1)
|
||||
versionTag=${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || github.event.pull_request.head.sha }}
|
||||
echo ::set-output name=npmVersion::${npmVersion}
|
||||
echo ::set-output name=version::${versionTag}
|
||||
|
||||
- name: Print config
|
||||
run: |
|
||||
git rev-parse --verify HEAD
|
||||
git status
|
||||
echo "steps.tags.outputs.version=${{ steps.tags.outputs.version }}"
|
||||
|
||||
- name: Build and export to local Docker
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
file: dockerfiles/Dockerfile
|
||||
build-args: |
|
||||
APP=${{ matrix.app }}
|
||||
NODE_VERSION=${{ steps.tags.outputs.npmVersion }}
|
||||
load: true
|
||||
tags: |
|
||||
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
|
||||
|
||||
- name: Sanity check docker image
|
||||
run: |
|
||||
echo "Check .env file"
|
||||
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat .env
|
||||
echo "Check ipfs-hash"
|
||||
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat ipfs-hash
|
||||
echo "List html directory"
|
||||
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local ls -lah
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
file: dockerfiles/Dockerfile
|
||||
build-args: |
|
||||
APP=${{ matrix.app }}
|
||||
NODE_VERSION=${{ steps.tags.outputs.npmVersion }}
|
||||
tags: |
|
||||
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:latest
|
||||
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:${{ steps.tags.outputs.version }}
|
||||
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||
@@ -8,6 +8,7 @@ on:
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- announcements
|
||||
- ui-toolkit
|
||||
- react-helpers
|
||||
- tailwindcss-config
|
||||
@@ -18,29 +19,27 @@ on:
|
||||
jobs:
|
||||
publish:
|
||||
name: Build & Publish - Tag
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: 'read'
|
||||
actions: 'read'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: User Node.js 16
|
||||
id: Node
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16.15.1
|
||||
- name: Restore node_modules from cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: node_modules-${{ hashFiles('**/yarn.lock') }}
|
||||
node-version-file: '.nvmrc'
|
||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||
cache: yarn
|
||||
|
||||
- name: Install root dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build project
|
||||
run: yarn nx build ${{inputs.project}}
|
||||
|
||||
- name: Publish project to @vegaprotocol
|
||||
uses: JS-DevTools/npm-publish@v1
|
||||
with:
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
name: Unit tests & build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
- main
|
||||
pull_request:
|
||||
jobs:
|
||||
pr:
|
||||
name: Test and lint - PR
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: 'read'
|
||||
actions: 'read'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Derive appropriate SHAs for base and head for `nx affected` commands
|
||||
uses: nrwl/nx-set-shas@v2
|
||||
with:
|
||||
main-branch-name: ${{ github.base_ref }}
|
||||
- name: Use Node.js 16
|
||||
id: Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16.15.1
|
||||
- name: Restore node_modules from cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: node_modules-${{ hashFiles('**/yarn.lock') }}
|
||||
- name: Install root dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Check formatting
|
||||
run: yarn nx format:check
|
||||
- name: Lint affected
|
||||
run: yarn nx affected:lint --max-warnings=0
|
||||
- name: Test affected
|
||||
run: yarn nx affected:test
|
||||
- name: Build affected
|
||||
run: yarn nx affected:build
|
||||
- name: Build affected spec
|
||||
run: yarn nx affected --target=build-spec
|
||||
@@ -46,3 +46,6 @@ cypress.env.json
|
||||
|
||||
# Next.js
|
||||
.next
|
||||
|
||||
#cypress
|
||||
/apps/trading-e2e/cypress/reports/
|
||||
|
||||
@@ -21,6 +21,7 @@ module.exports = defineConfig({
|
||||
chromeWebSecurity: false,
|
||||
viewportWidth: 1440,
|
||||
viewportHeight: 900,
|
||||
testIsolation: false,
|
||||
},
|
||||
env: {
|
||||
environment: 'CUSTOM',
|
||||
|
||||
@@ -6,18 +6,10 @@ context('Home Page', function () {
|
||||
describe('Stats page', { tags: '@smoke' }, function () {
|
||||
const statsValue = '[data-testid="stats-value"]';
|
||||
|
||||
it('Should show connected environment', function () {
|
||||
const deployedEnv = Cypress.env('environment').toUpperCase();
|
||||
cy.get('[data-testid="stats-environment"]').should(
|
||||
'have.text',
|
||||
`/ ${deployedEnv}`
|
||||
);
|
||||
});
|
||||
|
||||
it('should show connected environment stats', function () {
|
||||
const statTitles = {
|
||||
0: 'Status',
|
||||
1: 'Height',
|
||||
1: 'Block height',
|
||||
2: 'Uptime',
|
||||
3: 'Total nodes',
|
||||
4: 'Total staked',
|
||||
@@ -36,27 +28,27 @@ context('Home Page', function () {
|
||||
|
||||
cy.get('[data-testid="stats-title"]')
|
||||
.each(($list, index) => {
|
||||
cy.wrap($list).should('have.text', statTitles[index]);
|
||||
cy.wrap($list).should('contain.text', statTitles[index]);
|
||||
})
|
||||
.then(($list) => {
|
||||
cy.wrap($list).should('have.length', 16);
|
||||
});
|
||||
|
||||
cy.get(statsValue).eq(0).should('have.text', 'CONNECTED');
|
||||
cy.get(statsValue).eq(0).should('contain.text', 'CONNECTED');
|
||||
cy.get(statsValue).eq(1).should('not.be.empty');
|
||||
cy.get(statsValue)
|
||||
.eq(2)
|
||||
.invoke('text')
|
||||
.should('match', /\d+d \d+h \d+m \d+s/i);
|
||||
cy.get(statsValue).eq(3).should('have.text', '2');
|
||||
cy.get(statsValue).eq(3).should('contain.text', '2');
|
||||
cy.get(statsValue)
|
||||
.eq(4)
|
||||
.invoke('text')
|
||||
.should('match', /\d+\.\d\d(?!\d)/i);
|
||||
cy.get(statsValue).eq(5).should('have.text', '0');
|
||||
cy.get(statsValue).eq(6).should('have.text', '0');
|
||||
cy.get(statsValue).eq(7).should('have.text', '0');
|
||||
cy.get(statsValue).eq(8).should('have.text', '0');
|
||||
cy.get(statsValue).eq(5).should('contain.text', '0');
|
||||
cy.get(statsValue).eq(6).should('contain.text', '0');
|
||||
cy.get(statsValue).eq(7).should('contain.text', '0');
|
||||
cy.get(statsValue).eq(8).should('contain.text', '0');
|
||||
cy.get(statsValue).eq(9).should('not.be.empty');
|
||||
cy.get(statsValue).eq(10).should('not.be.empty');
|
||||
cy.get(statsValue).eq(11).should('not.be.empty');
|
||||
@@ -86,75 +78,4 @@ context('Home Page', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Git info', function () {
|
||||
it('git info is rendered on the footer of the page', function () {
|
||||
cy.getByTestId('git-info').within(() => {
|
||||
cy.getByTestId('git-network-data').within(() => {
|
||||
cy.contains('Reading network data from').should('be.visible');
|
||||
cy.get('span').should('have.text', Cypress.env('networkQueryUrl'));
|
||||
cy.getByTestId('link').should('be.visible');
|
||||
});
|
||||
|
||||
cy.getByTestId('git-eth-data').within(() => {
|
||||
cy.contains('Reading Ethereum data from').should('be.visible');
|
||||
cy.get('span').should('have.text', Cypress.env('ethUrl'));
|
||||
});
|
||||
|
||||
cy.getByTestId('git-commit-hash').within(() => {
|
||||
cy.contains('Version/commit hash:').should('be.visible');
|
||||
cy.getByTestId('link').should('have.text', Cypress.env('commitHash'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Search bar', function () {
|
||||
it('Successful search for specific id by block id', function () {
|
||||
const blockId = '973624';
|
||||
search(blockId);
|
||||
cy.url().should('include', blockId);
|
||||
});
|
||||
|
||||
it('Successful search for specific id by tx hash', function () {
|
||||
const txHash =
|
||||
'9ED3718AA8308E7E08EC588EE7AADAF49711D2138860D8914B4D81A2054D9FB8';
|
||||
search(txHash);
|
||||
cy.url().should('include', txHash);
|
||||
});
|
||||
|
||||
it('Successful search for specific id by tx id', function () {
|
||||
const txId =
|
||||
'0x61DCCEBB955087F50D0B85382DAE138EDA9631BF1A4F92E563D528904AA38898';
|
||||
search(txId);
|
||||
cy.url().should('include', txId);
|
||||
});
|
||||
|
||||
it('Error message displayed when invalid search by wrong string length', function () {
|
||||
search('9ED3718AA8308E7E08EC588EE7AADAF497D2138860D8914B4D81A2054D9FB8');
|
||||
validateSearchError("Something doesn't look right");
|
||||
});
|
||||
|
||||
it('Error message displayed when invalid search by invalid hash', function () {
|
||||
search(
|
||||
'9ED3718AA8308E7E08ECht8EE753DAF49711D2138860D8914B4D81A2054D9FB8'
|
||||
);
|
||||
validateSearchError('Transaction is not hexadecimal');
|
||||
});
|
||||
|
||||
it('Error message displayed when searching empty field', function () {
|
||||
cy.get('[data-testid="search"]').clear();
|
||||
cy.get('[data-testid="search-button"]').click();
|
||||
validateSearchError('Search required');
|
||||
});
|
||||
|
||||
function search(searchTxt) {
|
||||
cy.get('[data-testid="search"]').clear().type(searchTxt);
|
||||
cy.get('[data-testid="search-button"]').click();
|
||||
}
|
||||
|
||||
function validateSearchError(expectedError) {
|
||||
cy.get('[data-testid="search-error"]').should('have.text', expectedError);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
NX_TENDERMINT_URL=https://tm.n00.stagnet3.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n00.stagnet3.vega.xyz/websocket
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet3/vegawallet-stagnet3.toml
|
||||
NX_VEGA_NETWORKS='{"SANDBOX":"https://sandbox.explorer.vega.xyz","STAGNET1":"https://stagnet1.token.vega.xyz", "STAGNET3":"https://stagnet3.explorer.vega.xyz","VALIDATOR_TESTNET":"https://validator-testnet.fairground.wtf","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
|
||||
NX_VEGA_NETWORKS='{"STAGNET1":"https://stagnet1.token.vega.xyz", "STAGNET3":"https://stagnet3.explorer.vega.xyz","VALIDATOR_TESTNET":"https://validator-testnet.fairground.wtf","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
|
||||
NX_VEGA_ENV=STAGNET3
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
|
||||
@@ -3,6 +3,7 @@ NX_TENDERMINT_URL=http://localhost:26617
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
|
||||
NX_VEGA_ENV=CUSTOM
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS=1
|
||||
|
||||
@@ -8,3 +8,4 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://dev.token.vega.xyz
|
||||
NX_VEGA_URL=https://api.devnet1.vega.xyz/graphql
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
@@ -6,3 +6,4 @@ NX_VEGA_ENV=MAINNET
|
||||
NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/rest/
|
||||
NX_ETHERSCAN_URL=https://etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://token.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
|
||||
@@ -1,19 +0,0 @@
|
||||
# App configuration variables
|
||||
NX_TENDERMINT_URL=https://tm.be.mainnet-mirror.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.xyz/websocket
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/mainnet-mirror/vegawallet-mainnet-mirror.toml
|
||||
NX_VEGA_ENV=MIRROR
|
||||
NX_BLOCK_EXPLORER=https://be.mainnet-mirror.vega.xyz/rest/
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://mainnet-mirror.token.vega.xyz
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS=1
|
||||
NX_EXPLORER_GENESIS=1
|
||||
NX_EXPLORER_GOVERNANCE=1
|
||||
NX_EXPLORER_NETWORK_PARAMETERS=1
|
||||
NX_EXPLORER_PARTIES=1
|
||||
NX_EXPLORER_VALIDATORS=1
|
||||
NX_EXPLORER_MARKETS=0
|
||||
NX_EXPLORER_ORACLES=0
|
||||
NX_EXPLORER_TXS_LIST=1
|
||||
@@ -1,11 +0,0 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_ENV=SANDBOX
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/sandbox/vegawallet-sandbox.toml
|
||||
NX_VEGA_EXPLORER_URL=https://sandbox.explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_TENDERMINT_URL=https://tm.sandbox.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.sandbox.vega.xyz/websocket
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://sandbox.token.vega.xyz
|
||||
@@ -11,3 +11,4 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
NX_BLOCK_EXPLORER=https://be.stagnet1.vega.xyz/rest
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://stagnet1.token.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
@@ -5,3 +5,4 @@ NX_VEGA_ENV=STAGNET3
|
||||
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
|
||||
NX_VEGA_GOVERNANCE_URL=https://stagnet3.token.vega.xyz
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases/
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
@@ -8,3 +8,4 @@ NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://token.fairground.wtf
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
@@ -5,8 +5,9 @@ NX_VEGA_URL=https://api-validators-testnet.vega.rocks/graphql
|
||||
NX_VEGA_REST=https://api-validators-testnet.vega.rocks/
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
|
||||
NX_VEGA_GOVERNANCE_URL=https://validator-testnet.governance.vega.xyz
|
||||
NX_TENDERMINT_URL=https://tm-be.validators-testnet.vega.rocks
|
||||
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/
|
||||
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/rest
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket
|
||||
|
||||
@@ -4,3 +4,4 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26607/websocket
|
||||
NX_VEGA_ENV=CUSTOM
|
||||
NX_BLOCK_EXPLORER=
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
|
||||
|
||||
@@ -1,87 +1,21 @@
|
||||
import { NetworkLoader, useInitializeEnv } from '@vegaprotocol/environment';
|
||||
import { Header } from './components/header';
|
||||
import { Main } from './components/main';
|
||||
import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-websocket-provider';
|
||||
import { Footer } from './components/footer/footer';
|
||||
import {
|
||||
AnnouncementBanner,
|
||||
ExternalLink,
|
||||
Icon,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
AssetDetailsDialog,
|
||||
useAssetDetailsDialogStore,
|
||||
} from '@vegaprotocol/assets';
|
||||
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { DEFAULT_CACHE_CONFIG } from '@vegaprotocol/apollo-client';
|
||||
import classNames from 'classnames';
|
||||
import { useState } from 'react';
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
import { router } from './routes/router-config';
|
||||
|
||||
const DialogsContainer = () => {
|
||||
const { isOpen, id, trigger, asJson, setOpen } = useAssetDetailsDialogStore();
|
||||
return (
|
||||
<AssetDetailsDialog
|
||||
assetId={id}
|
||||
trigger={trigger || null}
|
||||
asJson={asJson}
|
||||
open={isOpen}
|
||||
onChange={setOpen}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const MainnetSimAd = () => {
|
||||
const [shouldDisplayBanner, setShouldDisplayBanner] = useState<boolean>(true);
|
||||
|
||||
// Return an empty div so that the grid layout in _app.page.ts
|
||||
// renders correctly
|
||||
if (!shouldDisplayBanner) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<AnnouncementBanner>
|
||||
<div className="grid grid-cols-[auto_1fr] gap-4 font-alpha calt uppercase text-center text-lg text-white">
|
||||
<button
|
||||
className="flex items-center"
|
||||
onClick={() => setShouldDisplayBanner(false)}
|
||||
>
|
||||
<Icon name="cross" className="w-6 h-6" ariaLabel="dismiss" />
|
||||
</button>
|
||||
<div>
|
||||
<span className="pr-4">Mainnet sim 3 is live!</span>
|
||||
<ExternalLink href="https://fairground.wtf/">Learn more</ExternalLink>
|
||||
</div>
|
||||
</div>
|
||||
</AnnouncementBanner>
|
||||
);
|
||||
};
|
||||
const splashLoading = (
|
||||
<Splash>
|
||||
<Loader />
|
||||
</Splash>
|
||||
);
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<TendermintWebsocketProvider>
|
||||
<NetworkLoader cache={DEFAULT_CACHE_CONFIG}>
|
||||
<div
|
||||
className={classNames(
|
||||
'max-w-[1500px] min-h-[100vh]',
|
||||
'mx-auto my-0',
|
||||
'grid grid-rows-[auto_1fr_auto] grid-cols-1',
|
||||
'border-vega-light-200 dark:border-vega-dark-200 lg:border-l lg:border-r',
|
||||
'antialiased text-black dark:text-white',
|
||||
'overflow-hidden relative'
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<MainnetSimAd />
|
||||
<Header />
|
||||
</div>
|
||||
<div>
|
||||
<Main />
|
||||
</div>
|
||||
<div>
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
<DialogsContainer />
|
||||
<RouterProvider router={router} fallbackElement={splashLoading} />
|
||||
</NetworkLoader>
|
||||
</TendermintWebsocketProvider>
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@ export const Footer = () => {
|
||||
const [nodeSwitcherOpen, setNodeSwitcherOpen] = useState(false);
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const showFullFeedbackLabel = useMemo(
|
||||
() => ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize),
|
||||
() => ['md', 'lg', 'xl', 'xxl', 'xxxl'].includes(screenSize),
|
||||
[screenSize]
|
||||
);
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ jest.mock('../search', () => ({
|
||||
}));
|
||||
|
||||
const renderComponent = () => (
|
||||
<MemoryRouter>
|
||||
<MemoryRouter initialEntries={['/txs']}>
|
||||
<Header />
|
||||
</MemoryRouter>
|
||||
);
|
||||
@@ -24,6 +24,7 @@ describe('Header', () => {
|
||||
|
||||
expect(screen.getByTestId('navigation')).toHaveTextContent('Explorer');
|
||||
});
|
||||
|
||||
it('should render search', () => {
|
||||
render(renderComponent());
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { matchPath, useLocation } from 'react-router-dom';
|
||||
import { matchPath, useLocation, useMatch } from 'react-router-dom';
|
||||
import {
|
||||
ThemeSwitcher,
|
||||
Navigation,
|
||||
@@ -13,23 +13,26 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import { Routes } from '../../routes/route-names';
|
||||
import { NetworkSwitcher } from '@vegaprotocol/environment';
|
||||
import type { Navigable } from '../../routes/router-config';
|
||||
import routerConfig from '../../routes/router-config';
|
||||
import { isNavigable } from '../../routes/router-config';
|
||||
import { routerConfig } from '../../routes/router-config';
|
||||
import { useMemo } from 'react';
|
||||
import compact from 'lodash/compact';
|
||||
import { Search } from '../search';
|
||||
|
||||
const routeToNavigationItem = (r: Navigable) => (
|
||||
<NavigationItem key={r.name}>
|
||||
<NavigationLink to={r.path}>{r.text}</NavigationLink>
|
||||
<NavigationItem key={r.handle.name}>
|
||||
<NavigationLink to={r.path}>{r.handle.text}</NavigationLink>
|
||||
</NavigationItem>
|
||||
);
|
||||
|
||||
export const Header = () => {
|
||||
const isHome = Boolean(useMatch(Routes.HOME));
|
||||
const pages = routerConfig[0].children || [];
|
||||
const mainItems = compact(
|
||||
[Routes.TX, Routes.BLOCKS, Routes.ORACLES, Routes.VALIDATORS].map((n) =>
|
||||
routerConfig.find((r) => r.path === n)
|
||||
pages.find((r) => r.path === n)
|
||||
)
|
||||
);
|
||||
).filter(isNavigable);
|
||||
|
||||
const groupedItems = compact(
|
||||
[
|
||||
@@ -39,8 +42,8 @@ export const Header = () => {
|
||||
Routes.GOVERNANCE,
|
||||
Routes.NETWORK_PARAMETERS,
|
||||
Routes.GENESIS,
|
||||
].map((n) => routerConfig.find((r) => r.path === n))
|
||||
);
|
||||
].map((n) => pages.find((r) => r.path === n))
|
||||
).filter(isNavigable);
|
||||
|
||||
const { pathname } = useLocation();
|
||||
|
||||
@@ -67,7 +70,7 @@ export const Header = () => {
|
||||
actions={
|
||||
<>
|
||||
<ThemeSwitcher />
|
||||
<Search />
|
||||
{!isHome && <Search />}
|
||||
</>
|
||||
}
|
||||
onResize={(width, el) => {
|
||||
@@ -90,7 +93,7 @@ export const Header = () => {
|
||||
hide={[NavigationBreakpoint.Small, NavigationBreakpoint.Narrow]}
|
||||
>
|
||||
{mainItems.map(routeToNavigationItem)}
|
||||
{groupedItems && (
|
||||
{groupedItems && groupedItems.length > 0 && (
|
||||
<NavigationItem>
|
||||
<NavigationTrigger isActive={Boolean(isOnOther)}>
|
||||
{t('Other')}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { AppRouter } from '../../routes';
|
||||
|
||||
export const Main = () => {
|
||||
return (
|
||||
<main className="p-4">
|
||||
<AppRouter />
|
||||
</main>
|
||||
);
|
||||
};
|
||||
@@ -60,9 +60,11 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
|
||||
<>
|
||||
<MarketInfoTable
|
||||
noBorder={false}
|
||||
data={trigger}
|
||||
data={{
|
||||
maxValidPrice: trigger.maxValidPrice,
|
||||
minValidPrice: trigger.minValidPrice,
|
||||
}}
|
||||
decimalPlaces={market.decimalPlaces}
|
||||
omits={['referencePrice', '__typename']}
|
||||
/>
|
||||
<MarketInfoTable
|
||||
noBorder={false}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import React from 'react';
|
||||
|
||||
interface RouteErrorBoundaryProps {
|
||||
children: React.ReactElement;
|
||||
}
|
||||
|
||||
export class RouteErrorBoundary extends React.Component<
|
||||
RouteErrorBoundaryProps,
|
||||
{ hasError: boolean }
|
||||
> {
|
||||
constructor(props: RouteErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError() {
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
override componentDidCatch(error: Error) {
|
||||
console.log(`Error caught in App error boundary ${error.message}`, error);
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (this.state.hasError) {
|
||||
return <h1>{t('Something went wrong')}</h1>;
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
@@ -1,189 +1,70 @@
|
||||
import {
|
||||
detectTypeByFetching,
|
||||
detectTypeFromQuery,
|
||||
getSearchType,
|
||||
determineType,
|
||||
isBlock,
|
||||
isHexadecimal,
|
||||
isNetworkParty,
|
||||
isNonHex,
|
||||
SearchTypes,
|
||||
toHex,
|
||||
toNonHex,
|
||||
isHash,
|
||||
} from './detect-search';
|
||||
import { DATA_SOURCES } from '../../config';
|
||||
|
||||
global.fetch = jest.fn();
|
||||
|
||||
describe('Detect Search', () => {
|
||||
it("should detect that it's a hexadecimal", () => {
|
||||
const expected = true;
|
||||
const testString =
|
||||
'0x073ceaab59e5f2dd0561dec4883e7ee5bc7165cd4de34717a3ab8f2cbe3007f9';
|
||||
const actual = isHexadecimal(testString);
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
|
||||
it("should detect that it's not hexadecimal", () => {
|
||||
const expected = true;
|
||||
const testString =
|
||||
'073ceaab59e5f2dd0561dec4883e7ee5bc7165cd4de34717a3ab8f2cbe3007f9';
|
||||
const actual = isNonHex(testString);
|
||||
expect(actual).toBe(expected);
|
||||
it.each([
|
||||
['0000000000000000000000000000000000000000000000000000000000000000', true],
|
||||
['0000000000000000000000000000000000000000000000000000000000000001', true],
|
||||
[
|
||||
'LOOONG0000000000000000000000000000000000000000000000000000000000000000',
|
||||
false,
|
||||
],
|
||||
['xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', false],
|
||||
['something else', false],
|
||||
])("should detect that it's a hash", (input, expected) => {
|
||||
expect(isHash(input)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should detect that it's a network party", () => {
|
||||
const expected = true;
|
||||
const testString = 'network';
|
||||
const actual = isNetworkParty(testString);
|
||||
expect(actual).toBe(expected);
|
||||
expect(isNetworkParty('network')).toBe(true);
|
||||
expect(isNetworkParty('web')).toBe(false);
|
||||
});
|
||||
|
||||
it("should detect that it's a block", () => {
|
||||
const expected = true;
|
||||
const testString = '3188';
|
||||
const actual = isBlock(testString);
|
||||
expect(actual).toBe(expected);
|
||||
expect(isBlock('123')).toBe(true);
|
||||
expect(isBlock('x123')).toBe(false);
|
||||
});
|
||||
|
||||
it('should convert from non-hex to hex', () => {
|
||||
const expected = '0x123';
|
||||
const testString = '123';
|
||||
const actual = toHex(testString);
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
|
||||
it('should convert from hex to non-hex', () => {
|
||||
const expected = '123';
|
||||
const testString = '0x123';
|
||||
const actual = toNonHex(testString);
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
|
||||
it("should detect type client side from query if it's a hexadecimal", () => {
|
||||
const expected = [SearchTypes.Party, SearchTypes.Transaction];
|
||||
const testString =
|
||||
'0x4624293CFE3D8B67A0AB448BAFF8FBCF1A1B770D9D5F263761D3D6CBEA94D97F';
|
||||
const actual = detectTypeFromQuery(testString);
|
||||
expect(actual).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
it("should detect type client side from query if it's a non hex", () => {
|
||||
const expected = [SearchTypes.Party, SearchTypes.Transaction];
|
||||
const testString =
|
||||
'4624293CFE3D8B67A0AB448BAFF8FBCF1A1B770D9D5F263761D3D6CBEA94D97F';
|
||||
const actual = detectTypeFromQuery(testString);
|
||||
expect(actual).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
it("should detect type client side from query if it's a network party", () => {
|
||||
const expected = [SearchTypes.Party];
|
||||
const testString = 'network';
|
||||
const actual = detectTypeFromQuery(testString);
|
||||
expect(actual).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
it("should detect type client side from query if it's a block (number)", () => {
|
||||
const expected = [SearchTypes.Block];
|
||||
const testString = '23432';
|
||||
const actual = detectTypeFromQuery(testString);
|
||||
expect(actual).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
it("detectTypeByFetching should call fetch with non-hex query it's a transaction", async () => {
|
||||
const query = '0xabc';
|
||||
const type = SearchTypes.Transaction;
|
||||
// @ts-ignore issue related to polyfill
|
||||
fetch.mockImplementation(
|
||||
jest.fn(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
transaction: {
|
||||
hash: query,
|
||||
},
|
||||
}),
|
||||
})
|
||||
)
|
||||
);
|
||||
const result = await detectTypeByFetching(query);
|
||||
expect(fetch).toHaveBeenCalledWith(
|
||||
`${DATA_SOURCES.blockExplorerUrl}/transactions/${toNonHex(query)}`
|
||||
);
|
||||
expect(result).toBe(type);
|
||||
});
|
||||
|
||||
it("detectTypeByFetching should call fetch with non-hex query it's a party", async () => {
|
||||
const query = 'abc';
|
||||
const type = SearchTypes.Party;
|
||||
// @ts-ignore issue related to polyfill
|
||||
fetch.mockImplementation(
|
||||
jest.fn(() =>
|
||||
Promise.resolve({
|
||||
ok: false,
|
||||
})
|
||||
)
|
||||
);
|
||||
const result = await detectTypeByFetching(query);
|
||||
expect(result).toBe(type);
|
||||
});
|
||||
|
||||
it('getSearchType should return party from fetch response', async () => {
|
||||
const query =
|
||||
'0x4624293CFE3D8B67A0AB448BAFF8FBCF1A1B770D9D5F263761D3D6CBEA94D97F';
|
||||
const expected = SearchTypes.Party;
|
||||
// @ts-ignore issue related to polyfill
|
||||
fetch.mockImplementation(
|
||||
jest.fn(() =>
|
||||
Promise.resolve({
|
||||
ok: false,
|
||||
})
|
||||
)
|
||||
);
|
||||
const result = await getSearchType(query);
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('getSearchType should return transaction from fetch response', async () => {
|
||||
const query =
|
||||
'4624293CFE3D8B67A0AB448BAFF8FBCF1A1B770D9D5F263761D3D6CBEA94D97F';
|
||||
const expected = SearchTypes.Transaction;
|
||||
// @ts-ignore issue related to polyfill
|
||||
fetch.mockImplementation(
|
||||
jest.fn(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
transaction: {
|
||||
hash: query,
|
||||
},
|
||||
}),
|
||||
})
|
||||
)
|
||||
);
|
||||
const result = await getSearchType(query);
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('getSearchType should return undefined from transaction response', async () => {
|
||||
const query = 'u';
|
||||
const expected = undefined;
|
||||
const result = await getSearchType(query);
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('getSearchType should return block if query is number', async () => {
|
||||
const query = '123';
|
||||
const expected = SearchTypes.Block;
|
||||
const result = await getSearchType(query);
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('getSearchType should return party if query is network', async () => {
|
||||
const query = 'network';
|
||||
const expected = SearchTypes.Party;
|
||||
const result = await getSearchType(query);
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
it.each([
|
||||
[
|
||||
'0000000000000000000000000000000000000000000000000000000000000000',
|
||||
SearchTypes.Transaction,
|
||||
],
|
||||
[
|
||||
'0000000000000000000000000000000000000000000000000000000000000001',
|
||||
SearchTypes.Party,
|
||||
],
|
||||
['123', SearchTypes.Block],
|
||||
['network', SearchTypes.Party],
|
||||
['something else', SearchTypes.Unknown],
|
||||
])(
|
||||
"detectTypeByFetching should call fetch with non-hex query it's a transaction",
|
||||
async (input, type) => {
|
||||
// @ts-ignore issue related to polyfill
|
||||
fetch.mockImplementation(
|
||||
jest.fn(() =>
|
||||
Promise.resolve({
|
||||
ok:
|
||||
input ===
|
||||
'0000000000000000000000000000000000000000000000000000000000000000',
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
transaction: {
|
||||
hash: input,
|
||||
},
|
||||
}),
|
||||
})
|
||||
)
|
||||
);
|
||||
const result = await determineType(input);
|
||||
expect(result).toBe(type);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { remove0x } from '@vegaprotocol/utils';
|
||||
import { DATA_SOURCES } from '../../config';
|
||||
import type { BlockExplorerTransaction } from '../../routes/types/block-explorer-response';
|
||||
|
||||
@@ -6,15 +7,20 @@ export enum SearchTypes {
|
||||
Party = 'party',
|
||||
Block = 'block',
|
||||
Order = 'order',
|
||||
Unknown = 'unknown',
|
||||
}
|
||||
|
||||
export const TX_LENGTH = 64;
|
||||
export const HASH_LENGTH = 64;
|
||||
|
||||
export const isHash = (value: string) =>
|
||||
/[0-9a-fA-F]+/.test(remove0x(value)) &&
|
||||
remove0x(value).length === HASH_LENGTH;
|
||||
|
||||
export const isHexadecimal = (search: string) =>
|
||||
search.startsWith('0x') && search.length === 2 + TX_LENGTH;
|
||||
search.startsWith('0x') && search.length === 2 + HASH_LENGTH;
|
||||
|
||||
export const isNonHex = (search: string) =>
|
||||
!search.startsWith('0x') && search.length === TX_LENGTH;
|
||||
!search.startsWith('0x') && search.length === HASH_LENGTH;
|
||||
|
||||
export const isBlock = (search: string) => !Number.isNaN(Number(search));
|
||||
|
||||
@@ -23,121 +29,44 @@ export const isNetworkParty = (search: string) => search === 'network';
|
||||
export const toHex = (query: string) =>
|
||||
isHexadecimal(query) ? query : `0x${query}`;
|
||||
|
||||
export const toNonHex = (query: string) =>
|
||||
isNonHex(query) ? query : `${query.replace('0x', '')}`;
|
||||
export const toNonHex = remove0x;
|
||||
|
||||
export const detectTypeFromQuery = (
|
||||
query: string
|
||||
): SearchTypes[] | undefined => {
|
||||
const i = query.toLowerCase();
|
||||
|
||||
if (isHexadecimal(i) || isNonHex(i)) {
|
||||
return [SearchTypes.Party, SearchTypes.Transaction];
|
||||
} else if (isNetworkParty(i)) {
|
||||
return [SearchTypes.Party];
|
||||
} else if (isBlock(i)) {
|
||||
return [SearchTypes.Block];
|
||||
/**
|
||||
* Determine the type of the given query
|
||||
*/
|
||||
export const determineType = async (query: string): Promise<SearchTypes> => {
|
||||
const value = query.toLowerCase();
|
||||
if (isHash(value)) {
|
||||
// it can be either `SearchTypes.Party` or `SearchTypes.Transaction`
|
||||
if (await isTransactionHash(value)) {
|
||||
return SearchTypes.Transaction;
|
||||
} else {
|
||||
return SearchTypes.Party;
|
||||
}
|
||||
} else if (isNetworkParty(value)) {
|
||||
return SearchTypes.Party;
|
||||
} else if (isBlock(value)) {
|
||||
return SearchTypes.Block;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return SearchTypes.Unknown;
|
||||
};
|
||||
|
||||
export const detectTypeByFetching = async (
|
||||
query: string
|
||||
): Promise<SearchTypes | undefined> => {
|
||||
const hash = toNonHex(query);
|
||||
/**
|
||||
* Checks if given input is a transaction hash by querying the transactions
|
||||
* endpoint
|
||||
*/
|
||||
export const isTransactionHash = async (input: string): Promise<boolean> => {
|
||||
const hash = remove0x(input);
|
||||
const request = await fetch(
|
||||
`${DATA_SOURCES.blockExplorerUrl}/transactions/${hash}`
|
||||
);
|
||||
|
||||
if (request?.ok) {
|
||||
const body: BlockExplorerTransaction = await request.json();
|
||||
|
||||
if (body?.transaction) {
|
||||
return SearchTypes.Transaction;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return SearchTypes.Party;
|
||||
};
|
||||
|
||||
// Code commented out because the current solution to detect a hex is temporary (by process of elimination)
|
||||
// export const detectTypeByFetching = async (
|
||||
// query: string,
|
||||
// type: SearchTypes
|
||||
// ): Promise<SearchTypes | undefined> => {
|
||||
// const TYPES = [SearchTypes.Party, SearchTypes.Transaction];
|
||||
//
|
||||
// if (!TYPES.includes(type)) {
|
||||
// throw new Error('Search type provided not recognised');
|
||||
// }
|
||||
//
|
||||
// if (type === SearchTypes.Transaction) {
|
||||
// const hash = toNonHex(query);
|
||||
// const request = await fetch(
|
||||
// `${DATA_SOURCES.blockExplorerUrl}/transactions/${hash}`
|
||||
// );
|
||||
//
|
||||
// if (request?.ok) {
|
||||
// const body: BlockExplorerTransaction = await request.json();
|
||||
//
|
||||
// if (body?.transaction) {
|
||||
// return SearchTypes.Transaction;
|
||||
// }
|
||||
// }
|
||||
// } else if (type === SearchTypes.Party) {
|
||||
// const party = toNonHex(query);
|
||||
//
|
||||
// const request = await fetch(
|
||||
// `${DATA_SOURCES.blockExplorerUrl}/transactions?limit=1&filters[tx.submitter]=${party}`
|
||||
// );
|
||||
//
|
||||
// if (request.ok) {
|
||||
// const body: BlockExplorerTransactions = await request.json();
|
||||
//
|
||||
// if (body?.transactions?.length) {
|
||||
// return SearchTypes.Party;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return undefined;
|
||||
// };
|
||||
|
||||
// export const getSearchType = async (
|
||||
// query: string
|
||||
// ): Promise<SearchTypes | undefined> => {
|
||||
// const searchTypes = detectTypeFromQuery(query);
|
||||
// const hasResults = searchTypes?.length;
|
||||
//
|
||||
// if (hasResults) {
|
||||
// if (hasResults > 1) {
|
||||
// const promises = searchTypes.map((type) =>
|
||||
// detectTypeByFetching(query, type)
|
||||
// );
|
||||
// const results = await Promise.all(promises);
|
||||
// return results.find((result) => result !== undefined);
|
||||
// }
|
||||
//
|
||||
// return searchTypes[0];
|
||||
// }
|
||||
//
|
||||
// return undefined;
|
||||
// };
|
||||
|
||||
export const getSearchType = async (
|
||||
query: string
|
||||
): Promise<SearchTypes | undefined> => {
|
||||
const searchTypes = detectTypeFromQuery(query);
|
||||
const hasResults = searchTypes?.length;
|
||||
|
||||
if (hasResults) {
|
||||
if (hasResults > 1) {
|
||||
return await detectTypeByFetching(query);
|
||||
}
|
||||
|
||||
return searchTypes[0];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -1,157 +1,82 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { Search } from './search';
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
import { SearchForm } from './search';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { Routes } from '../../routes/route-names';
|
||||
import { SearchTypes, getSearchType } from './detect-search';
|
||||
|
||||
global.fetch = jest.fn();
|
||||
const mockedNavigate = jest.fn();
|
||||
const mockGetSearchType = getSearchType as jest.MockedFunction<
|
||||
typeof getSearchType
|
||||
>;
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useNavigate: () => mockedNavigate,
|
||||
}));
|
||||
|
||||
jest.mock('./detect-search', () => ({
|
||||
...jest.requireActual('./detect-search'),
|
||||
getSearchType: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
mockedNavigate.mockClear();
|
||||
});
|
||||
|
||||
const renderComponent = () => (
|
||||
<MemoryRouter>
|
||||
<Search />
|
||||
</MemoryRouter>
|
||||
);
|
||||
const renderComponent = () =>
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<SearchForm />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const getInputs = () => ({
|
||||
input: screen.getByTestId('search'),
|
||||
button: screen.getByTestId('search-button'),
|
||||
});
|
||||
|
||||
describe('Search', () => {
|
||||
describe('SearchForm', () => {
|
||||
it('should render search input and button', () => {
|
||||
render(renderComponent());
|
||||
renderComponent();
|
||||
expect(screen.getByTestId('search')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('search-button')).toHaveTextContent('Search');
|
||||
});
|
||||
|
||||
it('should render error if input is not known', async () => {
|
||||
render(renderComponent());
|
||||
const { button, input } = getInputs();
|
||||
fireEvent.change(input, { target: { value: 'asd' } });
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(await screen.findByTestId('search-error')).toHaveTextContent(
|
||||
'Transaction type is not recognised'
|
||||
it.each([
|
||||
[
|
||||
Routes.TX,
|
||||
'0000000000000000000000000000000000000000000000000000000000000000',
|
||||
],
|
||||
[
|
||||
Routes.PARTIES,
|
||||
'0000000000000000000000000000000000000000000000000000000000000001',
|
||||
],
|
||||
[Routes.BLOCKS, '123'],
|
||||
[undefined, 'something else'],
|
||||
])('should redirect to %s', async (route, input) => {
|
||||
// @ts-ignore issue related to polyfill
|
||||
fetch.mockImplementation(
|
||||
jest.fn(() =>
|
||||
Promise.resolve({
|
||||
ok:
|
||||
input ===
|
||||
'0000000000000000000000000000000000000000000000000000000000000000',
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
transaction: {
|
||||
hash: input,
|
||||
},
|
||||
}),
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should render error if no input is given', async () => {
|
||||
render(renderComponent());
|
||||
const { button } = getInputs();
|
||||
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(await screen.findByTestId('search-error')).toHaveTextContent(
|
||||
'Search query required'
|
||||
);
|
||||
});
|
||||
|
||||
it('should redirect to transactions page', async () => {
|
||||
render(renderComponent());
|
||||
const { button, input } = getInputs();
|
||||
mockGetSearchType.mockResolvedValue(SearchTypes.Transaction);
|
||||
fireEvent.change(input, {
|
||||
target: {
|
||||
value:
|
||||
'0x1234567890123456789012345678901234567890123456789012345678901234',
|
||||
},
|
||||
renderComponent();
|
||||
await act(async () => {
|
||||
fireEvent.change(screen.getByTestId('search'), {
|
||||
target: {
|
||||
value: input,
|
||||
},
|
||||
});
|
||||
fireEvent.click(screen.getByTestId('search-button'));
|
||||
});
|
||||
|
||||
fireEvent.click(button);
|
||||
await waitFor(() => {
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
`${Routes.TX}/0x1234567890123456789012345678901234567890123456789012345678901234`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should redirect to transactions page without proceeding 0x', async () => {
|
||||
render(renderComponent());
|
||||
const { button, input } = getInputs();
|
||||
mockGetSearchType.mockResolvedValue(SearchTypes.Transaction);
|
||||
fireEvent.change(input, {
|
||||
target: {
|
||||
value:
|
||||
'1234567890123456789012345678901234567890123456789012345678901234',
|
||||
},
|
||||
});
|
||||
|
||||
fireEvent.click(button);
|
||||
await waitFor(() => {
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
`${Routes.TX}/0x1234567890123456789012345678901234567890123456789012345678901234`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should redirect to parties page', async () => {
|
||||
render(renderComponent());
|
||||
const { button, input } = getInputs();
|
||||
mockGetSearchType.mockResolvedValue(SearchTypes.Party);
|
||||
fireEvent.change(input, {
|
||||
target: {
|
||||
value:
|
||||
'0x1234567890123456789012345678901234567890123456789012345678901234',
|
||||
},
|
||||
});
|
||||
|
||||
fireEvent.click(button);
|
||||
await waitFor(() => {
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
`${Routes.PARTIES}/0x1234567890123456789012345678901234567890123456789012345678901234`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should redirect to parties page without proceeding 0x', async () => {
|
||||
render(renderComponent());
|
||||
const { button, input } = getInputs();
|
||||
mockGetSearchType.mockResolvedValue(SearchTypes.Party);
|
||||
fireEvent.change(input, {
|
||||
target: {
|
||||
value:
|
||||
'1234567890123456789012345678901234567890123456789012345678901234',
|
||||
},
|
||||
});
|
||||
|
||||
fireEvent.click(button);
|
||||
await waitFor(() => {
|
||||
expect(mockedNavigate).toBeCalledWith(
|
||||
`${Routes.PARTIES}/0x1234567890123456789012345678901234567890123456789012345678901234`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should redirect to blocks page if passed a number', async () => {
|
||||
render(renderComponent());
|
||||
const { button, input } = getInputs();
|
||||
mockGetSearchType.mockResolvedValue(SearchTypes.Block);
|
||||
fireEvent.change(input, {
|
||||
target: {
|
||||
value: '123',
|
||||
},
|
||||
});
|
||||
|
||||
fireEvent.click(button);
|
||||
await waitFor(() => {
|
||||
expect(mockedNavigate).toBeCalledWith(`${Routes.BLOCKS}/123`);
|
||||
expect(mockedNavigate).toBeCalledTimes(route ? 1 : 0);
|
||||
if (route) {
|
||||
// eslint-disable-next-line jest/no-conditional-expect
|
||||
expect(mockedNavigate).toBeCalledWith(`${route}/${input}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Button, Input, InputError } from '@vegaprotocol/ui-toolkit';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { getSearchType, SearchTypes, toHex } from './detect-search';
|
||||
import { Routes } from '../../routes/route-names';
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||
import classNames from 'classnames';
|
||||
import { remove0x } from '@vegaprotocol/utils';
|
||||
import { determineType, isBlock, isHash, SearchTypes } from './detect-search';
|
||||
|
||||
interface FormFields {
|
||||
search: string;
|
||||
@@ -30,101 +31,26 @@ const MagnifyingGlass = () => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
const Clear = () => (
|
||||
<svg
|
||||
className="w-3 h-3"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M11.3748 1.37478L1.37478 11.3748L0.625244 10.6252L10.6252 0.625244L11.3748 1.37478Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M1.37478 0.625244L11.3748 10.6252L10.6252 11.3748L0.625244 1.37478L1.37478 0.625244Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Search = () => {
|
||||
const { register, handleSubmit } = useForm<FormFields>();
|
||||
const navigate = useNavigate();
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (fields: FormFields) => {
|
||||
setError(null);
|
||||
|
||||
const query = fields.search;
|
||||
|
||||
if (!query) {
|
||||
return setError(new Error(t('Search query required')));
|
||||
}
|
||||
|
||||
const result = await getSearchType(query);
|
||||
const urlAsHex = toHex(query);
|
||||
const unrecognisedError = new Error(
|
||||
t('Transaction type is not recognised')
|
||||
);
|
||||
|
||||
if (result) {
|
||||
switch (result) {
|
||||
case SearchTypes.Party:
|
||||
return navigate(`${Routes.PARTIES}/${urlAsHex}`);
|
||||
case SearchTypes.Transaction:
|
||||
return navigate(`${Routes.TX}/${urlAsHex}`);
|
||||
case SearchTypes.Block:
|
||||
return navigate(`${Routes.BLOCKS}/${Number(query)}`);
|
||||
default:
|
||||
return setError(unrecognisedError);
|
||||
}
|
||||
}
|
||||
|
||||
return setError(unrecognisedError);
|
||||
},
|
||||
[navigate]
|
||||
);
|
||||
|
||||
const searchForm = (
|
||||
<form className="block min-w-[290px]" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="flex relative items-stretch gap-2 text-xs">
|
||||
<label htmlFor="search" className="sr-only">
|
||||
{t('Search by block number or transaction hash')}
|
||||
</label>
|
||||
<button
|
||||
className={classNames(
|
||||
'absolute top-[50%] translate-y-[-50%] left-2',
|
||||
'text-vega-light-300 dark:text-vega-dark-300'
|
||||
)}
|
||||
>
|
||||
<MagnifyingGlass />
|
||||
</button>
|
||||
<Input
|
||||
{...register('search')}
|
||||
id="search"
|
||||
data-testid="search"
|
||||
className={classNames(
|
||||
'peer',
|
||||
'pl-8 py-2 text-xs',
|
||||
'border rounded border-vega-light-200 dark:border-vega-dark-200'
|
||||
)}
|
||||
hasError={Boolean(error?.message)}
|
||||
type="text"
|
||||
placeholder={t('Enter block number, public key or transaction hash')}
|
||||
/>
|
||||
{error?.message && (
|
||||
<div
|
||||
className={classNames(
|
||||
'hidden peer-focus:block',
|
||||
'bg-white dark:bg-black',
|
||||
'border rounded-b border-t-0 border-vega-light-200 dark:border-vega-dark-200',
|
||||
'absolute top-[100%] flex-1 w-full pb-2 px-2 text-black dark:text-white'
|
||||
)}
|
||||
>
|
||||
<InputError
|
||||
data-testid="search-error"
|
||||
intent="danger"
|
||||
className="text-xs"
|
||||
>
|
||||
{error.message}
|
||||
</InputError>
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
className="hidden [.search-dropdown_&]:block"
|
||||
type="submit"
|
||||
size="xs"
|
||||
data-testid="search-button"
|
||||
>
|
||||
{t('Search')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
const searchForm = <SearchForm />;
|
||||
|
||||
const searchTrigger = (
|
||||
<DropdownMenu.Root>
|
||||
@@ -154,10 +80,135 @@ export const Search = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="hidden [.nav-search-full_&]:block">{searchForm}</div>
|
||||
<div className="hidden [.nav-search-full_&]:block min-w-[290px]">
|
||||
{searchForm}
|
||||
</div>
|
||||
<div className="hidden [.nav-search-compact_&]:block">
|
||||
{searchTrigger}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const SearchForm = () => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
setError,
|
||||
clearErrors,
|
||||
formState,
|
||||
watch,
|
||||
} = useForm<FormFields>();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (fields: FormFields) => {
|
||||
clearErrors();
|
||||
const type = await determineType(fields.search);
|
||||
if (type) {
|
||||
switch (type) {
|
||||
case SearchTypes.Party:
|
||||
return navigate(`${Routes.PARTIES}/${remove0x(fields.search)}`);
|
||||
case SearchTypes.Transaction:
|
||||
return navigate(`${Routes.TX}/${remove0x(fields.search)}`);
|
||||
case SearchTypes.Block:
|
||||
return navigate(`${Routes.BLOCKS}/${Number(fields.search)}`);
|
||||
}
|
||||
}
|
||||
|
||||
setError('search', new Error(t('The search term is not a valid query')));
|
||||
},
|
||||
[clearErrors, navigate, setError]
|
||||
);
|
||||
|
||||
const searchQuery = watch('search', '');
|
||||
|
||||
return (
|
||||
<form className="block min-w-[200px]" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="flex relative items-stretch gap-2 text-xs">
|
||||
<div className="relative w-full">
|
||||
<label htmlFor="search" className="sr-only">
|
||||
{t('Search by block number or transaction hash')}
|
||||
</label>
|
||||
<button
|
||||
className={classNames(
|
||||
'absolute top-[50%] translate-y-[-50%] left-2',
|
||||
'text-vega-light-300 dark:text-vega-dark-300'
|
||||
)}
|
||||
>
|
||||
<MagnifyingGlass />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setValue('search', '');
|
||||
clearErrors();
|
||||
}}
|
||||
className={classNames(
|
||||
{ hidden: searchQuery.length === 0 },
|
||||
'absolute top-[50%] translate-y-[-50%] right-2',
|
||||
'text-vega-light-300 dark:text-vega-dark-300'
|
||||
)}
|
||||
>
|
||||
<Clear />
|
||||
</button>
|
||||
<Input
|
||||
{...register('search', {
|
||||
required: t('Search query is required'),
|
||||
validate: (value) =>
|
||||
isHash(value) ||
|
||||
isBlock(value) ||
|
||||
t('Search query has to be a number or a 64 character hash'),
|
||||
onBlur: () => clearErrors('search'),
|
||||
})}
|
||||
id="search"
|
||||
data-testid="search"
|
||||
className={classNames(
|
||||
'pl-8 py-2 text-xs',
|
||||
{ 'pr-8': searchQuery.length > 1 },
|
||||
'border rounded border-vega-light-200 dark:border-vega-dark-200',
|
||||
{
|
||||
'border-vega-pink dark:border-vega-pink': Boolean(
|
||||
formState.errors.search
|
||||
),
|
||||
}
|
||||
)}
|
||||
hasError={Boolean(formState.errors.search)}
|
||||
type="text"
|
||||
placeholder={t(
|
||||
'Enter block number, public key or transaction hash'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{formState.errors.search && (
|
||||
<div
|
||||
className={classNames(
|
||||
'[nav_&]:border [nav_&]:rounded [nav_&]:border-vega-light-300 [nav_&]:dark:border-vega-light-300',
|
||||
'[.search-dropdown_&]:border [.search-dropdown_&]:rounded [.search-dropdown_&]:border-vega-light-300 [.search-dropdown_&]:dark:border-vega-light-300',
|
||||
'bg-white dark:bg-black',
|
||||
'absolute top-[100%] flex-1 w-full pb-2 px-2 text-black dark:text-white'
|
||||
)}
|
||||
>
|
||||
<InputError
|
||||
data-testid="search-error"
|
||||
intent="danger"
|
||||
className="text-xs"
|
||||
>
|
||||
{formState.errors.search.message}
|
||||
</InputError>
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant="primary"
|
||||
type="submit"
|
||||
size="xs"
|
||||
data-testid="search-button"
|
||||
className="[nav_&]:hidden"
|
||||
>
|
||||
{t('Search')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import { StatsManager } from '@vegaprotocol/network-stats';
|
||||
import { SearchForm } from '../../components/search';
|
||||
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||
|
||||
const Home = () => {
|
||||
const classnames = 'mt-4 grid grid-cols-1 lg:grid-cols-2 lg:gap-4';
|
||||
|
||||
const classnames = 'mt-4 mb-4';
|
||||
useDocumentTitle();
|
||||
|
||||
return (
|
||||
<section>
|
||||
<StatsManager className={classnames} />
|
||||
<div className="p-20 max-sm:py-10 max-sm:px-0">
|
||||
<SearchForm />
|
||||
</div>
|
||||
<div className="px-20 max-sm:px-0">
|
||||
<StatsManager className={classnames} />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useRoutes } from 'react-router-dom';
|
||||
import { RouteErrorBoundary } from '../components/router-error-boundary';
|
||||
|
||||
import routerConfig from './router-config';
|
||||
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export interface RouteChildProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const AppRouter = () => {
|
||||
const routes = useRoutes(routerConfig);
|
||||
|
||||
const splashLoading = (
|
||||
<Splash>
|
||||
<Loader />
|
||||
</Splash>
|
||||
);
|
||||
|
||||
return (
|
||||
<RouteErrorBoundary>
|
||||
<React.Suspense fallback={splashLoading}>{routes}</React.Suspense>
|
||||
</RouteErrorBoundary>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,166 @@
|
||||
import {
|
||||
AssetDetailsDialog,
|
||||
useAssetDetailsDialogStore,
|
||||
} from '@vegaprotocol/assets';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { AnnouncementBanner } from '@vegaprotocol/announcements';
|
||||
import {
|
||||
BackgroundVideo,
|
||||
BreadcrumbsContainer,
|
||||
ButtonLink,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
isRouteErrorResponse,
|
||||
Link,
|
||||
Outlet,
|
||||
useMatch,
|
||||
useRouteError,
|
||||
} from 'react-router-dom';
|
||||
import { Footer } from '../components/footer/footer';
|
||||
import { Header } from '../components/header';
|
||||
import { Routes } from './route-names';
|
||||
|
||||
const DialogsContainer = () => {
|
||||
const { isOpen, id, trigger, asJson, setOpen } = useAssetDetailsDialogStore();
|
||||
return (
|
||||
<AssetDetailsDialog
|
||||
assetId={id}
|
||||
trigger={trigger || null}
|
||||
asJson={asJson}
|
||||
open={isOpen}
|
||||
onChange={setOpen}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Layout = () => {
|
||||
const isHome = Boolean(useMatch(Routes.HOME));
|
||||
const { ANNOUNCEMENTS_CONFIG_URL } = useEnvironment();
|
||||
const fixedWidthClasses = 'w-full max-w-[1500px] mx-auto';
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={classNames(
|
||||
'min-h-screen',
|
||||
'mx-auto my-0',
|
||||
'grid grid-rows-[auto_1fr_auto] grid-cols-1',
|
||||
'border-vega-light-200 dark:border-vega-dark-200',
|
||||
'antialiased text-black dark:text-white',
|
||||
'overflow-hidden relative'
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
{ANNOUNCEMENTS_CONFIG_URL && (
|
||||
<AnnouncementBanner
|
||||
app="explorer"
|
||||
configUrl={ANNOUNCEMENTS_CONFIG_URL}
|
||||
/>
|
||||
)}
|
||||
<Header />
|
||||
</div>
|
||||
<div className={fixedWidthClasses}>
|
||||
<main className="p-4">
|
||||
{!isHome && <BreadcrumbsContainer className="mb-4" />}
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
<div className={fixedWidthClasses}>
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
<DialogsContainer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ErrorBoundary = () => {
|
||||
const error = useRouteError();
|
||||
|
||||
const errorTitle = isRouteErrorResponse(error)
|
||||
? `${error.status} ${error.statusText}`
|
||||
: t('Something went wrong');
|
||||
|
||||
const errorMessage = isRouteErrorResponse(error)
|
||||
? error.error?.message
|
||||
: (error as Error).message || JSON.stringify(error);
|
||||
|
||||
return (
|
||||
<>
|
||||
<BackgroundVideo className="brightness-50" />
|
||||
<div
|
||||
className={classNames(
|
||||
'max-w-[620px] p-2 mt-[10vh]',
|
||||
'mx-auto my-0',
|
||||
'antialiased text-white',
|
||||
'overflow-hidden relative',
|
||||
'flex flex-col gap-2'
|
||||
)}
|
||||
>
|
||||
<div className="flex gap-4">
|
||||
<div>{GHOST}</div>
|
||||
<h1 className="text-[2.7rem] font-alpha calt break-words uppercase">
|
||||
{errorTitle}
|
||||
</h1>
|
||||
</div>
|
||||
<div className="text-sm mt-10 overflow-auto break-all font-mono">
|
||||
{errorMessage}
|
||||
</div>
|
||||
<div>
|
||||
<ButtonLink onClick={() => window.location.reload()}>
|
||||
{t('Try refreshing')}
|
||||
</ButtonLink>{' '}
|
||||
{t('or go back to')}{' '}
|
||||
<Link className="underline" to="/">
|
||||
{t('Home')}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const GHOST = (
|
||||
<svg
|
||||
width="56"
|
||||
height="85"
|
||||
viewBox="0 0 56 85"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M41 0.5H3V60.5H41V0.5Z" fill="white" />
|
||||
<path d="M15 18.5H13V20.5H15V18.5Z" fill="black" />
|
||||
<path d="M17 20.5H15V22.5H17V20.5Z" fill="black" />
|
||||
<path d="M19 18.5H17V20.5H19V18.5Z" fill="black" />
|
||||
<path d="M15 22.5H13V24.5H15V22.5Z" fill="black" />
|
||||
<path d="M19 22.5H17V24.5H19V22.5Z" fill="black" />
|
||||
<path d="M29 28.5H15V30.5H29V28.5Z" fill="black" />
|
||||
<path d="M27 18.5H25V20.5H27V18.5Z" fill="black" />
|
||||
<path d="M29 20.5H27V22.5H29V20.5Z" fill="black" />
|
||||
<path d="M31 18.5H29V20.5H31V18.5Z" fill="black" />
|
||||
<path d="M27 22.5H25V24.5H27V22.5Z" fill="black" />
|
||||
<path d="M31 22.5H29V24.5H31V22.5Z" fill="black" />
|
||||
<path d="M31 26.5H29V28.5H31V26.5Z" fill="black" />
|
||||
<path d="M19 60.5H17V84.5H19V60.5Z" fill="black" />
|
||||
<path d="M27 60.5H25V84.5H27V60.5Z" fill="black" />
|
||||
<path
|
||||
d="M3 42.5V58.64V60.5V64.5H21V60.5H23V64.5H41V60.5V58.64V42.5H3Z"
|
||||
fill="#FF077F"
|
||||
/>
|
||||
<path d="M35 46.5H41V42.5H3V46.5H31H35Z" fill="#CB0666" />
|
||||
<path d="M3 32.32V29.5L0 32.5V60.5H2V33.33L3 32.32Z" fill="black" />
|
||||
<path d="M41 31.8V29.49L54.79 21.53L55.79 23.26L41 31.8Z" fill="black" />
|
||||
<path d="M36 54.5H35V55.5H36V54.5Z" fill="black" />
|
||||
<path d="M35 53.5H34V54.5H35V53.5Z" fill="black" />
|
||||
<path d="M34 48.5H33V53.5H34V48.5Z" fill="black" />
|
||||
<path d="M38 48.5H37V52.5H38V48.5Z" fill="black" />
|
||||
<path d="M37 53.5H36V54.5H37V53.5Z" fill="black" />
|
||||
<path d="M39 52.5H38V53.5H39V52.5Z" fill="black" />
|
||||
<path
|
||||
d="M55.7901 23.27L53.0601 22.54L45.1001 8.75L46.8301 7.75L55.7901 23.27Z"
|
||||
fill="black"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -8,7 +8,6 @@ import { Oracle } from './oracles/id';
|
||||
import Party from './parties';
|
||||
import { Parties } from './parties/home';
|
||||
import { Party as PartySingle } from './parties/id';
|
||||
import Txs from './txs';
|
||||
import { ValidatorsPage } from './validators';
|
||||
import Genesis from './genesis';
|
||||
import { Block } from './blocks/id';
|
||||
@@ -20,23 +19,55 @@ import flags from '../config/flags';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Routes } from './route-names';
|
||||
import { NetworkParameters } from './network-parameters';
|
||||
import type { RouteObject } from 'react-router-dom';
|
||||
import type { Params, RouteObject } from 'react-router-dom';
|
||||
import { createBrowserRouter } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { MarketPage, MarketsPage } from './markets';
|
||||
import type { ReactNode } from 'react';
|
||||
import { ErrorBoundary, Layout } from './layout';
|
||||
import compact from 'lodash/compact';
|
||||
import { AssetLink, MarketLink } from '../components/links';
|
||||
import { truncateMiddle } from '@vegaprotocol/ui-toolkit';
|
||||
import { remove0x } from '@vegaprotocol/utils';
|
||||
|
||||
export type Navigable = {
|
||||
path: string;
|
||||
name: string;
|
||||
text: string;
|
||||
handle: {
|
||||
name: string;
|
||||
text: string;
|
||||
};
|
||||
};
|
||||
export const isNavigable = (item: RouteObject): item is Navigable =>
|
||||
(item as Navigable).path !== undefined &&
|
||||
(item as Navigable).handle !== undefined &&
|
||||
(item as Navigable).handle.name !== undefined &&
|
||||
(item as Navigable).handle.text !== undefined;
|
||||
|
||||
export type Breadcrumbable = {
|
||||
handle: { breadcrumb: (data?: Params<string>) => ReactNode | string };
|
||||
};
|
||||
export const isBreadcrumbable = (item: RouteObject): item is Breadcrumbable =>
|
||||
(item as Breadcrumbable).handle !== undefined &&
|
||||
(item as Breadcrumbable).handle.breadcrumb !== undefined;
|
||||
|
||||
type RouteItem =
|
||||
| RouteObject
|
||||
| (RouteObject & Navigable)
|
||||
| (RouteObject & Breadcrumbable);
|
||||
type Route = RouteItem & {
|
||||
children?: RouteItem[];
|
||||
};
|
||||
type Route = RouteObject & Navigable;
|
||||
|
||||
const partiesRoutes: Route[] = flags.parties
|
||||
? [
|
||||
{
|
||||
path: Routes.PARTIES,
|
||||
name: t('Parties'),
|
||||
text: t('Parties'),
|
||||
element: <Party />,
|
||||
handle: {
|
||||
name: t('Parties'),
|
||||
text: t('Parties'),
|
||||
breadcrumb: () => <Link to={Routes.PARTIES}>{t('Parties')}</Link>,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
@@ -45,6 +76,13 @@ const partiesRoutes: Route[] = flags.parties
|
||||
{
|
||||
path: ':party',
|
||||
element: <PartySingle />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.PARTIES, params.party)}>
|
||||
{truncateMiddle(params.party as string)}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -55,8 +93,11 @@ const assetsRoutes: Route[] = flags.assets
|
||||
? [
|
||||
{
|
||||
path: Routes.ASSETS,
|
||||
text: t('Assets'),
|
||||
name: t('Assets'),
|
||||
handle: {
|
||||
name: t('Assets'),
|
||||
text: t('Assets'),
|
||||
breadcrumb: () => <Link to={Routes.ASSETS}>{t('Assets')}</Link>,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
@@ -65,6 +106,11 @@ const assetsRoutes: Route[] = flags.assets
|
||||
{
|
||||
path: ':assetId',
|
||||
element: <AssetPage />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<AssetLink assetId={params.assetId as string} />
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -75,8 +121,13 @@ const genesisRoutes: Route[] = flags.genesis
|
||||
? [
|
||||
{
|
||||
path: Routes.GENESIS,
|
||||
name: t('Genesis'),
|
||||
text: t('Genesis Parameters'),
|
||||
handle: {
|
||||
name: t('Genesis'),
|
||||
text: t('Genesis Parameters'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.GENESIS}>{t('Genesis Parameters')}</Link>
|
||||
),
|
||||
},
|
||||
element: <Genesis />,
|
||||
},
|
||||
]
|
||||
@@ -86,8 +137,13 @@ const governanceRoutes: Route[] = flags.governance
|
||||
? [
|
||||
{
|
||||
path: Routes.GOVERNANCE,
|
||||
name: t('Governance proposals'),
|
||||
text: t('Governance Proposals'),
|
||||
handle: {
|
||||
name: t('Governance proposals'),
|
||||
text: t('Governance Proposals'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.GOVERNANCE}>{t('Governance Proposals')}</Link>
|
||||
),
|
||||
},
|
||||
element: <Proposals />,
|
||||
},
|
||||
]
|
||||
@@ -97,8 +153,11 @@ const marketsRoutes: Route[] = flags.markets
|
||||
? [
|
||||
{
|
||||
path: Routes.MARKETS,
|
||||
name: t('Markets'),
|
||||
text: t('Markets'),
|
||||
handle: {
|
||||
name: t('Markets'),
|
||||
text: t('Markets'),
|
||||
breadcrumb: () => <Link to={Routes.MARKETS}>{t('Markets')}</Link>,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
@@ -107,6 +166,11 @@ const marketsRoutes: Route[] = flags.markets
|
||||
{
|
||||
path: ':marketId',
|
||||
element: <MarketPage />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<MarketLink id={params.marketId as string} />
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -117,8 +181,15 @@ const networkParametersRoutes: Route[] = flags.networkParameters
|
||||
? [
|
||||
{
|
||||
path: Routes.NETWORK_PARAMETERS,
|
||||
name: t('NetworkParameters'),
|
||||
text: t('Network Parameters'),
|
||||
handle: {
|
||||
name: t('NetworkParameters'),
|
||||
text: t('Network Parameters'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.NETWORK_PARAMETERS}>
|
||||
{t('Network Parameters')}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
element: <NetworkParameters />,
|
||||
},
|
||||
]
|
||||
@@ -128,80 +199,133 @@ const validators: Route[] = flags.validators
|
||||
? [
|
||||
{
|
||||
path: Routes.VALIDATORS,
|
||||
name: t('Validators'),
|
||||
text: t('Validators'),
|
||||
handle: {
|
||||
name: t('Validators'),
|
||||
text: t('Validators'),
|
||||
breadcrumb: () => (
|
||||
<Link to={Routes.VALIDATORS}>{t('Validators')}</Link>
|
||||
),
|
||||
},
|
||||
element: <ValidatorsPage />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const routerConfig: Route[] = [
|
||||
const linkTo = (...segments: (string | undefined)[]) =>
|
||||
compact(segments).join('/');
|
||||
|
||||
export const routerConfig: Route[] = [
|
||||
{
|
||||
path: Routes.HOME,
|
||||
name: t('Home'),
|
||||
text: t('Home'),
|
||||
element: <Home />,
|
||||
index: true,
|
||||
},
|
||||
{
|
||||
path: Routes.TX,
|
||||
name: t('Txs'),
|
||||
text: t('Transactions'),
|
||||
element: <Txs />,
|
||||
children: [
|
||||
{
|
||||
path: 'pending',
|
||||
element: <PendingTxs />,
|
||||
},
|
||||
{
|
||||
path: ':txHash',
|
||||
element: <Tx />,
|
||||
},
|
||||
{
|
||||
index: true,
|
||||
element: <TxsList />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.BLOCKS,
|
||||
name: t('Blocks'),
|
||||
text: t('Blocks'),
|
||||
element: <BlockPage />,
|
||||
element: <Layout />,
|
||||
handle: {
|
||||
name: t('Home'),
|
||||
text: t('Home'),
|
||||
breadcrumb: () => <Link to={Routes.HOME}>{t('Home')}</Link>,
|
||||
},
|
||||
errorElement: <ErrorBoundary />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Blocks />,
|
||||
element: <Home />,
|
||||
},
|
||||
{
|
||||
path: ':block',
|
||||
element: <Block />,
|
||||
path: Routes.TX,
|
||||
handle: {
|
||||
name: t('Txs'),
|
||||
text: t('Transactions'),
|
||||
breadcrumb: () => <Link to={Routes.TX}>{t('Transactions')}</Link>,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'pending',
|
||||
element: <PendingTxs />,
|
||||
handle: {
|
||||
breadcrumb: () => (
|
||||
<Link to={linkTo(Routes.TX, 'pending')}>
|
||||
{t('Pending transactions')}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
path: ':txHash',
|
||||
element: <Tx />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.TX, params.txHash)}>
|
||||
{truncateMiddle(remove0x(params.txHash as string))}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
index: true,
|
||||
element: <TxsList />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.BLOCKS,
|
||||
handle: {
|
||||
name: t('Blocks'),
|
||||
text: t('Blocks'),
|
||||
breadcrumb: () => <Link to={Routes.BLOCKS}>{t('Blocks')}</Link>,
|
||||
},
|
||||
element: <BlockPage />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Blocks />,
|
||||
},
|
||||
{
|
||||
path: ':block',
|
||||
element: <Block />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.BLOCKS, params.block)}>
|
||||
{params.block}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.ORACLES,
|
||||
handle: {
|
||||
name: t('Oracles'),
|
||||
text: t('Oracles'),
|
||||
breadcrumb: () => <Link to={Routes.ORACLES}>{t('Oracles')}</Link>,
|
||||
},
|
||||
element: <OraclePage />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Oracles />,
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
element: <Oracle />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.ORACLES, params.id)}>
|
||||
{truncateMiddle(params.id as string)}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
...partiesRoutes,
|
||||
...assetsRoutes,
|
||||
...genesisRoutes,
|
||||
...governanceRoutes,
|
||||
...marketsRoutes,
|
||||
...networkParametersRoutes,
|
||||
...validators,
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.ORACLES,
|
||||
name: t('Oracles'),
|
||||
text: t('Oracles'),
|
||||
element: <OraclePage />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Oracles />,
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
element: <Oracle />,
|
||||
},
|
||||
],
|
||||
},
|
||||
...partiesRoutes,
|
||||
...assetsRoutes,
|
||||
...genesisRoutes,
|
||||
...governanceRoutes,
|
||||
...marketsRoutes,
|
||||
...networkParametersRoutes,
|
||||
...validators,
|
||||
];
|
||||
|
||||
export default routerConfig;
|
||||
export const router = createBrowserRouter(routerConfig);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { remove0x } from '@vegaprotocol/utils';
|
||||
import { useFetch } from '@vegaprotocol/react-helpers';
|
||||
import { DATA_SOURCES } from '../../../config';
|
||||
@@ -8,9 +7,6 @@ import { TxDetails } from './tx-details';
|
||||
import type { BlockExplorerTransaction } from '../../../routes/types/block-explorer-response';
|
||||
import { toNonHex } from '../../../components/search/detect-search';
|
||||
import { PageHeader } from '../../../components/page-header';
|
||||
import { Routes } from '../../../routes/route-names';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import { useDocumentTitle } from '../../../hooks/use-document-title';
|
||||
|
||||
const Tx = () => {
|
||||
@@ -35,17 +31,6 @@ const Tx = () => {
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Link
|
||||
className="font-normal underline underline-offset-4 block mb-5"
|
||||
to={`/${Routes.TX}`}
|
||||
>
|
||||
<Icon
|
||||
className="text-vega-light-150 dark:text-vega-light-150"
|
||||
name={IconNames.CHEVRON_LEFT}
|
||||
/>
|
||||
All Transactions
|
||||
</Link>
|
||||
|
||||
<PageHeader
|
||||
title="transaction"
|
||||
truncateStart={5}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import './styles.css';
|
||||
|
||||
import App from './app/app';
|
||||
@@ -10,8 +9,6 @@ const root = rootElement && createRoot(rootElement);
|
||||
|
||||
root?.render(
|
||||
<StrictMode>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
<App />
|
||||
</StrictMode>
|
||||
);
|
||||
|
||||
@@ -10,9 +10,13 @@ NX_ETHEREUM_CHAIN_ID=1440
|
||||
NX_ETH_URL_CONNECT=1
|
||||
NX_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
|
||||
NX_LOCAL_PROVIDER_URL=http://localhost:8545/
|
||||
NX_ETH_LOCAL_PROVIDER_URL=http://localhost:8545/
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
|
||||
@@ -25,6 +25,7 @@ module.exports = defineConfig({
|
||||
viewportWidth: 1440,
|
||||
viewportHeight: 900,
|
||||
numTestsKeptInMemory: 5,
|
||||
testIsolation: false,
|
||||
},
|
||||
env: {
|
||||
ethProviderUrl: 'http://localhost:8545/',
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
export const upgradeProposalsData = {
|
||||
lastBlockHeight: '2014133',
|
||||
protocolUpgradeProposals: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
upgradeBlockHeight: '2015942',
|
||||
vegaReleaseTag: 'v1',
|
||||
approvers: [
|
||||
'02a6531716b7a6d82779b7793c3ad2fcb47290ea2ff0912c56f61219bd9675ff',
|
||||
'121934387281812a2d5e6913e5d57c0f85a8f169e2752347ee2e23b52d46623c',
|
||||
'65c80e2f5f84e2109eec30810f137ba04cbbecaba8f27706c146cc6c6f90db29',
|
||||
'bd6339d2428c79ac3bc9011771236d17bac92bcb1806423388d52fb440043aef',
|
||||
],
|
||||
status: 'PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED',
|
||||
__typename: 'ProtocolUpgradeProposal',
|
||||
},
|
||||
__typename: 'ProtocolUpgradeProposalEdge',
|
||||
},
|
||||
{
|
||||
node: {
|
||||
upgradeBlockHeight: '1955065',
|
||||
vegaReleaseTag: 'v0.71.0+dev-12156-bca1d57e',
|
||||
approvers: [
|
||||
'02a6531716b7a6d82779b7793c3ad2fcb47290ea2ff0912c56f61219bd9675ff',
|
||||
'121934387281812a2d5e6913e5d57c0f85a8f169e2752347ee2e23b52d46623c',
|
||||
'65c80e2f5f84e2109eec30810f137ba04cbbecaba8f27706c146cc6c6f90db29',
|
||||
'bd6339d2428c79ac3bc9011771236d17bac92bcb1806423388d52fb440043aef',
|
||||
],
|
||||
status: 'PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED',
|
||||
__typename: 'ProtocolUpgradeProposal',
|
||||
},
|
||||
__typename: 'ProtocolUpgradeProposalEdge',
|
||||
},
|
||||
],
|
||||
__typename: 'ProtocolUpgradeProposalConnection',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
export const nodeData = {
|
||||
epoch: {
|
||||
id: '204731',
|
||||
timestamps: {
|
||||
start: '2023-04-14T09:30:09.452005Z',
|
||||
end: null,
|
||||
expiry: '2023-04-14T09:31:09.452005Z',
|
||||
__typename: 'EpochTimestamps',
|
||||
},
|
||||
__typename: 'Epoch',
|
||||
},
|
||||
nodesConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
avatarUrl:
|
||||
'https://www.gravatar.com/avatar/y2hwc4xjds7zvlam3y4in94q2rcdimsn?d=identicon',
|
||||
id: 'f337b5cc50c49a928e49129a2eca62277a81b4b7336b6e4131f8f0431e8db029',
|
||||
name: 'lovely-fisherman',
|
||||
pubkey:
|
||||
'02a6531716b7a6d82779b7793c3ad2fcb47290ea2ff0912c56f61219bd9675ff',
|
||||
stakedByOperator: '3000000000000000000000',
|
||||
stakedByDelegates: '0',
|
||||
stakedTotal: '3000000000000000000000',
|
||||
pendingStake: '0',
|
||||
rankingScore: {
|
||||
rankingScore: '0.4999166805532412',
|
||||
stakeScore: '0.2499583402766206',
|
||||
performanceScore: '1',
|
||||
votingPower: '2499',
|
||||
status: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
__typename: 'RankingScore',
|
||||
},
|
||||
__typename: 'Node',
|
||||
},
|
||||
__typename: 'NodeEdge',
|
||||
},
|
||||
{
|
||||
node: {
|
||||
avatarUrl:
|
||||
'https://www.gravatar.com/avatar/g1tw70qnoo0tf8ror30jefwhae92zy16?d=identicon',
|
||||
id: 'cdadcf885556e372b6b39679d6ad46854d1d9c1e982da7ff2929544df01f9088',
|
||||
name: 'magnificent-door',
|
||||
pubkey:
|
||||
'121934387281812a2d5e6913e5d57c0f85a8f169e2752347ee2e23b52d46623c',
|
||||
stakedByOperator: '3000000000000000000000',
|
||||
stakedByDelegates: '0',
|
||||
stakedTotal: '3000000000000000000000',
|
||||
pendingStake: '0',
|
||||
rankingScore: {
|
||||
rankingScore: '0.4999166805532412',
|
||||
stakeScore: '0.2499583402766206',
|
||||
performanceScore: '1',
|
||||
votingPower: '2499',
|
||||
status: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
__typename: 'RankingScore',
|
||||
},
|
||||
__typename: 'Node',
|
||||
},
|
||||
__typename: 'NodeEdge',
|
||||
},
|
||||
{
|
||||
node: {
|
||||
avatarUrl:
|
||||
'https://www.gravatar.com/avatar/s2e7hp7soq5zdhxeap7p21onetsjiy6w?d=identicon',
|
||||
id: '9a13de2548fef30d928e71e76004c98ec7ad9d8fc3ddcd16ddee210b28ea4cfc',
|
||||
name: 'helpful-tree',
|
||||
pubkey:
|
||||
'bd6339d2428c79ac3bc9011771236d17bac92bcb1806423388d52fb440043aef',
|
||||
stakedByOperator: '3000000000000000000000',
|
||||
stakedByDelegates: '2000000000000000000',
|
||||
stakedTotal: '3002000000000000000000',
|
||||
pendingStake: '0',
|
||||
rankingScore: {
|
||||
rankingScore: '0.5002499583402766',
|
||||
stakeScore: '0.2501249791701383',
|
||||
performanceScore: '1',
|
||||
votingPower: '2501',
|
||||
status: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
__typename: 'RankingScore',
|
||||
},
|
||||
__typename: 'Node',
|
||||
},
|
||||
__typename: 'NodeEdge',
|
||||
},
|
||||
{
|
||||
node: {
|
||||
avatarUrl:
|
||||
'https://www.gravatar.com/avatar/3gop683kg2cvd8rpv286hzagqt3yjsdq?d=identicon',
|
||||
id: '93ac271ab95038333587e47d11461e6edc0126e6e77156281c545f4c5650a6d0',
|
||||
name: 'easy-cookie',
|
||||
pubkey:
|
||||
'65c80e2f5f84e2109eec30810f137ba04cbbecaba8f27706c146cc6c6f90db29',
|
||||
stakedByOperator: '3000000000000000000000',
|
||||
stakedByDelegates: '0',
|
||||
stakedTotal: '3000000000000000000000',
|
||||
pendingStake: '0',
|
||||
rankingScore: {
|
||||
rankingScore: '0.4999166805532412',
|
||||
stakeScore: '0.2499583402766206',
|
||||
performanceScore: '1',
|
||||
votingPower: '2499',
|
||||
status: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
__typename: 'RankingScore',
|
||||
},
|
||||
__typename: 'Node',
|
||||
},
|
||||
__typename: 'NodeEdge',
|
||||
},
|
||||
],
|
||||
__typename: 'NodesConnection',
|
||||
},
|
||||
nodeData: {
|
||||
stakedTotal: '12002000000000000000000',
|
||||
__typename: 'NodeData',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,325 @@
|
||||
export const proposalsData = {
|
||||
proposalsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: 'e8ba9d268e12514644fd1fc7ff289292f4ce6489cc32cc73133aea52c04aef89',
|
||||
rationale: {
|
||||
title: 'Add asset Wrapped Ether',
|
||||
description: 'Proposal to add asset WETH to Vega network',
|
||||
__typename: 'ProposalRationale',
|
||||
},
|
||||
reference: '',
|
||||
state: 'STATE_OPEN',
|
||||
datetime: '2026-12-01T11:41:28.654288Z',
|
||||
rejectionReason: null,
|
||||
party: {
|
||||
id: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||
__typename: 'Party',
|
||||
},
|
||||
errorDetails: null,
|
||||
terms: {
|
||||
closingDatetime: '2026-12-01T11:45:33Z',
|
||||
enactmentDatetime: '2026-12-01T11:45:43Z',
|
||||
change: {
|
||||
name: 'Wrapped Ether',
|
||||
symbol: 'WETH',
|
||||
decimals: 18,
|
||||
quantum: '0.0008',
|
||||
source: {
|
||||
contractAddress: '0x9B18C6CaD886D5653783E2B25759124760F4407F',
|
||||
withdrawThreshold: '1',
|
||||
lifetimeLimit: '400000000000000000',
|
||||
__typename: 'ERC20',
|
||||
},
|
||||
__typename: 'NewAsset',
|
||||
},
|
||||
__typename: 'ProposalTerms',
|
||||
},
|
||||
votes: {
|
||||
yes: {
|
||||
totalTokens: '0',
|
||||
totalNumber: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
__typename: 'ProposalVoteSide',
|
||||
},
|
||||
no: {
|
||||
totalTokens: '0',
|
||||
totalNumber: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
__typename: 'ProposalVoteSide',
|
||||
},
|
||||
__typename: 'ProposalVotes',
|
||||
},
|
||||
__typename: 'Proposal',
|
||||
},
|
||||
__typename: 'ProposalEdge',
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: 'd848fc7881f13d366df5f61ab139d5fcfa72bf838151bb51b54381870e357931',
|
||||
rationale: {
|
||||
title: 'Add asset Dai Stablecoin',
|
||||
description: 'Proposal to add asset DAI to Vega network',
|
||||
__typename: 'ProposalRationale',
|
||||
},
|
||||
reference: '',
|
||||
state: 'STATE_ENACTED',
|
||||
datetime: '2022-11-29T15:49:57.80978Z',
|
||||
rejectionReason: null,
|
||||
party: {
|
||||
id: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||
__typename: 'Party',
|
||||
},
|
||||
errorDetails: null,
|
||||
terms: {
|
||||
closingDatetime: '2023-04-13T15:52:58Z',
|
||||
enactmentDatetime: '2023-04-13T15:53:08Z',
|
||||
change: {
|
||||
name: 'Dai Stablecoin',
|
||||
symbol: 'DAI',
|
||||
decimals: 18,
|
||||
quantum: '1',
|
||||
source: {
|
||||
contractAddress: '0xad018fB8ec00bfd622B91C83E684a6AC7bB8fbA4',
|
||||
withdrawThreshold: '1',
|
||||
lifetimeLimit: '500000000000000000000',
|
||||
__typename: 'ERC20',
|
||||
},
|
||||
__typename: 'NewAsset',
|
||||
},
|
||||
__typename: 'ProposalTerms',
|
||||
},
|
||||
votes: {
|
||||
yes: {
|
||||
totalTokens: '0',
|
||||
totalNumber: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
__typename: 'ProposalVoteSide',
|
||||
},
|
||||
no: {
|
||||
totalTokens: '0',
|
||||
totalNumber: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
__typename: 'ProposalVoteSide',
|
||||
},
|
||||
__typename: 'ProposalVotes',
|
||||
},
|
||||
__typename: 'Proposal',
|
||||
},
|
||||
__typename: 'ProposalEdge',
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: 'ccbd651b4a1167fd73c4a0340ac759fa0a31ca487ad46a13254b741ad71947ed',
|
||||
rationale: {
|
||||
title: 'New DAI market',
|
||||
description: 'New DAI market',
|
||||
__typename: 'ProposalRationale',
|
||||
},
|
||||
reference: '0VFQusmmESdrP5GuL8naB6lxfoE3RPGaEeo7abdN',
|
||||
state: 'STATE_ENACTED',
|
||||
datetime: '2022-11-26T19:36:19.26034Z',
|
||||
rejectionReason: null,
|
||||
party: {
|
||||
id: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||
__typename: 'Party',
|
||||
},
|
||||
errorDetails: null,
|
||||
terms: {
|
||||
closingDatetime: '2022-11-26T19:36:42Z',
|
||||
enactmentDatetime: '2023-03-22T13:57:37Z',
|
||||
change: {
|
||||
instrument: {
|
||||
name: 'UNIDAI Monthly (Dec 2022)',
|
||||
code: 'UNIDAI.MF21',
|
||||
futureProduct: {
|
||||
settlementAsset: { symbol: 'tDAI', __typename: 'Asset' },
|
||||
__typename: 'FutureProduct',
|
||||
},
|
||||
__typename: 'InstrumentConfiguration',
|
||||
},
|
||||
__typename: 'NewMarket',
|
||||
},
|
||||
__typename: 'ProposalTerms',
|
||||
},
|
||||
votes: {
|
||||
yes: {
|
||||
totalTokens: '0',
|
||||
totalNumber: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
__typename: 'ProposalVoteSide',
|
||||
},
|
||||
no: {
|
||||
totalTokens: '0',
|
||||
totalNumber: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
__typename: 'ProposalVoteSide',
|
||||
},
|
||||
__typename: 'ProposalVotes',
|
||||
},
|
||||
__typename: 'Proposal',
|
||||
},
|
||||
__typename: 'ProposalEdge',
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: 'bc70383f0e9515b15542cf4c63590cd2ca46b3363ba7c4a72af0e62112b3951b',
|
||||
rationale: {
|
||||
title: 'USDC-III',
|
||||
description: 'USDC-III D List test',
|
||||
__typename: 'ProposalRationale',
|
||||
},
|
||||
reference: '',
|
||||
state: 'STATE_ENACTED',
|
||||
datetime: '2022-11-22T15:17:28.829605Z',
|
||||
rejectionReason: null,
|
||||
party: {
|
||||
id: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||
__typename: 'Party',
|
||||
},
|
||||
errorDetails: null,
|
||||
terms: {
|
||||
closingDatetime: '2022-11-22T15:18:56Z',
|
||||
enactmentDatetime: '2022-11-22T15:19:06Z',
|
||||
change: {
|
||||
name: 'USDC-III',
|
||||
symbol: 'USDC-III',
|
||||
decimals: 18,
|
||||
quantum: '1',
|
||||
source: {
|
||||
contractAddress: '0x1F1A067aEC530b66BA5128C9Db76825eC22c3C6b',
|
||||
withdrawThreshold: '100000000000000000000000000',
|
||||
lifetimeLimit: '1000000000000000000000000000',
|
||||
__typename: 'ERC20',
|
||||
},
|
||||
__typename: 'NewAsset',
|
||||
},
|
||||
__typename: 'ProposalTerms',
|
||||
},
|
||||
votes: {
|
||||
yes: {
|
||||
totalTokens: '0',
|
||||
totalNumber: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
__typename: 'ProposalVoteSide',
|
||||
},
|
||||
no: {
|
||||
totalTokens: '0',
|
||||
totalNumber: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
__typename: 'ProposalVoteSide',
|
||||
},
|
||||
__typename: 'ProposalVotes',
|
||||
},
|
||||
__typename: 'Proposal',
|
||||
},
|
||||
__typename: 'ProposalEdge',
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: '9d9b2a9d0179d0e4ccb317f6c4a5db0b905d893190bfb5e5499985ef313281c8',
|
||||
rationale: {
|
||||
title: 'New BTC market',
|
||||
description: 'New BTC market',
|
||||
__typename: 'ProposalRationale',
|
||||
},
|
||||
reference: 'AXeRWS3TvLBFDgWOSHQpKFJf3NTbnWK6310q02fZ',
|
||||
state: 'STATE_ENACTED',
|
||||
datetime: '2022-11-26T19:36:19.26034Z',
|
||||
rejectionReason: null,
|
||||
party: {
|
||||
id: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||
__typename: 'Party',
|
||||
},
|
||||
errorDetails: null,
|
||||
terms: {
|
||||
closingDatetime: '2022-11-26T19:36:42Z',
|
||||
enactmentDatetime: '2023-03-22T13:57:37Z',
|
||||
change: {
|
||||
instrument: {
|
||||
name: 'ETHBTC Quarterly (Feb 2023)',
|
||||
code: 'ETHBTC.QM21',
|
||||
futureProduct: {
|
||||
settlementAsset: { symbol: 'tBTC', __typename: 'Asset' },
|
||||
__typename: 'FutureProduct',
|
||||
},
|
||||
__typename: 'InstrumentConfiguration',
|
||||
},
|
||||
__typename: 'NewMarket',
|
||||
},
|
||||
__typename: 'ProposalTerms',
|
||||
},
|
||||
votes: {
|
||||
yes: {
|
||||
totalTokens: '0',
|
||||
totalNumber: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
__typename: 'ProposalVoteSide',
|
||||
},
|
||||
no: {
|
||||
totalTokens: '0',
|
||||
totalNumber: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
__typename: 'ProposalVoteSide',
|
||||
},
|
||||
__typename: 'ProposalVotes',
|
||||
},
|
||||
__typename: 'Proposal',
|
||||
},
|
||||
__typename: 'ProposalEdge',
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: '9c48796e7988769ededc2b2b02220b00e93f65f23e8141bf1fd23a6983d95943',
|
||||
rationale: {
|
||||
title: 'Update governance.proposal.asset.requiredMajority',
|
||||
description:
|
||||
'Proposal to update governance.proposal.asset.requiredMajority to 300}',
|
||||
__typename: 'ProposalRationale',
|
||||
},
|
||||
reference: '',
|
||||
state: 'STATE_ENACTED',
|
||||
datetime: '2022-11-22T13:22:52.370655Z',
|
||||
rejectionReason: null,
|
||||
party: {
|
||||
id: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||
__typename: 'Party',
|
||||
},
|
||||
errorDetails: null,
|
||||
terms: {
|
||||
closingDatetime: '2022-11-22T13:27:13Z',
|
||||
enactmentDatetime: '2022-11-22T13:27:33Z',
|
||||
change: {
|
||||
networkParameter: {
|
||||
key: 'governance.proposal.asset.requiredParticipation',
|
||||
value: '0.000001',
|
||||
__typename: 'NetworkParameter',
|
||||
},
|
||||
__typename: 'UpdateNetworkParameter',
|
||||
},
|
||||
__typename: 'ProposalTerms',
|
||||
},
|
||||
votes: {
|
||||
yes: {
|
||||
totalTokens: '0',
|
||||
totalNumber: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
__typename: 'ProposalVoteSide',
|
||||
},
|
||||
no: {
|
||||
totalTokens: '0',
|
||||
totalNumber: '0',
|
||||
totalEquityLikeShareWeight: '0',
|
||||
__typename: 'ProposalVoteSide',
|
||||
},
|
||||
__typename: 'ProposalVotes',
|
||||
},
|
||||
__typename: 'Proposal',
|
||||
},
|
||||
__typename: 'ProposalEdge',
|
||||
},
|
||||
],
|
||||
__typename: 'ProposalsConnection',
|
||||
},
|
||||
};
|
||||
@@ -44,7 +44,8 @@ describe(
|
||||
function () {
|
||||
before('connect wallets and set approval limit', function () {
|
||||
cy.visit('/');
|
||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||
ethereumWalletConnect();
|
||||
cy.associateTokensToVegaWallet('1');
|
||||
});
|
||||
|
||||
beforeEach('visit proposals tab', function () {
|
||||
@@ -56,7 +57,7 @@ describe(
|
||||
navigateTo(navigation.proposals);
|
||||
});
|
||||
|
||||
// 3001-VOTE-055
|
||||
// 3001-VOTE-050 3001-VOTE-054 3001-VOTE-055 3002-PROP-019
|
||||
it('Newly created raw proposal details - shows proposal title and full description', function () {
|
||||
createRawProposal();
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
@@ -213,6 +214,7 @@ describe(
|
||||
|
||||
// 3001-VOTE-042, 3001-VOTE-057, 3001-VOTE-058, 3001-VOTE-059, 3001-VOTE-060
|
||||
it('Newly created proposal details - ability to increase associated tokens - by voting again after association', function () {
|
||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||
createRawProposal();
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
getSubmittedProposalFromProposalList(rawProposal.rationale.title)
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
createUpdateNetworkProposalTxBody,
|
||||
createFreeFormProposalTxBody,
|
||||
} from '../../support/proposal.functions';
|
||||
import { ensureSpecifiedUnstakedTokensAreAssociated } from '../../support/staking.functions';
|
||||
import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
|
||||
import { vegaWalletSetSpecifiedApprovalAmount } from '../../support/wallet-teardown.functions';
|
||||
|
||||
@@ -33,10 +32,6 @@ context(
|
||||
before('Connect wallets and set approval', function () {
|
||||
cy.visit('/');
|
||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||
cy.connectVegaWallet();
|
||||
ethereumWalletConnect();
|
||||
ensureSpecifiedUnstakedTokensAreAssociated('1');
|
||||
cy.clearLocalStorage();
|
||||
});
|
||||
|
||||
beforeEach('visit proposals', function () {
|
||||
@@ -114,7 +109,7 @@ context(
|
||||
navigateTo(navigation.proposals);
|
||||
cy.reload();
|
||||
waitForSpinner();
|
||||
cy.get(openProposals).within(() => {
|
||||
cy.get(openProposals, { timeout: 6000 }).within(() => {
|
||||
cy.contains(proposalTitle)
|
||||
.parentsUntil('[data-testid="proposals-list-item"]')
|
||||
.within(() => cy.get(viewProposalButton).click());
|
||||
@@ -128,7 +123,7 @@ context(
|
||||
.and('be.visible');
|
||||
});
|
||||
|
||||
// 3001-VOTE-048 3001-VOTE-049
|
||||
// 3001-VOTE-048 3001-VOTE-049 3001-VOTE-050
|
||||
it('Able to fail proposal due to lack of participation', function () {
|
||||
const proposalTitle = 'Add New free form proposal with short enactment';
|
||||
const proposalTx = createFreeFormProposalTxBody();
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
waitForSpinner,
|
||||
navigateTo,
|
||||
navigation,
|
||||
closeDialog,
|
||||
} from '../../support/common.functions';
|
||||
import {
|
||||
clickOnValidatorFromList,
|
||||
@@ -41,7 +42,6 @@ const vegaWalletNameElement = '[data-testid="wallet-name"]';
|
||||
const vegaWallet = '[data-testid="vega-wallet"]';
|
||||
const connectToVegaWalletButton = '[data-testid="connect-to-vega-wallet-btn"]';
|
||||
const newProposalSubmitButton = '[data-testid="proposal-submit"]';
|
||||
const dialogCloseButton = '[data-testid="dialog-close"]';
|
||||
const viewProposalButton = '[data-testid="view-proposal-btn"]';
|
||||
const rawProposalData = '[data-testid="proposal-data"]';
|
||||
const minVoteButton = '[data-testid="min-vote"]';
|
||||
@@ -177,7 +177,7 @@ context(
|
||||
'be.visible'
|
||||
);
|
||||
cy.contains('Proposal rejected', proposalTimeout).should('be.visible');
|
||||
cy.get(dialogCloseButton).click();
|
||||
closeDialog();
|
||||
waitForProposalSync();
|
||||
navigateTo(navigation.proposals);
|
||||
cy.get(rejectProposalsLink).click();
|
||||
@@ -214,7 +214,7 @@ context(
|
||||
enterRawProposalBody(createTenDigitUnixTimeStampForSpecifiedDays(8));
|
||||
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
|
||||
cy.get(feedbackError).should('have.text', errorMsg);
|
||||
cy.get(dialogCloseButton).click();
|
||||
closeDialog();
|
||||
});
|
||||
|
||||
// 3002-PROP-009
|
||||
@@ -227,12 +227,12 @@ context(
|
||||
enterRawProposalBody(createTenDigitUnixTimeStampForSpecifiedDays(8));
|
||||
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
|
||||
cy.get(feedbackError).should('have.text', errorMsg);
|
||||
cy.get(dialogCloseButton).click();
|
||||
closeDialog();
|
||||
});
|
||||
|
||||
it('Unable to create a freeform proposal - when json parent section contains unexpected field', function () {
|
||||
const errorMsg =
|
||||
'Invalid params: the transaction does not use a valid Vega command: unknown field unexpected" in vega.commands.v1.ProposalSubmission';
|
||||
'Invalid params: the transaction does not use a valid Vega command: unknown field "unexpected" in vega.commands.v1.ProposalSubmission';
|
||||
|
||||
// 3001-VOTE-038 3002-PROP-013 3002-PROP-014
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
@@ -251,7 +251,7 @@ context(
|
||||
|
||||
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
|
||||
cy.get(feedbackError).should('have.text', errorMsg);
|
||||
cy.get(dialogCloseButton).click();
|
||||
closeDialog();
|
||||
cy.get(rawProposalData)
|
||||
.invoke('val')
|
||||
.should('contain', "i shouldn't be here");
|
||||
@@ -279,7 +279,7 @@ context(
|
||||
|
||||
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
|
||||
cy.get(feedbackError).should('have.text', errorMsg);
|
||||
cy.get(dialogCloseButton).click();
|
||||
closeDialog();
|
||||
});
|
||||
|
||||
// 1005-PROP-009
|
||||
@@ -313,7 +313,7 @@ context(
|
||||
|
||||
it('Unable to vote on a proposal - when vega wallet disconnected - option to connect from within', function () {
|
||||
createRawProposal();
|
||||
cy.get('[data-testid="manage-vega-wallet"]').click();
|
||||
cy.get('[data-testid="manage-vega-wallet"]:visible').click();
|
||||
cy.get('[data-testid="disconnect"]').click();
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
getSubmittedProposalFromProposalList(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
closeDialog,
|
||||
navigateTo,
|
||||
navigation,
|
||||
waitForSpinner,
|
||||
@@ -39,7 +40,6 @@ const maxVoteDeadline = '[data-testid="max-vote"]';
|
||||
const minValidationDeadline = '[data-testid="min-validation"]';
|
||||
const minEnactDeadline = '[data-testid="min-enactment"]';
|
||||
const maxEnactDeadline = '[data-testid="max-enactment"]';
|
||||
const dialogCloseButton = '[data-testid="dialog-close"]';
|
||||
const inputError = '[data-testid="input-error-text"]';
|
||||
const enactmentDeadlineError =
|
||||
'[data-testid="enactment-before-voting-deadline"]';
|
||||
@@ -48,6 +48,7 @@ const feedbackError = '[data-testid="Error"]';
|
||||
const viewProposalBtn = 'view-proposal-btn';
|
||||
const liquidityVoteStatus = 'liquidity-votes-status';
|
||||
const tokenVoteStatus = 'token-votes-status';
|
||||
const proposalTermsSection = 'proposal';
|
||||
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
|
||||
const epochTimeout = Cypress.env('epochTimeout');
|
||||
const proposalTimeout = { timeout: 14000 };
|
||||
@@ -68,7 +69,6 @@ context(
|
||||
{ tags: '@slow' },
|
||||
function () {
|
||||
before('connect wallets and set approval limit', function () {
|
||||
cy.createMarket();
|
||||
cy.visit('/');
|
||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||
});
|
||||
@@ -78,6 +78,7 @@ context(
|
||||
waitForSpinner();
|
||||
cy.connectVegaWallet();
|
||||
ethereumWalletConnect();
|
||||
cy.createMarket();
|
||||
ensureSpecifiedUnstakedTokensAreAssociated('1');
|
||||
navigateTo(navigation.proposals);
|
||||
});
|
||||
@@ -194,7 +195,7 @@ context(
|
||||
'have.text',
|
||||
'Invalid params: proposal_submission.terms.closing_timestamp (cannot be after enactment time)'
|
||||
);
|
||||
cy.get(dialogCloseButton).click();
|
||||
closeDialog();
|
||||
cy.get(minVoteDeadline).click();
|
||||
cy.get(enactmentDeadlineError).should('not.exist');
|
||||
});
|
||||
@@ -217,7 +218,7 @@ context(
|
||||
|
||||
it('Unable to submit new market proposal with missing/invalid fields', function () {
|
||||
const errorMsg =
|
||||
'Invalid params: the transaction is not a valid Vega command: unknown field "filters" in vega.DataSourceDefinition';
|
||||
'Invalid params: the transaction does not use a valid Vega command: unknown field "invalid" in vega.NewMarket';
|
||||
|
||||
goToMakeNewProposal(governanceProposalType.NEW_MARKET);
|
||||
cy.get(newProposalSubmitButton).should('be.visible').click();
|
||||
@@ -286,7 +287,7 @@ context(
|
||||
);
|
||||
});
|
||||
|
||||
// 3001-VOTE-092
|
||||
// 3001-VOTE-092 3004-PMAC-001
|
||||
it('Able to submit update market proposal and vote for proposal', function () {
|
||||
vegaWalletFaucetAssetsWithoutCheck(
|
||||
'fUSDC',
|
||||
@@ -341,14 +342,15 @@ context(
|
||||
'Currently expected to pass'
|
||||
);
|
||||
getProposalInformationFromTable('Expected to pass')
|
||||
.contains('👍 by Token vote')
|
||||
.contains('👍 by token vote')
|
||||
.should('be.visible');
|
||||
});
|
||||
|
||||
// 3001-VOTE-026 3001-VOTE-027 3001-VOTE-028 3001-VOTE-095 3001-VOTE-096 3005-PASN-001
|
||||
it('Able to submit new asset proposal using min deadlines', function () {
|
||||
const proposalTitle = 'Test new asset proposal';
|
||||
goToMakeNewProposal(governanceProposalType.NEW_ASSET);
|
||||
cy.get(newProposalTitle).type('Test new asset proposal');
|
||||
cy.get(newProposalTitle).type(proposalTitle);
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
cy.fixture('/proposals/new-asset').then((newAssetProposal) => {
|
||||
const newAssetPayload = JSON.stringify(newAssetProposal);
|
||||
@@ -367,7 +369,7 @@ context(
|
||||
cy.contains('Proposal waiting for node vote', proposalTimeout).should(
|
||||
'be.visible'
|
||||
);
|
||||
cy.get(dialogCloseButton).click();
|
||||
closeDialog();
|
||||
cy.get(newProposalSubmitButton).should('be.visible').click();
|
||||
// cannot submit a proposal with ERC20 address already in use
|
||||
cy.contains('Proposal rejected', proposalTimeout).should('be.visible');
|
||||
@@ -377,6 +379,17 @@ context(
|
||||
'PROPOSAL_ERROR_ERC20_ADDRESS_ALREADY_IN_USE'
|
||||
);
|
||||
});
|
||||
closeDialog();
|
||||
navigateTo(navigation.proposals);
|
||||
cy.contains(proposalTitle)
|
||||
.parentsUntil(proposalListItem)
|
||||
.within(() => {
|
||||
cy.getByTestId(viewProposalBtn).click();
|
||||
});
|
||||
cy.getByTestId(proposalTermsSection).within(() => {
|
||||
cy.contains('USDT Coin').should('be.visible');
|
||||
cy.contains('USDT').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('Unable to submit new asset proposal with missing/invalid fields', function () {
|
||||
@@ -415,6 +428,12 @@ context(
|
||||
getProposalInformationFromTable('Proposed enactment') // 3001-VOTE-044
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
// 3001-VOTE-030 3001-VOTE-031
|
||||
cy.getByTestId(proposalTermsSection).within(() => {
|
||||
cy.contains('UpdateAsset').should('be.visible');
|
||||
cy.contains('UpdateERC20').should('be.visible');
|
||||
cy.contains('"lifetimeLimit": "10"').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('Able to submit update asset proposal using max deadline', function () {
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
generateFreeFormProposalTitle,
|
||||
getProposalIdFromList,
|
||||
getProposalInformationFromTable,
|
||||
getSortOrderOfSuppliedArray,
|
||||
getSubmittedProposalFromProposalList,
|
||||
goToMakeNewProposal,
|
||||
governanceProposalType,
|
||||
@@ -24,9 +23,9 @@ import { ensureSpecifiedUnstakedTokensAreAssociated } from '../../support/stakin
|
||||
import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
|
||||
import { vegaWalletSetSpecifiedApprovalAmount } from '../../support/wallet-teardown.functions';
|
||||
|
||||
const proposalDetailsTitle = '[data-testid="proposal-title"]';
|
||||
const openProposals = '[data-testid="open-proposals"]';
|
||||
const voteStatus = '[data-testid="vote-status"]';
|
||||
const proposalClosingDate = '[data-testid="vote-details"]';
|
||||
const viewProposalButton = '[data-testid="view-proposal-btn"]';
|
||||
|
||||
describe('Governance flow for proposal list', { tags: '@slow' }, function () {
|
||||
@@ -45,16 +44,8 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
|
||||
});
|
||||
|
||||
it('Newly created proposals list - proposals closest to closing date appear higher in list', function () {
|
||||
const minCloseDays = 2;
|
||||
const maxCloseDays = 3;
|
||||
|
||||
// 3001-VOTE-005
|
||||
const proposalDays = [
|
||||
minCloseDays + 1,
|
||||
maxCloseDays,
|
||||
minCloseDays + 3,
|
||||
minCloseDays + 2,
|
||||
];
|
||||
const proposalDays = [364, 50, 2];
|
||||
for (let index = 0; index < proposalDays.length; index++) {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
enterRawProposalBody(
|
||||
@@ -64,19 +55,12 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
|
||||
waitForProposalSync();
|
||||
}
|
||||
|
||||
const arrayOfProposals: string[] = [];
|
||||
|
||||
navigateTo(navigation.proposals);
|
||||
cy.get(proposalDetailsTitle)
|
||||
.each((proposalTitleElement) => {
|
||||
arrayOfProposals.push(proposalTitleElement.text());
|
||||
})
|
||||
.then(() => {
|
||||
cy.wrap(getSortOrderOfSuppliedArray(arrayOfProposals)).should(
|
||||
'equal',
|
||||
'descending'
|
||||
);
|
||||
});
|
||||
cy.get(openProposals).within(() => {
|
||||
cy.get(proposalClosingDate).first().should('contain.text', 'year');
|
||||
cy.get(proposalClosingDate).should('contain.text', 'months');
|
||||
cy.get(proposalClosingDate).last().should('contain.text', 'days');
|
||||
});
|
||||
});
|
||||
|
||||
it('Newly created proposals list - able to filter by proposerID to show it in list', function () {
|
||||
|
||||
@@ -25,31 +25,37 @@ import {
|
||||
vegaWalletSetSpecifiedApprovalAmount,
|
||||
vegaWalletTeardown,
|
||||
} from '../../support/wallet-teardown.functions';
|
||||
const stakeValidatorListTotalStake = '[col-id="stake"] > div > span';
|
||||
const stakeValidatorListTotalShare = '[col-id="stakeShare"] > div > span';
|
||||
const stakeValidatorListValidatorStake = '[col-id="stake"] > div > span';
|
||||
const stakeRemoveStakeRadioButton = '[data-testid="remove-stake-radio"]';
|
||||
const stakeTokenAmountInputBox = '[data-testid="token-amount-input"]';
|
||||
const stakeTokenSubmitButton = '[data-testid="token-input-submit-button"]';
|
||||
const stakeAddStakeRadioButton = '[data-testid="add-stake-radio"]';
|
||||
const stakeMaximumTokens = '[data-testid="token-amount-use-maximum"]';
|
||||
const totalStake = '[data-testid="total-stake"]';
|
||||
const stakeShare = '[data-testid="stake-percentage"]';
|
||||
const stakeValidatorListTotalStake = 'total-stake';
|
||||
const stakeValidatorListTotalShare = 'total-stake-share';
|
||||
const stakeValidatorListStakePercentage = 'stake-percentage';
|
||||
const userStakeBtn = 'my-stake-btn';
|
||||
const userStake = 'user-stake';
|
||||
const userStakeShare = 'user-stake-share';
|
||||
const viewAllValidatorsToggle = 'validators-view-toggle-all';
|
||||
const viewStakedByMeToggle = 'validators-view-toggle-myStake';
|
||||
const stakeRemoveStakeRadioButton = 'remove-stake-radio';
|
||||
const stakeTokenAmountInputBox = 'token-amount-input';
|
||||
const stakeTokenSubmitButton = 'token-input-submit-button';
|
||||
const stakeAddStakeRadioButton = 'add-stake-radio';
|
||||
const stakeMaximumTokens = 'token-amount-use-maximum';
|
||||
const vegaWalletAssociatedBalance = 'currency-value';
|
||||
const vegaWalletStakedBalances = 'vega-wallet-balance-staked-validators';
|
||||
const ethWallet = 'ethereum-wallet';
|
||||
const vegaWallet = 'vega-wallet';
|
||||
const vegaWalletPublicKeyShort = Cypress.env('vegaWalletPublicKeyShort');
|
||||
const vegaWalletAssociatedBalance = '[data-testid="currency-value"]';
|
||||
const vegaWalletStakedBalances =
|
||||
'[data-testid="vega-wallet-balance-staked-validators"]';
|
||||
const ethWalletContainer = '[data-testid="ethereum-wallet"]';
|
||||
const vegaWallet = '[data-testid="vega-wallet"]';
|
||||
const txTimeout = Cypress.env('txTimeout');
|
||||
const epochTimeout = Cypress.env('epochTimeout');
|
||||
|
||||
const getEthereumWallet = () => cy.get(`[data-testid="${ethWallet}"]:visible`);
|
||||
const getVegaWallet = () => cy.get(`[data-testid="${vegaWallet}"]:visible`);
|
||||
|
||||
context(
|
||||
'Staking Tab - with eth and vega wallets connected',
|
||||
{ tags: '@slow' },
|
||||
function () {
|
||||
// 2001-STKE-002, 2001-STKE-032
|
||||
before('visit staking tab and connect vega wallet', function () {
|
||||
cy.clearAllLocalStorage();
|
||||
cy.visit('/');
|
||||
ethereumWalletConnect();
|
||||
// this is a workaround for #2422 which can be removed once issue is resolved
|
||||
@@ -91,6 +97,39 @@ context(
|
||||
validateValidatorListTotalStakeAndShare('0', '2.00', '100.00%');
|
||||
});
|
||||
|
||||
it('Able to view validators staked by me', function () {
|
||||
ensureSpecifiedUnstakedTokensAreAssociated('4');
|
||||
cy.get('button').contains('Select a validator to nominate').click();
|
||||
clickOnValidatorFromList(0);
|
||||
stakingValidatorPageAddStake('2');
|
||||
closeStakingDialog();
|
||||
navigateTo(navigation.validators);
|
||||
cy.getByTestId(userStake, epochTimeout)
|
||||
.first()
|
||||
.should('have.text', '2.00');
|
||||
cy.getByTestId('total-stake').first().realHover();
|
||||
cy.getByTestId('staked-by-user-tooltip')
|
||||
.first()
|
||||
.should('have.text', 'Staked by me: 2.00');
|
||||
cy.getByTestId('total-pending-stake').first().realHover();
|
||||
cy.getByTestId('pending-user-stake-tooltip')
|
||||
.first()
|
||||
.should('have.text', 'My pending stake: 0.00');
|
||||
cy.getByTestId(userStakeShare).invoke('text').should('not.be.empty'); // Adjust when #3286 is resolved
|
||||
cy.getByTestId(userStakeBtn).should('exist').click();
|
||||
verifyThisEpochValue(2.0);
|
||||
navigateTo(navigation.validators);
|
||||
cy.getByTestId(viewStakedByMeToggle).click();
|
||||
cy.getByTestId(userStakeBtn).should('have.length', 1);
|
||||
cy.getByTestId(viewAllValidatorsToggle).click();
|
||||
clickOnValidatorFromList(1);
|
||||
stakingValidatorPageAddStake('2');
|
||||
closeStakingDialog();
|
||||
navigateTo(navigation.validators);
|
||||
cy.getByTestId(viewStakedByMeToggle).click();
|
||||
cy.getByTestId(userStakeBtn).should('have.length', 2);
|
||||
});
|
||||
|
||||
it('Able to stake against a validator - using vega from vesting contract', function () {
|
||||
stakingPageAssociateTokens('3', { type: 'contract' });
|
||||
verifyUnstakedBalance(3.0);
|
||||
@@ -109,14 +148,13 @@ context(
|
||||
});
|
||||
|
||||
it('Able to stake against a validator - using vega from both wallet and vesting contract', function () {
|
||||
vegaWalletTeardown();
|
||||
stakingPageAssociateTokens('3', { type: 'contract' });
|
||||
navigateTo(navigation.validators);
|
||||
stakingPageAssociateTokens('4', { type: 'wallet' });
|
||||
verifyUnstakedBalance(7.0);
|
||||
verifyEthWalletTotalAssociatedBalance('3.0');
|
||||
verifyEthWalletTotalAssociatedBalance('4.0');
|
||||
verifyEthWalletTotalAssociatedBalance('3.0');
|
||||
verifyEthWalletTotalAssociatedBalance('4.0');
|
||||
cy.get('button').contains('Select a validator to nominate').click();
|
||||
clickOnValidatorFromList(0);
|
||||
stakingValidatorPageAddStake('6');
|
||||
@@ -136,7 +174,7 @@ context(
|
||||
clickOnValidatorFromList(0);
|
||||
stakingValidatorPageAddStake('2');
|
||||
verifyUnstakedBalance(3.0);
|
||||
cy.get(vegaWalletStakedBalances, txTimeout)
|
||||
cy.getByTestId(vegaWalletStakedBalances, txTimeout)
|
||||
.parent()
|
||||
.should('contain', 2.0, txTimeout);
|
||||
closeStakingDialog();
|
||||
@@ -144,40 +182,44 @@ context(
|
||||
clickOnValidatorFromList(1);
|
||||
stakingValidatorPageAddStake('1');
|
||||
verifyUnstakedBalance(2.0);
|
||||
cy.get(vegaWalletStakedBalances, txTimeout)
|
||||
cy.getByTestId(vegaWalletStakedBalances, txTimeout)
|
||||
.should('have.length', 4, txTimeout)
|
||||
.eq(0)
|
||||
.should('contain', 2.0, txTimeout);
|
||||
cy.get(vegaWalletStakedBalances, txTimeout)
|
||||
cy.getByTestId(vegaWalletStakedBalances, txTimeout)
|
||||
.eq(1)
|
||||
.should('contain', 1.0, txTimeout);
|
||||
closeStakingDialog();
|
||||
navigateTo(navigation.validators);
|
||||
cy.get(`[row-id="${0}"]`).within(() => {
|
||||
cy.get(stakeValidatorListTotalStake)
|
||||
.should('have.text', '2.00')
|
||||
.and('be.visible');
|
||||
cy.get(stakeValidatorListTotalShare)
|
||||
.should('have.text', '66.67%')
|
||||
.and('be.visible');
|
||||
cy.get(stakeValidatorListValidatorStake)
|
||||
.scrollIntoView()
|
||||
.should('have.text', '2.00')
|
||||
.and('be.visible');
|
||||
});
|
||||
cy.get(`[row-id="${1}"]`).within(() => {
|
||||
cy.get(stakeValidatorListTotalStake)
|
||||
.scrollIntoView()
|
||||
.should('have.text', '1.00')
|
||||
.and('be.visible');
|
||||
cy.get(stakeValidatorListTotalShare)
|
||||
.should('have.text', '33.33%')
|
||||
.and('be.visible');
|
||||
cy.get(stakeValidatorListValidatorStake)
|
||||
.scrollIntoView()
|
||||
.should('have.text', '1.00')
|
||||
.and('be.visible');
|
||||
});
|
||||
cy.get(`[row-id="${0}"]`)
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.getByTestId(stakeValidatorListTotalStake)
|
||||
.should('have.text', '2.00')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(stakeValidatorListTotalShare)
|
||||
.should('have.text', '66.67%')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(stakeValidatorListTotalStake)
|
||||
.scrollIntoView()
|
||||
.should('have.text', '2.00')
|
||||
.and('be.visible');
|
||||
});
|
||||
cy.get(`[row-id="${1}"]`)
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.getByTestId(stakeValidatorListTotalStake)
|
||||
.scrollIntoView()
|
||||
.should('have.text', '1.00')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(stakeValidatorListTotalShare)
|
||||
.should('have.text', '33.33%')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(stakeValidatorListTotalStake)
|
||||
.scrollIntoView()
|
||||
.should('have.text', '1.00')
|
||||
.and('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
// 2001-STKE-041
|
||||
@@ -203,9 +245,15 @@ context(
|
||||
verifyStakedBalance(2.0);
|
||||
verifyNextEpochValue(2.0);
|
||||
verifyThisEpochValue(2.0);
|
||||
cy.get(totalStake, epochTimeout).should('contain.text', '2');
|
||||
cy.getByTestId(stakeValidatorListTotalStake, epochTimeout).should(
|
||||
'contain.text',
|
||||
'2'
|
||||
);
|
||||
waitForBeginningOfEpoch();
|
||||
cy.get(stakeShare).should('have.text', '100%');
|
||||
cy.getByTestId(stakeValidatorListStakePercentage).should(
|
||||
'have.text',
|
||||
'100%'
|
||||
);
|
||||
navigateTo(navigation.validators);
|
||||
validateValidatorListTotalStakeAndShare('0', '2.00', '100.00%');
|
||||
}
|
||||
@@ -227,12 +275,16 @@ context(
|
||||
verifyUnstakedBalance(3.0);
|
||||
verifyNextEpochValue(0.0);
|
||||
verifyThisEpochValue(0.0);
|
||||
cy.get(vegaWalletStakedBalances, txTimeout).should(
|
||||
cy.getByTestId(vegaWalletStakedBalances, txTimeout).should(
|
||||
'not.exist',
|
||||
txTimeout
|
||||
);
|
||||
navigateTo(navigation.validators);
|
||||
validateValidatorListTotalStakeAndShare('0', '0.00', '0.00%');
|
||||
|
||||
cy.getByTestId(userStakeBtn).should('not.exist');
|
||||
cy.getByTestId(userStake).should('not.exist');
|
||||
cy.getByTestId(userStakeShare).should('not.exist');
|
||||
});
|
||||
|
||||
it('Unable to remove a stake with a negative value for a validator', function () {
|
||||
@@ -246,10 +298,10 @@ context(
|
||||
closeStakingDialog();
|
||||
navigateTo(navigation.validators);
|
||||
clickOnValidatorFromList(0);
|
||||
cy.get(stakeRemoveStakeRadioButton, txTimeout).click();
|
||||
cy.get(stakeTokenAmountInputBox).type('-0.1');
|
||||
cy.getByTestId(stakeRemoveStakeRadioButton, txTimeout).click();
|
||||
cy.getByTestId(stakeTokenAmountInputBox).type('-0.1');
|
||||
cy.contains('Waiting for next epoch to start', epochTimeout);
|
||||
cy.get(stakeTokenSubmitButton)
|
||||
cy.getByTestId(stakeTokenSubmitButton)
|
||||
.should('be.disabled', epochTimeout)
|
||||
.and('contain', `Remove -0.1 $VEGA tokens at the end of epoch`)
|
||||
.and('be.visible');
|
||||
@@ -266,10 +318,10 @@ context(
|
||||
closeStakingDialog();
|
||||
navigateTo(navigation.validators);
|
||||
clickOnValidatorFromList(0);
|
||||
cy.get(stakeRemoveStakeRadioButton).click();
|
||||
cy.get(stakeTokenAmountInputBox).type('4');
|
||||
cy.getByTestId(stakeRemoveStakeRadioButton).click();
|
||||
cy.getByTestId(stakeTokenAmountInputBox).type('4');
|
||||
cy.contains('Waiting for next epoch to start', epochTimeout);
|
||||
cy.get(stakeTokenSubmitButton)
|
||||
cy.getByTestId(stakeTokenSubmitButton)
|
||||
.should('be.disabled', epochTimeout)
|
||||
.and('contain', `Remove 4 $VEGA tokens at the end of epoch`)
|
||||
.and('be.visible');
|
||||
@@ -285,17 +337,17 @@ context(
|
||||
verifyStakedBalance(2.0);
|
||||
closeStakingDialog();
|
||||
stakingPageDisassociateAllTokens();
|
||||
cy.get(ethWalletContainer).within(() => {
|
||||
getEthereumWallet().within(() => {
|
||||
cy.contains(vegaWalletPublicKeyShort, txTimeout).should('not.exist');
|
||||
});
|
||||
verifyEthWalletTotalAssociatedBalance('0.0');
|
||||
cy.get(vegaWallet).within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
|
||||
getVegaWallet().within(() => {
|
||||
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
|
||||
'contain',
|
||||
'0.00'
|
||||
);
|
||||
});
|
||||
cy.get(vegaWalletStakedBalances, txTimeout).should(
|
||||
cy.getByTestId(vegaWalletStakedBalances, txTimeout).should(
|
||||
'not.exist',
|
||||
txTimeout
|
||||
);
|
||||
@@ -313,17 +365,17 @@ context(
|
||||
verifyStakedBalance(2.0);
|
||||
closeStakingDialog();
|
||||
stakingPageDisassociateAllTokens('contract');
|
||||
cy.get(ethWalletContainer).within(() => {
|
||||
getEthereumWallet().within(() => {
|
||||
cy.contains(vegaWalletPublicKeyShort, txTimeout).should('not.exist');
|
||||
});
|
||||
verifyEthWalletTotalAssociatedBalance('0.0');
|
||||
cy.get(vegaWallet).within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
|
||||
getVegaWallet().within(() => {
|
||||
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
|
||||
'contain',
|
||||
'0.00'
|
||||
);
|
||||
});
|
||||
cy.get(vegaWalletStakedBalances, txTimeout).should(
|
||||
cy.getByTestId(vegaWalletStakedBalances, txTimeout).should(
|
||||
'not.exist',
|
||||
txTimeout
|
||||
);
|
||||
@@ -342,8 +394,8 @@ context(
|
||||
closeStakingDialog();
|
||||
stakingPageDisassociateTokens('1');
|
||||
verifyEthWalletTotalAssociatedBalance('2.0');
|
||||
cy.get(vegaWallet).within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
|
||||
getVegaWallet().within(() => {
|
||||
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
|
||||
'contain',
|
||||
'2.00'
|
||||
);
|
||||
@@ -409,8 +461,8 @@ context(
|
||||
verifyUnstakedBalance(0.0);
|
||||
closeStakingDialog();
|
||||
stakingPageAssociateTokens('6');
|
||||
cy.get(vegaWallet).within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
|
||||
getVegaWallet().within(() => {
|
||||
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
|
||||
'contain',
|
||||
'12.00'
|
||||
);
|
||||
@@ -429,9 +481,12 @@ context(
|
||||
verifyUnstakedBalance(1.0);
|
||||
closeStakingDialog();
|
||||
clickOnValidatorFromList(0);
|
||||
cy.get(stakeAddStakeRadioButton).click();
|
||||
cy.get(stakeMaximumTokens, { timeout: 60000 }).click();
|
||||
cy.get(stakeTokenSubmitButton).should('contain', 'Add 1 $VEGA tokens');
|
||||
cy.getByTestId(stakeAddStakeRadioButton).click();
|
||||
cy.getByTestId(stakeMaximumTokens, { timeout: 60000 }).click();
|
||||
cy.getByTestId(stakeTokenSubmitButton).should(
|
||||
'contain',
|
||||
'Add 1 $VEGA tokens'
|
||||
);
|
||||
});
|
||||
|
||||
afterEach('Teardown Wallet', function () {
|
||||
|
||||
@@ -25,11 +25,11 @@ const vegaWalletUnstakedBalance =
|
||||
'[data-testid="vega-wallet-balance-unstaked"]';
|
||||
const txTimeout = Cypress.env('txTimeout');
|
||||
const vegaWalletPublicKeyShort = Cypress.env('vegaWalletPublicKeyShort');
|
||||
const ethWalletAssociateButton = '[data-testid="associate-btn"]';
|
||||
const ethWalletAssociateButton = '[data-testid="associate-btn"]:visible';
|
||||
const associateWalletRadioButton = '[data-testid="associate-radio-wallet"]';
|
||||
const tokenAmountInputBox = '[data-testid="token-amount-input"]';
|
||||
const tokenSubmitButton = '[data-testid="token-input-submit-button"]';
|
||||
const ethWalletDissociateButton = '[href="/token/disassociate"]';
|
||||
const ethWalletDissociateButton = '[href="/token/disassociate"]:visible';
|
||||
const vestingContractSection = '[data-testid="vega-in-vesting-contract"]';
|
||||
const vegaInWalletSection = '[data-testid="vega-in-wallet"]';
|
||||
const connectedVegaKey = '[data-testid="connected-vega-key"]';
|
||||
@@ -78,12 +78,12 @@ context(
|
||||
|
||||
cy.getByTestId('currency-title', txTimeout).should(
|
||||
'have.length.above',
|
||||
3
|
||||
6
|
||||
);
|
||||
validateWalletCurrency('Associated', '0.00');
|
||||
validateWalletCurrency('Pending association', '2.00');
|
||||
validateWalletCurrency('Total associated after pending', '2.00');
|
||||
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
|
||||
cy.getByTestId('currency-title', txTimeout).should('have.length', 6);
|
||||
|
||||
// 0005-ETXN-002
|
||||
verifyEthWalletAssociatedBalance('2.0');
|
||||
@@ -111,12 +111,12 @@ context(
|
||||
stakingPageDisassociateTokens('2');
|
||||
cy.getByTestId('currency-title', txTimeout).should(
|
||||
'have.length.above',
|
||||
3
|
||||
6
|
||||
);
|
||||
validateWalletCurrency('Associated', '2.00');
|
||||
validateWalletCurrency('Pending association', '2.00');
|
||||
validateWalletCurrency('Total associated after pending', '0.00');
|
||||
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
|
||||
cy.getByTestId('currency-title', txTimeout).should('have.length', 6);
|
||||
cy.getByTestId('eth-wallet-associated-balances', txTimeout).should(
|
||||
'not.exist'
|
||||
);
|
||||
@@ -183,6 +183,7 @@ context(
|
||||
// 1004-ASSO-018
|
||||
// 1004-ASSO-024
|
||||
// 1004-ASSO-023
|
||||
// 1004-ASSO-032
|
||||
|
||||
stakingPageAssociateTokens('2', {
|
||||
type: 'contract',
|
||||
@@ -191,12 +192,12 @@ context(
|
||||
|
||||
cy.getByTestId('currency-title', txTimeout).should(
|
||||
'have.length.above',
|
||||
3
|
||||
6
|
||||
);
|
||||
validateWalletCurrency('Associated', '0.00');
|
||||
validateWalletCurrency('Pending association', '2.00');
|
||||
validateWalletCurrency('Total associated after pending', '2.00');
|
||||
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
|
||||
cy.getByTestId('currency-title', txTimeout).should('have.length', 6);
|
||||
verifyEthWalletAssociatedBalance('2.0');
|
||||
verifyEthWalletTotalAssociatedBalance('2.0');
|
||||
cy.get(vegaWallet).within(() => {
|
||||
@@ -209,12 +210,12 @@ context(
|
||||
});
|
||||
cy.getByTestId('currency-title', txTimeout).should(
|
||||
'have.length.above',
|
||||
3
|
||||
6
|
||||
);
|
||||
validateWalletCurrency('Associated', '2.00');
|
||||
validateWalletCurrency('Pending association', '1.00');
|
||||
validateWalletCurrency('Total associated after pending', '1.00');
|
||||
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
|
||||
cy.getByTestId('currency-title', txTimeout).should('have.length', 6);
|
||||
verifyEthWalletAssociatedBalance('1.0');
|
||||
verifyEthWalletTotalAssociatedBalance('1.0');
|
||||
});
|
||||
@@ -265,7 +266,7 @@ context(
|
||||
// 1004-ASSO-008
|
||||
// 1004-ASSO-010
|
||||
// No warning visible as described in AC, but the button is disabled
|
||||
cy.get(ethWalletAssociateButton).first().click();
|
||||
cy.get(ethWalletAssociateButton).click();
|
||||
cy.get(associateWalletRadioButton, { timeout: 30000 }).click();
|
||||
cy.get(tokenSubmitButton, txTimeout).should('be.disabled'); // button disabled with no input
|
||||
cy.get(tokenAmountInputBox, { timeout: 10000 }).type('6500000');
|
||||
@@ -277,12 +278,12 @@ context(
|
||||
vegaWalletAssociate('2');
|
||||
cy.getByTestId('currency-title', txTimeout).should(
|
||||
'have.length.above',
|
||||
3
|
||||
6
|
||||
);
|
||||
validateWalletCurrency('Associated', '0.00');
|
||||
validateWalletCurrency('Pending association', '2.00');
|
||||
validateWalletCurrency('Total associated after pending', '2.00');
|
||||
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
|
||||
cy.getByTestId('currency-title', txTimeout).should('have.length', 6);
|
||||
validateWalletCurrency('Associated', '2.00');
|
||||
});
|
||||
|
||||
@@ -293,24 +294,24 @@ context(
|
||||
});
|
||||
cy.getByTestId('currency-title', txTimeout).should(
|
||||
'have.length.above',
|
||||
3
|
||||
6
|
||||
);
|
||||
validateWalletCurrency('Associated', '2.00');
|
||||
validateWalletCurrency('Pending association', '2.00');
|
||||
validateWalletCurrency('Total associated after pending', '0.00');
|
||||
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
|
||||
cy.getByTestId('currency-title', txTimeout).should('have.length', 6);
|
||||
validateWalletCurrency('Associated', '0.00');
|
||||
});
|
||||
|
||||
it('Able to associate tokens to different public key of connected vega wallet', function () {
|
||||
cy.get(ethWalletAssociateButton).first().click();
|
||||
cy.get(ethWalletAssociateButton).click();
|
||||
cy.get(associateWalletRadioButton).click();
|
||||
cy.get(connectedVegaKey).should(
|
||||
'have.text',
|
||||
Cypress.env('vegaWalletPublicKey')
|
||||
);
|
||||
|
||||
cy.get('[data-testid="manage-vega-wallet"]').click();
|
||||
cy.get('[data-testid="manage-vega-wallet"]:visible').click();
|
||||
cy.get('[data-testid="select-keypair-button"]').eq(0).click();
|
||||
cy.get(connectedVegaKey).should(
|
||||
'have.text',
|
||||
|
||||
@@ -6,159 +6,205 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
cy.get('nav', { timeout: 10000 }).should('be.visible');
|
||||
});
|
||||
|
||||
describe('with wallets disconnected', function () {
|
||||
describe('Links and buttons', function () {
|
||||
it('should have link for proposal page', function () {
|
||||
cy.getByTestId('home-proposals').within(() => {
|
||||
cy.get('[href="/proposals"]')
|
||||
.should('exist')
|
||||
.and('have.text', 'Browse, vote, and propose');
|
||||
});
|
||||
});
|
||||
it('should show open or enacted proposals with proposal summary', function () {
|
||||
cy.get('body').then(($body) => {
|
||||
if (!$body.find('[data-testid="proposals-list-item"]').length) {
|
||||
cy.createMarket();
|
||||
cy.reload();
|
||||
waitForSpinner();
|
||||
}
|
||||
});
|
||||
cy.getByTestId('proposals-list-item')
|
||||
.should('have.length.at.least', 1)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.getByTestId('proposal-title')
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
cy.getByTestId('proposal-type')
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
cy.getByTestId('proposal-description')
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
cy.getByTestId('proposal-details')
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
cy.getByTestId('proposal-status')
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
cy.getByTestId('vote-details')
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
cy.getByTestId('view-proposal-btn').should('be.visible');
|
||||
});
|
||||
});
|
||||
it('should have external link for governance', function () {
|
||||
cy.getByTestId('home-proposals').within(() => {
|
||||
cy.getByTestId('external-link')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', 'https://vega.xyz/governance');
|
||||
});
|
||||
});
|
||||
it('should have link for validator page', function () {
|
||||
cy.getByTestId('home-validators').within(() => {
|
||||
cy.get('[href="/validators"]')
|
||||
.first()
|
||||
.should('exist')
|
||||
.and('have.text', 'Browse, and stake');
|
||||
});
|
||||
});
|
||||
it('should have external link for validators', function () {
|
||||
cy.getByTestId('home-validators').within(() => {
|
||||
cy.getByTestId('external-link')
|
||||
.should('have.attr', 'href')
|
||||
.and(
|
||||
'contain',
|
||||
'https://community.vega.xyz/c/mainnet-validator-candidates'
|
||||
);
|
||||
});
|
||||
});
|
||||
it('should have information on active nodes', function () {
|
||||
cy.getByTestId('node-information')
|
||||
.first()
|
||||
.should('contain.text', '2')
|
||||
.and('contain.text', 'active nodes');
|
||||
});
|
||||
it('should have information on consensus nodes', function () {
|
||||
cy.getByTestId('node-information')
|
||||
.last()
|
||||
.should('contain.text', '2')
|
||||
.and('contain.text', 'consensus nodes');
|
||||
});
|
||||
it('should contain link to specific validators', function () {
|
||||
cy.getByTestId('validators')
|
||||
.should('have.length', '2')
|
||||
.each(($validator) => {
|
||||
cy.wrap($validator).find('a').should('have.attr', 'href');
|
||||
});
|
||||
});
|
||||
it('should have link for rewards page', function () {
|
||||
cy.getByTestId('home-rewards').within(() => {
|
||||
cy.get('[href="/rewards"]')
|
||||
.first()
|
||||
.should('exist')
|
||||
.and('have.text', 'See rewards');
|
||||
});
|
||||
});
|
||||
it('should have link for withdrawal page', function () {
|
||||
cy.getByTestId('home-vega-token').within(() => {
|
||||
cy.get('[href="/token/withdraw"]')
|
||||
.first()
|
||||
.should('exist')
|
||||
.and('have.text', 'Manage tokens');
|
||||
});
|
||||
describe('Links and buttons', function () {
|
||||
it('should have link for proposal page', function () {
|
||||
cy.getByTestId('home-proposals').within(() => {
|
||||
cy.get('[href="/proposals"]')
|
||||
.should('exist')
|
||||
.and('have.text', 'Browse, vote, and propose');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Mobile view - navigation bar', function () {
|
||||
before('Change to mobile resolution', function () {
|
||||
cy.viewport('iphone-xr');
|
||||
});
|
||||
|
||||
it('should have burger button', () => {
|
||||
cy.getByTestId('button-menu-drawer').should('be.visible').click();
|
||||
cy.getByTestId('menu-drawer').should('be.visible');
|
||||
});
|
||||
|
||||
it('should have link for proposal page', function () {
|
||||
cy.getByTestId('menu-drawer').within(() => {
|
||||
cy.get('[href="/proposals"]')
|
||||
.should('exist')
|
||||
.and('have.text', 'Proposals');
|
||||
it('should display announcement banner', function () {
|
||||
cy.getByTestId('app-announcement')
|
||||
.should('contain.text', 'TEST ANNOUNCEMENT!')
|
||||
.within(() => {
|
||||
cy.getByTestId('external-link')
|
||||
.should('have.attr', 'href', 'https://fairground.wtf')
|
||||
.and('have.text', 'CLICK LINK');
|
||||
});
|
||||
cy.getByTestId('app-announcement-close').should('be.visible').click();
|
||||
cy.getByTestId('app-announcement').should('not.exist');
|
||||
});
|
||||
|
||||
it('should show open or enacted proposals with proposal summary', function () {
|
||||
cy.get('body').then(($body) => {
|
||||
if (!$body.find('[data-testid="proposals-list-item"]').length) {
|
||||
cy.createMarket();
|
||||
cy.reload();
|
||||
waitForSpinner();
|
||||
}
|
||||
});
|
||||
it('should have link for validator page', function () {
|
||||
cy.getByTestId('menu-drawer').within(() => {
|
||||
cy.get('[href="/validators"]')
|
||||
cy.getByTestId('proposals-list-item')
|
||||
.should('have.length.at.least', 1)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.getByTestId('proposal-title')
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
cy.getByTestId('proposal-type').invoke('text').should('not.be.empty');
|
||||
cy.getByTestId('proposal-description')
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
cy.getByTestId('proposal-details')
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
cy.getByTestId('proposal-status')
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
cy.getByTestId('vote-details').invoke('text').should('not.be.empty');
|
||||
cy.getByTestId('view-proposal-btn').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have external link for governance', function () {
|
||||
cy.getByTestId('home-proposals').within(() => {
|
||||
cy.getByTestId('external-link')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', 'https://vega.xyz/governance');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have link for validator page', function () {
|
||||
cy.getByTestId('home-validators').within(() => {
|
||||
cy.get('[href="/validators"]')
|
||||
.first()
|
||||
.should('exist')
|
||||
.and('have.text', 'Browse, and stake');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have external link for validators', function () {
|
||||
cy.getByTestId('home-validators').within(() => {
|
||||
cy.getByTestId('external-link')
|
||||
.should('have.attr', 'href')
|
||||
.and(
|
||||
'contain',
|
||||
'https://community.vega.xyz/c/mainnet-validator-candidates'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have information on active nodes', function () {
|
||||
cy.getByTestId('node-information')
|
||||
.first()
|
||||
.should('contain.text', '2')
|
||||
.and('contain.text', 'active nodes');
|
||||
});
|
||||
|
||||
it('should have information on consensus nodes', function () {
|
||||
cy.getByTestId('node-information')
|
||||
.last()
|
||||
.should('contain.text', '2')
|
||||
.and('contain.text', 'consensus nodes');
|
||||
});
|
||||
|
||||
it('should contain link to specific validators', function () {
|
||||
cy.getByTestId('validators')
|
||||
.should('have.length', '2')
|
||||
.each(($validator) => {
|
||||
cy.wrap($validator).find('a').should('have.attr', 'href');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have link for rewards page', function () {
|
||||
cy.getByTestId('home-rewards').within(() => {
|
||||
cy.get('[href="/rewards"]')
|
||||
.first()
|
||||
.should('exist')
|
||||
.and('have.text', 'See rewards');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have link for withdrawal page', function () {
|
||||
cy.getByTestId('home-vega-token').within(() => {
|
||||
cy.get('[href="/token/withdraw"]')
|
||||
.first()
|
||||
.should('exist')
|
||||
.and('have.text', 'Manage tokens');
|
||||
});
|
||||
});
|
||||
|
||||
it('should display network data', function () {
|
||||
cy.getByTestId('git-network-data')
|
||||
.should('contain.text', 'Reading network data from')
|
||||
.within(() => {
|
||||
cy.get('span')
|
||||
.first()
|
||||
.should('exist')
|
||||
.and('have.text', 'Validators');
|
||||
.should('have.text', 'http://localhost:3028/query');
|
||||
cy.getByTestId('link').should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should have link for rewards page', function () {
|
||||
cy.getByTestId('menu-drawer').within(() => {
|
||||
cy.get('[href="/rewards"]')
|
||||
.first()
|
||||
.should('exist')
|
||||
.and('have.text', 'Rewards');
|
||||
it('should display eth data', function () {
|
||||
cy.getByTestId('git-eth-data')
|
||||
.should('contain.text', 'Reading Ethereum data from')
|
||||
.within(() => {
|
||||
cy.get('span').should('have.text', 'http://localhost:8545');
|
||||
});
|
||||
});
|
||||
it('should have link for withdrawal page', function () {
|
||||
cy.getByTestId('menu-drawer').within(() => {
|
||||
cy.get('[href="/token/withdraw"]')
|
||||
.first()
|
||||
.should('exist')
|
||||
.and('have.text', 'Withdraw');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
after(function () {
|
||||
cy.viewport(
|
||||
Cypress.config('viewportWidth'),
|
||||
Cypress.config('viewportHeight')
|
||||
);
|
||||
it('should contain link for known issues on Github', function () {
|
||||
cy.getByTestId('git-info').within(() => {
|
||||
cy.contains('Known issues and feedback on')
|
||||
.find('[data-testid="link"]')
|
||||
.should(
|
||||
'have.attr',
|
||||
'href',
|
||||
'https://github.com/vegaprotocol/feedback/discussions'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Mobile view - navigation bar', function () {
|
||||
before('Change to mobile resolution', function () {
|
||||
cy.viewport('iphone-xr');
|
||||
});
|
||||
|
||||
it('should have burger button', () => {
|
||||
cy.getByTestId('button-menu-drawer').should('be.visible').click();
|
||||
cy.getByTestId('menu-drawer').should('be.visible');
|
||||
});
|
||||
|
||||
it('should have link for proposal page', function () {
|
||||
cy.getByTestId('menu-drawer').within(() => {
|
||||
cy.get('[href="/proposals"]')
|
||||
.should('exist')
|
||||
.and('have.text', 'Proposals');
|
||||
});
|
||||
});
|
||||
it('should have link for validator page', function () {
|
||||
cy.getByTestId('menu-drawer').within(() => {
|
||||
cy.get('[href="/validators"]')
|
||||
.first()
|
||||
.should('exist')
|
||||
.and('have.text', 'Validators');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have link for rewards page', function () {
|
||||
cy.getByTestId('menu-drawer').within(() => {
|
||||
cy.get('[href="/rewards"]')
|
||||
.first()
|
||||
.should('exist')
|
||||
.and('have.text', 'Rewards');
|
||||
});
|
||||
});
|
||||
it('should have link for withdrawal page', function () {
|
||||
cy.getByTestId('menu-drawer').within(() => {
|
||||
cy.get('[href="/token/withdraw"]')
|
||||
.first()
|
||||
.should('exist')
|
||||
.and('have.text', 'Withdraw');
|
||||
});
|
||||
});
|
||||
|
||||
after(function () {
|
||||
cy.viewport(
|
||||
Cypress.config('viewportWidth'),
|
||||
Cypress.config('viewportHeight')
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,11 +3,14 @@ import {
|
||||
navigation,
|
||||
verifyPageHeader,
|
||||
verifyTabHighlighted,
|
||||
waitForSpinner,
|
||||
} from '../../support/common.functions';
|
||||
import {
|
||||
goToMakeNewProposal,
|
||||
governanceProposalType,
|
||||
} from '../../support/governance.functions';
|
||||
import { mockNetworkUpgradeProposal } from '../../support/proposal.functions';
|
||||
|
||||
const proposalDocumentationLink = '[data-testid="proposal-documentation-link"]';
|
||||
const newProposalButton = '[data-testid="new-proposal-link"]';
|
||||
const newProposalLink = '[data-testid="new-proposal-link"]';
|
||||
const governanceDocsUrl = 'https://vega.xyz/governance';
|
||||
const connectToVegaWalletButton = '[data-testid="connect-to-vega-wallet-btn"]';
|
||||
@@ -21,56 +24,140 @@ context(
|
||||
navigateTo(navigation.proposals);
|
||||
});
|
||||
|
||||
describe('with no network change proposals', function () {
|
||||
it('should have governance tab highlighted', function () {
|
||||
verifyTabHighlighted(navigation.proposals);
|
||||
it('should have governance tab highlighted', function () {
|
||||
verifyTabHighlighted(navigation.proposals);
|
||||
});
|
||||
|
||||
it('should have GOVERNANCE header visible', function () {
|
||||
verifyPageHeader('Proposals');
|
||||
});
|
||||
|
||||
it('should be able to see a working link for - find out more about Vega governance', function () {
|
||||
// 3001-VOTE-001
|
||||
cy.get(proposalDocumentationLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Find out more about Vega governance')
|
||||
.and('have.attr', 'href')
|
||||
.and('equal', governanceDocsUrl);
|
||||
|
||||
// 3002-PROP-001
|
||||
cy.request(governanceDocsUrl)
|
||||
.its('body')
|
||||
.then((body) => {
|
||||
if (!body.includes('Govern the network')) {
|
||||
assert.include(
|
||||
body,
|
||||
'Govern the network',
|
||||
`Checking that governance link destination includes 'Govern the network' text`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to see button for - new proposal', function () {
|
||||
// 3001-VOTE-002
|
||||
cy.get(newProposalLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'New proposal')
|
||||
.and('have.attr', 'href')
|
||||
.and('equal', '/proposals/propose');
|
||||
});
|
||||
|
||||
it('should be able to see a connect wallet button - if vega wallet disconnected and user is submitting new proposal', function () {
|
||||
goToMakeNewProposal(governanceProposalType.NETWORK_PARAMETER);
|
||||
cy.get(connectToVegaWalletButton)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect Vega wallet');
|
||||
});
|
||||
|
||||
it('should see open network upgrade proposal on homepage', function () {
|
||||
mockNetworkUpgradeProposal();
|
||||
cy.visit('/');
|
||||
cy.getByTestId('home-proposal-list').within(() => {
|
||||
cy.getByTestId('protocol-upgrade-proposals-list-item').should('exist');
|
||||
cy.getByTestId('protocol-upgrade-proposal-title').should(
|
||||
'have.text',
|
||||
'Vega release v1'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have GOVERNANCE header visible', function () {
|
||||
verifyPageHeader('Proposals');
|
||||
});
|
||||
|
||||
it('should be able to see a working link for - find out more about Vega governance', function () {
|
||||
// 3001-VOTE-001
|
||||
cy.get(proposalDocumentationLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Find out more about Vega governance')
|
||||
.and('have.attr', 'href')
|
||||
.and('equal', governanceDocsUrl);
|
||||
|
||||
// 3002-PROP-001
|
||||
cy.request(governanceDocsUrl)
|
||||
.its('body')
|
||||
.then((body) => {
|
||||
if (!body.includes('Govern the network')) {
|
||||
assert.include(
|
||||
body,
|
||||
'Govern the network',
|
||||
`Checking that governance link destination includes 'Govern the network' text`
|
||||
);
|
||||
}
|
||||
it('should see network upgrade proposals in proposals list', function () {
|
||||
mockNetworkUpgradeProposal();
|
||||
navigateTo(navigation.proposals);
|
||||
cy.getByTestId('open-proposals').within(() => {
|
||||
cy.get('li')
|
||||
.eq(0)
|
||||
.should(
|
||||
'have.attr',
|
||||
'data-testid',
|
||||
'protocol-upgrade-proposals-list-item'
|
||||
)
|
||||
.within(() => {
|
||||
cy.get('h2').should('have.text', 'Vega release v1');
|
||||
cy.getByTestId('protocol-upgrade-proposal-type').should(
|
||||
'have.text',
|
||||
'Network Upgrade'
|
||||
);
|
||||
cy.getByTestId('protocol-upgrade-proposal-release-tag').should(
|
||||
'have.text',
|
||||
'Vega release tagv1'
|
||||
);
|
||||
cy.getByTestId('protocol-upgrade-proposal-block-height').should(
|
||||
'have.text',
|
||||
'Upgrade block height2015942'
|
||||
);
|
||||
cy.getByTestId('protocol-upgrade-proposal-status').should(
|
||||
'have.text',
|
||||
'Approved'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to see button for - new proposal', function () {
|
||||
// 3001-VOTE-002
|
||||
cy.get(newProposalLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'New proposal')
|
||||
.and('have.attr', 'href')
|
||||
.and('equal', '/proposals/propose');
|
||||
cy.getByTestId('closed-proposals').within(() => {
|
||||
cy.getByTestId('protocol-upgrade-proposals-list-item').should(
|
||||
'have.length',
|
||||
1
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Skipping this test for now, the new proposal button no longer takes a user directly
|
||||
// to a proposal form, instead it takes them to a page where they can select a proposal type.
|
||||
// Keeping this test here for now as it can be repurposed to test the new proposal forms.
|
||||
it.skip('should be able to see a connect wallet button - if vega wallet disconnected and new proposal button selected', function () {
|
||||
cy.get(newProposalButton).should('be.visible').click();
|
||||
cy.get(connectToVegaWalletButton)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect Vega wallet');
|
||||
navigateTo(navigation.proposals);
|
||||
waitForSpinner();
|
||||
it('should see details of network upgrade proposal', function () {
|
||||
mockNetworkUpgradeProposal();
|
||||
navigateTo(navigation.proposals);
|
||||
cy.getByTestId('protocol-upgrade-proposals-list-item')
|
||||
.first()
|
||||
.find('[data-testid="view-proposal-btn"]')
|
||||
.click();
|
||||
cy.getByTestId('protocol-upgrade-proposal').within(() => {
|
||||
cy.get('h1').should('have.text', 'Vega Release v1');
|
||||
cy.getByTestId('protocol-upgrade-block-height').should(
|
||||
'have.text',
|
||||
'2015942 (currently 2014133)'
|
||||
);
|
||||
cy.getByTestId('protocol-upgrade-state').should(
|
||||
'have.text',
|
||||
'Approved'
|
||||
);
|
||||
cy.getByTestId('protocol-upgrade-release-tag').should(
|
||||
'have.text',
|
||||
'v1'
|
||||
);
|
||||
cy.getByTestId('protocol-upgrade-approval-status')
|
||||
.should('contain.text', '99.98% approval (% validator voting power)')
|
||||
.and('contain.text', '(67% voting power required)');
|
||||
cy.get('h2').should('contain.text', 'Approvers (4/4 validators)');
|
||||
cy.getByTestId('validator-name')
|
||||
.should('have.length', 4)
|
||||
.each(($validator) => {
|
||||
cy.wrap($validator).find('a').should('have.attr', 'href');
|
||||
});
|
||||
cy.getByTestId('validator-voting-power').each(
|
||||
($validatorVotingPower) => {
|
||||
cy.wrap($validatorVotingPower)
|
||||
.invoke('text')
|
||||
.should('contain', '%');
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,29 +26,31 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
|
||||
cy.connectPublicKey(vegaWalletPubKey);
|
||||
});
|
||||
|
||||
it('Able to connect public key via wallet', function () {
|
||||
verifyConnectedToPubKey();
|
||||
cy.getByTestId('currency-title', { timeout: 10000 })
|
||||
.should('have.length.at.least', 4)
|
||||
.and('contain.text', 'USDC (fake)');
|
||||
});
|
||||
|
||||
it('Able to connect public key using url', function () {
|
||||
cy.getByTestId('exit-view').click();
|
||||
cy.visit(`/?address=${vegaWalletPubKey}`);
|
||||
verifyConnectedToPubKey();
|
||||
});
|
||||
|
||||
it('Able to connect public key via wallet and view assets in wallet', function () {
|
||||
verifyConnectedToPubKey();
|
||||
cy.getByTestId('currency-title', { timeout: 10000 })
|
||||
.should('have.length.at.least', 4)
|
||||
.and('contain.text', 'USDC (fake)');
|
||||
});
|
||||
|
||||
it('Unable to submit proposal with public key', function () {
|
||||
const expectedErrorTxt = `You are connected in a view only state for public key: ${vegaWalletPubKey}. In order to send transactions you must connect to a real wallet.`;
|
||||
|
||||
navigateTo(navigation.proposals);
|
||||
goToMakeNewProposal('Freeform');
|
||||
enterUniqueFreeFormProposalBody('50', 'pub key proposal test');
|
||||
cy.getByTestId('dialog-content').within(() => {
|
||||
cy.get('h1').should('have.text', 'Transaction failed');
|
||||
cy.getByTestId('Error').should('have.text', expectedErrorTxt);
|
||||
});
|
||||
cy.getByTestId('dialog-content')
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get('h1').should('have.text', 'Transaction failed');
|
||||
cy.getByTestId('Error').should('have.text', expectedErrorTxt);
|
||||
});
|
||||
});
|
||||
|
||||
it('Able to disconnect via banner', function () {
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
navigation,
|
||||
verifyPageHeader,
|
||||
} from '../../support/common.functions';
|
||||
import { waitForBeginningOfEpoch } from '../../support/staking.functions';
|
||||
|
||||
const viewToggle = '[data-testid="epoch-reward-view-toggle-total"]';
|
||||
const warning = '[data-testid="callout"]';
|
||||
@@ -37,6 +38,41 @@ context(
|
||||
it('should have toggle for seeing total vs individual rewards', function () {
|
||||
cy.get(viewToggle).should('be.visible');
|
||||
});
|
||||
|
||||
// Skipping due to bug #3471 causing flaky failuress
|
||||
it.skip('should have option to view go to next and previous page', function () {
|
||||
waitForBeginningOfEpoch();
|
||||
cy.getByTestId('page-info')
|
||||
.should('contain.text', 'Page ')
|
||||
.invoke('text')
|
||||
.then(($currentPage) => {
|
||||
const currentPageNumber = Number($currentPage.slice(5));
|
||||
cy.getByTestId('goto-next-page').click();
|
||||
cy.getByTestId('page-info')
|
||||
.invoke('text')
|
||||
.then(($newPageNumber) => {
|
||||
const newPageNumber = Number($newPageNumber.slice(5));
|
||||
expect(newPageNumber).to.be.greaterThan(currentPageNumber);
|
||||
cy.getByTestId('goto-previous-page').click();
|
||||
cy.getByTestId('page-info').should(
|
||||
'contain.text',
|
||||
$currentPage
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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()
|
||||
.find('h2')
|
||||
.first()
|
||||
.should('have.text', 'EPOCH 1');
|
||||
cy.getByTestId('goto-first-page').click();
|
||||
cy.get('h2').should('not.contain.text', 'EPOCH 1');
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
import {
|
||||
navigateTo,
|
||||
navigation,
|
||||
verifyPageHeader,
|
||||
verifyTabHighlighted,
|
||||
@@ -34,8 +33,8 @@ const stakeNumberRegex = /^\d*\.?\d*$/;
|
||||
|
||||
context('Validators Page - verify elements on page', function () {
|
||||
before('navigate to validators page', function () {
|
||||
cy.visit('/');
|
||||
navigateTo(navigation.validators);
|
||||
cy.clearAllLocalStorage();
|
||||
cy.visit('/validators');
|
||||
});
|
||||
|
||||
describe('with wallets disconnected', { tags: '@smoke' }, function () {
|
||||
@@ -74,7 +73,7 @@ context('Validators Page - verify elements on page', function () {
|
||||
});
|
||||
|
||||
it('Should be able to see validator stake', function () {
|
||||
cy.get('[col-id="stake"] > div > span > span')
|
||||
cy.getByTestId('total-stake')
|
||||
.should('have.length.at.least', 1)
|
||||
.each(($stake) => {
|
||||
cy.wrap($stake).should('not.be.empty');
|
||||
@@ -82,7 +81,7 @@ context('Validators Page - verify elements on page', function () {
|
||||
});
|
||||
|
||||
it('Should be able to see validator stake tooltip', function () {
|
||||
cy.get('[col-id="stake"] > div > span > span').first().realHover();
|
||||
cy.getByTestId('total-stake').first().realHover();
|
||||
|
||||
cy.get(stakedByOperatorToolTip)
|
||||
.invoke('text')
|
||||
@@ -96,17 +95,15 @@ context('Validators Page - verify elements on page', function () {
|
||||
});
|
||||
|
||||
it('Should be able to see validator normalised voting power', function () {
|
||||
cy.get('[col-id="normalisedVotingPower"] > div > span > span')
|
||||
cy.getByTestId('normalised-voting-power')
|
||||
.should('have.length.at.least', 1)
|
||||
.each(($vPower) => {
|
||||
cy.wrap($vPower).should('not.be.empty');
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to see validator voting power tooltip', function () {
|
||||
cy.get('[col-id="normalisedVotingPower"] > div > span > span')
|
||||
.first()
|
||||
.realHover();
|
||||
it('Should be able to see validator normalised voting power tooltip', function () {
|
||||
cy.getByTestId('normalised-voting-power').first().realHover();
|
||||
|
||||
cy.get(unnormalisedVotingPowerToolTip)
|
||||
.invoke('text')
|
||||
@@ -118,7 +115,7 @@ context('Validators Page - verify elements on page', function () {
|
||||
|
||||
// 2002-SINC-018
|
||||
it('Should be able to see validator total penalties', function () {
|
||||
cy.get('[col-id="totalPenalties"] > div > span > span')
|
||||
cy.getByTestId('total-penalty')
|
||||
.should('have.length.at.least', 1)
|
||||
.each(($penalties) => {
|
||||
cy.wrap($penalties).should('contain.text', '0%');
|
||||
@@ -126,7 +123,7 @@ context('Validators Page - verify elements on page', function () {
|
||||
});
|
||||
|
||||
it('Should be able to see validator penalties tooltip', function () {
|
||||
cy.get('[col-id="totalPenalties"] > div > span > span').realHover();
|
||||
cy.getByTestId('total-penalty').realHover();
|
||||
|
||||
cy.get(performancePenaltyToolTip)
|
||||
.invoke('text')
|
||||
@@ -140,7 +137,7 @@ context('Validators Page - verify elements on page', function () {
|
||||
});
|
||||
|
||||
it('Should be able to see validator pending stake', function () {
|
||||
cy.get('[col-id="pendingStake"] > div > span')
|
||||
cy.getByTestId('total-pending-stake')
|
||||
.should('have.length.at.least', 1)
|
||||
.each(($pendingStake) => {
|
||||
cy.wrap($pendingStake).should('contain.text', '0.00');
|
||||
|
||||
@@ -43,23 +43,25 @@ context(
|
||||
|
||||
// 1005-VEST-020 1005-VEST-021
|
||||
it('Tokens in vesting contract for eth wallet is displayed on wallet window', function () {
|
||||
cy.getByTestId('vega-in-vesting-contract').within(() => {
|
||||
cy.getByTestId('currency-title')
|
||||
.should('contain.text', 'VEGA')
|
||||
.and('contain.text', 'In vesting contract');
|
||||
cy.get('[data-testid="currency-value"]:visible').should(
|
||||
'have.text',
|
||||
lockedTokensInVestingContract
|
||||
);
|
||||
cy.get('[data-testid="currency-locked"]:visible').should(
|
||||
'have.text',
|
||||
lockedTokensInVestingContract
|
||||
);
|
||||
cy.get('[data-testid="currency-unlocked"]:visible').should(
|
||||
'have.text',
|
||||
'0.00'
|
||||
);
|
||||
});
|
||||
cy.get('[data-testid="vega-in-vesting-contract"]:visible').within(
|
||||
() => {
|
||||
cy.getByTestId('currency-title')
|
||||
.should('contain.text', 'VEGA')
|
||||
.and('contain.text', 'In vesting contract');
|
||||
cy.get('[data-testid="currency-value"]:visible').should(
|
||||
'have.text',
|
||||
lockedTokensInVestingContract
|
||||
);
|
||||
cy.get('[data-testid="currency-locked"]:visible').should(
|
||||
'have.text',
|
||||
lockedTokensInVestingContract
|
||||
);
|
||||
cy.get('[data-testid="currency-unlocked"]:visible').should(
|
||||
'have.text',
|
||||
'0.00'
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
// 1005-VEST-022 1005-VEST-023
|
||||
it('Tokens amount displayed in vesting page', function () {
|
||||
|
||||
@@ -17,7 +17,7 @@ const vegaInWallet = '[data-testid="vega-in-wallet"]:visible';
|
||||
const progressBar = '[data-testid="progress-bar"]:visible';
|
||||
const currencyLocked = '[data-testid="currency-locked"]:visible';
|
||||
const currencyUnlocked = '[data-testid="currency-unlocked"]:visible';
|
||||
const dialog = '[role="dialog"]';
|
||||
const dialog = '[role="dialog"]:visible';
|
||||
const dialogHeader = '[data-testid="dialog-title"]';
|
||||
const dialogCloseBtn = '[data-testid="dialog-close"]';
|
||||
|
||||
@@ -68,8 +68,10 @@ context(
|
||||
it('should have connector list visible', function () {
|
||||
const connectList = [
|
||||
'Unknown',
|
||||
'MetaMask, Brave or other injected web wallet',
|
||||
'MetaMask',
|
||||
'Coinbase',
|
||||
'WalletConnect',
|
||||
'WalletConnect Legacy',
|
||||
];
|
||||
cy.get(connectorList).within(() => {
|
||||
cy.get('button').each(($btn, i) => {
|
||||
|
||||
@@ -7,7 +7,7 @@ const walletContainer = 'aside [data-testid="vega-wallet"]';
|
||||
const walletHeader = '[data-testid="wallet-header"] h1';
|
||||
const connectButton = '[data-testid="connect-vega-wallet"]';
|
||||
const getVegaLink = '[data-testid="link"]';
|
||||
const dialog = '[role="dialog"]';
|
||||
const dialog = '[role="dialog"]:visible';
|
||||
const dialogHeader = '[data-testid="dialog-title"]';
|
||||
const walletDialogHeader = '[data-testid="wallet-dialog-title"]';
|
||||
const connectorsList = '[data-testid="connectors-list"]';
|
||||
@@ -35,6 +35,7 @@ context(
|
||||
{ tags: '@regression' },
|
||||
() => {
|
||||
before('visit token home page', () => {
|
||||
cy.clearAllLocalStorage();
|
||||
cy.visit('/');
|
||||
cy.get(walletContainer, { timeout: 60000 }).should('be.visible');
|
||||
});
|
||||
@@ -333,10 +334,8 @@ context(
|
||||
.parent()
|
||||
.siblings()
|
||||
.invoke('text')
|
||||
.then((el) => {
|
||||
const value = parseFloat(el);
|
||||
cy.wrap(value).should('be.gte', parseFloat(expectedAmount));
|
||||
});
|
||||
.then(parseFloat)
|
||||
.should('be.gte', parseFloat(expectedAmount));
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(id)
|
||||
|
||||
@@ -12,6 +12,7 @@ context(
|
||||
{ tags: '@smoke' },
|
||||
function () {
|
||||
before('navigate to withdrawals page', function () {
|
||||
cy.clearAllLocalStorage();
|
||||
cy.visit('/');
|
||||
navigateTo(navigation.withdraw);
|
||||
});
|
||||
|
||||
@@ -26,9 +26,11 @@ const topLevelRoutes = [
|
||||
export function navigateTo(page: navigation) {
|
||||
if (!topLevelRoutes.includes(page)) {
|
||||
cy.getByTestId(tokenDropDown, { timeout: 10000 }).eq(0).click();
|
||||
cy.getByTestId('token-dropdown').within(() => {
|
||||
cy.get(page).eq(0).click();
|
||||
});
|
||||
cy.getByTestId('token-dropdown')
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get(page).eq(0).click();
|
||||
});
|
||||
} else {
|
||||
return cy.get(navigation.section, { timeout: 10000 }).within(() => {
|
||||
cy.get(page).eq(0).click();
|
||||
@@ -84,3 +86,7 @@ export function verifyEthWalletAssociatedBalance(amount: string) {
|
||||
.parent(txTimeout)
|
||||
.should('contain', amount, txTimeout);
|
||||
}
|
||||
|
||||
export function closeDialog() {
|
||||
cy.getByTestId('dialog-close').click();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { navigateTo, navigation } from './common.functions';
|
||||
import { closeDialog, navigateTo, navigation } from './common.functions';
|
||||
import { ensureSpecifiedUnstakedTokensAreAssociated } from './staking.functions';
|
||||
|
||||
const newProposalButton = '[data-testid="new-proposal-link"]';
|
||||
@@ -12,7 +12,6 @@ const voteButtons = '[data-testid="vote-buttons"]';
|
||||
const dialogTitle = '[data-testid="dialog-title"]';
|
||||
const proposalVoteDeadline = '[data-testid="proposal-vote-deadline"]';
|
||||
const newProposalSubmitButton = '[data-testid="proposal-submit"]';
|
||||
const dialogCloseButton = '[data-testid="dialog-close"]';
|
||||
const epochTimeout = Cypress.env('epochTimeout');
|
||||
const proposalTimeout = { timeout: 14000 };
|
||||
|
||||
@@ -125,7 +124,7 @@ export function voteForProposal(vote: string) {
|
||||
'have.text',
|
||||
'Transaction complete'
|
||||
);
|
||||
cy.get(dialogCloseButton).click();
|
||||
closeDialog();
|
||||
}
|
||||
|
||||
export function waitForProposalSync() {
|
||||
@@ -151,22 +150,11 @@ export function waitForProposalSync() {
|
||||
});
|
||||
}
|
||||
|
||||
export function getSortOrderOfSuppliedArray(suppliedArray: string[]) {
|
||||
const tempArray = [];
|
||||
for (let index = 1; index < suppliedArray.length; index++) {
|
||||
tempArray.push(
|
||||
suppliedArray[index - 1].localeCompare(suppliedArray[index])
|
||||
);
|
||||
}
|
||||
if (tempArray.every((n) => n <= 0)) return 'ascending';
|
||||
else if (tempArray.every((n) => n >= 0)) return 'descending';
|
||||
else return 'unsorted';
|
||||
}
|
||||
|
||||
export function goToMakeNewProposal(proposalType: string) {
|
||||
navigateTo(navigation.proposals);
|
||||
cy.get(newProposalButton).should('be.visible').click();
|
||||
cy.url().should('include', '/proposals/propose');
|
||||
cy.get(navigation.pageSpinner, { timeout: 20000 }).should('not.exist');
|
||||
cy.get('li').should('contain.text', proposalType).and('be.visible');
|
||||
cy.get('li').contains(proposalType).click();
|
||||
}
|
||||
@@ -176,7 +164,7 @@ export function waitForProposalSubmitted() {
|
||||
'be.visible'
|
||||
);
|
||||
cy.contains('Proposal submitted', proposalTimeout).should('be.visible');
|
||||
cy.get(dialogCloseButton).click();
|
||||
closeDialog();
|
||||
}
|
||||
|
||||
export function createRawProposal(proposerBalance?: string) {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { addSeconds, millisecondsToSeconds } from 'date-fns';
|
||||
import type { ProposalSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import { upgradeProposalsData } from '../fixtures/mocks/network-upgrade';
|
||||
import { proposalsData } from '../fixtures/mocks/proposals';
|
||||
import { nodeData } from '../fixtures/mocks/nodes';
|
||||
|
||||
export function createUpdateNetworkProposalTxBody(): ProposalSubmissionBody {
|
||||
const MIN_CLOSE_SEC = 5;
|
||||
@@ -80,3 +84,11 @@ export function createFreeFormProposalTxBody(): ProposalSubmissionBody {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function mockNetworkUpgradeProposal() {
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'Nodes', nodeData);
|
||||
aliasGQLQuery(req, 'Proposals', proposalsData);
|
||||
aliasGQLQuery(req, 'ProtocolUpgrades', upgradeProposalsData);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { closeDialog } from './common.functions';
|
||||
import { vegaWalletTeardown } from './wallet-teardown.functions';
|
||||
|
||||
const tokenAmountInputBox = '[data-testid="token-amount-input"]';
|
||||
@@ -14,11 +15,10 @@ const associateWalletRadioButton = '[data-testid="associate-radio-wallet"]';
|
||||
const associateContractRadioButton = '[data-testid="associate-radio-contract"]';
|
||||
const stakeMaximumTokens = '[data-testid="token-amount-use-maximum"]';
|
||||
const stakeValidatorListPendingStake = '[col-id="pendingStake"]';
|
||||
const stakeValidatorListTotalStake = '[col-id="stake"] > div > span';
|
||||
const stakeValidatorListTotalShare = '[col-id="stakeShare"] > div > span';
|
||||
const stakeValidatorListTotalStake = 'total-stake';
|
||||
const stakeValidatorListTotalShare = 'total-stake-share';
|
||||
const stakeValidatorListName = '[col-id="validator"]';
|
||||
const vegaKeySelector = '#vega-key-selector';
|
||||
const dialogCloseButton = '[data-testid="dialog-close"]';
|
||||
|
||||
const txTimeout = Cypress.env('txTimeout');
|
||||
const epochTimeout = Cypress.env('epochTimeout');
|
||||
@@ -54,7 +54,7 @@ export function stakingValidatorPageRemoveStake(stake: string) {
|
||||
.and('contain', `Remove ${stake} $VEGA tokens at the end of epoch`)
|
||||
.and('be.visible')
|
||||
.click();
|
||||
cy.get(dialogCloseButton).click();
|
||||
closeDialog();
|
||||
}
|
||||
|
||||
export function stakingPageAssociateTokens(
|
||||
@@ -184,16 +184,18 @@ export function validateValidatorListTotalStakeAndShare(
|
||||
) {
|
||||
cy.contains('Loading...', epochTimeout).should('not.exist');
|
||||
waitForBeginningOfEpoch();
|
||||
cy.get(`[row-id="${positionOnList}"]`).within(() => {
|
||||
cy.get(stakeValidatorListTotalStake, epochTimeout).should(
|
||||
'have.text',
|
||||
expectedTotalStake
|
||||
);
|
||||
cy.get(stakeValidatorListTotalShare, epochTimeout).should(
|
||||
'have.text',
|
||||
expectedTotalShare
|
||||
);
|
||||
});
|
||||
cy.get(`[row-id="${positionOnList}"]:visible`)
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.getByTestId(stakeValidatorListTotalStake, epochTimeout).should(
|
||||
'have.text',
|
||||
expectedTotalStake
|
||||
);
|
||||
cy.getByTestId(stakeValidatorListTotalShare, epochTimeout).should(
|
||||
'have.text',
|
||||
expectedTotalShare
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function ensureSpecifiedUnstakedTokensAreAssociated(
|
||||
@@ -218,13 +220,15 @@ export function ensureSpecifiedUnstakedTokensAreAssociated(
|
||||
}
|
||||
|
||||
export function closeStakingDialog() {
|
||||
cy.getByTestId('dialog-title').should(
|
||||
cy.get('[data-testid="dialog-title"]:visible').should(
|
||||
'contain.text',
|
||||
'At the beginning of the next epoch'
|
||||
);
|
||||
cy.getByTestId('dialog-content').within(() => {
|
||||
cy.get('a').should('have.text', 'Back to Staking').click();
|
||||
});
|
||||
cy.get('[data-testid="dialog-content"]:visible')
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get('a').should('have.text', 'Back to Staking').click();
|
||||
});
|
||||
}
|
||||
|
||||
export function validateWalletCurrency(
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
} from '@vegaprotocol/smart-contracts';
|
||||
import { ethers, Wallet } from 'ethers';
|
||||
|
||||
const vegaWalletContainer = 'aside [data-testid="vega-wallet"]';
|
||||
const associatedAmountInWallet = '[data-testid="associated-amount"]:visible';
|
||||
const vegaWalletContainer = 'aside [data-testid="vega-wallet"]:visible';
|
||||
const vegaWalletMnemonic = Cypress.env('vegaWalletMnemonic');
|
||||
const vegaWalletPubKey = Cypress.env('vegaWalletPublicKey');
|
||||
const vegaTokenContractAddress = Cypress.env('vegaTokenContractAddress');
|
||||
@@ -59,7 +60,7 @@ export async function faucetAsset(assetEthAddress: string) {
|
||||
}
|
||||
|
||||
export async function vegaWalletTeardown() {
|
||||
cy.get('[data-testid="associated-amount"]')
|
||||
cy.get(associatedAmountInWallet)
|
||||
.should('be.visible')
|
||||
.invoke('text')
|
||||
.then((associatedAmount) => {
|
||||
@@ -68,15 +69,12 @@ export async function vegaWalletTeardown() {
|
||||
$body.find('[data-testid="eth-wallet-associated-balances"]').length ||
|
||||
associatedAmount != '0.00'
|
||||
) {
|
||||
vegaWalletTeardownVesting(vestingContract);
|
||||
vegaWalletTeardownStaking(stakingBridgeContract);
|
||||
vegaWalletTeardownVesting(vestingContract);
|
||||
}
|
||||
});
|
||||
cy.get(vegaWalletContainer).within(() => {
|
||||
cy.getByTestId('vega-wallet-balance-staked-validators', {
|
||||
timeout: transactionTimeout,
|
||||
}).should('not.exist');
|
||||
cy.getByTestId('associated-amount', {
|
||||
cy.get(associatedAmountInWallet, {
|
||||
timeout: transactionTimeout,
|
||||
}).contains('0.00', {
|
||||
timeout: transactionTimeout,
|
||||
@@ -93,7 +91,7 @@ export async function vegaWalletSetSpecifiedApprovalAmount(
|
||||
await promiseWithTimeout(
|
||||
token.approve(
|
||||
ethStakingBridgeContractAddress,
|
||||
resetAmount.concat('000000000000000000')
|
||||
resetAmount + '0'.repeat(18)
|
||||
),
|
||||
10 * 60 * 1000,
|
||||
'set approval amount'
|
||||
@@ -107,13 +105,25 @@ async function vegaWalletTeardownStaking(stakingBridgeContract: StakingBridge) {
|
||||
{ timeout: transactionTimeout, log: false }
|
||||
).then((stakeBalance) => {
|
||||
if (Number(stakeBalance) != 0) {
|
||||
cy.wrap(
|
||||
stakingBridgeContract.remove_stake(
|
||||
String(stakeBalance),
|
||||
vegaWalletPubKey
|
||||
),
|
||||
{ timeout: transactionTimeout, log: false }
|
||||
);
|
||||
cy.get(vegaWalletContainer).within(() => {
|
||||
cy.getByTestId('currency-value')
|
||||
.invoke('text')
|
||||
.then(($associatedAmount) => {
|
||||
cy.wrap(
|
||||
stakingBridgeContract.remove_stake(
|
||||
String(stakeBalance),
|
||||
vegaWalletPubKey
|
||||
),
|
||||
{ timeout: transactionTimeout, log: false }
|
||||
);
|
||||
cy.get("[data-testid='currency-value']")
|
||||
.first()
|
||||
.invoke('text', {
|
||||
timeout: transactionTimeout,
|
||||
})
|
||||
.should('not.eq', $associatedAmount);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
|
||||
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
|
||||
@@ -5,6 +5,7 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_FAIRGROUND=false
|
||||
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
|
||||
NX_VEGA_CONFIG_URL=''
|
||||
|
||||
NX_VEGA_URL=http://localhost:3028/query
|
||||
@@ -19,3 +20,4 @@ NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
|
||||
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
|
||||
|
||||
@@ -10,3 +10,4 @@ NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-devnet1-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
@@ -11,3 +11,4 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
|
||||
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40:87edc2605e544f888305d7fc4a9141bd@o286262.ingest.sentry.io/5882996
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-mainnet-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_ENV=MIRROR
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/mainnet-mirror/vegawallet-mainnet-mirror.toml
|
||||
NX_VEGA_URL=https://api.n00.mainnet-mirror.vega.xyz/graphql
|
||||
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_VEGA_EXPLORER_URL=https://mirror.explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
@@ -1,8 +0,0 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_URL=https://api.sandbox.vega.xyz/graphql
|
||||
NX_VEGA_ENV=SANDBOX
|
||||
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","STAGNET1":"https://stagnet1.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/sandbox/vegawallet-sandbox.toml
|
||||
NX_VEGA_EXPLORER_URL=https://sandbox.explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
@@ -7,3 +7,4 @@ NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
@@ -8,3 +8,4 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
@@ -11,3 +11,4 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-testnet-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
|
||||
@@ -8,3 +8,4 @@ NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
|
||||
@@ -17,6 +17,12 @@ 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 {
|
||||
createConnectors,
|
||||
useEthTransactionManager,
|
||||
useEthWithdrawApprovalsManager,
|
||||
useWeb3ConnectStore,
|
||||
} from '@vegaprotocol/web3';
|
||||
import { Web3Provider } from '@vegaprotocol/web3';
|
||||
import { VegaWalletDialogs } from './components/vega-wallet-dialogs';
|
||||
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
||||
@@ -27,10 +33,10 @@ import {
|
||||
NetworkLoader,
|
||||
useInitializeEnv,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { createConnectors } from './lib/web3-connectors';
|
||||
import { ENV } from './config/env';
|
||||
import { ENV } from './config';
|
||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||
import { WithdrawalDialog } from '@vegaprotocol/withdraws';
|
||||
import { SplashLoader } from './components/splash-loader';
|
||||
|
||||
const cache: InMemoryCacheConfig = {
|
||||
typePolicies: {
|
||||
@@ -70,20 +76,52 @@ const cache: InMemoryCacheConfig = {
|
||||
|
||||
const Web3Container = ({
|
||||
chainId,
|
||||
providerUrl,
|
||||
}: {
|
||||
chainId: number;
|
||||
providerUrl: string;
|
||||
}) => {
|
||||
const InitializeHandlers = () => {
|
||||
useEthTransactionManager();
|
||||
useEthWithdrawApprovalsManager();
|
||||
return null;
|
||||
};
|
||||
const [connectors, initializeConnectors] = useWeb3ConnectStore((store) => [
|
||||
store.connectors,
|
||||
store.initialize,
|
||||
]);
|
||||
const { ETHEREUM_PROVIDER_URL, ETH_LOCAL_PROVIDER_URL, ETH_WALLET_MNEMONIC } =
|
||||
useEnvironment();
|
||||
useEffect(() => {
|
||||
if (chainId) {
|
||||
return initializeConnectors(
|
||||
createConnectors(
|
||||
ETHEREUM_PROVIDER_URL,
|
||||
Number(chainId),
|
||||
ETH_LOCAL_PROVIDER_URL,
|
||||
ETH_WALLET_MNEMONIC
|
||||
),
|
||||
Number(chainId)
|
||||
);
|
||||
}
|
||||
}, [
|
||||
chainId,
|
||||
ETHEREUM_PROVIDER_URL,
|
||||
initializeConnectors,
|
||||
ETH_LOCAL_PROVIDER_URL,
|
||||
ETH_WALLET_MNEMONIC,
|
||||
]);
|
||||
const sideBar = React.useMemo(() => {
|
||||
return [<EthWallet />, <VegaWallet />];
|
||||
}, []);
|
||||
const Connectors = React.useMemo(() => {
|
||||
return createConnectors(providerUrl, Number(chainId));
|
||||
}, [chainId, providerUrl]);
|
||||
|
||||
if (connectors.length === 0) {
|
||||
// Prevent loading when the connectors are not initialized
|
||||
return <SplashLoader />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Web3Provider connectors={Connectors}>
|
||||
<Web3Connector connectors={Connectors} chainId={Number(chainId)}>
|
||||
<Web3Provider connectors={connectors}>
|
||||
<Web3Connector connectors={connectors} chainId={Number(chainId)}>
|
||||
<VegaWalletProvider>
|
||||
<ContractsProvider>
|
||||
<AppLoader>
|
||||
@@ -97,6 +135,7 @@ const Web3Container = ({
|
||||
<NetworkInfo />
|
||||
</footer>
|
||||
</AppLayout>
|
||||
<InitializeHandlers />
|
||||
<VegaWalletDialogs />
|
||||
<TransactionModal />
|
||||
<WithdrawalDialog />
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
import { Loader } from '@vegaprotocol/ui-toolkit';
|
||||
import colors from 'tailwindcss/colors';
|
||||
import { useBalances } from '../../lib/balances/balances-store';
|
||||
import { useWeb3Disconnect } from '@vegaprotocol/web3';
|
||||
|
||||
const removeLeadingAddressSymbol = (key: string) => {
|
||||
if (key && key.length > 2 && key.slice(0, 2) === '0x') {
|
||||
@@ -184,6 +185,7 @@ export const EthWallet = () => {
|
||||
const { appDispatch } = useAppState();
|
||||
const { account, connector } = useWeb3React();
|
||||
const pendingTxs = usePendingTransactions();
|
||||
const disconnect = useWeb3Disconnect(connector);
|
||||
|
||||
return (
|
||||
<WalletCard>
|
||||
@@ -239,7 +241,7 @@ export const EthWallet = () => {
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
className="underline"
|
||||
onClick={() => connector.deactivate()}
|
||||
onClick={() => disconnect()}
|
||||
data-testid="disconnect-from-eth-wallet-button"
|
||||
>
|
||||
{t('disconnect')}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import classNames from 'classnames';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import type { ReactNode } from 'react';
|
||||
import { AnnouncementBanner } from '@vegaprotocol/announcements';
|
||||
import { Nav } from '../nav';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import React from 'react';
|
||||
|
||||
interface AppLayoutProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
export const AppLayout = ({ children }: AppLayoutProps) => {
|
||||
const { VEGA_ENV, ANNOUNCEMENTS_CONFIG_URL } = useEnvironment();
|
||||
const { isReadOnly } = useVegaWallet();
|
||||
const AppLayoutClasses = classNames(
|
||||
'app w-full max-w-[1500px] mx-auto grid min-h-full',
|
||||
'border-neutral-700 lg:border-l lg:border-r',
|
||||
'app w-full max-w-[1500px] mx-auto grid',
|
||||
'lg:text-body-large',
|
||||
{
|
||||
'grid-rows-[repeat(2,min-content)_1fr_min-content]': !isReadOnly,
|
||||
@@ -17,5 +21,18 @@ export const AppLayout = ({ children }: AppLayoutProps) => {
|
||||
}
|
||||
);
|
||||
|
||||
return <div className={AppLayoutClasses}>{children}</div>;
|
||||
return (
|
||||
<div className="min-h-full">
|
||||
<div className="lg:text-body-large">
|
||||
{ANNOUNCEMENTS_CONFIG_URL && (
|
||||
<AnnouncementBanner
|
||||
app="governance"
|
||||
configUrl={ANNOUNCEMENTS_CONFIG_URL}
|
||||
/>
|
||||
)}
|
||||
<Nav theme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'} />
|
||||
</div>
|
||||
<div className={AppLayoutClasses}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,33 +1,16 @@
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import {
|
||||
ViewingAsBanner,
|
||||
AnnouncementBanner,
|
||||
ExternalLink,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { ViewingAsBanner } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import React from 'react';
|
||||
|
||||
import { Nav } from '../nav';
|
||||
|
||||
export interface TemplateSidebarProps {
|
||||
children: React.ReactNode;
|
||||
sidebar: React.ReactNode[];
|
||||
}
|
||||
|
||||
export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const { isReadOnly, pubKey, disconnect } = useVegaWallet();
|
||||
return (
|
||||
<>
|
||||
<AnnouncementBanner>
|
||||
<div className="font-alpha calt uppercase text-center text-lg text-white">
|
||||
<span className="pr-4">Wait no longer, SIM3 is here!</span>
|
||||
<ExternalLink href="https://fairground.wtf/sim3">
|
||||
Learn more
|
||||
</ExternalLink>
|
||||
</div>
|
||||
</AnnouncementBanner>
|
||||
<Nav theme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'} />
|
||||
{isReadOnly ? (
|
||||
<ViewingAsBanner pubKey={pubKey} disconnect={disconnect} />
|
||||
) : null}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { getChainName, Web3ConnectDialog } from '@vegaprotocol/web3';
|
||||
import {
|
||||
getChainName,
|
||||
useWeb3ConnectStore,
|
||||
useWeb3Disconnect,
|
||||
Web3ConnectDialog,
|
||||
} from '@vegaprotocol/web3';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
@@ -51,7 +56,9 @@ interface Web3ContentProps {
|
||||
}
|
||||
|
||||
export const Web3Content = ({ children, appChainId }: Web3ContentProps) => {
|
||||
const { error, connector, chainId } = useWeb3React();
|
||||
const { connector, chainId } = useWeb3React();
|
||||
const error = useWeb3ConnectStore((store) => store.error);
|
||||
const disconnect = useWeb3Disconnect(connector);
|
||||
|
||||
useEffect(() => {
|
||||
if (connector?.connectEagerly) {
|
||||
@@ -67,7 +74,7 @@ export const Web3Content = ({ children, appChainId }: Web3ContentProps) => {
|
||||
<Splash>
|
||||
<div className="flex flex-col items-center gap-12">
|
||||
<p className="text-white">Something went wrong: {error.message}</p>
|
||||
<Button onClick={() => connector.deactivate()}>Disconnect</Button>
|
||||
<Button onClick={() => disconnect()}>Disconnect</Button>
|
||||
</div>
|
||||
</Splash>
|
||||
);
|
||||
@@ -80,7 +87,7 @@ export const Web3Content = ({ children, appChainId }: Web3ContentProps) => {
|
||||
<p className="text-white">
|
||||
This app only works on {getChainName(appChainId)}
|
||||
</p>
|
||||
<Button onClick={() => connector.deactivate()}>Disconnect</Button>
|
||||
<Button onClick={() => disconnect()}>Disconnect</Button>
|
||||
</div>
|
||||
</Splash>
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ const TRUTHY = ['1', 'true'];
|
||||
interface VegaContracts {
|
||||
claimAddress: string;
|
||||
lockedAddress: string;
|
||||
tokenVestingAddress?: string;
|
||||
}
|
||||
|
||||
const customClaimAddress = process.env['NX_CUSTOM_CLAIM_ADDRESS'] as string;
|
||||
@@ -36,21 +37,16 @@ export const ContractAddresses: {
|
||||
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
|
||||
lockedAddress: '0x0', // TODO not deployed to this env
|
||||
},
|
||||
SANDBOX: {
|
||||
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
|
||||
lockedAddress: '0x0', // TODO not deployed to this env
|
||||
},
|
||||
TESTNET: {
|
||||
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
|
||||
lockedAddress: '0x0', // TODO not deployed to this env
|
||||
},
|
||||
MIRROR: {
|
||||
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
|
||||
lockedAddress: '0x0', // TODO not deployed to this env
|
||||
},
|
||||
VALIDATOR_TESTNET: {
|
||||
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
|
||||
lockedAddress: '0x0', // TODO not deployed to this env
|
||||
// This is a fallback contract address for the validator testnet network which does not
|
||||
// have a vesting contract address set and is therefore not in the ethereum config
|
||||
tokenVestingAddress: '0xadFcb7f93a24F8743a8e548d74d2ecB373c92866',
|
||||
},
|
||||
MAINNET: {
|
||||
claimAddress: '0x0ee1fb382caf98e86e97e51f9f42f8b4654020f3',
|
||||
|
||||
@@ -49,6 +49,13 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
|
||||
signer = provider.getSigner();
|
||||
}
|
||||
|
||||
const tokenVestingAddress =
|
||||
config.token_vesting_contract?.address ||
|
||||
ENV.addresses.tokenVestingAddress;
|
||||
if (!tokenVestingAddress) {
|
||||
throw new Error('No token vesting address found');
|
||||
}
|
||||
|
||||
if (provider && config) {
|
||||
const staking = new StakingBridge(
|
||||
config.staking_bridge_contract.address,
|
||||
@@ -63,7 +70,7 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
|
||||
signer || provider
|
||||
),
|
||||
vesting: new TokenVesting(
|
||||
config.token_vesting_contract.address,
|
||||
tokenVestingAddress,
|
||||
signer || provider
|
||||
),
|
||||
claim: new Claim(ENV.addresses.claimAddress, signer || provider),
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { ObservableQuery } from '@apollo/client';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const useRefreshAfterEpoch = (
|
||||
epochExpiry: string | undefined,
|
||||
refetch: ObservableQuery['refetch']
|
||||
refetch: () => void
|
||||
) => {
|
||||
return useEffect(() => {
|
||||
const epochInterval = setInterval(() => {
|
||||
|
||||
@@ -221,8 +221,9 @@
|
||||
"votePending": "Casting vote",
|
||||
"voteError": "Something went wrong, and your vote was not seen by the network",
|
||||
"back": "back",
|
||||
"byTokenVote": "by Token vote",
|
||||
"byLiquidityVote": "by Liquidity vote",
|
||||
"byTokenVote": "by token vote",
|
||||
"byLiquidityVote": "by liquidity vote",
|
||||
"byLPVote": "by LP vote",
|
||||
"youDidNotVote": "Voting has ended. You did not vote",
|
||||
"voteState_Yes": "For",
|
||||
"voteState_No": "Against",
|
||||
@@ -442,8 +443,8 @@
|
||||
"rewardType": "Reward type",
|
||||
"rewardsAndFeesReceived": "Rewards and fees received",
|
||||
"ThisDoesNotIncludeFeesReceivedForMakersOrLiquidityProviders": "This does not include fees received for makers or liquidity providers",
|
||||
"totalDistributed": "TOTAL DISTRIBUTED",
|
||||
"earnedByMe": "EARNED BY ME",
|
||||
"totalDistributed": "Total distributed",
|
||||
"earnedByMe": "Earned by me",
|
||||
"noRewardsHaveBeenDistributedYet": "NO REWARDS HAVE BEEN DISTRIBUTED YET",
|
||||
"rewardsColAssetHeader": "ASSET",
|
||||
"rewardsColStakingHeader": "STAKING",
|
||||
@@ -594,6 +595,7 @@
|
||||
"noPercentage": "No percentage",
|
||||
"proposalTerms": "Proposal terms",
|
||||
"currentlySetTo": "Currently expected to ",
|
||||
"currently": "currently",
|
||||
"finalOutcomeMayDiffer": "Final outcome may differ",
|
||||
"votingPower": "Voting power",
|
||||
"normalisedVotingPower": "Normalised voting power",
|
||||
@@ -601,11 +603,14 @@
|
||||
"noValidators": "No validators",
|
||||
"validator": "Validator",
|
||||
"stake": "Stake",
|
||||
"myStake": "My stake",
|
||||
"stakeShare": "Stake share",
|
||||
"stakedByOperator": "Staked by operator",
|
||||
"stakedByDelegates": "Staked by delegates",
|
||||
"stakedByMe": "Staked by me",
|
||||
"totalStake": "Total stake",
|
||||
"pendingStake": "Pending stake",
|
||||
"myPendingStake": "My pending stake",
|
||||
"totalPenalties": "Total penalties",
|
||||
"noPenaltyDataFromLastEpoch": "No penalty data from last epoch",
|
||||
"stakeNeededForPromotion": "Stake needed for promotion",
|
||||
@@ -763,7 +768,7 @@
|
||||
"performancePenalty": "Performance penalty",
|
||||
"overstaked": "Overstaked",
|
||||
"overstakedPenalty": "Overstaked penalty",
|
||||
"homeProposalsIntro": "Decisions on the Vega network are on-chain, with tokenholders creating proposals that other tokenholders vote to approve or reject.",
|
||||
"homeProposalsIntro": "Decisions on the Vega network are on-chain, with tokenholders creating proposals that other tokenholders vote to approve or reject. Network upgrades are proposed and approved by validators.",
|
||||
"homeProposalsButtonText": "Browse, vote, and propose",
|
||||
"homeValidatorsIntro": "Vega runs on a delegated proof of stake blockchain, where validators earn fees for validating block transactions. Tokenholders can nominate validators by staking tokens to them.",
|
||||
"homeValidatorsButtonText": "Browse, and stake",
|
||||
@@ -771,5 +776,19 @@
|
||||
"homeRewardsButtonText": "See rewards",
|
||||
"homeVegaTokenIntro": "VEGA Token is a governance asset used to make and vote on proposals, and nominate validators.",
|
||||
"homeVegaTokenButtonText": "Manage tokens",
|
||||
"downloadProposalJson": "Download proposal as JSON"
|
||||
"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_UNSPECIFIED": "Unspecified",
|
||||
"vegaRelease{release}": "Vega Release {{release}}",
|
||||
"upgradeBlockHeight": "Upgrade block height",
|
||||
"vegaReleaseTag": "Vega release tag",
|
||||
"approvalStatus": "Approval status",
|
||||
"approvers": "Approvers",
|
||||
"approval (% validator voting power)": "approval (% validator voting power)",
|
||||
"67% voting power required": "67% voting power required",
|
||||
"Token": "Token",
|
||||
"associateVegaNow": "Associate $VEGA now"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
export const calculateEpochOffset = ({
|
||||
epochId,
|
||||
page,
|
||||
size,
|
||||
}: {
|
||||
epochId: number;
|
||||
page: number;
|
||||
size: number;
|
||||
}) => {
|
||||
// offset the epoch by the current page number times the page size while making sure it doesn't go below the minimum epoch value
|
||||
return {
|
||||
fromEpoch: Math.max(0, epochId - size * page) + 1,
|
||||
toEpoch: epochId - size * page + size,
|
||||
};
|
||||
};
|
||||
@@ -1,20 +1,17 @@
|
||||
import { ethers } from 'ethers';
|
||||
import type { Web3ReactHooks } from '@web3-react/core';
|
||||
import { initializeConnector } from '@web3-react/core';
|
||||
import { MetaMask } from '@web3-react/metamask';
|
||||
import { WalletConnect } from '@web3-react/walletconnect';
|
||||
import type { Connector } from '@web3-react/types';
|
||||
import { ENV } from '../config/env';
|
||||
import { UrlConnector } from '@vegaprotocol/web3';
|
||||
import {
|
||||
initializeMetaMaskConnector,
|
||||
initializeWalletConnector,
|
||||
initializeWalletConnectLegacyConnector,
|
||||
initializeCoinbaseConnector,
|
||||
WALLETCONNECT_PROJECT_ID,
|
||||
initializeUrlConnector,
|
||||
} from '@vegaprotocol/web3';
|
||||
|
||||
const [metamask, metamaskHooks] = initializeConnector<MetaMask>(
|
||||
(actions) => new MetaMask(actions)
|
||||
);
|
||||
|
||||
const [urlConnector, urlHooks] = initializeConnector<UrlConnector>(
|
||||
(actions) =>
|
||||
new UrlConnector(actions, ENV.localProviderUrl, ENV.ethWalletMnemonic)
|
||||
);
|
||||
initializeUrlConnector(ENV.localProviderUrl, ENV.ethWalletMnemonic);
|
||||
|
||||
export const createDefaultProvider = (providerUrl: string, chainId: number) => {
|
||||
return new ethers.providers.JsonRpcProvider(providerUrl, chainId);
|
||||
@@ -24,19 +21,14 @@ export const createConnectors = (providerUrl: string, chainId: number) => {
|
||||
if (isNaN(chainId)) {
|
||||
throw new Error('Invalid Ethereum chain ID for environment');
|
||||
}
|
||||
const [walletconnect, walletconnectHooks] =
|
||||
initializeConnector<WalletConnect>(
|
||||
(actions) =>
|
||||
new WalletConnect(actions, {
|
||||
rpc: {
|
||||
[chainId]: providerUrl,
|
||||
},
|
||||
}),
|
||||
[chainId]
|
||||
);
|
||||
|
||||
return [
|
||||
ENV.urlConnect ? [urlConnector, urlHooks] : null,
|
||||
[metamask, metamaskHooks],
|
||||
[walletconnect, walletconnectHooks],
|
||||
].filter(Boolean) as [Connector, Web3ReactHooks][];
|
||||
ENV.urlConnect
|
||||
? initializeUrlConnector(ENV.localProviderUrl, ENV.ethWalletMnemonic)
|
||||
: null,
|
||||
initializeMetaMaskConnector(),
|
||||
initializeCoinbaseConnector(providerUrl),
|
||||
initializeWalletConnector(WALLETCONNECT_PROJECT_ID, chainId, providerUrl),
|
||||
initializeWalletConnectLegacyConnector(chainId, providerUrl),
|
||||
].filter(Boolean) as unknown as [Connector, Web3ReactHooks][];
|
||||
};
|
||||
|
||||
@@ -10,23 +10,31 @@ import {
|
||||
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||
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 { useNodesQuery } from '../staking/home/__generated___/Nodes';
|
||||
import { useNodesQuery } from '../staking/home/__generated__/Nodes';
|
||||
import { useProposalsQuery } from '../proposals/proposals/__generated__/Proposals';
|
||||
import { getNotRejectedProposals } from '../proposals/proposals/proposals-container';
|
||||
import { useProtocolUpgradesQuery } from '../proposals/protocol-upgrade/__generated__/ProtocolUpgradeProposals';
|
||||
import {
|
||||
getNotRejectedProposals,
|
||||
getNotRejectedProtocolUpgradeProposals,
|
||||
} from '../proposals/proposals/proposals-container';
|
||||
import { Heading } from '../../components/heading';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import type { RouteChildProps } from '..';
|
||||
import type { ProposalFieldsFragment } from '../proposals/proposals/__generated__/Proposals';
|
||||
import type { NodesFragmentFragment } from '../staking/home/__generated___/Nodes';
|
||||
import type { NodesFragmentFragment } from '../staking/home/__generated__/Nodes';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from '../proposals/protocol-upgrade/__generated__/ProtocolUpgradeProposals';
|
||||
|
||||
const nodesToShow = 6;
|
||||
|
||||
const HomeProposals = ({
|
||||
proposals,
|
||||
protocolUpgradeProposals,
|
||||
}: {
|
||||
proposals: ProposalFieldsFragment[];
|
||||
protocolUpgradeProposals: ProtocolUpgradeProposalFieldsFragment[];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -47,6 +55,10 @@ const HomeProposals = ({
|
||||
data-testid="home-proposal-list"
|
||||
className="grid md:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2 gap-6"
|
||||
>
|
||||
{protocolUpgradeProposals.map((proposal, index) => (
|
||||
<ProtocolUpgradeProposalsListItem key={index} proposal={proposal} />
|
||||
))}
|
||||
|
||||
{proposals.map((proposal) => (
|
||||
<ProposalsListItem key={proposal.id} proposal={proposal} />
|
||||
))}
|
||||
@@ -107,20 +119,7 @@ const HomeNodes = ({
|
||||
|
||||
{trimmedActiveNodes.map(({ id, avatarUrl, name }) => (
|
||||
<div key={id} data-testid="validators" className="col-span-2">
|
||||
<Link to={`${Routes.VALIDATORS}/${id}`}>
|
||||
<RoundedWrapper paddingBottom={true} border={false}>
|
||||
<div className="flex items-center justify-center m-[-1rem] p-4 bg-neutral-900 hover:bg-neutral-800">
|
||||
{avatarUrl && (
|
||||
<img
|
||||
className="h-6 w-6 rounded-full mr-2"
|
||||
src={avatarUrl}
|
||||
alt={`Avatar icon for ${name}`}
|
||||
/>
|
||||
)}
|
||||
<span className="text-sm">{name}</span>
|
||||
</div>
|
||||
</RoundedWrapper>
|
||||
</Link>
|
||||
<ValidatorDetailsLink id={id} avatarUrl={avatarUrl} name={name} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -136,6 +135,35 @@ const HomeNodes = ({
|
||||
);
|
||||
};
|
||||
|
||||
interface ValidatorDetailsLinkProps {
|
||||
id: string;
|
||||
avatarUrl: string | null | undefined;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const ValidatorDetailsLink = ({
|
||||
id,
|
||||
avatarUrl,
|
||||
name,
|
||||
}: ValidatorDetailsLinkProps) => {
|
||||
return (
|
||||
<Link to={`${Routes.VALIDATORS}/${id}`}>
|
||||
<RoundedWrapper paddingBottom={true} border={false}>
|
||||
<div className="flex items-center justify-center m-[-1rem] p-3 bg-neutral-900 hover:bg-neutral-800">
|
||||
{avatarUrl && (
|
||||
<img
|
||||
className="h-6 w-6 rounded-full mr-2"
|
||||
src={avatarUrl}
|
||||
alt={`Avatar icon for ${name}`}
|
||||
/>
|
||||
)}
|
||||
<span className="text-sm">{name}</span>
|
||||
</div>
|
||||
</RoundedWrapper>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const GovernanceHome = ({ name }: RouteChildProps) => {
|
||||
useDocumentTitle(name);
|
||||
const { t } = useTranslation();
|
||||
@@ -149,6 +177,16 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
|
||||
errorPolicy: 'ignore',
|
||||
});
|
||||
|
||||
const {
|
||||
data: protocolUpgradesData,
|
||||
loading: protocolUpgradesLoading,
|
||||
error: protocolUpgradesError,
|
||||
} = useProtocolUpgradesQuery({
|
||||
pollInterval: 5000,
|
||||
fetchPolicy: 'network-only',
|
||||
errorPolicy: 'ignore',
|
||||
});
|
||||
|
||||
const {
|
||||
data: validatorsData,
|
||||
error: validatorsError,
|
||||
@@ -163,11 +201,38 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
|
||||
proposalsData
|
||||
? getNotRejectedProposals<ProposalFieldsFragment>(
|
||||
proposalsData.proposalsConnection
|
||||
).slice(0, 3)
|
||||
)
|
||||
: [],
|
||||
[proposalsData]
|
||||
);
|
||||
|
||||
const protocolUpgradeProposals = useMemo(
|
||||
() =>
|
||||
protocolUpgradesData
|
||||
? getNotRejectedProtocolUpgradeProposals<ProtocolUpgradeProposalFieldsFragment>(
|
||||
protocolUpgradesData.protocolUpgradeProposals
|
||||
).filter(
|
||||
(p) =>
|
||||
Number(p.upgradeBlockHeight) >
|
||||
Number(protocolUpgradesData.lastBlockHeight)
|
||||
)
|
||||
: [],
|
||||
[protocolUpgradesData]
|
||||
);
|
||||
|
||||
const totalProposalsDesired = 4;
|
||||
const protocolUpgradeProposalsToShow = protocolUpgradeProposals.slice(
|
||||
0,
|
||||
totalProposalsDesired
|
||||
);
|
||||
const proposalsToShow =
|
||||
protocolUpgradeProposalsToShow.length === totalProposalsDesired
|
||||
? []
|
||||
: proposals.slice(
|
||||
0,
|
||||
totalProposalsDesired - protocolUpgradeProposalsToShow.length
|
||||
);
|
||||
|
||||
const activeNodes = removePaginationWrapper(
|
||||
validatorsData?.nodesConnection.edges
|
||||
);
|
||||
@@ -182,11 +247,14 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
|
||||
|
||||
return (
|
||||
<AsyncRenderer
|
||||
loading={proposalsLoading || validatorsLoading}
|
||||
error={proposalsError || validatorsError}
|
||||
data={proposalsData && validatorsData}
|
||||
loading={proposalsLoading || protocolUpgradesLoading || validatorsLoading}
|
||||
error={proposalsError || protocolUpgradesError || validatorsError}
|
||||
data={proposalsData && protocolUpgradesData && validatorsData}
|
||||
>
|
||||
<HomeProposals proposals={proposals} />
|
||||
<HomeProposals
|
||||
proposals={proposalsToShow}
|
||||
protocolUpgradeProposals={protocolUpgradeProposalsToShow}
|
||||
/>
|
||||
|
||||
<HomeNodes
|
||||
activeNodes={activeNodes}
|
||||
|
||||
+2
-2
@@ -86,7 +86,7 @@ describe('Proposal Votes Table', () => {
|
||||
|
||||
it('displays if an update market proposal will pass by token vote', () => {
|
||||
renderComponent(updateMarketProposal, updateMarketProposalType);
|
||||
expect(screen.getByText('👍 by Token vote')).toBeInTheDocument();
|
||||
expect(screen.getByText('👍 by token vote')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays if an update market proposal will pass by LP vote', () => {
|
||||
@@ -110,6 +110,6 @@ describe('Proposal Votes Table', () => {
|
||||
}),
|
||||
updateMarketProposalType
|
||||
);
|
||||
expect(screen.getByText('👍 by Liquidity vote')).toBeInTheDocument();
|
||||
expect(screen.getByText('👍 by liquidity vote')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
+76
@@ -136,6 +136,82 @@ describe('Proposals list item details', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders proposal state: Update market proposal - set to pass by LP vote', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_OPEN,
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarket',
|
||||
},
|
||||
},
|
||||
votes: {
|
||||
yes: {
|
||||
...generateYesVotes(0),
|
||||
totalEquityLikeShareWeight: '1000',
|
||||
},
|
||||
no: {
|
||||
...generateNoVotes(0),
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||
'Set to pass by LP vote'
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders proposal state: Update market proposal - set to pass by token vote', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_OPEN,
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarket',
|
||||
},
|
||||
},
|
||||
votes: {
|
||||
yes: {
|
||||
...generateYesVotes(1000, 1000),
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
no: {
|
||||
...generateNoVotes(0),
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||
'Set to pass by token vote'
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders proposal state: Update market proposal - set to fail', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_OPEN,
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarket',
|
||||
},
|
||||
},
|
||||
votes: {
|
||||
yes: {
|
||||
...generateYesVotes(0),
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
no: {
|
||||
...generateNoVotes(0),
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('vote-status')).toHaveTextContent('Set to fail');
|
||||
});
|
||||
|
||||
it('Renders proposal state: Open - 5 minutes left to vote', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
|
||||
+27
-4
@@ -41,12 +41,22 @@ export const ProposalsListItemDetails = ({
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
}) => {
|
||||
const state = proposal?.state;
|
||||
const { willPassByTokenVote, majorityMet, participationMet } =
|
||||
useVoteInformation({
|
||||
proposal,
|
||||
});
|
||||
const {
|
||||
willPassByTokenVote,
|
||||
willPassByLPVote,
|
||||
majorityMet,
|
||||
participationMet,
|
||||
} = useVoteInformation({
|
||||
proposal,
|
||||
});
|
||||
const { t } = useTranslation();
|
||||
const { voteState } = useUserVote(proposal?.id);
|
||||
const isUpdateMarket = proposal?.terms.change.__typename === 'UpdateMarket';
|
||||
const updateMarketWillPass = willPassByTokenVote || willPassByLPVote;
|
||||
const updateMarketVotePassMethod = willPassByTokenVote
|
||||
? t('byTokenVote')
|
||||
: t('byLPVote');
|
||||
|
||||
let proposalStatus: ReactNode;
|
||||
let voteDetails: ReactNode;
|
||||
let voteStatus: ReactNode;
|
||||
@@ -128,6 +138,19 @@ export const ProposalsListItemDetails = ({
|
||||
</>
|
||||
);
|
||||
voteStatus =
|
||||
(isUpdateMarket &&
|
||||
(updateMarketWillPass ? (
|
||||
<>
|
||||
{t('Set to')}{' '}
|
||||
<StatusPass>
|
||||
{t('pass')} {updateMarketVotePassMethod}
|
||||
</StatusPass>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{t('Set to')} <StatusFail>{t('fail')}</StatusFail>
|
||||
</>
|
||||
))) ||
|
||||
(!participationMet && <ParticipationNotReached />) ||
|
||||
(!majorityMet && <MajorityNotReached />) ||
|
||||
(willPassByTokenVote ? (
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ const renderComponent = (proposals: ProposalQuery['proposal'][]) => (
|
||||
<MockedProvider mocks={[networkParamsQueryMock]}>
|
||||
<AppStateProvider>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<ProposalsList proposals={proposals} />
|
||||
<ProposalsList proposals={proposals} protocolUpgradeProposals={[]} />
|
||||
</VegaWalletContext.Provider>
|
||||
</AppStateProvider>
|
||||
</MockedProvider>
|
||||
|
||||
@@ -3,17 +3,21 @@ import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Heading, SubHeading } from '../../../../components/heading';
|
||||
import { ProposalsListItem } from '../proposals-list-item';
|
||||
import { ProtocolUpgradeProposalsListItem } from '../protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item';
|
||||
import { ProposalsListFilter } from '../proposals-list-filter';
|
||||
import Routes from '../../../routes';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { Link } from 'react-router-dom';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import { 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 '../../protocol-upgrade/__generated__/ProtocolUpgradeProposals';
|
||||
|
||||
interface ProposalsListProps {
|
||||
proposals: Array<ProposalFieldsFragment | ProposalQuery['proposal']>;
|
||||
protocolUpgradeProposals: ProtocolUpgradeProposalFieldsFragment[];
|
||||
lastBlockHeight?: string;
|
||||
}
|
||||
|
||||
interface SortedProposalsProps {
|
||||
@@ -21,7 +25,16 @@ interface SortedProposalsProps {
|
||||
closed: Array<ProposalFieldsFragment | ProposalQuery['proposal']>;
|
||||
}
|
||||
|
||||
export const ProposalsList = ({ proposals }: ProposalsListProps) => {
|
||||
interface SortedProtocolUpgradeProposalsProps {
|
||||
open: ProtocolUpgradeProposalFieldsFragment[];
|
||||
closed: ProtocolUpgradeProposalFieldsFragment[];
|
||||
}
|
||||
|
||||
export const ProposalsList = ({
|
||||
proposals,
|
||||
protocolUpgradeProposals,
|
||||
lastBlockHeight,
|
||||
}: ProposalsListProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [filterString, setFilterString] = useState('');
|
||||
const sortedProposals = proposals.reduce(
|
||||
@@ -39,6 +52,21 @@ export const ProposalsList = ({ proposals }: ProposalsListProps) => {
|
||||
}
|
||||
);
|
||||
|
||||
const sortedProtocolUpgradeProposals = protocolUpgradeProposals.reduce(
|
||||
(acc: SortedProtocolUpgradeProposalsProps, proposal) => {
|
||||
if (Number(proposal?.upgradeBlockHeight) > Number(lastBlockHeight)) {
|
||||
acc.open.push(proposal);
|
||||
} else {
|
||||
acc.closed.push(proposal);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
open: [],
|
||||
closed: [],
|
||||
}
|
||||
);
|
||||
|
||||
const filterPredicate = (
|
||||
p: ProposalFieldsFragment | ProposalQuery['proposal']
|
||||
) =>
|
||||
@@ -65,7 +93,7 @@ export const ProposalsList = ({ proposals }: ProposalsListProps) => {
|
||||
</div>
|
||||
<p className="mb-8">
|
||||
{t(
|
||||
`The Vega network is governed by the community. View active proposals, vote on them or propose changes to the network.`
|
||||
`The Vega network is governed by the community. View active proposals, vote on them or propose changes to the network. Network upgrades are proposed and approved by validators.`
|
||||
)}{' '}
|
||||
<ExternalLink
|
||||
data-testid="proposal-documentation-link"
|
||||
@@ -80,8 +108,15 @@ export const ProposalsList = ({ proposals }: ProposalsListProps) => {
|
||||
)}
|
||||
<section className="-mx-4 p-4 mb-8 bg-neutral-800">
|
||||
<SubHeading title={t('openProposals')} />
|
||||
{sortedProposals.open.length > 0 ? (
|
||||
{sortedProposals.open.length > 0 ||
|
||||
sortedProtocolUpgradeProposals.open.length > 0 ? (
|
||||
<ul data-testid="open-proposals">
|
||||
{sortedProtocolUpgradeProposals.open.map((proposal) => (
|
||||
<ProtocolUpgradeProposalsListItem
|
||||
key={proposal.upgradeBlockHeight}
|
||||
proposal={proposal}
|
||||
/>
|
||||
))}
|
||||
{sortedProposals.open.filter(filterPredicate).map((proposal) => (
|
||||
<ProposalsListItem key={proposal?.id} proposal={proposal} />
|
||||
))}
|
||||
@@ -94,8 +129,16 @@ export const ProposalsList = ({ proposals }: ProposalsListProps) => {
|
||||
</section>
|
||||
<section>
|
||||
<SubHeading title={t('closedProposals')} />
|
||||
{sortedProposals.closed.length > 0 ? (
|
||||
{sortedProposals.closed.length > 0 ||
|
||||
sortedProtocolUpgradeProposals.closed.length > 0 ? (
|
||||
<ul data-testid="closed-proposals">
|
||||
{sortedProtocolUpgradeProposals.closed.map((proposal) => (
|
||||
<ProtocolUpgradeProposalsListItem
|
||||
key={proposal.upgradeBlockHeight}
|
||||
proposal={proposal}
|
||||
/>
|
||||
))}
|
||||
|
||||
{sortedProposals.closed.filter(filterPredicate).map((proposal) => (
|
||||
<ProposalsListItem key={proposal?.id} proposal={proposal} />
|
||||
))}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from './protocol-update-proposal-detail-approvals';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user