Compare commits

..
Author SHA1 Message Date
Matthew Russell f784bd552c chore: fix tests 2023-05-09 13:36:52 -07:00
487 changed files with 6039 additions and 10736 deletions
@@ -1,74 +0,0 @@
name: After Release
on:
release:
types: [published]
jobs:
after-release:
runs-on: ubuntu-22.04
timeout-minutes: 30
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry (ghcr)
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to the Container registry (docker hub)
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: resolve ipfs hashes for release
run: |
echo "Tag name: ${{ github.event.release.tag_name }}"
echo "Name: ${{ github.event.release.name }}"
echo "Description: ${{ github.event.release.body }}"
until docker pull vegaprotocol/trading:${{ github.event.release.tag_name }}; do
echo "Image not pushed yet, waiting 60 seconds"
sleep 60
done
docker run --rm vegaprotocol/trading:${{ github.event.release.tag_name }} cat /ipfs-hash > ipfs-hash
curl -L https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz -o kubo.tgz
tar -xzf kubo.tgz
export PATH="$PATH:$PWD/kubo"
which ipfs
echo IPFS_V0=$(cat ipfs-hash) >> $GITHUB_ENV
echo IPFS_V1=$(ipfs cid format -v 1 -b base32 $(cat ipfs-hash)) >> $GITHUB_ENV
- name: Edit Release
uses: irongut/EditRelease@v1.2.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
id: ${{ github.event.release.id }}
body: |
---
# Deployments
* https://explorer.vega.xyz
* https://governance.vega.xyz
# IPFS releases
Tye IPFS hash of this release of the Trading app is:
CIDv0: ${{ env.IPFS_V0 }}
CIDv1: ${{ env.IPFS_V1 }}
You can always access the latest IPFS release by visiting [vega.trading](https://vega.trading).
You can also access Trading directly from an IPFS gateway.
BEWARE: The Trading interface uses [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported. You should always use an IPFS gateway that enforces [origin separation](https://ipfs.github.io/public-gateway-checker/).
Your settings are not remembered across different URLs.
IPFS gateways:
https://${{ env.IPFS_V1 }}.ipfs.dweb.link/
https://${{ env.IPFS_V1 }}.ipfs.cf-ipfs.com/
ipfs://${{ env.IPFS_V0 }}/
+1
View File
@@ -33,4 +33,5 @@ jobs:
config: baseUrl=${{ github.event.inputs.url }} config: baseUrl=${{ github.event.inputs.url }}
env: grepTags=@live env: grepTags=@live
env: env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+3 -3
View File
@@ -20,7 +20,7 @@ jobs:
project: ${{ fromJSON(inputs.projects) }} project: ${{ fromJSON(inputs.projects) }}
name: ${{ matrix.project }} name: ${{ matrix.project }}
runs-on: self-hosted-runner runs-on: self-hosted-runner
timeout-minutes: 60 timeout-minutes: 40
steps: steps:
# Checks if skip cache was requested # Checks if skip cache was requested
- name: Set skip-nx-cache flag - name: Set skip-nx-cache flag
@@ -66,7 +66,7 @@ jobs:
###### ######
- name: Run Cypress tests - name: Run Cypress tests
run: yarn nx run ${{ matrix.project }}:e2e ${{ env.SKIP_CACHE }} --browser chrome --env.grepTags="${{ inputs.tags }}" run: yarn nx run ${{ matrix.project }}:e2e ${{ env.SKIP_CACHE }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome --env.grepTags="${{ inputs.tags }}"
working-directory: frontend-monorepo working-directory: frontend-monorepo
env: env:
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }} CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
@@ -98,4 +98,4 @@ jobs:
if: ${{ failure() }} if: ${{ failure() }}
with: with:
name: test-report-${{ matrix.project }} name: test-report-${{ matrix.project }}
path: frontend-monorepo/apps/${{ matrix.project }}/cypress/reports path: frontend-monorepo/apps/trading-e2e/cypress/reports
+5 -49
View File
@@ -119,7 +119,7 @@ jobs:
run: | run: |
echo "Check ipfs-hash" echo "Check ipfs-hash"
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat /ipfs-hash docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat /ipfs-hash
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat /ipfs-hash > ${{ matrix.app }}-ipfs-hash docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat /ipfs-hash > ${{ matirx.app }}-ipfs-hash
echo "List html directory" echo "List html directory"
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local sh -c 'apk add --update tree; tree /usr/share/nginx/html' docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local sh -c 'apk add --update tree; tree /usr/share/nginx/html'
@@ -148,7 +148,6 @@ jobs:
ENV_NAME=${{ env.ENV_NAME }} ENV_NAME=${{ env.ENV_NAME }}
tags: | tags: |
vegaprotocol/${{ matrix.app }}:${{ github.ref_name }} vegaprotocol/${{ matrix.app }}:${{ github.ref_name }}
vegaprotocol/${{ matrix.app }}:mainnet
# bucket creation in github.com/vegaprotocol/terraform//frontend # bucket creation in github.com/vegaprotocol/terraform//frontend
- name: Publish dist to s3 - name: Publish dist to s3
@@ -170,51 +169,8 @@ jobs:
labels: ${{ matrix.app }}-preview labels: ${{ matrix.app }}-preview
number: ${{ github.event.number }} number: ${{ github.event.number }}
- name: Trigger fleek deployment - name: Add ipfs hash to release
uses: softprops/action-gh-release@v1
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }} if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
run: | with:
# display info about app files: ${{ matrix.app }}-ipfs-hash
curl -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"query": "query{getSiteById(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id latestDeploy{id status}}}"}' \
https://api.fleek.co/graphql
# trigger new deployment as base image is always set to vegaprotocol/trading:mainnet
curl -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"query": "mutation{triggerDeploy(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id status}}"}' \
https://api.fleek.co/graphql
- name: Update vega.trading DNS to redirect to the new console
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
run: |
curl -L https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz -o kubo.tgz
tar -xzf kubo.tgz
export PATH="$PATH:$PWD/kubo"
which ipfs
new_hash=$(cat ${{ matrix.app }}-ipfs-hash)
new_cid=$(ipfs cid format -v 1 -b base32 $new_hash)
# Generate console URL
new_console_url_type=ipfs
# new_console_url_type=ipns
new_console_url_domain=cf-ipfs.com
# new_console_url_domain=dweb.link
new_console_url="https://${new_cid}.${new_console_url_type}.${new_console_url_domain}/"
echo "new_console_url=${new_console_url}"
# Update record in DNSimple
# docs: https://developer.dnsimple.com/v2/zones/records/#updateZoneRecord
dnsimple_account_id=84895
dnsimple_zone_name=vega.trading
dnsimple_record_id=44409591
# see: https://dnsimple.com/a/84895/domains/vega.trading/records/44409591/edit
curl -H 'Authorization: Bearer ${{ secrets.DNSIMPLE_API_TOKEN }}' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-X PATCH \
-d "{
\"content\": \"${new_console_url}\"
}" \
https://api.dnsimple.com/v2/${dnsimple_account_id}/zones/${dnsimple_zone_name}/records/${dnsimple_record_id}
-77
View File
@@ -1,77 +0,0 @@
name: 'Rollback console'
on:
workflow_dispatch:
inputs:
version:
description: 'Version that should be set on rollback'
required: true
type: string
jobs:
rollback:
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry (docker hub)
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Retag mainnet
run: |
docker pull vegaprotocol/trading:${{ inputs.version }}
docker tag vegaprotocol/trading:${{ inputs.version }} vegaprotocol/trading:mainnet
docker push vegaprotocol/trading:mainnet
docker run --rm vegaprotocol/trading:mainnet cat /ipfs-hash > ipfs-hash
- name: Trigger fleek deployment
run: |
# display info about app
curl -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"query": "query{getSiteById(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id latestDeploy{id status}}}"}' \
https://api.fleek.co/graphql
# trigger new deployment as base image is always set to vegaprotocol/trading:mainnet
curl -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"query": "mutation{triggerDeploy(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id status}}"}' \
https://api.fleek.co/graphql
- name: Update vega.trading DNS to redirect to the new console
run: |
curl -L https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz -o kubo.tgz
tar -xzf kubo.tgz
export PATH="$PATH:$PWD/kubo"
which ipfs
new_hash=$(cat ipfs-hash)
new_cid=$(ipfs cid format -v 1 -b base32 $new_hash)
# Generate console URL
new_console_url_type=ipfs
# new_console_url_type=ipns
new_console_url_domain=cf-ipfs.com
# new_console_url_domain=dweb.link
new_console_url="https://${new_cid}.${new_console_url_type}.${new_console_url_domain}/"
echo "new_console_url=${new_console_url}"
# Update record in DNSimple
# docs: https://developer.dnsimple.com/v2/zones/records/#updateZoneRecord
dnsimple_account_id=84895
dnsimple_zone_name=vega.trading
dnsimple_record_id=44409591
# see: https://dnsimple.com/a/84895/domains/vega.trading/records/44409591/edit
curl -H 'Authorization: Bearer ${{ secrets.DNSIMPLE_API_TOKEN }}' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-X PATCH \
-d "{
\"content\": \"${new_console_url}\"
}" \
https://api.dnsimple.com/v2/${dnsimple_account_id}/zones/${dnsimple_zone_name}/records/${dnsimple_record_id}
+1 -1
View File
@@ -49,4 +49,4 @@ cypress.env.json
.next .next
#cypress #cypress
/apps/**/cypress/reports/ /apps/trading-e2e/cypress/reports/
+9 -9
View File
@@ -1,20 +1,20 @@
.PHONY: latest-release .PHONY: latest-release
latest-release: latest-release:
gh release list | head -n 1 | awk '{print $1}' gh release list | head -n 1 | awk '{print $1}'
.PHONY: show-latest-release .PHONY: show-latest-release
show-latest-release: show-latest-release:
gh release view `gh release list | head -n 1 | awk '{print $1}'` gh release view `gh release list | head -n 1 | awk '{print $1}'`
.PHONY: recalculate-ipfs .PHONY: recalculate-ipfs
recalculate-ipfs: recalculate-ipfs:
echo "ipfs hash inside the image" echo "ipfs hash inside the image"
docker run --rm ${TAG} cat /ipfs-hash docker run --rm ${TAG} cat /ipfs-hash
echo "recalculating ipfs hash" echo "recalculating ipfs hash"
docker run --rm ${TAG} ipfs add -r /usr/share/nginx/html docker run --rm ${TAG} ipfs add -rw /usr/share/nginx/html
.PHONY: eject-ipfs-hash .PHONY: eject-ipfs-hash
unpack: unpack:
docker create --name=dist ${TAG} docker create --name=dist ${TAG}
docker cp dist:/usr/share/nginx/html dist docker cp dist:/usr/share/nginx/html dist
docker rm dist docker rm dist
+1 -2
View File
@@ -1,11 +1,10 @@
const { defineConfig } = require('cypress'); const { defineConfig } = require('cypress');
module.exports = defineConfig({ module.exports = defineConfig({
reporter: '../../node_modules/cypress-mochawesome-reporter', projectId: 'et4snf',
e2e: { e2e: {
setupNodeEvents(on, config) { setupNodeEvents(on, config) {
require('cypress-mochawesome-reporter/plugin')(on);
require('@cypress/grep/src/plugin')(config); require('@cypress/grep/src/plugin')(config);
return config; return config;
}, },
-1
View File
@@ -15,6 +15,5 @@
import '@vegaprotocol/cypress'; import '@vegaprotocol/cypress';
import './common.functions.js'; import './common.functions.js';
import 'cypress-mochawesome-reporter/register';
import registerCypressGrep from '@cypress/grep'; import registerCypressGrep from '@cypress/grep';
registerCypressGrep(); registerCypressGrep();
-1
View File
@@ -2,7 +2,6 @@
NX_TENDERMINT_URL=https://be.vega.community NX_TENDERMINT_URL=https://be.vega.community
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_ENV=MAINNET NX_VEGA_ENV=MAINNET
NX_BLOCK_EXPLORER=https://be.vega.community/rest/ NX_BLOCK_EXPLORER=https://be.vega.community/rest/
NX_ETHERSCAN_URL=https://etherscan.io NX_ETHERSCAN_URL=https://etherscan.io
@@ -3,7 +3,6 @@ import { Header } from './header';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'),
NetworkSwitcher: () => ( NetworkSwitcher: () => (
<div data-testid="network-switcher">NetworkSwitcher</div> <div data-testid="network-switcher">NetworkSwitcher</div>
), ),
@@ -1,8 +1,8 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { MarketInfoWithData } from '@vegaprotocol/markets'; import type { MarketInfoWithData } from '@vegaprotocol/market-info';
import { LiquidityInfoPanel } from '@vegaprotocol/market-info';
import { LiquidityMonitoringParametersInfoPanel } from '@vegaprotocol/market-info';
import { import {
LiquidityInfoPanel,
LiquidityMonitoringParametersInfoPanel,
InstrumentInfoPanel, InstrumentInfoPanel,
KeyDetailsInfoPanel, KeyDetailsInfoPanel,
LiquidityPriceRangeInfoPanel, LiquidityPriceRangeInfoPanel,
@@ -12,8 +12,8 @@ import {
RiskModelInfoPanel, RiskModelInfoPanel,
RiskParametersInfoPanel, RiskParametersInfoPanel,
SettlementAssetInfoPanel, SettlementAssetInfoPanel,
} from '@vegaprotocol/markets'; } from '@vegaprotocol/market-info';
import { MarketInfoTable } from '@vegaprotocol/markets'; import { MarketInfoTable } from '@vegaprotocol/market-info';
import type { DataSourceDefinition } from '@vegaprotocol/types'; import type { DataSourceDefinition } from '@vegaprotocol/types';
import isEqual from 'lodash/isEqual'; import isEqual from 'lodash/isEqual';
@@ -1,4 +1,4 @@
import type { MarketFieldsFragment } from '@vegaprotocol/markets'; import type { MarketFieldsFragment } from '@vegaprotocol/market-list';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit'; import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
@@ -1,5 +1,3 @@
import { Icon } from '@vegaprotocol/ui-toolkit';
// https://github.com/vegaprotocol/vega/blob/develop/core/blockchain/response.go // https://github.com/vegaprotocol/vega/blob/develop/core/blockchain/response.go
export const ErrorCodes = new Map([ export const ErrorCodes = new Map([
[51, 'Transaction failed validation'], [51, 'Transaction failed validation'],
@@ -30,11 +28,7 @@ export const ChainResponseCode = ({
}: ChainResponseCodeProps) => { }: ChainResponseCodeProps) => {
const isSuccess = successCodes.has(code); const isSuccess = successCodes.has(code);
const icon = isSuccess ? ( const icon = isSuccess ? '✅' : '❌';
<Icon name="tick-circle" className="fill-vega-green-550" />
) : (
<Icon name="cross" className="fill-vega-pink-550" />
);
const label = ErrorCodes.get(code) || 'Unknown response code'; const label = ErrorCodes.get(code) || 'Unknown response code';
// Hack for batches with many errors - see https://github.com/vegaprotocol/vega/issues/7245 // Hack for batches with many errors - see https://github.com/vegaprotocol/vega/issues/7245
@@ -42,7 +36,7 @@ export const ChainResponseCode = ({
error && error.length > 100 ? error.replace(/,/g, ',\r\n') : error; error && error.length > 100 ? error.replace(/,/g, ',\r\n') : error;
return ( return (
<div title={`Response code: ${code} - ${label}`} className=" inline-block"> <div title={`Response code: ${code} - ${label}`} className="inline-block">
<span <span
className="mr-2" className="mr-2"
aria-label={isSuccess ? 'Success' : 'Warning'} aria-label={isSuccess ? 'Success' : 'Warning'}
@@ -4,7 +4,6 @@ import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint
import { TxDetailsShared } from './shared/tx-details-shared'; import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table'; import { TableCell, TableRow, TableWithTbody } from '../../table';
import ProposalLink from '../../links/proposal-link/proposal-link'; import ProposalLink from '../../links/proposal-link/proposal-link';
import { VoteIcon } from '../../vote-icon/vote-icon';
interface TxProposalVoteProps { interface TxProposalVoteProps {
txData: BlockExplorerTransactionResult | undefined; txData: BlockExplorerTransactionResult | undefined;
@@ -31,22 +30,27 @@ export const TxProposalVote = ({
return <>{t('Awaiting Block Explorer transaction details')}</>; return <>{t('Awaiting Block Explorer transaction details')}</>;
} }
const vote = txData.command.voteSubmission.value === 'VALUE_YES'; const vote = txData.command.voteSubmission.value ? '👍' : '👎';
return ( return (
<TableWithTbody className="mb-8" allowWrap={true}> <TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} /> <TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TableRow modifier="bordered">
<TableCell>{t('Proposal ID')}</TableCell>
<TableCell>{txData.command.voteSubmission.proposalId}</TableCell>
</TableRow>
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Proposal details')}</TableCell> <TableCell>{t('Proposal details')}</TableCell>
<TableCell> <TableCell>
<ProposalLink id={txData.command.voteSubmission.proposalId} /> <ProposalLink id={txData.command.voteSubmission.proposalId} />
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Proposal')}</TableCell>
<TableCell>{txData.command.voteSubmission.proposalId}</TableCell>
</TableRow>
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Vote')}</TableCell> <TableCell>{t('Vote')}</TableCell>
<TableCell> <TableCell>{vote}</TableCell>
<VoteIcon vote={vote} />
</TableCell>
</TableRow> </TableRow>
</TableWithTbody> </TableWithTbody>
); );
@@ -1,6 +1,5 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { components } from '../../../types/explorer'; import type { components } from '../../../types/explorer';
import { VoteIcon } from '../vote-icon/vote-icon';
interface TxOrderTypeProps { interface TxOrderTypeProps {
orderType: string; orderType: string;
@@ -138,15 +137,12 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
let type = displayString[orderType] || orderType; let type = displayString[orderType] || orderType;
let colours = let colours =
'text-white dark:text-white bg-vega-dark-150 dark:bg-vega-dark-250'; 'text-white dark:text-white bg-vega-dark-150 dark:bg-vega-dark-150';
// This will get unwieldy and should probably produce a different colour of tag // This will get unwieldy and should probably produce a different colour of tag
if (type === 'Chain Event' && !!command?.chainEvent) { if (type === 'Chain Event' && !!command?.chainEvent) {
type = getLabelForChainEvent(command.chainEvent); type = getLabelForChainEvent(command.chainEvent);
colours = 'text-white dark-text-white bg-vega-pink dark:bg-vega-pink'; colours = 'text-white dark-text-white bg-vega-pink dark:bg-vega-pink';
} else if (type === 'Validator Heartbeat') {
colours =
'text-white dark-text-white bg-vega-light-200 dark:bg-vega-dark-100';
} else if (type === 'Proposal' || type === 'Governance Proposal') { } else if (type === 'Proposal' || type === 'Governance Proposal') {
if (command && !!command.proposalSubmission) { if (command && !!command.proposalSubmission) {
type = getLabelForProposal(command.proposalSubmission); type = getLabelForProposal(command.proposalSubmission);
@@ -154,16 +150,6 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
colours = 'text-black bg-vega-yellow'; colours = 'text-black bg-vega-yellow';
} }
if (type === 'Vote on Proposal') {
return (
<VoteIcon
vote={command?.voteSubmission?.value === 'VALUE_YES'}
yesText="Proposal vote"
noText="Proposal vote"
/>
);
}
if (type === 'Vote on Proposal' || type === 'Vote Submission') { if (type === 'Vote on Proposal' || type === 'Vote Submission') {
colours = 'text-black bg-vega-yellow'; colours = 'text-black bg-vega-yellow';
} }
@@ -98,6 +98,6 @@ describe('Txs infinite list item', () => {
expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey'); expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey');
expect(screen.getByTestId('tx-type')).toHaveTextContent('testType'); expect(screen.getByTestId('tx-type')).toHaveTextContent('testType');
expect(screen.getByTestId('tx-block')).toHaveTextContent('1'); expect(screen.getByTestId('tx-block')).toHaveTextContent('1');
expect(screen.getByTestId('tx-success')).toHaveTextContent('Success'); expect(screen.getByTestId('tx-success')).toHaveTextContent('Success: ✅');
}); });
}); });
@@ -31,7 +31,7 @@ export const TxsInfiniteListItem = ({
return ( return (
<div <div
data-testid="transaction-row" data-testid="transaction-row"
className="flex items-center h-full border-t border-neutral-600 dark:border-neutral-800 txs-infinite-list-item grid grid-cols-10" className="flex items-center h-full border-t border-neutral-600 dark:border-neutral-800 txs-infinite-list-item grid grid-cols-10 py-2"
> >
<div <div
className="text-sm col-span-10 md:col-span-3 leading-none" className="text-sm col-span-10 md:col-span-3 leading-none"
@@ -83,7 +83,7 @@ export const TxsInfiniteListItem = ({
data-testid="tx-success" data-testid="tx-success"
> >
<span className="md:hidden uppercase text-vega-dark-300"> <span className="md:hidden uppercase text-vega-dark-300">
Success&nbsp; Success:&nbsp;
</span> </span>
{isNumber(code) ? ( {isNumber(code) ? (
<ChainResponseCode code={code} hideLabel={true} /> <ChainResponseCode code={code} hideLabel={true} />
@@ -1,37 +0,0 @@
import { render } from '@testing-library/react';
import { VoteIcon } from './vote-icon';
describe('Vote TX icon', () => {
it('should use the text For by default for yes votes', () => {
const yes = render(<VoteIcon vote={true} />);
expect(yes.getByTestId('label')).toHaveTextContent('For');
});
it('should use the yesText for yes votes if specified', () => {
const yes = render(<VoteIcon vote={true} yesText="Test" />);
expect(yes.getByTestId('label')).toHaveTextContent('Test');
});
it('should display the tick icon for yes votes', () => {
const no = render(<VoteIcon vote={true} />);
expect(no.getByRole('img')).toHaveAttribute(
'aria-label',
'tick-circle icon'
);
});
it('should use the text Against by default for no votes', () => {
const no = render(<VoteIcon vote={false} />);
expect(no.getByTestId('label')).toHaveTextContent('Against');
});
it('should use the noText for no votes if specified', () => {
const no = render(<VoteIcon vote={false} noText="Test" />);
expect(no.getByTestId('label')).toHaveTextContent('Test');
});
it('should display the delete icon for no votes', () => {
const no = render(<VoteIcon vote={false} />);
expect(no.getByRole('img')).toHaveAttribute('aria-label', 'delete icon');
});
});
@@ -1,40 +0,0 @@
import { Icon } from '@vegaprotocol/ui-toolkit';
import type { IconName } from '@vegaprotocol/ui-toolkit';
export interface VoteIconProps {
// True is a yes vote, false is undefined or no vorte
vote: boolean;
// Defaults to 'For', but can be any text
yesText?: string;
// Defaults to 'Against', but can be any text
noText?: string;
}
/**
* Displays a lozenge with an icon representing the way a user voted for a proposal.
* The yes and no text can be overridden
*
* @returns
*/
export function VoteIcon({
vote,
yesText = 'For',
noText = 'Against',
}: VoteIconProps) {
const label = vote ? yesText : noText;
const bg = vote ? 'bg-vega-green-550' : 'bg-vega-pink-550';
const icon: IconName = vote ? 'tick-circle' : 'delete';
const fill = vote ? 'vega-green-300' : 'vega-pink-300';
const text = vote ? 'vega-green-200' : 'vega-pink-200';
return (
<div
className={`voteicon inline-block my-1 py-1 px-2 py rounded-md text-white leading-one sm align-top ${bg}`}
>
<Icon name={icon} size={3} className={`mr-2 p-0 fill-${fill}`} />
<span className={`text-base text-${text}`} data-testid="label">
{label}
</span>
</div>
);
}
@@ -8,7 +8,7 @@ import { useScrollToLocation } from '../../hooks/scroll-to-location';
import { useDocumentTitle } from '../../hooks/use-document-title'; import { useDocumentTitle } from '../../hooks/use-document-title';
import compact from 'lodash/compact'; import compact from 'lodash/compact';
import { JsonViewerDialog } from '../../components/dialogs/json-viewer-dialog'; import { JsonViewerDialog } from '../../components/dialogs/json-viewer-dialog';
import { marketInfoWithDataProvider } from '@vegaprotocol/markets'; import { marketInfoWithDataProvider } from '@vegaprotocol/market-info';
import { PageTitle } from '../../components/page-helpers/page-title'; import { PageTitle } from '../../components/page-helpers/page-title';
export const MarketPage = () => { export const MarketPage = () => {
@@ -1,6 +1,6 @@
import { useScrollToLocation } from '../../hooks/scroll-to-location'; import { useScrollToLocation } from '../../hooks/scroll-to-location';
import { useDocumentTitle } from '../../hooks/use-document-title'; import { useDocumentTitle } from '../../hooks/use-document-title';
import { marketsProvider } from '@vegaprotocol/markets'; import { marketsProvider } from '@vegaprotocol/market-list';
import { RouteTitle } from '../../components/route-title'; import { RouteTitle } from '../../components/route-title';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
+5 -7
View File
@@ -32,9 +32,9 @@
line-height: calc(min(var(--ag-line-height, 26px), 26px) - 4px); line-height: calc(min(var(--ag-line-height, 26px), 26px) - 4px);
} }
.vega-ag-grid .ag-row, .vega-ag-grid .ag-row {
.vega-ag-grid .ag-cell { border-width: 1px 0;
border-width: 0; border-bottom: 1px solid transparent;
} }
/* Light variables */ /* Light variables */
@@ -46,6 +46,7 @@
--ag-header-column-separator-color: theme(colors.neutral[300]); --ag-header-column-separator-color: theme(colors.neutral[300]);
--ag-row-border-color: theme(colors.white); --ag-row-border-color: theme(colors.white);
--ag-row-hover-color: theme(colors.neutral[100]); --ag-row-hover-color: theme(colors.neutral[100]);
--ag-font-size: 12px;
} }
/* Dark variables */ /* Dark variables */
@@ -57,8 +58,5 @@
--ag-header-column-separator-color: theme(colors.neutral[600]); --ag-header-column-separator-color: theme(colors.neutral[600]);
--ag-row-border-color: theme(colors.black); --ag-row-border-color: theme(colors.black);
--ag-row-hover-color: theme(colors.neutral[800]); --ag-row-hover-color: theme(colors.neutral[800]);
} --ag-font-size: 12px;
.voteicon svg {
vertical-align: baseline;
} }
+1 -2
View File
@@ -1,11 +1,10 @@
const { defineConfig } = require('cypress'); const { defineConfig } = require('cypress');
module.exports = defineConfig({ module.exports = defineConfig({
reporter: '../../node_modules/cypress-mochawesome-reporter', projectId: 'et4snf',
e2e: { e2e: {
setupNodeEvents(on, config) { setupNodeEvents(on, config) {
require('cypress-mochawesome-reporter/plugin')(on);
require('@cypress/grep/src/plugin')(config); require('@cypress/grep/src/plugin')(config);
return config; return config;
}, },
@@ -7,7 +7,6 @@ import {
waitForSpinner, waitForSpinner,
navigateTo, navigateTo,
navigation, navigation,
turnTelemetryOff,
} from '../../support/common.functions'; } from '../../support/common.functions';
import { import {
clickOnValidatorFromList, clickOnValidatorFromList,
@@ -68,7 +67,6 @@ context(
'teardown wallet & drill into a specific validator', 'teardown wallet & drill into a specific validator',
function () { function () {
cy.clearLocalStorage(); cy.clearLocalStorage();
turnTelemetryOff();
cy.reload(); cy.reload();
waitForSpinner(); waitForSpinner();
cy.connectVegaWallet(); cy.connectVegaWallet();
@@ -254,10 +252,10 @@ context(
waitForBeginningOfEpoch(); waitForBeginningOfEpoch();
cy.getByTestId(stakeValidatorListStakePercentage).should( cy.getByTestId(stakeValidatorListStakePercentage).should(
'have.text', 'have.text',
'50.02%' '100%'
); );
navigateTo(navigation.validators); navigateTo(navigation.validators);
validateValidatorListTotalStakeAndShare('0', '3,002.00', '50.02%'); validateValidatorListTotalStakeAndShare('0', '2.00', '100.00%');
} }
); );
@@ -109,7 +109,7 @@ context(
); );
cy.getByTestId('protocol-upgrade-proposal-status').should( cy.getByTestId('protocol-upgrade-proposal-status').should(
'have.text', 'have.text',
'Approved by validators ' 'Approved '
); );
}); });
}); });
@@ -136,7 +136,7 @@ context(
); );
cy.getByTestId('protocol-upgrade-state').should( cy.getByTestId('protocol-upgrade-state').should(
'have.text', 'have.text',
'Approved by validators' 'Approved'
); );
cy.getByTestId('protocol-upgrade-release-tag').should( cy.getByTestId('protocol-upgrade-release-tag').should(
'have.text', 'have.text',
@@ -63,6 +63,7 @@ context(
}); });
it('should have option to go to last and newest page', function () { it('should have option to go to last and newest page', function () {
waitForBeginningOfEpoch();
cy.getByTestId('goto-last-page').click(); cy.getByTestId('goto-last-page').click();
cy.getByTestId('epoch-total-rewards-table') cy.getByTestId('epoch-total-rewards-table')
.last() .last()
@@ -29,7 +29,7 @@ const performancePenaltyToolTip = '[data-testid="performance-penalty-tooltip"]';
const overstakedPenaltyToolTip = '[data-testid="overstaked-penalty-tooltip"]'; const overstakedPenaltyToolTip = '[data-testid="overstaked-penalty-tooltip"]';
const totalPenaltyToolTip = '[data-testid="total-penalty-tooltip"]'; const totalPenaltyToolTip = '[data-testid="total-penalty-tooltip"]';
const epochCountDown = '[data-testid="epoch-countdown"]'; const epochCountDown = '[data-testid="epoch-countdown"]';
const stakeNumberRegex = /^\d{1,3}(,\d{3})*(\.\d+)?$/; const stakeNumberRegex = /^\d*\.?\d*$/;
context('Validators Page - verify elements on page', function () { context('Validators Page - verify elements on page', function () {
before('navigate to validators page', function () { before('navigate to validators page', function () {
@@ -84,13 +84,13 @@ context('Validators Page - verify elements on page', function () {
cy.get(stakedByOperatorToolTip) cy.get(stakedByOperatorToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Staked by operator: 3,000.00'); .should('contain', 'Staked by operator: 0.00');
cy.get(stakedByDelegatesToolTip) cy.get(stakedByDelegatesToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Staked by delegates: 0.00'); .should('contain', 'Staked by delegates: 0.00');
cy.get(totalStakedToolTip) cy.get(totalStakedToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Total stake: 3,000.00'); .should('contain', 'Total stake: 0.00');
}); });
it('Should be able to see validator normalised voting power', function () { it('Should be able to see validator normalised voting power', function () {
@@ -106,10 +106,10 @@ context('Validators Page - verify elements on page', function () {
cy.get(unnormalisedVotingPowerToolTip) cy.get(unnormalisedVotingPowerToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Unnormalised voting power: 20.00%'); .should('contain', 'Unnormalised voting power: 0.00%');
cy.get(normalisedVotingPowerToolTip) cy.get(normalisedVotingPowerToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Normalised voting power: 50.00%'); .should('contain', 'Normalised voting power: 0.10%');
}); });
// 2002-SINC-018 // 2002-SINC-018
@@ -126,13 +126,13 @@ context('Validators Page - verify elements on page', function () {
cy.get(performancePenaltyToolTip) cy.get(performancePenaltyToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Performance penalty: 0.00%'); .should('contain', 'Performance penalty: 100.00%');
cy.get(overstakedPenaltyToolTip) cy.get(overstakedPenaltyToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Overstaked penalty: 60.00%'); // value not asserted due to #2886 .should('contain', 'Overstaked penalty:'); // value not asserted due to #2886
cy.get(totalPenaltyToolTip) cy.get(totalPenaltyToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Total penalties: 60.00%'); .should('contain', 'Total penalties: 0.00%');
}); });
it('Should be able to see validator pending stake', function () { it('Should be able to see validator pending stake', function () {
-3
View File
@@ -8,7 +8,6 @@ import './wallet-eth.functions.ts';
import './wallet-teardown.functions.ts'; import './wallet-teardown.functions.ts';
import './wallet-vega.functions.ts'; import './wallet-vega.functions.ts';
import './proposal.functions.ts'; import './proposal.functions.ts';
import 'cypress-mochawesome-reporter/register';
import registerCypressGrep from '@cypress/grep'; import registerCypressGrep from '@cypress/grep';
import { aliasGQLQuery } from '@vegaprotocol/cypress'; import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { chainIdQuery, statisticsQuery } from '@vegaprotocol/mock'; import { chainIdQuery, statisticsQuery } from '@vegaprotocol/mock';
@@ -34,6 +33,4 @@ before(() => {
aliasGQLQuery(req, 'ChainId', chainIdQuery()); aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery()); aliasGQLQuery(req, 'Statistics', statisticsQuery());
}); });
// Self stake validators so they are displayed
cy.validatorsSelfDelegate();
}); });
@@ -3,6 +3,7 @@ import {
StakingBridge, StakingBridge,
Token, Token,
TokenVesting, TokenVesting,
TokenFaucetable,
CollateralBridge, CollateralBridge,
} from '@vegaprotocol/smart-contracts'; } from '@vegaprotocol/smart-contracts';
import { ethers, Wallet } from 'ethers'; import { ethers, Wallet } from 'ethers';
@@ -40,7 +41,7 @@ export async function depositAsset(
decimalPlaces: number decimalPlaces: number
) { ) {
// Approve asset // Approve asset
const faucet = new Token(assetEthAddress, signer); const faucet = new TokenFaucetable(assetEthAddress, signer);
cy.wrap( cy.wrap(
faucet.approve(Erc20BridgeAddress, amount + '0'.repeat(decimalPlaces + 1)), faucet.approve(Erc20BridgeAddress, amount + '0'.repeat(decimalPlaces + 1)),
{ {
@@ -61,7 +62,7 @@ export async function depositAsset(
} }
export async function faucetAsset(assetEthAddress: string) { export async function faucetAsset(assetEthAddress: string) {
const faucet = new Token(assetEthAddress, signer); const faucet = new TokenFaucetable(assetEthAddress, signer);
await promiseWithTimeout(faucet.faucet(), 10 * 60 * 1000, 'faucet asset'); await promiseWithTimeout(faucet.faucet(), 10 * 60 * 1000, 'faucet asset');
} }
-6
View File
@@ -1,6 +0,0 @@
function ReactMarkdown({ children }) {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{children}</>;
}
export default ReactMarkdown;
+15 -57
View File
@@ -2,6 +2,7 @@ import './i18n';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import * as Sentry from '@sentry/react'; import * as Sentry from '@sentry/react';
import { Integrations } from '@sentry/tracing';
import { BrowserRouter as Router, useLocation } from 'react-router-dom'; import { BrowserRouter as Router, useLocation } from 'react-router-dom';
import { AppLoader } from './app-loader'; import { AppLoader } from './app-loader';
import { NetworkInfo } from '@vegaprotocol/network-info'; import { NetworkInfo } from '@vegaprotocol/network-info';
@@ -16,7 +17,6 @@ import { AppStateProvider } from './contexts/app-state/app-state-provider';
import { ContractsProvider } from './contexts/contracts/contracts-provider'; import { ContractsProvider } from './contexts/contracts/contracts-provider';
import { AppRouter } from './routes'; import { AppRouter } from './routes';
import type { EthereumConfig } from '@vegaprotocol/web3'; import type { EthereumConfig } from '@vegaprotocol/web3';
import { WithdrawalApprovalDialogContainer } from '@vegaprotocol/web3';
import { import {
createConnectors, createConnectors,
useEthTransactionManager, useEthTransactionManager,
@@ -44,7 +44,7 @@ import {
} from '@vegaprotocol/environment'; } from '@vegaprotocol/environment';
import { ENV } from './config'; import { ENV } from './config';
import type { InMemoryCacheConfig } from '@apollo/client'; import type { InMemoryCacheConfig } from '@apollo/client';
import { CreateWithdrawalDialog } from '@vegaprotocol/withdraws'; import { WithdrawalDialog } from '@vegaprotocol/withdraws';
import { SplashLoader } from './components/splash-loader'; import { SplashLoader } from './components/splash-loader';
import { ToastsManager } from './toasts-manager'; import { ToastsManager } from './toasts-manager';
import { import {
@@ -53,7 +53,6 @@ import {
} from './components/telemetry-dialog/telemetry-dialog'; } from './components/telemetry-dialog/telemetry-dialog';
import { useLocalStorage } from '@vegaprotocol/react-helpers'; import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { isPartyNotFoundError } from './lib/party';
const cache: InMemoryCacheConfig = { const cache: InMemoryCacheConfig = {
typePolicies: { typePolicies: {
@@ -159,8 +158,7 @@ const Web3Container = ({
<InitializeHandlers /> <InitializeHandlers />
<VegaWalletDialogs /> <VegaWalletDialogs />
<TransactionModal /> <TransactionModal />
<CreateWithdrawalDialog /> <WithdrawalDialog />
<WithdrawalApprovalDialogContainer />
<TelemetryDialog /> <TelemetryDialog />
</> </>
</BalanceManager> </BalanceManager>
@@ -186,10 +184,6 @@ const ScrollToTop = () => {
return null; return null;
}; };
const removeQueryParams = (url: string) => {
return url.split('?')[0];
};
const AppContainer = () => { const AppContainer = () => {
const { config, loading, error } = useEthereumConfig(); const { config, loading, error } = useEthereumConfig();
const { const {
@@ -207,61 +201,25 @@ const AppContainer = () => {
]); ]);
useEffect(() => { useEffect(() => {
if (ENV.dsn && telemetryOn === 'true') { if (ENV.dsn && telemetryOn) {
Sentry.init({ Sentry.init({
dsn: ENV.dsn, dsn: ENV.dsn,
integrations: [new Integrations.BrowserTracing()],
tracesSampleRate: 0.1, tracesSampleRate: 0.1,
enabled: true, enabled: true,
environment: VEGA_ENV, environment: VEGA_ENV,
release: GIT_COMMIT_HASH, release: GIT_COMMIT_HASH,
beforeSend(event, hint) { beforeSend(event) {
const error = hint?.originalException; if (event.request?.url?.includes('/claim?')) {
const errorIsString = typeof error === 'string'; return {
const errorIsObject = error instanceof Error; ...event,
const requestUrl = event.request?.url; request: {
const transaction = event.transaction; ...event.request,
url: event.request?.url.split('?')[0],
if ( },
(errorIsString && isPartyNotFoundError({ message: error })) || };
(errorIsObject && isPartyNotFoundError(error))
) {
// This error is caused by a pubkey making an API request before
// it has interacted with the chain. This isn't needed in Sentry.
return null;
} }
return event;
const updatedRequest =
requestUrl && requestUrl.includes('/claim?')
? { ...event.request, url: removeQueryParams(requestUrl) }
: event.request;
const updatedTransaction =
transaction && transaction.includes('/claim?')
? removeQueryParams(transaction)
: transaction;
const updatedBreadcrumbs = event.breadcrumbs?.map((breadcrumb) => {
if (
breadcrumb.type === 'navigation' &&
breadcrumb.data?.to?.includes('/claim?')
) {
return {
...breadcrumb,
data: {
...breadcrumb.data,
to: removeQueryParams(breadcrumb.data.to),
},
};
}
return breadcrumb;
});
return {
...event,
request: updatedRequest,
transaction: updatedTransaction,
breadcrumbs: updatedBreadcrumbs ?? event.breadcrumbs,
};
}, },
}); });
Sentry.setTag('branch', GIT_BRANCH); Sentry.setTag('branch', GIT_BRANCH);
@@ -1,14 +1,23 @@
import { Button } from '@vegaprotocol/ui-toolkit'; import { Button } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useWeb3ConnectStore } from '@vegaprotocol/web3';
import {
AppStateActionType,
useAppState,
} from '../../contexts/app-state/app-state-context';
export const EthConnectPrompt = () => { export const EthConnectPrompt = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { open } = useWeb3ConnectStore(); const { appDispatch } = useAppState();
return ( return (
<Button <Button
variant="default" variant="default"
onClick={open} onClick={() =>
appDispatch({
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
isOpen: true,
})
}
fill={true} fill={true}
data-testid="connect-to-eth-btn" data-testid="connect-to-eth-btn"
> >
@@ -2,7 +2,10 @@ import { Button } from '@vegaprotocol/ui-toolkit';
import { useWeb3React } from '@web3-react/core'; import { useWeb3React } from '@web3-react/core';
import type { ReactElement } from 'react'; import type { ReactElement } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useWeb3ConnectStore } from '@vegaprotocol/web3'; import {
AppStateActionType,
useAppState,
} from '../../contexts/app-state/app-state-context';
export const EthWalletContainer = ({ export const EthWalletContainer = ({
children, children,
@@ -11,12 +14,21 @@ export const EthWalletContainer = ({
}) => { }) => {
const { account } = useWeb3React(); const { account } = useWeb3React();
const { t } = useTranslation(); const { t } = useTranslation();
const { open } = useWeb3ConnectStore(); const { appDispatch } = useAppState();
if (!account) { if (!account) {
return ( return (
<div className="w-full text-center"> <div className="w-full text-center">
<Button onClick={open}>{t('connectEthWallet')}</Button> <Button
onClick={() =>
appDispatch({
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
isOpen: true,
})
}
>
{t('connectEthWallet')}
</Button>
</div> </div>
); );
} }
@@ -27,11 +27,7 @@ import {
import { Loader } from '@vegaprotocol/ui-toolkit'; import { Loader } from '@vegaprotocol/ui-toolkit';
import colors from 'tailwindcss/colors'; import colors from 'tailwindcss/colors';
import { useBalances } from '../../lib/balances/balances-store'; import { useBalances } from '../../lib/balances/balances-store';
import { import { useEthereumConfig, useWeb3Disconnect } from '@vegaprotocol/web3';
useEthereumConfig,
useWeb3ConnectStore,
useWeb3Disconnect,
} from '@vegaprotocol/web3';
import { getChainName } from '@vegaprotocol/web3'; import { getChainName } from '@vegaprotocol/web3';
const removeLeadingAddressSymbol = (key: string) => { const removeLeadingAddressSymbol = (key: string) => {
@@ -193,7 +189,6 @@ export const EthWallet = () => {
const pendingTxs = usePendingTransactions(); const pendingTxs = usePendingTransactions();
const disconnect = useWeb3Disconnect(connector); const disconnect = useWeb3Disconnect(connector);
const { config } = useEthereumConfig(); const { config } = useEthereumConfig();
const { open } = useWeb3ConnectStore();
return ( return (
<WalletCard> <WalletCard>
@@ -238,7 +233,12 @@ export const EthWallet = () => {
) : ( ) : (
<Button <Button
fill={true} fill={true}
onClick={open} onClick={() =>
appDispatch({
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
isOpen: true,
})
}
data-testid="connect-to-eth-wallet-button" data-testid="connect-to-eth-wallet-button"
> >
{t('connectEthWalletToAssociate')} {t('connectEthWalletToAssociate')}
@@ -1,6 +1,6 @@
import { Link } from '@vegaprotocol/ui-toolkit'; import { Link } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ExternalLinks } from '@vegaprotocol/environment'; import { ExternalLinks } from '@vegaprotocol/utils';
export const DownloadWalletPrompt = () => { export const DownloadWalletPrompt = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -26,7 +26,6 @@ import type {
WalletDelegationFieldsFragment, WalletDelegationFieldsFragment,
} from './__generated__/Delegations'; } from './__generated__/Delegations';
import { DelegationsDocument } from './__generated__/Delegations'; import { DelegationsDocument } from './__generated__/Delegations';
import { isPartyNotFoundError } from '../../lib/party';
export const usePollForDelegations = () => { export const usePollForDelegations = () => {
const { token: vegaToken } = useContracts(); const { token: vegaToken } = useContracts();
@@ -76,7 +75,6 @@ export const usePollForDelegations = () => {
}) })
.then((res) => { .then((res) => {
if (!mounted) return; if (!mounted) return;
const canonisedDelegations = removePaginationWrapper( const canonisedDelegations = removePaginationWrapper(
res.data.party?.delegationsConnection?.edges res.data.party?.delegationsConnection?.edges
); );
@@ -205,15 +203,6 @@ export const usePollForDelegations = () => {
setDelegatedNodes(delegatedAmounts); setDelegatedNodes(delegatedAmounts);
}) })
.catch((err: Error) => { .catch((err: Error) => {
// if party isn't found, dont log to Sentry, just clear state as, user
// will not have any delagations or accounts
if (isPartyNotFoundError(err)) {
setDelegations([]);
setAccounts([]);
setDelegatedNodes([]);
setCurrentStakeAvailable(new BigNumber(0));
return;
}
Sentry.captureException(err); Sentry.captureException(err);
// If query fails stop interval. Its almost certain that the query // If query fails stop interval. Its almost certain that the query
// will just continue to fail // will just continue to fail
@@ -25,26 +25,20 @@ export function Web3Connector({
connectors, connectors,
chainId, chainId,
}: Web3ConnectorProps) { }: Web3ConnectorProps) {
const { open, close, isOpen } = useWeb3ConnectStore(); const { appState, appDispatch } = useAppState();
const setDialogOpen = useCallback( const setDialogOpen = useCallback(
(isOpen: boolean) => { (isOpen: boolean) => {
if (isOpen) { appDispatch({ type: AppStateActionType.SET_ETH_WALLET_OVERLAY, isOpen });
open();
} else {
close();
}
}, },
[open, close] [appDispatch]
); );
const appChainId = Number(chainId); const appChainId = Number(chainId);
return ( return (
<> <>
<Web3Content appChainId={appChainId}>{children}</Web3Content> <Web3Content appChainId={appChainId}>{children}</Web3Content>
<Web3ConnectDialog <Web3ConnectDialog
connectors={connectors} connectors={connectors}
dialogOpen={isOpen} dialogOpen={appState.ethConnectOverlay}
setDialogOpen={setDialogOpen} setDialogOpen={setDialogOpen}
desiredChainId={appChainId} desiredChainId={appChainId}
/> />
@@ -34,6 +34,9 @@ export interface AppState {
/** Whether or not the manage VEGA wallet overlay is open */ /** Whether or not the manage VEGA wallet overlay is open */
vegaWalletManageOverlay: boolean; vegaWalletManageOverlay: boolean;
/** Whether or not the connect to Ethereum wallet overlay is open */
ethConnectOverlay: boolean;
/** Whether or not the transaction modal is open */ /** Whether or not the transaction modal is open */
transactionOverlay: boolean; transactionOverlay: boolean;
/** /**
@@ -54,6 +57,7 @@ export enum AppStateActionType {
REFRESH_BALANCES, REFRESH_BALANCES,
SET_VEGA_WALLET_OVERLAY, SET_VEGA_WALLET_OVERLAY,
SET_VEGA_WALLET_MANAGE_OVERLAY, SET_VEGA_WALLET_MANAGE_OVERLAY,
SET_ETH_WALLET_OVERLAY,
SET_DRAWER, SET_DRAWER,
REFRESH_ASSOCIATED_BALANCES, REFRESH_ASSOCIATED_BALANCES,
SET_ASSOCIATION_BREAKDOWN, SET_ASSOCIATION_BREAKDOWN,
@@ -77,6 +81,10 @@ export type AppStateAction =
type: AppStateActionType.SET_VEGA_WALLET_MANAGE_OVERLAY; type: AppStateActionType.SET_VEGA_WALLET_MANAGE_OVERLAY;
isOpen: boolean; isOpen: boolean;
} }
| {
type: AppStateActionType.SET_ETH_WALLET_OVERLAY;
isOpen: boolean;
}
| { | {
type: AppStateActionType.SET_DRAWER; type: AppStateActionType.SET_DRAWER;
isOpen: boolean; isOpen: boolean;
@@ -16,6 +16,7 @@ const initialAppState: AppState = {
totalSupply: new BigNumber(0), totalSupply: new BigNumber(0),
vegaWalletOverlay: false, vegaWalletOverlay: false,
vegaWalletManageOverlay: false, vegaWalletManageOverlay: false,
ethConnectOverlay: false,
transactionOverlay: false, transactionOverlay: false,
bannerMessage: '', bannerMessage: '',
disconnectNotice: false, disconnectNotice: false,
@@ -44,6 +45,12 @@ function appStateReducer(state: AppState, action: AppStateAction): AppState {
vegaWalletOverlay: action.isOpen ? false : state.vegaWalletOverlay, vegaWalletOverlay: action.isOpen ? false : state.vegaWalletOverlay,
}; };
} }
case AppStateActionType.SET_ETH_WALLET_OVERLAY: {
return {
...state,
ethConnectOverlay: action.isOpen,
};
}
case AppStateActionType.SET_DRAWER: { case AppStateActionType.SET_DRAWER: {
return { return {
...state, ...state,
@@ -202,7 +202,6 @@
"tokenVotes": "Token votes", "tokenVotes": "Token votes",
"liquidityVotes": "Liquidity votes", "liquidityVotes": "Liquidity votes",
"castYourVote": "Cast your vote", "castYourVote": "Cast your vote",
"yourVote": "Your vote",
"for": "For", "for": "For",
"against": "Against", "against": "Against",
"majorityRequired": "Majority Required", "majorityRequired": "Majority Required",
@@ -594,9 +593,7 @@
"numberOfAgainstVotes": "Number of votes against", "numberOfAgainstVotes": "Number of votes against",
"yesPercentage": "Yes percentage", "yesPercentage": "Yes percentage",
"noPercentage": "No percentage", "noPercentage": "No percentage",
"proposalJson": "Full proposal JSON", "proposalTerms": "Proposal terms",
"proposalDetails": "Proposal details",
"proposalDescription": "Description",
"currentlySetTo": "Currently expected to ", "currentlySetTo": "Currently expected to ",
"currently": "currently", "currently": "currently",
"finalOutcomeMayDiffer": "Final outcome may differ", "finalOutcomeMayDiffer": "Final outcome may differ",
@@ -790,9 +787,9 @@
"homeVegaTokenButtonText": "Manage tokens", "homeVegaTokenButtonText": "Manage tokens",
"downloadProposalJson": "Download proposal as JSON", "downloadProposalJson": "Download proposal as JSON",
"networkUpgrade": "Network Upgrade", "networkUpgrade": "Network Upgrade",
"PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED": "Approved by validators", "PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED": "Approved",
"PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING": "Waiting for validator votes", "PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING": "Pending",
"PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED": "Declined by validators", "PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED": "Rejected",
"PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED": "Unspecified", "PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED": "Unspecified",
"vegaRelease{release}": "Vega Release {{release}}", "vegaRelease{release}": "Vega Release {{release}}",
"upgradeBlockHeight": "Upgrade block height", "upgradeBlockHeight": "Upgrade block height",
-8
View File
@@ -1,8 +0,0 @@
export const PARTY_NOT_FOUND = 'failed to get party for ID';
export const isPartyNotFoundError = (error: { message: string }) => {
if (error.message.includes(PARTY_NOT_FOUND)) {
return true;
}
return false;
};
@@ -50,22 +50,7 @@ export const useTranches = create<TranchesStore>()((set) => ({
?.map((t) => { ?.map((t) => {
const tranche_progress = const tranche_progress =
t.duration !== 0 ? (now - t.cliff_start) / t.duration : 0; t.duration !== 0 ? (now - t.cliff_start) / t.duration : 0;
let lockedDecimal; const lockedDecimal = tranche_progress < 0 ? 1 : 1 - tranche_progress;
if (t.duration !== 0) {
if (tranche_progress < 0) {
lockedDecimal = 1;
} else {
lockedDecimal = 1 - tranche_progress;
}
} else {
if (now < t.cliff_start) {
lockedDecimal = 1;
} else {
lockedDecimal = 0;
}
}
const clampedLockedDecimal = Math.max(0, Math.min(1, lockedDecimal));
return { return {
tranche_id: t.tranche_id, tranche_id: t.tranche_id,
tranche_start: secondsToDate(t.cliff_start), tranche_start: secondsToDate(t.cliff_start),
@@ -75,7 +60,7 @@ export const useTranches = create<TranchesStore>()((set) => ({
toBigNum(t.current_balance, decimals) toBigNum(t.current_balance, decimals)
), ),
locked_amount: toBigNum(t.initial_balance, decimals).times( locked_amount: toBigNum(t.initial_balance, decimals).times(
clampedLockedDecimal lockedDecimal
), ),
users: t.users, users: t.users,
}; };
+1 -2
View File
@@ -12,8 +12,7 @@ import { useRefreshAfterEpoch } from '../../hooks/use-refresh-after-epoch';
import { ProposalsListItem } from '../proposals/components/proposals-list-item'; import { ProposalsListItem } from '../proposals/components/proposals-list-item';
import { ProtocolUpgradeProposalsListItem } from '../proposals/components/protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item'; import { ProtocolUpgradeProposalsListItem } from '../proposals/components/protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item';
import Routes from '../routes'; import Routes from '../routes';
import { ExternalLinks } from '@vegaprotocol/environment'; import { ExternalLinks, removePaginationWrapper } from '@vegaprotocol/utils';
import { 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 { useProposalsQuery } from '../proposals/proposals/__generated__/Proposals';
import { import {
@@ -53,6 +53,7 @@ const mockAppState: AppState = {
totalSupply: new BigNumber(65000000), totalSupply: new BigNumber(65000000),
vegaWalletOverlay: false, vegaWalletOverlay: false,
vegaWalletManageOverlay: false, vegaWalletManageOverlay: false,
ethConnectOverlay: false,
transactionOverlay: false, transactionOverlay: false,
bannerMessage: '', bannerMessage: '',
disconnectNotice: false, disconnectNotice: false,
@@ -1 +0,0 @@
export { ProposalDescription } from './proposal-description';
@@ -1,51 +0,0 @@
import ReactMarkdown from 'react-markdown';
import classnames from 'classnames';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Icon, RoundedWrapper } from '@vegaprotocol/ui-toolkit';
import { SubHeading } from '../../../../components/heading';
export const ProposalDescription = ({
description,
}: {
description: string;
}) => {
const { t } = useTranslation();
const [showDescription, setShowDescription] = useState(false);
const showDescriptionIconClasses = classnames('mb-4', {
'rotate-180': showDescription,
});
return (
<section data-testid="proposal-description">
<button
onClick={() => setShowDescription(!showDescription)}
data-testid="proposal-description-toggle"
>
<div className="flex items-center gap-3">
<SubHeading title={t('proposalDescription')} />
<div className={showDescriptionIconClasses}>
<Icon name="chevron-down" size={8} />
</div>
</div>
</button>
{showDescription && (
<RoundedWrapper paddingBottom={true} marginBottomLarge={true}>
<div className="p-2">
<ReactMarkdown
className="react-markdown-container"
/* Prevents HTML embedded in the description from rendering */
skipHtml={true}
/* Stops users embedding images which could be used for tracking */
disallowedElements={['img']}
linkTarget="_blank"
>
{description}
</ReactMarkdown>
</div>
</RoundedWrapper>
)}
</section>
);
};
@@ -206,6 +206,28 @@ describe('Proposal header', () => {
).not.toBeInTheDocument(); ).not.toBeInTheDocument();
}); });
it('Renders Freeform proposal - long rationale (105 chars) - details', () => {
renderComponent(
generateProposal({
id: 'long',
rationale: {
title: '0x0',
description:
'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean dolor.',
},
terms: {
change: {
__typename: 'NewFreeform',
},
},
}),
false
);
expect(screen.getByTestId('proposal-description')).toHaveTextContent(
/Class aptent/
);
});
// Remove once proposals have rationale and re-enable above tests // Remove once proposals have rationale and re-enable above tests
it('Renders Freeform proposal - id for title', () => { it('Renders Freeform proposal - id for title', () => {
renderComponent( renderComponent(
@@ -5,6 +5,7 @@ import { Heading, SubHeading } from '../../../../components/heading';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals'; import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal'; import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import ReactMarkdown from 'react-markdown';
import { truncateMiddle } from '../../../../lib/truncate-middle'; import { truncateMiddle } from '../../../../lib/truncate-middle';
import { CurrentProposalState } from '../current-proposal-state'; import { CurrentProposalState } from '../current-proposal-state';
import { ProposalInfoLabel } from '../proposal-info-label'; import { ProposalInfoLabel } from '../proposal-info-label';
@@ -22,7 +23,12 @@ export const ProposalHeader = ({
let details: ReactNode; let details: ReactNode;
let proposalType = ''; let proposalType = '';
const title = proposal?.rationale.title.trim(); let title = proposal?.rationale.title.trim();
let description = proposal?.rationale.description.trim();
if (title?.length === 0 && description && description.length > 0) {
title = description;
description = '';
}
const titleContent = shorten(title ?? '', 100); const titleContent = shorten(title ?? '', 100);
@@ -139,6 +145,23 @@ export const ProposalHeader = ({
{details} {details}
</div> </div>
)} )}
{description && !isListItem && (
<div data-testid="proposal-description">
{/*<div className="uppercase mr-2">{t('ProposalDescription')}:</div>*/}
<SubHeading title={t('ProposalDescription')} />
<ReactMarkdown
className="react-markdown-container"
/* Prevents HTML embedded in the description from rendering */
skipHtml={true}
/* Stops users embedding images which could be used for tracking */
disallowedElements={['img']}
linkTarget="_blank"
>
{description}
</ReactMarkdown>
</div>
)}
</> </>
); );
}; };
@@ -1 +0,0 @@
export { ProposalJson } from './proposal-json';
@@ -0,0 +1 @@
export { ProposalTermsJson } from './proposal-terms-json';
@@ -1,15 +1,15 @@
import classnames from 'classnames';
import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Icon, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit'; import { Icon, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { SubHeading } from '../../../../components/heading'; import { SubHeading } from '../../../../components/heading';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals'; import type { PartialDeep } from 'type-fest';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal'; import type * as Schema from '@vegaprotocol/types';
import { useState } from 'react';
import classnames from 'classnames';
export const ProposalJson = ({ export const ProposalTermsJson = ({
proposal, terms,
}: { }: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal']; terms: PartialDeep<Schema.ProposalTerms>;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [showDetails, setShowDetails] = useState(false); const [showDetails, setShowDetails] = useState(false);
@@ -18,20 +18,20 @@ export const ProposalJson = ({
}); });
return ( return (
<section data-testid="proposal-json"> <section>
<button <button
onClick={() => setShowDetails(!showDetails)} onClick={() => setShowDetails(!showDetails)}
data-testid="proposal-json-toggle" data-testid="proposal-terms-toggle"
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<SubHeading title={t('proposalJson')} /> <SubHeading title={t('proposalTerms')} />
<div className={showDetailsIconClasses}> <div className={showDetailsIconClasses}>
<Icon name="chevron-down" size={8} /> <Icon name="chevron-down" size={8} />
</div> </div>
</div> </div>
</button> </button>
{showDetails && <SyntaxHighlighter data={proposal} />} {showDetails && <SyntaxHighlighter data={terms} />}
</section> </section>
); );
}; };
@@ -1 +0,0 @@
export { ProposalTerms } from './proposal-terms';
@@ -1,142 +0,0 @@
import {
RoundedWrapper,
KeyValueTable,
KeyValueTableRow,
Icon,
} from '@vegaprotocol/ui-toolkit';
import { BigNumber } from '../../../../lib/bignumber';
import { SubHeading } from '../../../../components/heading';
import { useTranslation } from 'react-i18next';
import { useState } from 'react';
import classnames from 'classnames';
interface ProposalTermsProps {
data: Record<string, unknown>;
}
const getParsedValue = (value: unknown) => {
if (typeof value === 'string') {
try {
// Check if value is a number string - if so use bignumber to maintain precision
if (/^\d+(\.\d+)?$/.test(value)) {
return new BigNumber(value).toString();
} else {
// This would convert, for example, a JSON object ('{"key":"value"}')
// into an actual JS object ({key: "value"})
return JSON.parse(value);
}
} catch (error) {
return value;
}
} else {
return value;
}
};
const RenderKeyValue = ({
title,
value,
}: {
title: string;
value: string | number;
}) => (
<KeyValueTable>
<KeyValueTableRow>
{title}
{value}
</KeyValueTableRow>
</KeyValueTable>
);
const RenderArray = ({ title, array }: { title: string; array: unknown[] }) => {
if (array.every((item) => typeof item === 'string')) {
return <RenderKeyValue title={title} value={array.join(', ')} />;
} else {
return (
<div>
<div className="mb-2">{title}</div>
{array.map((item, index) => (
<div key={index}>
<ProposalTermsRenderer data={item as Record<string, unknown>} />
</div>
))}
</div>
);
}
};
// Working with 'unknown' type as a proposal's terms can be in many shapes
const RenderTerm = ({ title, value }: { title: string; value: unknown }) => {
const parsedValue = getParsedValue(value);
if (parsedValue === null || typeof parsedValue === 'boolean') {
return <RenderKeyValue title={title} value={String(parsedValue)} />;
} else if (
typeof parsedValue === 'string' ||
typeof parsedValue === 'number'
) {
return <RenderKeyValue title={title} value={parsedValue} />;
} else if (typeof parsedValue === 'object') {
if (Array.isArray(parsedValue)) {
return <RenderArray title={title} array={parsedValue} />;
} else {
return (
<div>
<div className="my-2">{title}</div>
<ProposalTermsRenderer
data={parsedValue as Record<string, unknown>}
/>
</div>
);
}
} else if (parsedValue === undefined) {
return <span>{'undefined'}</span>;
} else {
return <span>{String(parsedValue)}</span>;
}
};
const ProposalTermsRenderer = ({ data }: ProposalTermsProps) => {
return (
<RoundedWrapper paddingBottom={true}>
{Object.keys(data)
.filter(
(key) =>
!['__typename', 'closingDatetime', 'enactmentDatetime'].includes(
key
)
)
.map((key, index) => (
<div key={index}>
<RenderTerm title={key} value={data[key]} />
</div>
))}
</RoundedWrapper>
);
};
export const ProposalTerms = ({ data }: ProposalTermsProps) => {
const { t } = useTranslation();
const [showTerms, setShowTerms] = useState(false);
const showTermsIconClasses = classnames('mb-4', {
'rotate-180': showTerms,
});
return (
<section data-testid="proposal-terms">
<button
onClick={() => setShowTerms(!showTerms)}
data-testid="proposal-terms-toggle"
>
<div className="flex items-center gap-3">
<SubHeading title={t('proposalDetails')} />
<div className={showTermsIconClasses}>
<Icon name="chevron-down" size={8} />
</div>
</div>
</button>
{showTerms && <ProposalTermsRenderer data={data} />}
</section>
);
};
@@ -25,11 +25,8 @@ jest.mock('../proposal-detail-header/proposal-header', () => ({
jest.mock('../proposal-change-table', () => ({ jest.mock('../proposal-change-table', () => ({
ProposalChangeTable: () => <div data-testid="proposal-change-table"></div>, ProposalChangeTable: () => <div data-testid="proposal-change-table"></div>,
})); }));
jest.mock('../proposal-json', () => ({ jest.mock('../proposal-terms-json', () => ({
ProposalJson: () => <div data-testid="proposal-json"></div>, ProposalTermsJson: () => <div data-testid="proposal-terms-json"></div>,
}));
jest.mock('../proposal-terms/proposal-terms', () => ({
ProposalTerms: () => <div data-testid="proposal-terms"></div>,
})); }));
jest.mock('../proposal-votes-table', () => ({ jest.mock('../proposal-votes-table', () => ({
ProposalVotesTable: () => <div data-testid="proposal-votes-table"></div>, ProposalVotesTable: () => <div data-testid="proposal-votes-table"></div>,
@@ -52,8 +49,7 @@ it('renders each section', async () => {
render(<Proposal proposal={proposal as ProposalQuery['proposal']} />); render(<Proposal proposal={proposal as ProposalQuery['proposal']} />);
expect(await screen.findByTestId('proposal-header')).toBeInTheDocument(); expect(await screen.findByTestId('proposal-header')).toBeInTheDocument();
expect(screen.getByTestId('proposal-change-table')).toBeInTheDocument(); expect(screen.getByTestId('proposal-change-table')).toBeInTheDocument();
expect(screen.getByTestId('proposal-json')).toBeInTheDocument(); expect(screen.getByTestId('proposal-terms-json')).toBeInTheDocument();
expect(screen.getByTestId('proposal-terms')).toBeInTheDocument();
expect(screen.getByTestId('proposal-votes-table')).toBeInTheDocument(); expect(screen.getByTestId('proposal-votes-table')).toBeInTheDocument();
expect(screen.getByTestId('proposal-vote-details')).toBeInTheDocument(); expect(screen.getByTestId('proposal-vote-details')).toBeInTheDocument();
expect(screen.queryByTestId('proposal-list-asset')).not.toBeInTheDocument(); expect(screen.queryByTestId('proposal-list-asset')).not.toBeInTheDocument();
@@ -6,10 +6,8 @@ import { AsyncRenderer, RoundedWrapper } from '@vegaprotocol/ui-toolkit';
import { ProposalHeader } from '../proposal-detail-header/proposal-header'; import { ProposalHeader } from '../proposal-detail-header/proposal-header';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals'; import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal'; import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { ProposalDescription } from '../proposal-description';
import { ProposalChangeTable } from '../proposal-change-table'; import { ProposalChangeTable } from '../proposal-change-table';
import { ProposalJson } from '../proposal-json'; import { ProposalTermsJson } from '../proposal-terms-json';
import { ProposalTerms } from '../proposal-terms';
import { ProposalVotesTable } from '../proposal-votes-table'; import { ProposalVotesTable } from '../proposal-votes-table';
import { VoteDetails } from '../vote-details'; import { VoteDetails } from '../vote-details';
import { ListAsset } from '../list-asset'; import { ListAsset } from '../list-asset';
@@ -22,7 +20,7 @@ export enum ProposalType {
PROPOSAL_NETWORK_PARAMETER = 'PROPOSAL_NETWORK_PARAMETER', PROPOSAL_NETWORK_PARAMETER = 'PROPOSAL_NETWORK_PARAMETER',
PROPOSAL_FREEFORM = 'PROPOSAL_FREEFORM', PROPOSAL_FREEFORM = 'PROPOSAL_FREEFORM',
} }
export interface ProposalProps { interface ProposalProps {
proposal: ProposalFieldsFragment | ProposalQuery['proposal']; proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
} }
@@ -80,11 +78,9 @@ export const Proposal = ({ proposal }: ProposalProps) => {
<AsyncRenderer data={params} loading={loading} error={error}> <AsyncRenderer data={params} loading={loading} error={error}>
<section data-testid="proposal"> <section data-testid="proposal">
<ProposalHeader proposal={proposal} isListItem={false} /> <ProposalHeader proposal={proposal} isListItem={false} />
<div className="my-10"> <div className="my-10">
<ProposalChangeTable proposal={proposal} /> <ProposalChangeTable proposal={proposal} />
</div> </div>
{proposal.terms.change.__typename === 'NewAsset' && {proposal.terms.change.__typename === 'NewAsset' &&
proposal.terms.change.source.__typename === 'ERC20' && proposal.terms.change.source.__typename === 'ERC20' &&
proposal.id ? ( proposal.id ? (
@@ -94,20 +90,7 @@ export const Proposal = ({ proposal }: ProposalProps) => {
lifetimeLimit={proposal.terms.change.source.lifetimeLimit} lifetimeLimit={proposal.terms.change.source.lifetimeLimit}
/> />
) : null} ) : null}
<div className="mb-12">
<div className="mb-4">
<ProposalDescription description={proposal.rationale.description} />
</div>
<div className="mb-4">
<ProposalTerms data={proposal.terms} />
</div>
<div className="mb-6">
<ProposalJson proposal={proposal} />
</div>
<div className="mb-10">
<RoundedWrapper paddingBottom={true}> <RoundedWrapper paddingBottom={true}>
<VoteDetails <VoteDetails
proposal={proposal} proposal={proposal}
@@ -119,10 +102,10 @@ export const Proposal = ({ proposal }: ProposalProps) => {
/> />
</RoundedWrapper> </RoundedWrapper>
</div> </div>
<div className="mb-4"> <div className="mb-4">
<ProposalVotesTable proposal={proposal} proposalType={proposalType} /> <ProposalVotesTable proposal={proposal} proposalType={proposalType} />
</div> </div>
<ProposalTermsJson terms={proposal.terms} />
</section> </section>
</AsyncRenderer> </AsyncRenderer>
); );
@@ -24,6 +24,7 @@ const openProposalClosesNextMonth = generateProposal({
}, },
terms: { terms: {
closingDatetime: nextMonth.toString(), closingDatetime: nextMonth.toString(),
enactmentDatetime: nextMonth.toString(),
}, },
}); });
@@ -35,6 +36,7 @@ const openProposalClosesNextWeek = generateProposal({
}, },
terms: { terms: {
closingDatetime: nextWeek.toString(), closingDatetime: nextWeek.toString(),
enactmentDatetime: nextWeek.toString(),
}, },
}); });
@@ -43,6 +45,7 @@ const enactedProposalClosedLastWeek = generateProposal({
state: ProposalState.STATE_ENACTED, state: ProposalState.STATE_ENACTED,
terms: { terms: {
closingDatetime: lastWeek.toString(), closingDatetime: lastWeek.toString(),
enactmentDatetime: lastWeek.toString(),
}, },
}); });
@@ -51,6 +54,7 @@ const failedProposalClosedLastMonth = generateProposal({
state: ProposalState.STATE_FAILED, state: ProposalState.STATE_FAILED,
terms: { terms: {
closingDatetime: lastMonth.toString(), closingDatetime: lastMonth.toString(),
enactmentDatetime: lastMonth.toString(),
}, },
}); });
@@ -8,11 +8,12 @@ import { ProposalsListFilter } from '../proposals-list-filter';
import Routes from '../../../routes'; import Routes from '../../../routes';
import { Button, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit'; import { Button, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { createDocsLinks, ExternalLinks } from '@vegaprotocol/utils';
import { ExternalLink } from '@vegaprotocol/ui-toolkit'; import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal'; import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals'; import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals'; import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { DocsLinks, ExternalLinks } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
interface ProposalsListProps { interface ProposalsListProps {
proposals: Array<ProposalFieldsFragment | ProposalQuery['proposal']>; proposals: Array<ProposalFieldsFragment | ProposalQuery['proposal']>;
@@ -35,6 +36,7 @@ export const ProposalsList = ({
protocolUpgradeProposals, protocolUpgradeProposals,
lastBlockHeight, lastBlockHeight,
}: ProposalsListProps) => { }: ProposalsListProps) => {
const { VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const [filterString, setFilterString] = useState(''); const [filterString, setFilterString] = useState('');
const sortedProposals = proposals.reduce( const sortedProposals = proposals.reduce(
@@ -81,9 +83,9 @@ export const ProposalsList = ({
marginBottom={false} marginBottom={false}
title={t('pageTitleProposals')} title={t('pageTitleProposals')}
/> />
{DocsLinks && ( {VEGA_DOCS_URL && (
<div className="xs:justify-self-end" data-testid="new-proposal-link"> <div className="xs:justify-self-end" data-testid="new-proposal-link">
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE}> <ExternalLink href={createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}>
<Button variant="primary" size="sm"> <Button variant="primary" size="sm">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
{t('NewProposal')} {t('NewProposal')}
@@ -2,10 +2,9 @@ import { render, screen } from '@testing-library/react';
import { ProposalFormTerms } from './proposal-form-terms'; import { ProposalFormTerms } from './proposal-form-terms';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'), useEnvironment: () => ({
DocsLinks: { VEGA_DOCS_URL: 'https://docs.vega.xyz',
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals', }),
},
})); }));
const renderComponent = () => { const renderComponent = () => {
@@ -5,7 +5,8 @@ import {
ExternalLink, ExternalLink,
TextArea, TextArea,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { DocsLinks } from '@vegaprotocol/environment'; import { createDocsLinks } from '@vegaprotocol/utils';
import { useEnvironment } from '@vegaprotocol/environment';
import type { UseFormRegisterReturn } from 'react-hook-form'; import type { UseFormRegisterReturn } from 'react-hook-form';
interface ProposalFormTermsProps { interface ProposalFormTermsProps {
@@ -21,20 +22,23 @@ export const ProposalFormTerms = ({
labelOverride, labelOverride,
docsLink, docsLink,
}: ProposalFormTermsProps) => { }: ProposalFormTermsProps) => {
const { VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<FormGroup <FormGroup
label={labelOverride || t('ProposalTerms')} label={labelOverride || t('ProposalTerms')}
labelFor="proposal-terms" labelFor="proposal-terms"
> >
{docsLink && DocsLinks && ( {docsLink && VEGA_DOCS_URL && (
<div className="mt-[-4px] mb-2 text-sm font-light"> <div className="mt-[-4px] mb-2 text-sm font-light">
<span className="mr-1">{t('ProposalTermsText')}</span> <span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink <ExternalLink
href={`${DocsLinks.PROPOSALS_GUIDE}${docsLink}`} href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${docsLink}`}
target="_blank" target="_blank"
> >
{`${DocsLinks.PROPOSALS_GUIDE}${docsLink}`} {`${createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}${docsLink}`}
</ExternalLink> </ExternalLink>
</div> </div>
)} )}
@@ -32,9 +32,7 @@ describe('ProtocolUpgradeProposalDetailInfo', () => {
it('should render the state', () => { it('should render the state', () => {
const { getByTestId } = renderComponent(); const { getByTestId } = renderComponent();
expect(getByTestId('protocol-upgrade-state')).toHaveTextContent( expect(getByTestId('protocol-upgrade-state')).toHaveTextContent('Pending');
'Waiting for validator votes'
);
}); });
it('should render the vega release tag', () => { it('should render the vega release tag', () => {
@@ -26,34 +26,28 @@ describe('ProtocolUpgradeProposalsListItem', () => {
status: status:
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED, ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED,
icon: 'protocol-upgrade-proposal-status-icon-rejected', icon: 'protocol-upgrade-proposal-status-icon-rejected',
text: 'Declined by validators',
}, },
{ {
status: status:
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING, ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING,
icon: 'protocol-upgrade-proposal-status-icon-pending', icon: 'protocol-upgrade-proposal-status-icon-pending',
text: 'Waiting for validator votes',
}, },
{ {
status: status:
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED, ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED,
icon: 'protocol-upgrade-proposal-status-icon-approved', icon: 'protocol-upgrade-proposal-status-icon-approved',
text: 'Approved by validators',
}, },
{ {
status: status:
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED, ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED,
icon: 'protocol-upgrade-proposal-status-icon-unspecified', icon: 'protocol-upgrade-proposal-status-icon-unspecified',
text: 'Unspecified',
}, },
]; ];
statuses.forEach(({ status, icon, text }) => { statuses.forEach(({ status, icon }) => {
renderComponent({ ...proposal, status }); renderComponent({ ...proposal, status });
const statusIcon = screen.getByTestId(icon); const statusIcon = screen.getByTestId(icon);
const textContent = screen.getByText(text);
expect(statusIcon).toBeInTheDocument(); expect(statusIcon).toBeInTheDocument();
expect(textContent).toBeInTheDocument();
}); });
}); });
@@ -111,9 +111,7 @@ describe('Vote buttons', () => {
</AppStateProvider> </AppStateProvider>
); );
expect( expect(
screen.getByText( screen.getByText('You need some VEGA tokens to participate in governance')
'You need some VEGA tokens to participate in governance.'
)
).toBeTruthy(); ).toBeTruthy();
}); });
@@ -2,12 +2,7 @@ import { format } from 'date-fns';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet'; import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { import { AsyncRenderer, Button, ButtonLink } from '@vegaprotocol/ui-toolkit';
AsyncRenderer,
Button,
ButtonLink,
ExternalLink,
} from '@vegaprotocol/ui-toolkit';
import { addDecimal, toBigNum } from '@vegaprotocol/utils'; import { addDecimal, toBigNum } from '@vegaprotocol/utils';
import { ProposalState, VoteValue } from '@vegaprotocol/types'; import { ProposalState, VoteValue } from '@vegaprotocol/types';
import { import {
@@ -166,12 +161,7 @@ export const VoteButtons = ({
{changeVote || (voteState === VoteState.NotCast && proposalVotable) ? ( {changeVote || (voteState === VoteState.NotCast && proposalVotable) ? (
<> <>
{currentStakeAvailable.isLessThanOrEqualTo(0) && ( {currentStakeAvailable.isLessThanOrEqualTo(0) && (
<> <p data-testid="no-stake-available">{t('noGovernanceTokens')}</p>
<p data-testid="no-stake-available">{t('noGovernanceTokens')}.</p>
<ExternalLink href="https://blog.vega.xyz/how-to-vote-on-vega-2195d1e52ec5">
{t('findOutMoreAboutHowToVote')}
</ExternalLink>
</>
)} )}
<div className="flex gap-4" data-testid="vote-buttons"> <div className="flex gap-4" data-testid="vote-buttons">
@@ -1,6 +1,6 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from 'date-fns';
import { RoundedWrapper, Icon, ExternalLink } from '@vegaprotocol/ui-toolkit'; import { RoundedWrapper, Icon } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import { ProposalState } from '@vegaprotocol/types'; import { ProposalState } from '@vegaprotocol/types';
import { useVoteSubmit, VoteProgress } from '@vegaprotocol/proposals'; import { useVoteSubmit, VoteProgress } from '@vegaprotocol/proposals';
@@ -202,12 +202,7 @@ export const VoteDetails = ({
)} )}
<section className="mt-10"> <section className="mt-10">
{proposal?.state === ProposalState.STATE_OPEN ? ( <SubHeading title={t('castYourVote')} />
<SubHeading title={t('castYourVote')} />
) : (
<SubHeading title={t('yourVote')} />
)}
{pubKey ? ( {pubKey ? (
proposal && ( proposal && (
<VoteButtonsContainer <VoteButtonsContainer
@@ -229,9 +224,6 @@ export const VoteDetails = ({
<Icon name={'info-sign'} /> <Icon name={'info-sign'} />
<div>{t('connectAVegaWalletToVote')}</div> <div>{t('connectAVegaWalletToVote')}</div>
</div> </div>
<ExternalLink href="https://blog.vega.xyz/how-to-vote-on-vega-2195d1e52ec5">
{t('findOutMoreAboutHowToVote')}
</ExternalLink>
</div> </div>
<ConnectToVega /> <ConnectToVega />
</RoundedWrapper> </RoundedWrapper>
@@ -16,6 +16,7 @@ const mockAppState: AppState = {
totalSupply: mockTotalSupply, totalSupply: mockTotalSupply,
vegaWalletOverlay: false, vegaWalletOverlay: false,
vegaWalletManageOverlay: false, vegaWalletManageOverlay: false,
ethConnectOverlay: false,
transactionOverlay: false, transactionOverlay: false,
bannerMessage: '', bannerMessage: '',
disconnectNotice: false, disconnectNotice: false,
@@ -20,11 +20,8 @@ import { useProtocolUpgradeProposalsQuery } from '@vegaprotocol/proposals';
const orderByDate = (arr: ProposalFieldsFragment[]) => const orderByDate = (arr: ProposalFieldsFragment[]) =>
orderBy( orderBy(
arr, arr,
[ [(p) => new Date(p?.terms?.closingDatetime).getTime(), (p) => p.id],
(p) => new Date(p?.terms?.closingDatetime).getTime(), ['desc', 'desc']
(p) => new Date(p?.datetime).getTime(),
],
['asc', 'asc']
); );
const orderByUpgradeBlockHeight = ( const orderByUpgradeBlockHeight = (
@@ -10,10 +10,9 @@ import type { MockedResponse } from '@apollo/client/testing';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'), useEnvironment: () => ({
DocsLinks: { VEGA_DOCS_URL: 'https://docs.vega.xyz',
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals', }),
},
})); }));
const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = { const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
@@ -5,7 +5,7 @@ import {
useProposalSubmit, useProposalSubmit,
deadlineToRoundedHours, deadlineToRoundedHours,
} from '@vegaprotocol/proposals'; } from '@vegaprotocol/proposals';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { import {
ProposalFormDescription, ProposalFormDescription,
ProposalFormSubheader, ProposalFormSubheader,
@@ -17,6 +17,7 @@ import {
import { ProposalMinRequirements } from '../../components/shared'; import { ProposalMinRequirements } from '../../components/shared';
import { AsyncRenderer, ExternalLink } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer, ExternalLink } from '@vegaprotocol/ui-toolkit';
import { Heading } from '../../../../components/heading'; import { Heading } from '../../../../components/heading';
import { createDocsLinks } from '@vegaprotocol/utils';
import { import {
NetworkParams, NetworkParams,
useNetworkParams, useNetworkParams,
@@ -41,7 +42,7 @@ export const ProposeFreeform = () => {
NetworkParams.governance_proposal_freeform_minProposerBalance, NetworkParams.governance_proposal_freeform_minProposerBalance,
NetworkParams.spam_protection_proposal_min_tokens, NetworkParams.spam_protection_proposal_min_tokens,
]); ]);
const { VEGA_EXPLORER_URL } = useEnvironment(); const { VEGA_DOCS_URL, VEGA_EXPLORER_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
register, register,
@@ -110,13 +111,17 @@ export const ProposeFreeform = () => {
userAction={ProposalUserAction.CREATE} userAction={ProposalUserAction.CREATE}
/> />
{DocsLinks && ( {VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link"> <p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span> <span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink <ExternalLink
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`} href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
target="_blank" target="_blank"
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink> >{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p> </p>
)} )}
@@ -10,10 +10,9 @@ import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import type { MockedResponse } from '@apollo/client/testing'; import type { MockedResponse } from '@apollo/client/testing';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'), useEnvironment: () => ({
DocsLinks: { VEGA_DOCS_URL: 'https://docs.vega.xyz',
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals', }),
},
})); }));
const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = { const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
@@ -1,7 +1,10 @@
import { useState } from 'react'; import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { suitableForSyntaxHighlighter } from '@vegaprotocol/utils'; import {
createDocsLinks,
suitableForSyntaxHighlighter,
} from '@vegaprotocol/utils';
import { useNetworkParams } from '@vegaprotocol/network-parameters'; import { useNetworkParams } from '@vegaprotocol/network-parameters';
import { import {
getClosingTimestamp, getClosingTimestamp,
@@ -9,7 +12,7 @@ import {
useProposalSubmit, useProposalSubmit,
doesValueEquateToParam, doesValueEquateToParam,
} from '@vegaprotocol/proposals'; } from '@vegaprotocol/proposals';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { import {
ProposalFormDescription, ProposalFormDescription,
ProposalFormSubheader, ProposalFormSubheader,
@@ -81,7 +84,7 @@ export const ProposeNetworkParameter = () => {
loading: networkParamsLoading, loading: networkParamsLoading,
error: networkParamsError, error: networkParamsError,
} = useNetworkParams(); } = useNetworkParams();
const { VEGA_EXPLORER_URL } = useEnvironment(); const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
register, register,
@@ -172,13 +175,17 @@ export const ProposeNetworkParameter = () => {
userAction={ProposalUserAction.CREATE} userAction={ProposalUserAction.CREATE}
/> />
{DocsLinks && ( {VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link"> <p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span> <span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink <ExternalLink
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`} href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
target="_blank" target="_blank"
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink> >{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p> </p>
)} )}
@@ -10,10 +10,9 @@ import type { MockedResponse } from '@apollo/client/testing';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'), useEnvironment: () => ({
DocsLinks: { VEGA_DOCS_URL: 'https://docs.vega.xyz',
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals', }),
},
})); }));
const newAssetNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = { const newAssetNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
@@ -7,8 +7,8 @@ import {
useProposalSubmit, useProposalSubmit,
doesValueEquateToParam, doesValueEquateToParam,
} from '@vegaprotocol/proposals'; } from '@vegaprotocol/proposals';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { validateJson } from '@vegaprotocol/utils'; import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
import { import {
NetworkParams, NetworkParams,
useNetworkParams, useNetworkParams,
@@ -53,7 +53,7 @@ export const ProposeNewAsset = () => {
NetworkParams.governance_proposal_asset_minProposerBalance, NetworkParams.governance_proposal_asset_minProposerBalance,
NetworkParams.spam_protection_proposal_min_tokens, NetworkParams.spam_protection_proposal_min_tokens,
]); ]);
const { VEGA_EXPLORER_URL } = useEnvironment(); const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
register, register,
@@ -142,13 +142,17 @@ export const ProposeNewAsset = () => {
userAction={ProposalUserAction.CREATE} userAction={ProposalUserAction.CREATE}
/> />
{DocsLinks && ( {VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link"> <p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span> <span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink <ExternalLink
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`} href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
target="_blank" target="_blank"
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink> >{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p> </p>
)} )}
@@ -10,10 +10,9 @@ import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'), useEnvironment: () => ({
DocsLinks: { VEGA_DOCS_URL: 'https://docs.vega.xyz',
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals', }),
},
})); }));
const newMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = { const newMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
@@ -6,8 +6,8 @@ import {
useProposalSubmit, useProposalSubmit,
doesValueEquateToParam, doesValueEquateToParam,
} from '@vegaprotocol/proposals'; } from '@vegaprotocol/proposals';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { validateJson } from '@vegaprotocol/utils'; import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
import { import {
NetworkParams, NetworkParams,
useNetworkParams, useNetworkParams,
@@ -51,7 +51,7 @@ export const ProposeNewMarket = () => {
NetworkParams.governance_proposal_market_minProposerBalance, NetworkParams.governance_proposal_market_minProposerBalance,
NetworkParams.spam_protection_proposal_min_tokens, NetworkParams.spam_protection_proposal_min_tokens,
]); ]);
const { VEGA_EXPLORER_URL } = useEnvironment(); const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
register, register,
@@ -132,13 +132,17 @@ export const ProposeNewMarket = () => {
userAction={ProposalUserAction.CREATE} userAction={ProposalUserAction.CREATE}
/> />
{DocsLinks && ( {VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link"> <p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span> <span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink <ExternalLink
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`} href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
target="_blank" target="_blank"
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink> >{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p> </p>
)} )}
@@ -2,11 +2,12 @@ import Routes from '../../routes';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { ExternalLink } from '@vegaprotocol/ui-toolkit'; import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { Heading } from '../../../components/heading'; import { Heading } from '../../../components/heading';
import { createDocsLinks } from '@vegaprotocol/utils';
export const Propose = () => { export const Propose = () => {
const { VEGA_EXPLORER_URL } = useEnvironment(); const { VEGA_DOCS_URL, VEGA_EXPLORER_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
@@ -14,11 +15,14 @@ export const Propose = () => {
<section className="pb-6"> <section className="pb-6">
<Heading title={t('NewProposal')} /> <Heading title={t('NewProposal')} />
<div className="text-sm"> <div className="text-sm">
{DocsLinks && ( {VEGA_DOCS_URL && (
<p> <p>
<span className="mr-1">{t('ProposalTermsText')}</span> <span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE} target="_blank"> <ExternalLink
{DocsLinks.PROPOSALS_GUIDE} href={createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}
target="_blank"
>
{createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}
</ExternalLink> </ExternalLink>
</p> </p>
)} )}
@@ -1,6 +1,6 @@
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { Heading } from '../../../../components/heading'; import { Heading } from '../../../../components/heading';
import { import {
AsyncRenderer, AsyncRenderer,
@@ -9,7 +9,7 @@ import {
InputError, InputError,
TextArea, TextArea,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { validateJson } from '@vegaprotocol/utils'; import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
import { import {
NetworkParams, NetworkParams,
useNetworkParams, useNetworkParams,
@@ -39,7 +39,7 @@ export const ProposeRaw = () => {
NetworkParams.governance_proposal_freeform_minProposerBalance, NetworkParams.governance_proposal_freeform_minProposerBalance,
NetworkParams.spam_protection_proposal_min_tokens, NetworkParams.spam_protection_proposal_min_tokens,
]); ]);
const { VEGA_EXPLORER_URL } = useEnvironment(); const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
register, register,
@@ -79,11 +79,14 @@ export const ProposeRaw = () => {
spamProtectionMin={params.spam_protection_proposal_min_tokens} spamProtectionMin={params.spam_protection_proposal_min_tokens}
/> />
{DocsLinks && ( {VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link"> <p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span> <span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE} target="_blank"> <ExternalLink
{DocsLinks.PROPOSALS_GUIDE} href={createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}
target="_blank"
>
{createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}
</ExternalLink> </ExternalLink>
</p> </p>
)} )}
@@ -10,10 +10,9 @@ import type { MockedResponse } from '@apollo/client/testing';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'), useEnvironment: () => ({
DocsLinks: { VEGA_DOCS_URL: 'https://docs.vega.xyz',
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals', }),
},
})); }));
const updateAssetNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = { const updateAssetNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
@@ -6,8 +6,8 @@ import {
useProposalSubmit, useProposalSubmit,
doesValueEquateToParam, doesValueEquateToParam,
} from '@vegaprotocol/proposals'; } from '@vegaprotocol/proposals';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { validateJson } from '@vegaprotocol/utils'; import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
import { import {
NetworkParams, NetworkParams,
useNetworkParams, useNetworkParams,
@@ -51,7 +51,7 @@ export const ProposeUpdateAsset = () => {
NetworkParams.governance_proposal_updateAsset_minProposerBalance, NetworkParams.governance_proposal_updateAsset_minProposerBalance,
NetworkParams.spam_protection_proposal_min_tokens, NetworkParams.spam_protection_proposal_min_tokens,
]); ]);
const { VEGA_EXPLORER_URL } = useEnvironment(); const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
register, register,
@@ -132,13 +132,17 @@ export const ProposeUpdateAsset = () => {
userAction={ProposalUserAction.CREATE} userAction={ProposalUserAction.CREATE}
/> />
{DocsLinks && ( {VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link"> <p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span> <span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink <ExternalLink
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`} href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
target="_blank" target="_blank"
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink> >{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p> </p>
)} )}
@@ -7,8 +7,8 @@ import {
getEnactmentTimestamp, getEnactmentTimestamp,
useProposalSubmit, useProposalSubmit,
} from '@vegaprotocol/proposals'; } from '@vegaprotocol/proposals';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { validateJson } from '@vegaprotocol/utils'; import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
import { import {
NetworkParams, NetworkParams,
useNetworkParams, useNetworkParams,
@@ -98,7 +98,7 @@ export const ProposeUpdateMarket = () => {
const [selectedMarket, setSelectedMarket] = useState<string | undefined>( const [selectedMarket, setSelectedMarket] = useState<string | undefined>(
undefined undefined
); );
const { VEGA_EXPLORER_URL } = useEnvironment(); const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
register, register,
@@ -181,13 +181,17 @@ export const ProposeUpdateMarket = () => {
userAction={ProposalUserAction.CREATE} userAction={ProposalUserAction.CREATE}
/> />
{DocsLinks && ( {VEGA_DOCS_URL && (
<p className="text-sm" data-testid="proposal-docs-link"> <p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span> <span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink <ExternalLink
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`} href={`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}
target="_blank" target="_blank"
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink> >{`${
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
}${DOCS_LINK}`}</ExternalLink>
</p> </p>
)} )}
@@ -54,16 +54,22 @@ export const TrancheItem = ({
{formatNumber(total, 2)} {formatNumber(total, 2)}
</span> </span>
</div> </div>
<div className="grid grid-cols-2 my-2"> <table className="w-full">
<div> <tbody>
<span>{t('Starts unlocking')}:</span>{' '} <tr>
<span>{format(tranche.tranche_start, DATE_FORMAT_LONG)}</span> <td>{t('Starts unlocking')}</td>
</div> <td className="text-right">
<div className="justify-self-end"> {format(tranche.tranche_start, DATE_FORMAT_LONG)}
<span>{t('Fully unlocked')}:</span>{' '} </td>
<span>{format(tranche.tranche_end, DATE_FORMAT_LONG)}</span> </tr>
</div> <tr>
</div> <td>{t('Fully unlocked')}</td>
<td className="text-right">
{format(tranche.tranche_end, DATE_FORMAT_LONG)}
</td>
</tr>
</tbody>
</table>
<LockedProgress <LockedProgress
locked={locked} locked={locked}
unlocked={unlocked} unlocked={unlocked}
@@ -25,7 +25,7 @@ const mockData = {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES: { ACCOUNT_TYPE_FEES_LIQUIDITY: {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
@@ -55,9 +55,7 @@ describe('EpochIndividualRewardsTable', () => {
expect( expect(
getByTestId('ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES') getByTestId('ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES')
).toBeInTheDocument(); ).toBeInTheDocument();
expect( expect(getByTestId('ACCOUNT_TYPE_FEES_LIQUIDITY')).toBeInTheDocument();
getByTestId('ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES')
).toBeInTheDocument();
expect( expect(
getByTestId('ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS') getByTestId('ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS')
).toBeInTheDocument(); ).toBeInTheDocument();
@@ -24,7 +24,7 @@ describe('generateEpochIndividualRewardsList', () => {
}; };
const reward3: RewardFieldsFragment = { const reward3: RewardFieldsFragment = {
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
amount: '200', amount: '200',
percentageOfTotal: '0.2', percentageOfTotal: '0.2',
receivedAt: new Date(), receivedAt: new Date(),
@@ -34,7 +34,7 @@ describe('generateEpochIndividualRewardsList', () => {
}; };
const reward4: RewardFieldsFragment = { const reward4: RewardFieldsFragment = {
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
amount: '100', amount: '100',
percentageOfTotal: '0.1', percentageOfTotal: '0.1',
receivedAt: new Date(), receivedAt: new Date(),
@@ -44,7 +44,7 @@ describe('generateEpochIndividualRewardsList', () => {
}; };
const reward5: RewardFieldsFragment = { const reward5: RewardFieldsFragment = {
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
amount: '150', amount: '150',
percentageOfTotal: '0.15', percentageOfTotal: '0.15',
receivedAt: new Date(), receivedAt: new Date(),
@@ -105,7 +105,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: { [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
@@ -172,7 +172,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: { [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
amount: '200', amount: '200',
percentageOfTotal: '0.2', percentageOfTotal: '0.2',
}, },
@@ -202,7 +202,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: { [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
@@ -237,7 +237,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: { [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
amount: '100', amount: '100',
percentageOfTotal: '0.1', percentageOfTotal: '0.1',
}, },
@@ -285,7 +285,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: { [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
amount: '150', amount: '150',
percentageOfTotal: '0.15', percentageOfTotal: '0.15',
}, },
@@ -320,7 +320,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: { [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
amount: '200', amount: '200',
percentageOfTotal: '0.2', percentageOfTotal: '0.2',
}, },
@@ -350,7 +350,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: { [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
@@ -395,7 +395,7 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0', amount: '0',
percentageOfTotal: '0', percentageOfTotal: '0',
}, },
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: { [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
amount: '100', amount: '100',
percentageOfTotal: '0.1', percentageOfTotal: '0.1',
}, },
@@ -79,24 +79,7 @@ export const generateEpochIndividualRewardsList = ({
epoch?.rewards.push(asset); epoch?.rewards.push(asset);
} }
if (!asset.rewardTypes[rewardType]) { asset.rewardTypes[rewardType] = { amount, percentageOfTotal };
asset.rewardTypes[rewardType] = { amount, percentageOfTotal };
} else {
const previousAmount = asset.rewardTypes[rewardType]?.amount;
const previousPercentageOfTotal =
asset.rewardTypes[rewardType]?.percentageOfTotal;
asset.rewardTypes[rewardType] = {
amount: previousAmount
? new BigNumber(previousAmount).plus(amount).toString()
: amount,
percentageOfTotal: previousPercentageOfTotal
? new BigNumber(previousPercentageOfTotal)
.plus(percentageOfTotal)
.toString()
: percentageOfTotal,
};
}
// totalAmount is the sum of all rewardTypes amounts // totalAmount is the sum of all rewardTypes amounts
asset.totalAmount = Object.values(asset.rewardTypes).reduce( asset.totalAmount = Object.values(asset.rewardTypes).reduce(
@@ -29,7 +29,7 @@ const rewardsList = [
amount: '0', amount: '0',
}, },
{ {
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
amount: '0', amount: '0',
}, },
{ {
@@ -78,9 +78,7 @@ describe('EpochTotalRewardsTable', () => {
expect( expect(
getByTestId('ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES') getByTestId('ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES')
).toBeInTheDocument(); ).toBeInTheDocument();
expect( expect(getByTestId('ACCOUNT_TYPE_FEES_LIQUIDITY')).toBeInTheDocument();
getByTestId('ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES')
).toBeInTheDocument();
expect( expect(
getByTestId('ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS') getByTestId('ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS')
).toBeInTheDocument(); ).toBeInTheDocument();
@@ -162,10 +162,9 @@ describe('generateEpochAssetRewardsList', () => {
}, },
], ],
[ [
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
{ {
rewardType: rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '0', amount: '0',
}, },
], ],
@@ -228,7 +227,7 @@ describe('generateEpochAssetRewardsList', () => {
node: { node: {
epoch: 2, epoch: 2,
assetId: '1', assetId: '1',
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
amount: '5', amount: '5',
}, },
}, },
@@ -288,10 +287,9 @@ describe('generateEpochAssetRewardsList', () => {
}, },
], ],
[ [
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
{ {
rewardType: rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '0', amount: '0',
}, },
], ],
@@ -353,10 +351,9 @@ describe('generateEpochAssetRewardsList', () => {
}, },
], ],
[ [
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
{ {
rewardType: rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '5', amount: '5',
}, },
], ],
@@ -419,7 +416,7 @@ describe('generateEpochAssetRewardsList', () => {
node: { node: {
epoch: 2, epoch: 2,
assetId: '1', assetId: '1',
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
amount: '6', amount: '6',
}, },
}, },
@@ -427,7 +424,7 @@ describe('generateEpochAssetRewardsList', () => {
node: { node: {
epoch: 2, epoch: 2,
assetId: '1', assetId: '1',
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
amount: '27', amount: '27',
}, },
}, },
@@ -500,10 +497,9 @@ describe('generateEpochAssetRewardsList', () => {
}, },
], ],
[ [
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
{ {
rewardType: rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '33', amount: '33',
}, },
], ],
@@ -565,10 +561,9 @@ describe('generateEpochAssetRewardsList', () => {
}, },
], ],
[ [
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
{ {
rewardType: rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '0', amount: '0',
}, },
], ],
@@ -642,10 +637,9 @@ describe('generateEpochAssetRewardsList', () => {
}, },
], ],
[ [
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES, AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
{ {
rewardType: rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '0', amount: '0',
}, },
], ],
@@ -11,6 +11,7 @@ import {
ExternalLink, ExternalLink,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import { createDocsLinks } from '@vegaprotocol/utils';
import { import {
useNetworkParams, useNetworkParams,
NetworkParams, NetworkParams,
@@ -20,7 +21,7 @@ import { EpochCountdown } from '../../../components/epoch-countdown';
import { Heading, SubHeading } from '../../../components/heading'; import { Heading, SubHeading } from '../../../components/heading';
import { EpochIndividualRewards } from '../epoch-individual-rewards/epoch-individual-rewards'; import { EpochIndividualRewards } from '../epoch-individual-rewards/epoch-individual-rewards';
import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch'; import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch';
import { DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { ConnectToSeeRewards } from '../connect-to-see-rewards'; import { ConnectToSeeRewards } from '../connect-to-see-rewards';
import { EpochTotalRewards } from '../epoch-total-rewards/epoch-total-rewards'; import { EpochTotalRewards } from '../epoch-total-rewards/epoch-total-rewards';
@@ -28,6 +29,7 @@ type RewardsView = 'total' | 'individual';
export const RewardsPage = () => { export const RewardsPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { VEGA_DOCS_URL } = useEnvironment();
const { pubKey, pubKeys } = useVegaWallet(); const { pubKey, pubKeys } = useVegaWallet();
const [toggleRewardsView, setToggleRewardsView] = const [toggleRewardsView, setToggleRewardsView] =
useState<RewardsView>('total'); useState<RewardsView>('total');
@@ -66,9 +68,9 @@ export const RewardsPage = () => {
<Heading title={t('pageTitleRewards')} /> <Heading title={t('pageTitleRewards')} />
<p className="mb-12"> <p className="mb-12">
{t('rewardsIntro')}{' '} {t('rewardsIntro')}{' '}
{DocsLinks && ( {VEGA_DOCS_URL && (
<ExternalLink <ExternalLink
href={DocsLinks.REWARDS_GUIDE} href={createDocsLinks(VEGA_DOCS_URL).REWARDS_GUIDE}
target="_blank" target="_blank"
data-testid="rewards-guide-link" data-testid="rewards-guide-link"
className="text-white" className="text-white"
@@ -26,7 +26,7 @@ export const RowAccountTypes = {
columnTitle: 'rewardsColPriceMakingHeader', columnTitle: 'rewardsColPriceMakingHeader',
description: 'rewardsColPriceMakingTooltip', description: 'rewardsColPriceMakingTooltip',
}, },
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: { [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
columnTitle: 'rewardsColLiquidityProvisionHeader', columnTitle: 'rewardsColLiquidityProvisionHeader',
description: 'rewardsColLiquidityProvisionTooltip', description: 'rewardsColLiquidityProvisionTooltip',
}, },
+4 -7
View File
@@ -229,7 +229,7 @@ const redirects = [
const routerConfig = [ const routerConfig = [
{ {
path: Routes.HOME, path: Routes.HOME,
element: <LazyHome name="Home" />, element: <LazyHome name="Token" />,
}, },
{ {
path: Routes.PROPOSALS, path: Routes.PROPOSALS,
@@ -291,7 +291,7 @@ const routerConfig = [
// Not lazy as loaded when a user first hits the site // Not lazy as loaded when a user first hits the site
children: [ children: [
{ {
element: <Home name="Token" />, element: <Home name="Home" />,
index: true, index: true,
}, },
{ {
@@ -325,11 +325,8 @@ const routerConfig = [
}, },
], ],
}, },
{ path: 'associate', element: <LazyStakingAssociate name="Associate" /> }, { path: 'associate', element: <LazyStakingAssociate /> },
{ { path: 'disassociate', element: <LazyStakingDisassociate /> },
path: 'disassociate',
element: <LazyStakingDisassociate name="Disassociate" />,
},
], ],
}, },
{ {
@@ -1,14 +1,12 @@
import { useEthereumConfig } from '@vegaprotocol/web3'; import { useEthereumConfig } from '@vegaprotocol/web3';
import { StakingWalletsContainer } from './components/staking-wallets-container'; import { StakingWalletsContainer } from './components/staking-wallets-container/staking-wallets-container';
import { AssociatePage } from './associate-page'; import { AssociatePage } from './associate-page';
import { AssociatePageNoVega } from './associate-page-no-vega'; import { AssociatePageNoVega } from './associate-page-no-vega';
import { Heading } from '../../../components/heading'; import { Heading } from '../../../components/heading';
import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useDocumentTitle } from '../../../hooks/use-document-title';
import type { RouteChildProps } from '../../index';
export const AssociateContainer = ({ name }: RouteChildProps) => { export const AssociateContainer = () => {
useDocumentTitle(name);
const { t } = useTranslation(); const { t } = useTranslation();
const { config } = useEthereumConfig(); const { config } = useEthereumConfig();
@@ -39,7 +39,7 @@ jest.mock('@vegaprotocol/wallet', () => ({
})); }));
const renderComponent = () => { const renderComponent = () => {
return render(<Disassociate name="Disassociate" />); return render(<Disassociate />);
}; };
describe('Disassociate', () => { describe('Disassociate', () => {
@@ -3,12 +3,10 @@ import { useWeb3React } from '@web3-react/core';
import { EthConnectPrompt } from '../../../components/eth-connect-prompt'; import { EthConnectPrompt } from '../../../components/eth-connect-prompt';
import { DisassociatePage } from './components/disassociate-page'; import { DisassociatePage } from './components/disassociate-page';
import { Heading } from '../../../components/heading'; import { Heading } from '../../../components/heading';
import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useDocumentTitle } from '../../../hooks/use-document-title';
import type { RouteChildProps } from '../../index';
export const DisassociateContainer = ({ name }: RouteChildProps) => { export const DisassociateContainer = () => {
useDocumentTitle(name);
const { t } = useTranslation(); const { t } = useTranslation();
const { account } = useWeb3React(); const { account } = useWeb3React();
const { pubKey } = useVegaWallet(); const { pubKey } = useVegaWallet();
@@ -1,12 +1,14 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { EpochData } from './epoch-data'; import { EpochData } from './epoch-data';
import { DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { ExternalLink } from '@vegaprotocol/ui-toolkit'; import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { createDocsLinks } from '@vegaprotocol/utils';
import { Heading } from '../../../components/heading'; import { Heading } from '../../../components/heading';
import React from 'react'; import React from 'react';
export const Staking = () => { export const Staking = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { VEGA_DOCS_URL } = useEnvironment();
return ( return (
<> <>
@@ -14,9 +16,9 @@ export const Staking = () => {
<section> <section>
<p className="mb-12"> <p className="mb-12">
{t('stakingIntro')}{' '} {t('stakingIntro')}{' '}
{DocsLinks && ( {VEGA_DOCS_URL && (
<ExternalLink <ExternalLink
href={DocsLinks.STAKING_GUIDE} href={createDocsLinks(VEGA_DOCS_URL).STAKING_GUIDE}
target="_blank" target="_blank"
data-testid="staking-guide-link" data-testid="staking-guide-link"
className="text-white" className="text-white"
@@ -131,25 +131,20 @@ export const ConsensusValidatorsTable = ({
const gridRef = useRef<AgGridReact | null>(null); const gridRef = useRef<AgGridReact | null>(null);
const thirdOfTotalStake = useMemo(
() => new BigNumber(totalStake).dividedBy(3),
[totalStake]
);
const nodes = useMemo(() => { const nodes = useMemo(() => {
if (!data) return []; if (!data) return [];
let canonisedNodes = data let canonisedNodes = data
.sort((a, b) => { .sort((a, b) => {
const aStakedTotal = new BigNumber(a.stakedTotal); const aVotingPower = new BigNumber(a.rankingScore.votingPower);
const bStakedTotal = new BigNumber(b.stakedTotal); const bVotingPower = new BigNumber(b.rankingScore.votingPower);
return bStakedTotal.minus(aStakedTotal).toNumber(); return bVotingPower.minus(aVotingPower).toNumber();
}) })
.map((node, index) => { .map((node, index) => {
const stakedTotalRanking = index + 1; const votingPowerRanking = index + 1;
return { return {
...node, ...node,
stakedTotalRanking, votingPowerRanking,
}; };
}) })
.map( .map(
@@ -162,7 +157,7 @@ export const ConsensusValidatorsTable = ({
stakedTotal, stakedTotal,
rankingScore: { stakeScore, votingPower }, rankingScore: { stakeScore, votingPower },
pendingStake, pendingStake,
stakedTotalRanking, votingPowerRanking,
stakedByUser, stakedByUser,
pendingUserStake, pendingUserStake,
userStakeShare, userStakeShare,
@@ -180,7 +175,7 @@ export const ConsensusValidatorsTable = ({
return { return {
id, id,
[ValidatorFields.RANKING_INDEX]: stakedTotalRanking, [ValidatorFields.RANKING_INDEX]: votingPowerRanking,
[ValidatorFields.VALIDATOR]: { [ValidatorFields.VALIDATOR]: {
avatarUrl: logo, avatarUrl: logo,
name, name,
@@ -221,7 +216,7 @@ export const ConsensusValidatorsTable = ({
: undefined, : undefined,
[ValidatorFields.PENDING_USER_STAKE]: pendingUserStake, [ValidatorFields.PENDING_USER_STAKE]: pendingUserStake,
[ValidatorFields.USER_STAKE_SHARE]: userStakeShare [ValidatorFields.USER_STAKE_SHARE]: userStakeShare
? formatNumberPercentage(new BigNumber(userStakeShare), 2) ? formatNumberPercentage(new BigNumber(userStakeShare))
: undefined, : undefined,
}; };
} }
@@ -241,12 +236,16 @@ export const ConsensusValidatorsTable = ({
return canonisedNodes; return canonisedNodes;
} }
// The point of identifying and hiding the group that could halt the network
// is that we assume the top 1/3 of stake is held by considerably less than
// 1/3 of the validators and we really want people not to stake any more to
// that group, because we want to make it require as many difference
// validators to collude as possible to halt the network, so we hide them.
const { topThird, remaining } = canonisedNodes.reduce( const { topThird, remaining } = canonisedNodes.reduce(
(acc, node) => { (acc, node) => {
if (acc.cumulativeScore.isLessThan(thirdOfTotalStake)) { if (acc.cumulativeScore < 100 / 3) {
const prev = acc.cumulativeScore; acc.cumulativeScore += parseFloat(
acc.cumulativeScore = prev.plus( node[ValidatorFields.NORMALISED_VOTING_POWER]
new BigNumber(node[ValidatorFields.STAKE])
); );
acc.topThird.push(node); acc.topThird.push(node);
return acc; return acc;
@@ -254,70 +253,61 @@ export const ConsensusValidatorsTable = ({
acc.remaining.push(node); acc.remaining.push(node);
return acc; return acc;
}, },
{ topThird: [], remaining: [], cumulativeScore: new BigNumber(0) } as { { topThird: [], remaining: [], cumulativeScore: 0 } as {
topThird: CanonisedConsensusNodeProps[]; topThird: CanonisedConsensusNodeProps[];
remaining: CanonisedConsensusNodeProps[]; remaining: CanonisedConsensusNodeProps[];
cumulativeScore: BigNumber; cumulativeScore: number;
} }
); );
// We need to combine the top third of validators into a single node, this // We need to combine the top third of validators into a single node, this
// way the combined values can be passed to AG grid so that the combined cell's // way the combined values can be passed to AG grid so that the combined cell's
// values are correct for ordering. // values are correct for ordering.
const combinedTopThird = topThird.reduce( const combinedTopThird = topThird.reduce((acc, node) => {
(acc, node) => { const {
const { [ValidatorFields.STAKE]: stake,
[ValidatorFields.STAKE]: stake, [ValidatorFields.STAKE_SHARE]: stakeShare,
[ValidatorFields.STAKE_SHARE]: stakeShare, [ValidatorFields.PENDING_STAKE]: pendingStake,
[ValidatorFields.PENDING_STAKE]: pendingStake, [ValidatorFields.NORMALISED_VOTING_POWER]: normalisedVotingPower,
[ValidatorFields.NORMALISED_VOTING_POWER]: normalisedVotingPower, [ValidatorFields.TOTAL_PENALTIES]: totalPenalties,
[ValidatorFields.TOTAL_PENALTIES]: totalPenalties, } = node;
} = node;
const { const {
[ValidatorFields.STAKE]: accStake, [ValidatorFields.STAKE]: accStake,
[ValidatorFields.STAKE_SHARE]: accStakeShare, [ValidatorFields.STAKE_SHARE]: accStakeShare,
[ValidatorFields.PENDING_STAKE]: accPendingStake, [ValidatorFields.PENDING_STAKE]: accPendingStake,
[ValidatorFields.NORMALISED_VOTING_POWER]: accNormalisedVotingPower, [ValidatorFields.NORMALISED_VOTING_POWER]: accNormalisedVotingPower,
[ValidatorFields.TOTAL_PENALTIES]: accTotalPenalties, [ValidatorFields.TOTAL_PENALTIES]: accTotalPenalties,
} = acc; } = acc;
return { return {
...acc, ...acc,
[ValidatorFields.STAKE]: new BigNumber(accStake) [ValidatorFields.STAKE]: toBigNum(accStake, decimals)
.plus(new BigNumber(stake)) .plus(toBigNum(stake, decimals))
.toString(), .toString(),
[ValidatorFields.STAKE_SHARE]: formatNumberPercentage( [ValidatorFields.STAKE_SHARE]: formatNumberPercentage(
new BigNumber(parseFloat(accStakeShare)).plus( new BigNumber(parseFloat(accStakeShare)).plus(
new BigNumber(parseFloat(stakeShare)) new BigNumber(parseFloat(stakeShare))
),
2
), ),
[ValidatorFields.PENDING_STAKE]: toBigNum(accPendingStake, decimals) 2
.plus(toBigNum(pendingStake, decimals)) ),
.toString(), [ValidatorFields.PENDING_STAKE]: toBigNum(accPendingStake, decimals)
[ValidatorFields.NORMALISED_VOTING_POWER]: formatNumberPercentage( .plus(toBigNum(pendingStake, decimals))
new BigNumber(parseFloat(accNormalisedVotingPower)).plus( .toString(),
new BigNumber(parseFloat(normalisedVotingPower)) [ValidatorFields.NORMALISED_VOTING_POWER]: formatNumberPercentage(
), new BigNumber(parseFloat(accNormalisedVotingPower)).plus(
2 new BigNumber(parseFloat(normalisedVotingPower))
), ),
[ValidatorFields.TOTAL_PENALTIES]: formatNumberPercentage( 2
new BigNumber(parseFloat(accTotalPenalties)).plus( ),
new BigNumber(parseFloat(totalPenalties)) [ValidatorFields.TOTAL_PENALTIES]: formatNumberPercentage(
), new BigNumber(parseFloat(accTotalPenalties)).plus(
2 new BigNumber(parseFloat(totalPenalties))
), ),
}; 2
}, ),
{ };
[ValidatorFields.STAKE]: '0', });
[ValidatorFields.STAKE_SHARE]: '0',
[ValidatorFields.PENDING_STAKE]: '0',
[ValidatorFields.NORMALISED_VOTING_POWER]: '0',
[ValidatorFields.TOTAL_PENALTIES]: '0',
}
);
return [ return [
{ {
@@ -332,7 +322,6 @@ export const ConsensusValidatorsTable = ({
decimals, decimals,
hideTopThird, hideTopThird,
previousEpochData, previousEpochData,
thirdOfTotalStake,
totalStake, totalStake,
validatorsView, validatorsView,
]); ]);
@@ -363,7 +352,6 @@ export const ConsensusValidatorsTable = ({
headerTooltip: t('StakeDescription').toString(), headerTooltip: t('StakeDescription').toString(),
cellRenderer: TotalStakeRenderer, cellRenderer: TotalStakeRenderer,
width: 120, width: 120,
sort: 'desc',
}, },
{ {
field: ValidatorFields.PENDING_STAKE, field: ValidatorFields.PENDING_STAKE,
@@ -385,6 +373,7 @@ export const ConsensusValidatorsTable = ({
headerTooltip: t('NormalisedVotingPowerDescription').toString(), headerTooltip: t('NormalisedVotingPowerDescription').toString(),
cellRenderer: VotingPowerRenderer, cellRenderer: VotingPowerRenderer,
width: 200, width: 200,
sort: 'desc',
}, },
{ {
field: ValidatorFields.TOTAL_PENALTIES, field: ValidatorFields.TOTAL_PENALTIES,
@@ -57,16 +57,16 @@ export const StandbyPendingValidatorsTable = ({
return data return data
.sort((a, b) => { .sort((a, b) => {
const aStakedTotal = new BigNumber(a.stakedTotal); const aVotingPower = new BigNumber(a.rankingScore.votingPower);
const bStakedTotal = new BigNumber(b.stakedTotal); const bVotingPower = new BigNumber(b.rankingScore.votingPower);
return bStakedTotal.minus(aStakedTotal).toNumber(); return bVotingPower.minus(aVotingPower).toNumber();
}) })
.map((node, index) => { .map((node, index) => {
const stakedTotalRanking = index + 1; const votingPowerRanking = index + 1;
return { return {
...node, ...node,
stakedTotalRanking, votingPowerRanking,
}; };
}) })
.map( .map(
@@ -79,7 +79,7 @@ export const StandbyPendingValidatorsTable = ({
stakedTotal, stakedTotal,
rankingScore: { stakeScore }, rankingScore: { stakeScore },
pendingStake, pendingStake,
stakedTotalRanking, votingPowerRanking,
stakedByUser, stakedByUser,
pendingUserStake, pendingUserStake,
userStakeShare, userStakeShare,
@@ -125,7 +125,7 @@ export const StandbyPendingValidatorsTable = ({
return { return {
id, id,
[ValidatorFields.RANKING_INDEX]: stakedTotalRanking, [ValidatorFields.RANKING_INDEX]: votingPowerRanking,
[ValidatorFields.VALIDATOR]: { [ValidatorFields.VALIDATOR]: {
avatarUrl, avatarUrl,
name, name,
@@ -166,7 +166,7 @@ export const StandbyPendingValidatorsTable = ({
: undefined, : undefined,
[ValidatorFields.PENDING_USER_STAKE]: pendingUserStake, [ValidatorFields.PENDING_USER_STAKE]: pendingUserStake,
[ValidatorFields.USER_STAKE_SHARE]: userStakeShare [ValidatorFields.USER_STAKE_SHARE]: userStakeShare
? formatNumberPercentage(new BigNumber(userStakeShare), 2) ? formatNumberPercentage(new BigNumber(userStakeShare))
: undefined, : undefined,
}; };
} }
@@ -210,7 +210,6 @@ export const StandbyPendingValidatorsTable = ({
headerTooltip: t('StakeDescription').toString(), headerTooltip: t('StakeDescription').toString(),
cellRenderer: TotalStakeRenderer, cellRenderer: TotalStakeRenderer,
width: 120, width: 120,
sort: 'desc',
}, },
{ {
field: ValidatorFields.PENDING_STAKE, field: ValidatorFields.PENDING_STAKE,
@@ -234,6 +233,7 @@ export const StandbyPendingValidatorsTable = ({
}), }),
cellRenderer: StakeNeededForPromotionRenderer, cellRenderer: StakeNeededForPromotionRenderer,
width: 210, width: 210,
sort: 'asc',
}, },
{ {
field: ValidatorFields.TOTAL_PENALTIES, field: ValidatorFields.TOTAL_PENALTIES,
@@ -2,8 +2,12 @@ import { useMemo, useState } from 'react';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
import { removePaginationWrapper, toBigNum } from '@vegaprotocol/utils'; import {
import { DocsLinks } from '@vegaprotocol/environment'; createDocsLinks,
removePaginationWrapper,
toBigNum,
} from '@vegaprotocol/utils';
import { useEnvironment } from '@vegaprotocol/environment';
import { Link as UTLink, Toggle } from '@vegaprotocol/ui-toolkit'; import { Link as UTLink, Toggle } from '@vegaprotocol/ui-toolkit';
import { formatNumber } from '../../../../lib/format-number'; import { formatNumber } from '../../../../lib/format-number';
@@ -38,6 +42,7 @@ export const ValidatorTables = ({
previousEpochData, previousEpochData,
}: ValidatorsTableProps) => { }: ValidatorsTableProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { VEGA_DOCS_URL } = useEnvironment();
const { const {
appState: { decimals }, appState: { decimals },
} = useAppState(); } = useAppState();
@@ -208,12 +213,12 @@ export const ValidatorTables = ({
<> <>
<SubHeading title={t('status-pending')} /> <SubHeading title={t('status-pending')} />
<p> <p>
{DocsLinks && ( {VEGA_DOCS_URL && (
<> <>
<span>{t('pendingDescription1')} </span> <span>{t('pendingDescription1')} </span>
<span> <span>
<UTLink <UTLink
href={DocsLinks.STAKING_GUIDE} href={createDocsLinks(VEGA_DOCS_URL).STAKING_GUIDE}
target="_blank" target="_blank"
data-testid="validator-forum-link" data-testid="validator-forum-link"
> >
@@ -9,7 +9,6 @@ import { usePreviousEpochQuery } from '../__generated__/PreviousEpoch';
import type { ReactElement } from 'react'; import type { ReactElement } from 'react';
import type { StakingQuery } from '../__generated__/Staking'; import type { StakingQuery } from '../__generated__/Staking';
import type { PreviousEpochQuery } from '../__generated__/PreviousEpoch'; import type { PreviousEpochQuery } from '../__generated__/PreviousEpoch';
import { isPartyNotFoundError } from '../../../lib/party';
// TODO should only request a single node. When migrating from deprecated APIs we should address this. // TODO should only request a single node. When migrating from deprecated APIs we should address this.
@@ -29,7 +28,6 @@ export const NodeContainer = ({
const { t } = useTranslation(); const { t } = useTranslation();
const { pubKey } = useVegaWallet(); const { pubKey } = useVegaWallet();
const { delegationsPagination } = ENV; const { delegationsPagination } = ENV;
const { data, loading, error, refetch } = useStakingQuery({ const { data, loading, error, refetch } = useStakingQuery({
variables: { variables: {
partyId: pubKey || '', partyId: pubKey || '',
@@ -39,9 +37,7 @@ export const NodeContainer = ({
} }
: undefined, : undefined,
}, },
errorPolicy: 'all',
}); });
const { data: previousEpochData } = usePreviousEpochQuery({ const { data: previousEpochData } = usePreviousEpochQuery({
variables: { variables: {
epochId: (Number(data?.epoch.id) - 1).toString(), epochId: (Number(data?.epoch.id) - 1).toString(),
@@ -51,7 +47,7 @@ export const NodeContainer = ({
useRefreshAfterEpoch(data?.epoch.timestamps.expiry, refetch); useRefreshAfterEpoch(data?.epoch.timestamps.expiry, refetch);
if (error && !isPartyNotFoundError(error)) { if (error) {
return ( return (
<Callout intent={Intent.Danger} title={t('Something went wrong')}> <Callout intent={Intent.Danger} title={t('Something went wrong')}>
<pre> <pre>
@@ -1,11 +1,7 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import { useEnvironment } from '@vegaprotocol/environment';
useEnvironment, import { createDocsLinks, ExternalLinks, toBigNum } from '@vegaprotocol/utils';
DocsLinks,
ExternalLinks,
} from '@vegaprotocol/environment';
import { toBigNum } from '@vegaprotocol/utils';
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
import { import {
Link as UTLink, Link as UTLink,
@@ -68,7 +64,7 @@ export const ValidatorTable = ({
stakedTotal, stakedTotal,
previousEpochData, previousEpochData,
}: ValidatorTableProps) => { }: ValidatorTableProps) => {
const { ETHERSCAN_URL } = useEnvironment(); const { ETHERSCAN_URL, VEGA_DOCS_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
appState: { decimals }, appState: { decimals },
@@ -94,9 +90,9 @@ export const ValidatorTable = ({
<> <>
<p className="mb-12"> <p className="mb-12">
{t('validatorFormIntro')}{' '} {t('validatorFormIntro')}{' '}
{DocsLinks && ( {VEGA_DOCS_URL && (
<ExternalLink <ExternalLink
href={DocsLinks.STAKING_GUIDE} href={createDocsLinks(VEGA_DOCS_URL).STAKING_GUIDE}
target="_blank" target="_blank"
data-testid="validator-table-staking-guide-link" data-testid="validator-table-staking-guide-link"
className="text-white" className="text-white"
+3 -8
View File
@@ -60,14 +60,9 @@ export const getOverstakingPenalty = (
} }
return formatNumberPercentage( return formatNumberPercentage(
BigNumber.max( new BigNumber(1)
new BigNumber(1) .minus(new BigNumber(validatorScore).dividedBy(new BigNumber(stakeScore)))
.minus( .times(100),
new BigNumber(validatorScore).dividedBy(new BigNumber(stakeScore))
)
.times(100),
new BigNumber(0)
),
2 2
); );
}; };
+1 -2
View File
@@ -3,8 +3,7 @@ import { Trans, useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Heading, SubHeading } from '../../components/heading'; import { Heading, SubHeading } from '../../components/heading';
import { ExternalLinks } from '@vegaprotocol/environment'; import { ExternalLinks, toBigNum } from '@vegaprotocol/utils';
import { toBigNum } from '@vegaprotocol/utils';
import { useAppState } from '../../contexts/app-state/app-state-context'; import { useAppState } from '../../contexts/app-state/app-state-context';
import { useDocumentTitle } from '../../hooks/use-document-title'; import { useDocumentTitle } from '../../hooks/use-document-title';
import type { RouteChildProps } from '..'; import type { RouteChildProps } from '..';

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