Compare commits

..
Author SHA1 Message Date
Matthew Russell c4b05dad21 chore: remove unused release script 2024-02-27 16:16:45 +00:00
575 changed files with 11829 additions and 9357 deletions
+1
View File
@@ -4,5 +4,6 @@ tmp/*
.dockerignore
dockerfiles
node_modules
.git
.github
.vscode
+1 -2
View File
@@ -196,9 +196,9 @@ jobs:
cypress:
needs: [build-sources, check-e2e-needed]
name: '(CI) cypress'
if: ${{ needs.check-e2e-needed.outputs.run-tests == 'true' }}
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
if: needs.check-e2e-needed.outputs.run-tests == 'true' && (contains(needs.build-sources.outputs.projects, 'governance') || contains(needs.build-sources.outputs.projects, 'explorer'))
with:
projects: ${{ needs.build-sources.outputs.projects-e2e }}
tags: '@smoke'
@@ -287,7 +287,6 @@ jobs:
steps:
- run: |
result="${{ needs.cypress.result }}"
echo "Result: $result"
if [[ $result == "success" || $result == "skipped" ]]; then
exit 0
else
+36
View File
@@ -0,0 +1,36 @@
name: Cypress Console tests -- live environment
# This workflow runs using provided url
on:
workflow_dispatch:
inputs:
url:
description: 'Url'
required: true
type: string
jobs:
cypress-run:
name: Run Cypress Trading tests -- live environment
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Use Node.js 20
id: Node
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- name: Run Cypress tests
uses: cypress-io/github-action@v4
with:
browser: chrome
record: true
project: ./apps/trading-e2e
config: baseUrl=${{ github.event.inputs.url }}
env: grepTags=@live
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -12,6 +12,7 @@ on:
options:
- explorer-e2e
- governance-e2e
- trading-e2e
tags:
description: 'Test tags to run'
required: true
+1 -1
View File
@@ -10,5 +10,5 @@ jobs:
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
with:
projects: '["explorer-e2e","governance-e2e"]'
projects: '["explorer-e2e","governance-e2e","trading-e2e"]'
tags: '@smoke @regression @slow'
+2 -2
View File
@@ -25,7 +25,7 @@ jobs:
- name: Install dependencies
run: |
rm package.json
npm install --no-save @commitlint/cli@16.3.0 @commitlint/config-conventional@18.6.1 @commitlint/config-nx-scopes@18.6.1 nx@17.1.2
npm install --no-save @commitlint/cli @commitlint/config-conventional @commitlint/config-nx-scopes nx
- name: Check PR title
run: echo "${{ github.event.pull_request.title }}" | npx @commitlint/cli@16.3.0 --config ./commitlint.config-ci.js
run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js
-28
View File
@@ -1,28 +0,0 @@
# path to a directory with all packages
storage: ../tmp/local-registry/storage
# a list of other known repositories we can talk to
uplinks:
npmjs:
url: https://registry.yarnpkg.com
maxage: 60m
packages:
'**':
# give all users (including non-authenticated users) full access
# because it is a local registry
access: $all
publish: $all
unpublish: $all
# if package is not available locally, proxy requests to npm registry
proxy: npmjs
# log settings
logs:
type: stdout
format: pretty
level: warn
publish:
allow_offline: true # set offline to true to allow publish offline
+2 -4
View File
@@ -1,9 +1,7 @@
const { join } = require('path');
const { createGlobPatternsForDependencies } = require('@nx/react/tailwind');
const { theme } = require('../../libs/tailwindcss-config/src/theme');
const {
vegaCustomClasses,
} = require('../../libs/tailwindcss-config/src/vega-custom-classes');
const theme = require('../../libs/tailwindcss-config/src/theme');
const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom-classes');
module.exports = {
content: [
@@ -7,7 +7,6 @@ import {
navigateTo,
navigation,
turnTelemetryOff,
setRiskAccepted,
} from '../../support/common.functions';
import {
clickOnValidatorFromList,
@@ -58,7 +57,6 @@ context(
// 1002-STKE-002, 1002-STKE-032
before('visit staking tab and connect vega wallet', function () {
cy.visit('/');
setRiskAccepted();
ethereumWalletConnect();
cy.connectVegaWallet();
vegaWalletSetSpecifiedApprovalAmount('1000');
@@ -5,7 +5,6 @@ import {
navigateTo,
navigation,
turnTelemetryOff,
setRiskAccepted,
} from '../../support/common.functions';
import {
stakingPageAssociateTokens,
@@ -58,7 +57,6 @@ context(
function () {
cy.clearLocalStorage();
turnTelemetryOff();
setRiskAccepted();
cy.mockChainId();
cy.reload();
waitForSpinner();
@@ -79,23 +79,23 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
});
});
it.skip('should have information on active nodes', function () {
it('should have information on active nodes', function () {
cy.getByTestId('node-information')
.first()
.should('contain.text', '2')
.should('contain.text', '1')
.and('contain.text', 'active nodes');
});
it.skip('should have information on consensus nodes', function () {
it('should have information on consensus nodes', function () {
cy.getByTestId('node-information')
.last()
.should('contain.text', '2')
.should('contain.text', '1')
.and('contain.text', 'consensus nodes');
});
it.skip('should contain link to specific validators', function () {
it('should contain link to specific validators', function () {
cy.getByTestId('validators')
.should('have.length', '2')
.should('have.length', '1')
.each(($validator) => {
cy.wrap($validator).find('a').should('have.attr', 'href');
});
@@ -153,7 +153,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
.invoke('text')
.should('not.eq', currentBlockHeight);
});
cy.getByTestId('subscription-cell').should('be.be.visible');
cy.getByTestId('subscription-cell').should('have.text', 'Yes');
});
cy.getByTestId('connect').should('be.disabled');
cy.getByTestId('node-url-custom').click({ force: true });
@@ -34,6 +34,12 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
cy.connectPublicKey(vegaWalletPubKey);
});
it('Able to connect public key using url', function () {
cy.getByTestId('exit-view').click();
cy.visit(`/?address=${vegaWalletPubKey}`);
verifyConnectedToPubKey();
});
it.skip('Able to connect public key via wallet and view assets in wallet', function () {
verifyConnectedToPubKey();
cy.getByTestId('currency-title', { timeout: 10000 })
@@ -3,7 +3,6 @@
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import {
navigation,
setRiskAccepted,
verifyPageHeader,
verifyTabHighlighted,
} from '../../support/common.functions';
@@ -188,7 +187,6 @@ context('Validators Page - verify elements on page', function () {
before('connect wallets and click on validator', function () {
cy.mockChainId();
cy.visit('/validators');
setRiskAccepted();
cy.connectVegaWallet();
clickOnValidatorFromList(0);
});
@@ -1,8 +1,5 @@
import { truncateByChars } from '@vegaprotocol/utils';
import {
setRiskAccepted,
waitForSpinner,
} from '../../support/common.functions';
import { waitForSpinner } from '../../support/common.functions';
import {
vegaWalletFaucetAssetsWithoutCheck,
vegaWalletTeardown,
@@ -14,6 +11,7 @@ const connectButton = 'connect-vega-wallet';
const getVegaLink = 'link';
const dialog = '[role="dialog"]:visible';
const dialogHeader = 'dialog-title';
const walletDialogHeader = 'wallet-dialog-title';
const connectorsList = 'connectors-list';
const dialogCloseBtn = 'dialog-close';
const accountNo = 'vega-account-truncated';
@@ -36,7 +34,6 @@ context(
() => {
before('visit token home page', () => {
cy.visit('/');
setRiskAccepted();
cy.get(walletContainer, { timeout: 60000 }).should('be.visible');
});
@@ -66,12 +63,17 @@ context(
it('should have Connect Vega header visible', () => {
cy.get(dialog).within(() => {
cy.getByTestId(connectorsList)
cy.getByTestId(walletDialogHeader)
.should('be.visible')
.and(
'have.text',
'Get the Vega WalletGet MetaMask>_Command Line WalletView as public key'
);
.and('have.text', 'Get a Vega wallet');
});
});
it('should have jsonRpc and hosted connection options visible on list', function () {
cy.getByTestId(connectorsList).within(() => {
cy.getByTestId('connector-jsonRpc')
.should('be.visible')
.and('have.text', 'Use the Desktop App/CLI');
});
});
@@ -86,6 +88,7 @@ context(
before('connect vega wallet', function () {
cy.mockChainId();
cy.visit('/');
cy.wait('@ChainId');
cy.connectVegaWallet();
vegaWalletTeardown();
});
@@ -102,12 +102,6 @@ export function turnTelemetryOff() {
);
}
export function setRiskAccepted() {
cy.window().then((win) =>
win.localStorage.setItem('vega_wallet_risk_accepted', 'true')
);
}
export function dissociateFromSecondWalletKey() {
const secondWalletKey = Cypress.env('vegaWalletPublicKey2Short');
cy.getByTestId('vega-in-wallet')
+1
View File
@@ -14,6 +14,7 @@ NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/m
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
+1
View File
@@ -15,6 +15,7 @@ NX_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit suppl
NX_LOCAL_PROVIDER_URL=http://localhost:8545/
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
NX_VEGA_REST_URL=http://localhost:3008/api/v2/
+1
View File
@@ -8,6 +8,7 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_EXPLORER_URL=#
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-devnet1-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_REST_URL=https://api.n00.devnet1.vega.xyz/api/v2/
+1
View File
@@ -10,6 +10,7 @@ NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40:87edc2605e544f888305d7fc4a9141bd@o286262.ingest.sentry.io/5882996
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-mainnet-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
NX_VEGA_REST_URL=https://api.vega.community/api/v2/
+1
View File
@@ -9,6 +9,7 @@ NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
NX_VEGA_EXPLORER_URL=https://explorer.mainnet-mirror.vega.rocks
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-mainnet-mirror-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
NX_VEGA_REST_URL=https://api.mainnet-mirror.vega.rocks/api/v2/
+1
View File
@@ -5,6 +5,7 @@ NX_VEGA_NETWORKS='{"DEVNET":"https://dev.governance.vega.xyz","STAGNET1":"https:
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_REST_URL=https://api.n00.stagnet1.vega.xyz/api/v2/
+1
View File
@@ -9,6 +9,7 @@ NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-testnet-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_REST_URL=https://api.n07.testnet.vega.xyz/api/v2/
+17 -5
View File
@@ -1,7 +1,7 @@
import * as Sentry from '@sentry/react';
import { toBigNum } from '@vegaprotocol/utils';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet, useEagerConnect } from '@vegaprotocol/wallet-react';
import { useVegaWallet, useEagerConnect } from '@vegaprotocol/wallet';
import { useFeatureFlags, useEnvironment } from '@vegaprotocol/environment';
import { useWeb3React } from '@web3-react/core';
import React, { Suspense } from 'react';
@@ -15,6 +15,20 @@ import {
} from './contexts/app-state/app-state-context';
import { useContracts } from './contexts/contracts/contracts-context';
import { useRefreshAssociatedBalances } from './hooks/use-refresh-associated-balances';
import { useConnectors } from './lib/vega-connectors';
import { useSearchParams } from 'react-router-dom';
const useVegaWalletEagerConnect = () => {
const connectors = useConnectors();
const vegaConnecting = useEagerConnect(connectors);
const { pubKey, connect } = useVegaWallet();
const [searchParams] = useSearchParams();
const [query] = React.useState(searchParams.get('address'));
if (query && !pubKey) {
connect(connectors.view);
}
return vegaConnecting;
};
export const AppLoader = ({ children }: { children: React.ReactElement }) => {
const featureFlags = useFeatureFlags((state) => state.flags);
@@ -26,9 +40,9 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
const { token, staking, vesting } = useContracts();
const setAssociatedBalances = useRefreshAssociatedBalances();
const [balancesLoaded, setBalancesLoaded] = React.useState(false);
const vegaWalletStatus = useEagerConnect();
const vegaConnecting = useVegaWalletEagerConnect();
const loaded = balancesLoaded && vegaWalletStatus !== 'connecting';
const loaded = balancesLoaded && !vegaConnecting;
React.useEffect(() => {
const run = async () => {
@@ -169,5 +183,3 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
}
return <Suspense fallback={loading}>{children}</Suspense>;
};
AppLoader.displayName = 'AppLoader';
+141 -31
View File
@@ -1,6 +1,7 @@
import './i18n';
import React, { useEffect } from 'react';
import * as Sentry from '@sentry/react';
import { BrowserRouter as Router, useLocation } from 'react-router-dom';
import { AppLoader } from './app-loader';
import { NetworkInfo } from '@vegaprotocol/network-info';
@@ -25,7 +26,7 @@ import {
} from '@vegaprotocol/web3';
import { Web3Provider } from '@vegaprotocol/web3';
import { VegaWalletDialogs } from './components/vega-wallet-dialogs';
import { WalletProvider } from '@vegaprotocol/wallet-react';
import { VegaWalletProvider, useChainId } from '@vegaprotocol/wallet';
import {
useVegaTransactionManager,
useVegaTransactionUpdater,
@@ -35,21 +36,26 @@ import { useEthereumConfig } from '@vegaprotocol/web3';
import {
useEnvironment,
NetworkLoader,
useInitializeEnv,
NodeGuard,
NodeSwitcherDialog,
useNodeSwitcherStore,
DocsLinks,
NodeFailure,
AppLoader as Loader,
useInitializeEnv,
} from '@vegaprotocol/environment';
import { ENV } from './config';
import type { InMemoryCacheConfig } from '@apollo/client';
import { CreateWithdrawalDialog } from '@vegaprotocol/withdraws';
import { SplashLoader } from './components/splash-loader';
import { ToastsManager } from './toasts-manager';
import { TelemetryDialog } from './components/telemetry-dialog/telemetry-dialog';
import {
TelemetryDialog,
TELEMETRY_ON,
} from './components/telemetry-dialog/telemetry-dialog';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { useTranslation } from 'react-i18next';
import { useSentryInit } from './hooks/use-sentry-init';
import { useVegaWalletConfig } from './hooks/use-vega-wallet-config';
import { isPartyNotFoundError } from './lib/party';
const cache: InMemoryCacheConfig = {
typePolicies: {
@@ -98,12 +104,32 @@ const Web3Container = ({
/** Ethereum provider url */
providerUrl: string;
}) => {
const InitializeHandlers = () => {
useVegaTransactionManager();
useVegaTransactionUpdater();
useEthTransactionManager();
useEthTransactionUpdater();
useEthWithdrawApprovalsManager();
return null;
};
const [connectors, initializeConnectors] = useWeb3ConnectStore((store) => [
store.connectors,
store.initialize,
]);
const { ETHEREUM_PROVIDER_URL, ETH_LOCAL_PROVIDER_URL, ETH_WALLET_MNEMONIC } =
useEnvironment();
const {
ETHEREUM_PROVIDER_URL,
ETH_LOCAL_PROVIDER_URL,
ETH_WALLET_MNEMONIC,
VEGA_ENV,
VEGA_URL,
VEGA_EXPLORER_URL,
CHROME_EXTENSION_URL,
MOZILLA_EXTENSION_URL,
VEGA_WALLET_URL,
} = useEnvironment();
const vegaChainId = useChainId(VEGA_URL);
useEffect(() => {
if (chainId) {
@@ -124,31 +150,50 @@ const Web3Container = ({
ETH_LOCAL_PROVIDER_URL,
ETH_WALLET_MNEMONIC,
]);
const sideBar = React.useMemo(() => {
return [<EthWallet />, <VegaWallet />];
}, []);
const vegaWalletConfig = useVegaWalletConfig();
if (!vegaWalletConfig || connectors.length === 0) {
if (connectors.length === 0) {
// Prevent loading when the connectors are not initialized
return <SplashLoader />;
}
if (
!VEGA_URL ||
!VEGA_WALLET_URL ||
!VEGA_EXPLORER_URL ||
!DocsLinks ||
!CHROME_EXTENSION_URL ||
!MOZILLA_EXTENSION_URL ||
!vegaChainId
) {
return null;
}
return (
<Web3Provider connectors={connectors}>
<Web3Connector connectors={connectors} chainId={Number(chainId)}>
<WalletProvider config={vegaWalletConfig}>
<VegaWalletProvider
config={{
network: VEGA_ENV,
vegaUrl: VEGA_URL,
chainId: vegaChainId,
vegaWalletServiceUrl: VEGA_WALLET_URL,
links: {
explorer: VEGA_EXPLORER_URL,
concepts: DocsLinks?.VEGA_WALLET_CONCEPTS_URL,
chromeExtensionUrl: CHROME_EXTENSION_URL,
mozillaExtensionUrl: MOZILLA_EXTENSION_URL,
},
}}
>
<ContractsProvider>
<AppLoader>
<BalanceManager>
<>
<AppLayout>
<TemplateSidebar
sidebar={
<>
<EthWallet />
<VegaWallet />
</>
}
>
<TemplateSidebar sidebar={sideBar}>
<AppRouter />
</TemplateSidebar>
<footer className="p-4 break-all border-t border-neutral-700">
@@ -166,7 +211,7 @@ const Web3Container = ({
</BalanceManager>
</AppLoader>
</ContractsProvider>
</WalletProvider>
</VegaWalletProvider>
</Web3Connector>
</Web3Provider>
);
@@ -186,9 +231,20 @@ const ScrollToTop = () => {
return null;
};
const removeQueryParams = (url: string) => {
return url.split('?')[0];
};
const AppContainer = () => {
const { config, loading, error } = useEthereumConfig();
const { VEGA_URL, ETHEREUM_PROVIDER_URL } = useEnvironment();
const {
VEGA_ENV,
VEGA_URL,
GIT_COMMIT_HASH,
GIT_BRANCH,
ETHEREUM_PROVIDER_URL,
} = useEnvironment();
const [telemetryOn] = useLocalStorage(TELEMETRY_ON);
const { t } = useTranslation();
const [nodeSwitcherOpen, setNodeSwitcher] = useNodeSwitcherStore((store) => [
store.dialogOpen,
@@ -198,7 +254,70 @@ const AppContainer = () => {
// Hacky skip all the loading & web3 init for geo restricted users
const isRestricted = document?.location?.pathname?.includes('/restricted');
useSentryInit();
useEffect(() => {
if (ENV.dsn && telemetryOn === 'true') {
Sentry.init({
dsn: ENV.dsn,
tracesSampleRate: 0.1,
enabled: true,
environment: VEGA_ENV,
release: GIT_COMMIT_HASH,
beforeSend(event, hint) {
const error = hint?.originalException;
const errorIsString = typeof error === 'string';
const errorIsObject = error instanceof Error;
const requestUrl = event.request?.url;
const transaction = event.transaction;
if (
(errorIsString && isPartyNotFoundError({ message: error })) ||
(errorIsObject && isPartyNotFoundError(error))
) {
// This error is caused by a pubkey making an API request before
// it has interacted with the chain. This isn't needed in Sentry.
return null;
}
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('commit', GIT_COMMIT_HASH);
} else {
Sentry.close();
}
}, [GIT_COMMIT_HASH, GIT_BRANCH, VEGA_ENV, telemetryOn]);
if (isRestricted) {
return (
@@ -240,15 +359,6 @@ const AppContainer = () => {
);
};
const InitializeHandlers = () => {
useVegaTransactionManager();
useVegaTransactionUpdater();
useEthTransactionManager();
useEthTransactionUpdater();
useEthWithdrawApprovalsManager();
return null;
};
function App() {
useInitializeEnv();
@@ -7,7 +7,7 @@ import { useGetAssociationBreakdown } from '../../hooks/use-get-association-brea
import { useGetUserBalances } from '../../hooks/use-get-user-balances';
import { useBalances } from '../../lib/balances/balances-store';
import type { ReactElement } from 'react';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useListenForStakingEvents as useListenForAssociationEvents } from '../../hooks/use-listen-for-staking-events';
import { useTranches } from '../../lib/tranches/tranches-store';
import { useUserTrancheBalances } from '../../routes/redemption/hooks';
@@ -1,13 +1,18 @@
import { Button } from '@vegaprotocol/ui-toolkit';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useDialogStore } from '@vegaprotocol/wallet-react';
import { useVegaWalletDialogStore } from '@vegaprotocol/wallet';
export const ConnectToVega = () => {
const { t } = useTranslation();
const openVegaWalletDialog = useDialogStore((store) => store.open);
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
openVegaWalletDialog: store.openVegaWalletDialog,
}));
return (
<Button
onClick={openVegaWalletDialog}
onClick={() => {
openVegaWalletDialog();
}}
data-testid="connect-to-vega-wallet-btn"
variant="primary"
>
@@ -1,44 +0,0 @@
import {
useLinks,
DApp,
CONSOLE_REWARDS_PAGE,
} from '@vegaprotocol/environment';
import {
ExternalLink,
Intent,
NotificationBanner,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { Trans } from 'react-i18next';
import { useMatch } from 'react-router-dom';
import Routes from '../../routes/routes';
import { type ReactNode } from 'react';
const ConsoleRewardsLink = ({ children }: { children: ReactNode }) => {
const consoleLink = useLinks(DApp.Console);
return (
<ExternalLink
href={consoleLink(CONSOLE_REWARDS_PAGE)}
className="underline inline-flex gap-1 items-center"
title="Rewards in Console"
>
<span>{children}</span>
<VegaIcon size={12} name={VegaIconNames.OPEN_EXTERNAL} />
</ExternalLink>
);
};
export const RewardsMovedNotification = () => {
const onRewardsPage = useMatch(Routes.REWARDS);
if (!onRewardsPage) return null;
return (
<NotificationBanner intent={Intent.Warning}>
<Trans
i18nKey="rewardsMovedNotification"
components={[<ConsoleRewardsLink>Console</ConsoleRewardsLink>]}
/>
</NotificationBanner>
);
};
@@ -1,5 +1,5 @@
import classNames from 'classnames';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import type { ReactNode } from 'react';
import { AnnouncementBanner } from '@vegaprotocol/announcements';
import { Nav } from '../nav';
@@ -10,7 +10,6 @@ import {
ProtocolUpgradeProposalNotification,
} from '@vegaprotocol/proposals';
import { ViewingAsBanner } from '@vegaprotocol/ui-toolkit';
import { RewardsMovedNotification } from '../notifications/rewards-moved-notification';
interface AppLayoutProps {
children: ReactNode;
@@ -46,10 +45,8 @@ export const AppLayout = ({ children }: AppLayoutProps) => {
const NotificationsContainer = () => {
const { isReadOnly, pubKey, disconnect } = useVegaWallet();
return (
<div data-testid="banners">
<RewardsMovedNotification />
<ProtocolUpgradeProposalNotification
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
/>
@@ -1,8 +1,8 @@
import { Children, type ReactNode } from 'react';
import React from 'react';
export interface TemplateSidebarProps {
children: ReactNode;
sidebar: ReactNode;
children: React.ReactNode;
sidebar: React.ReactNode[];
}
export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
@@ -12,9 +12,9 @@ export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
{children}
</main>
<aside className="col-start-2 row-start-1 row-span-2 hidden lg:block p-4 bg-banner bg-contain border-l border-neutral-700">
{Children.map(sidebar, (child, i) => (
{sidebar.map((Component, i) => (
<section className="mb-4 last:mb-0" key={i}>
{child}
{Component}
</section>
))}
</aside>
@@ -1,5 +1,5 @@
import { Button } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet, useDialogStore } from '@vegaprotocol/wallet-react';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import React from 'react';
import { useTranslation } from 'react-i18next';
@@ -10,7 +10,9 @@ interface VegaWalletContainerProps {
export const VegaWalletContainer = ({ children }: VegaWalletContainerProps) => {
const { t } = useTranslation();
const { pubKey } = useVegaWallet();
const openVegaWalletDialog = useDialogStore((store) => store.open);
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
openVegaWalletDialog: store.openVegaWalletDialog,
}));
if (!pubKey) {
return (
@@ -9,7 +9,7 @@ import Routes from '../../routes/routes';
export const RiskMessage = () => {
return (
<>
<div className="bg-vega-light-100 dark:bg-vega-dark-100 p-6">
<div className="bg-vega-light-100 dark:bg-vega-dark-100 p-6 mb-6">
<ul className="list-[square] ml-4">
<li>
{t(
@@ -23,7 +23,7 @@ export const RiskMessage = () => {
</li>
</ul>
</div>
<p>
<p className="mb-8">
{t(
'By using the Vega Governance App, you acknowledge that you have read and understood the'
)}{' '}
@@ -1,39 +1,25 @@
import {
ConnectDialogWithRiskAck,
useDialogStore,
} from '@vegaprotocol/wallet-react';
VegaConnectDialog,
VegaManageDialog,
ViewAsDialog,
} from '@vegaprotocol/wallet';
import {
AppStateActionType,
useAppState,
} from '../../contexts/app-state/app-state-context';
import { useConnectors } from '../../lib/vega-connectors';
import { RiskMessage } from './risk-message';
import { VegaManageDialog } from '../manage-dialog';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { Networks, useEnvironment } from '@vegaprotocol/environment';
export const VegaWalletDialogs = () => {
const { VEGA_ENV } = useEnvironment();
const { appState, appDispatch } = useAppState();
const [riskAccepted, setRiskAccepted] = useLocalStorage(
'vega_wallet_risk_accepted'
);
const vegaWalletDialogOpen = useDialogStore((store) => store.isOpen);
const setVegaWalletDialog = useDialogStore((store) => store.set);
const connectors = useConnectors();
return (
<>
<ConnectDialogWithRiskAck
open={vegaWalletDialogOpen}
onChange={setVegaWalletDialog}
riskAccepted={
VEGA_ENV === Networks.TESTNET ? riskAccepted === 'true' : true
}
riskAckContent={<RiskMessage />}
onRiskAccepted={() => setRiskAccepted('true')}
onRiskRejected={() => {
setRiskAccepted('false');
setVegaWalletDialog(false);
}}
<VegaConnectDialog
connectors={connectors}
riskMessage={<RiskMessage />}
/>
<VegaManageDialog
dialogOpen={appState.vegaWalletManageOverlay}
setDialogOpen={(open) =>
@@ -43,6 +29,8 @@ export const VegaWalletDialogs = () => {
})
}
/>
<ViewAsDialog connector={connectors.view} />
</>
);
};
@@ -4,13 +4,14 @@ import keyBy from 'lodash/keyBy';
import uniq from 'lodash/uniq';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { ENV } from '../../config';
import noIcon from '../../images/token-no-icon.png';
import vegaBlack from '../../images/vega_black.png';
import vegaVesting from '../../images/vega_vesting.png';
import { BigNumber } from '../../lib/bignumber';
import { type WalletCardAssetProps } from '../wallet-card';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useContracts } from '../../contexts/contracts/contracts-context';
import * as Schema from '@vegaprotocol/types';
import {
@@ -36,6 +37,7 @@ export const usePollForDelegations = () => {
const { t } = useTranslation();
const { pubKey } = useVegaWallet();
const client = useApolloClient();
const { delegationsPagination } = ENV;
const [delegations, setDelegations] = React.useState<
WalletDelegationFieldsFragment[]
>([]);
@@ -66,9 +68,11 @@ export const usePollForDelegations = () => {
query: DelegationsDocument,
variables: {
partyId: pubKey,
delegationsPagination: {
first: 50,
},
delegationsPagination: delegationsPagination
? {
first: Number(delegationsPagination),
}
: undefined,
},
fetchPolicy: 'network-only',
})
@@ -232,14 +236,14 @@ export const usePollForDelegations = () => {
// will just continue to fail
clearInterval(interval);
});
}, 20000);
}, 1000);
}
return () => {
clearInterval(interval);
mounted = false;
};
}, [client, decimals, pubKey, t, vegaToken.address]);
}, [delegationsPagination, client, decimals, pubKey, t, vegaToken.address]);
return { delegations, currentStakeAvailable, delegatedNodes, accounts };
};
@@ -1,11 +1,11 @@
import { ButtonLink, Link } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next';
import { ExternalLinks } from '@vegaprotocol/environment';
import { useConnect } from '@vegaprotocol/wallet-react';
import { useViewAsDialog } from '@vegaprotocol/wallet';
export const VegaWalletPrompt = () => {
const { t } = useTranslation();
const { connect } = useConnect();
const setViewAsDialog = useViewAsDialog((state) => state.setOpen);
return (
<>
<h3 className="mt-4 mb-2">{t('getWallet')}</h3>
@@ -16,7 +16,7 @@ export const VegaWalletPrompt = () => {
<ButtonLink
className="text-neutral-500"
data-testid="view-as-user"
onClick={() => connect('viewParty')}
onClick={() => setViewAsDialog(true)}
>
{t('viewAsParty')}
</ButtonLink>
@@ -25,7 +25,7 @@ import {
} from '../wallet-card';
import { VegaWalletPrompt } from './vega-wallet-prompt';
import { usePollForDelegations } from './hooks';
import { useVegaWallet, useDialogStore } from '@vegaprotocol/wallet-react';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { Button, ButtonLink } from '@vegaprotocol/ui-toolkit';
import { toBigNum } from '@vegaprotocol/utils';
import { usePendingBalancesStore } from '../../hooks/use-pending-balances-manager';
@@ -34,17 +34,16 @@ import omit from 'lodash/omit';
export const VegaWallet = () => {
const { t } = useTranslation();
const { status, pubKey, pubKeys } = useVegaWallet();
const { pubKey, pubKeys } = useVegaWallet();
const pubKeyObj = useMemo(() => {
return pubKeys?.find((pk) => pk.publicKey === pubKey);
}, [pubKey, pubKeys]);
const child =
status === 'connected' ? (
<VegaWalletConnected vegaKeys={pubKeys.map((pk) => pk.publicKey)} />
) : (
<VegaWalletNotConnected />
);
const child = !pubKeys ? (
<VegaWalletNotConnected />
) : (
<VegaWalletConnected vegaKeys={pubKeys.map((pk) => pk.publicKey)} />
);
return (
<section className="vega-wallet" data-testid="vega-wallet">
@@ -76,7 +75,9 @@ export const VegaWallet = () => {
const VegaWalletNotConnected = () => {
const { t } = useTranslation();
const openVegaWalletDialog = useDialogStore((store) => store.open);
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
openVegaWalletDialog: store.openVegaWalletDialog,
}));
return (
<>
<Button
+1
View File
@@ -65,6 +65,7 @@ export const ENV = {
docsUrl: windowOrDefault('NX_VEGA_DOCS_URL'),
ethWalletMnemonic: windowOrDefault('NX_ETH_WALLET_MNEMONIC'),
localProviderUrl: windowOrDefault('NX_LOCAL_PROVIDER_URL'),
delegationsPagination: windowOrDefault('NX_DELEGATIONS_PAGINATION'),
rest: windowOrDefault('NX_VEGA_REST_URL'),
addresses:
ContractAddresses[(envName === 'local' ? 'CUSTOM' : envName) as Networks],
@@ -111,4 +111,3 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
</ContractsContext.Provider>
);
};
ContractsProvider.displayName = 'ContractsProvider';
@@ -7,7 +7,7 @@ import { useWeb3React } from '@web3-react/core';
export const useListenForStakingEvents = (
contract: Contract | undefined,
vegaPublicKey: string | undefined,
vegaPublicKey: string | null,
numberOfConfirmations: number
) => {
const { account } = useWeb3React();
@@ -1,6 +1,6 @@
import * as Sentry from '@sentry/react';
import { toBigNum } from '@vegaprotocol/utils';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useEthereumConfig } from '@vegaprotocol/web3';
import React from 'react';
@@ -1,81 +0,0 @@
import { useEffect } from 'react';
import * as Sentry from '@sentry/react';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { TELEMETRY_ON } from '../components/telemetry-dialog/telemetry-dialog';
import { useEnvironment } from '@vegaprotocol/environment';
import { ENV } from '../config';
import { isPartyNotFoundError } from '../lib/party';
export const useSentryInit = () => {
const { VEGA_ENV, GIT_COMMIT_HASH, GIT_BRANCH } = useEnvironment();
const [telemetryOn] = useLocalStorage(TELEMETRY_ON);
useEffect(() => {
if (ENV.dsn && telemetryOn === 'true') {
Sentry.init({
dsn: ENV.dsn,
tracesSampleRate: 0.1,
enabled: true,
environment: VEGA_ENV,
release: GIT_COMMIT_HASH,
beforeSend(event, hint) {
const error = hint?.originalException;
const errorIsString = typeof error === 'string';
const errorIsObject = error instanceof Error;
const requestUrl = event.request?.url;
const transaction = event.transaction;
if (
(errorIsString && isPartyNotFoundError({ message: error })) ||
(errorIsObject && isPartyNotFoundError(error))
) {
// This error is caused by a pubkey making an API request before
// it has interacted with the chain. This isn't needed in Sentry.
return null;
}
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('commit', GIT_COMMIT_HASH);
} else {
Sentry.close();
}
}, [GIT_COMMIT_HASH, GIT_BRANCH, VEGA_ENV, telemetryOn]);
};
const removeQueryParams = (url: string) => {
return url.split('?')[0];
};
@@ -1,41 +0,0 @@
import { useMemo } from 'react';
import {
InjectedConnector,
JsonRpcConnector,
SnapConnector,
ViewPartyConnector,
createConfig,
fairground,
stagnet,
mainnet,
} from '@vegaprotocol/wallet';
import { useEnvironment } from '@vegaprotocol/environment';
export const useVegaWalletConfig = () => {
const { VEGA_ENV, VEGA_URL, VEGA_WALLET_URL } = useEnvironment();
return useMemo(() => {
if (!VEGA_ENV || !VEGA_URL || !VEGA_WALLET_URL) return;
const injected = new InjectedConnector();
const jsonRpc = new JsonRpcConnector({
url: VEGA_WALLET_URL,
});
const snap = new SnapConnector({
node: new URL(VEGA_URL).origin,
snapId: 'npm:@vegaprotocol/snap',
version: '1.0.1',
});
const viewParty = new ViewPartyConnector();
const config = createConfig({
chains: [mainnet, fairground, stagnet],
defaultChainId: fairground.id,
connectors: [injected, snap, jsonRpc, viewParty],
});
return config;
}, [VEGA_ENV, VEGA_URL, VEGA_WALLET_URL]);
};
+1 -1
View File
@@ -31,7 +31,7 @@ i18n
load: 'languageOnly',
debug: isInDev,
// have a common namespace used around the full app
ns: ['governance', 'wallet', 'wallet-react'],
ns: ['governance'],
defaultNS: 'governance',
keySeparator: false, // we use content as keys
nsSeparator: false,
@@ -0,0 +1,30 @@
import { useFeatureFlags } from '@vegaprotocol/environment';
import { useMemo } from 'react';
import {
JsonRpcConnector,
ViewConnector,
InjectedConnector,
SnapConnector,
DEFAULT_SNAP_ID,
} from '@vegaprotocol/wallet';
const urlParams = new URLSearchParams(window.location.search);
export const jsonRpc = new JsonRpcConnector();
export const injected = new InjectedConnector();
export const view = new ViewConnector(urlParams.get('address'));
export const snap = new SnapConnector(DEFAULT_SNAP_ID);
export const useConnectors = () => {
const featureFlags = useFeatureFlags((state) => state.flags);
return useMemo(
() => ({
injected,
jsonRpc,
view,
snap: featureFlags.METAMASK_SNAPS ? snap : undefined,
}),
[featureFlags.METAMASK_SNAPS]
);
};
@@ -5,6 +5,7 @@ import {
ProposalState,
VoteValue,
} from '@vegaprotocol/types';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import {
generateNoVotes,
@@ -15,7 +16,9 @@ import { ProposalHeader, NewTransferSummary } from './proposal-header';
import {
lastWeek,
nextWeek,
mockWalletContext,
createUserVoteQueryMock,
networkParamsQueryMock,
} from '../../test-helpers/mocks';
import { BrowserRouter } from 'react-router-dom';
import { VoteState } from '../vote-details/use-user-vote';
@@ -42,12 +45,14 @@ const renderComponent = (
render(
<AppStateProvider>
<BrowserRouter>
<MockedProvider mocks={mocks}>
<ProposalHeader
proposal={proposal}
isListItem={isListItem}
voteState={voteState}
/>
<MockedProvider mocks={[networkParamsQueryMock, ...mocks]}>
<VegaWalletContext.Provider value={mockWalletContext}>
<ProposalHeader
proposal={proposal}
isListItem={isListItem}
voteState={voteState}
/>
</VegaWalletContext.Provider>
</MockedProvider>
</BrowserRouter>
</AppStateProvider>
@@ -155,7 +160,7 @@ describe('Proposal header', () => {
screen.queryByTestId('proposal-description')
).not.toBeInTheDocument();
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
/Update to market: MarketId/
'Update to market: MarketId'
);
});
@@ -38,7 +38,6 @@ import { differenceInHours, format, formatDistanceToNowStrict } from 'date-fns';
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
import { MarketName } from '../proposal/market-name';
import { Indicator } from '../proposal/indicator';
import { type ProposalNode } from '../proposal/proposal-utils';
const ProposalTypeTags = ({
proposal,
@@ -541,12 +540,10 @@ const BatchProposalStateText = ({
export const ProposalHeader = ({
proposal,
restData,
isListItem = true,
voteState,
}: {
proposal: Proposal | BatchProposal;
restData?: ProposalNode | null;
isListItem?: boolean;
voteState?: VoteState | null;
}) => {
@@ -598,7 +595,7 @@ export const ProposalHeader = ({
)}
</div>
<ProposalDetails proposal={proposal} />
<VoteBreakdown proposal={proposal} restData={restData} />
<VoteBreakdown proposal={proposal} />
</>
);
};
@@ -91,28 +91,6 @@ export type ProposalNode = {
proposal: ProposalData;
proposalType: ProposalNodeType;
proposals: SubProposalData[];
yes?: [
{
partyId: string;
elsPerMarket?: [
{
marketId: string;
els: string;
}
];
}
];
no?: [
{
partyId: string;
elsPerMarket?: [
{
marketId: string;
els: string;
}
];
}
];
};
type SingleProposalNode = ProposalNode & {
@@ -1,12 +1,12 @@
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import { VegaWalletProvider } from '@vegaprotocol/wallet';
import { type VegaWalletConfig } from '@vegaprotocol/wallet';
import { render, screen } from '@testing-library/react';
import { generateProposal } from '../../test-helpers/generate-proposals';
import { Proposal } from './proposal';
import { ProposalState } from '@vegaprotocol/types';
import { type Proposal as IProposal } from '../../types';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
jest.mock('@vegaprotocol/network-parameters', () => ({
...jest.requireActual('@vegaprotocol/network-parameters'),
@@ -24,44 +24,45 @@ jest.mock('@vegaprotocol/network-parameters', () => ({
error: null,
})),
}));
jest.mock('../proposal-detail-header/proposal-header', () => ({
ProposalHeader: () => <div data-testid="proposal-header"></div>,
}));
jest.mock('../proposal-change-table', () => ({
ProposalChangeTable: () => <div data-testid="proposal-change-table"></div>,
}));
jest.mock('../proposal-json', () => ({
ProposalJson: () => <div data-testid="proposal-json"></div>,
}));
jest.mock('../list-asset', () => ({
ListAsset: () => <div data-testid="proposal-list-asset"></div>,
}));
jest.mock('../list-asset', () => ({
ListAsset: () => <div data-testid="proposal-list-asset"></div>,
}));
jest.mock('../vote-details', () => ({
UserVote: () => <div data-testid="user-vote"></div>,
}));
jest.mock('./proposal-change-details', () => ({
ProposalChangeDetails: () => <div data-testid="proposal-change-details" />,
ProposalChangeDetails: () => (
<div data-testid="proposal-change-details"></div>
),
}));
const vegaWalletConfig: VegaWalletConfig = {
network: 'TESTNET',
vegaUrl: 'https://vega.xyz',
vegaWalletServiceUrl: 'https://wallet.vega.xyz',
links: {
explorer: 'explorer',
concepts: 'concepts',
chromeExtensionUrl: 'chrome',
mozillaExtensionUrl: 'mozilla',
},
chainId: 'VEGA_CHAIN_ID',
};
const renderComponent = (proposal: IProposal) => {
return render(
render(
<MemoryRouter>
<MockedProvider>
<MockedWalletProvider>
<AppStateProvider>
<Proposal restData={null} proposal={proposal} />
</AppStateProvider>
</MockedWalletProvider>
<VegaWalletProvider config={vegaWalletConfig}>
<Proposal restData={null} proposal={proposal} />
</VegaWalletProvider>
</MockedProvider>
</MemoryRouter>
);
@@ -23,7 +23,7 @@ export interface ProposalProps {
export const Proposal = ({ proposal, restData }: ProposalProps) => {
const { t } = useTranslation();
const { submit, finalizedVote, transaction } = useVoteSubmit();
const { submit, Dialog, finalizedVote, transaction } = useVoteSubmit();
const { voteState, voteDatetime } = useUserVote(proposal?.id, finalizedVote);
return (
@@ -48,7 +48,6 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
<ProposalHeader
proposal={proposal}
restData={restData}
isListItem={false}
voteState={voteState}
/>
@@ -89,6 +88,7 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
<UserVote
proposal={proposal}
submit={submit}
dialog={Dialog}
transaction={transaction}
voteState={voteState}
voteDatetime={voteDatetime}
@@ -1,14 +1,15 @@
import { BrowserRouter as Router } from 'react-router-dom';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { render, screen } from '@testing-library/react';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { ProposalsListItemDetails } from './proposals-list-item-details';
import { mockWalletContext } from '../../test-helpers/mocks';
const renderComponent = (id: string) =>
render(
<Router>
<AppStateProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<ProposalsListItemDetails id={id} />
</AppStateProvider>
</VegaWalletContext.Provider>
</Router>
);
@@ -1,32 +1,25 @@
import { act, render, screen } from '@testing-library/react';
import {
MockedWalletProvider,
mockConfig,
} from '@vegaprotocol/wallet-react/testing';
import { render, screen } from '@testing-library/react';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { ProposalFormSubmit } from './proposal-form-submit';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
const renderComponent = (isSubmitting: boolean) => {
const renderComponent = (
context: VegaWalletContextShape,
isSubmitting: boolean
) => {
render(
<MockedWalletProvider>
<AppStateProvider>
<AppStateProvider>
<VegaWalletContext.Provider value={context}>
<ProposalFormSubmit isSubmitting={isSubmitting} />
</AppStateProvider>
</MockedWalletProvider>
</VegaWalletContext.Provider>
</AppStateProvider>
);
};
describe('Proposal Form Submit', () => {
const pubKey = { publicKey: '123456__123456', name: 'test' };
afterEach(() => {
act(() => {
mockConfig.reset();
});
});
it('should display connection message and button if wallet not connected', () => {
renderComponent(false);
renderComponent({ pubKey: null } as VegaWalletContextShape, false);
expect(
screen.getByText('Connect your wallet to submit a proposal')
@@ -37,22 +30,28 @@ describe('Proposal Form Submit', () => {
});
it('should display submit button if wallet is connected', () => {
mockConfig.store.setState({
pubKey: pubKey.publicKey,
keys: [pubKey],
});
renderComponent(false);
const pubKey = { publicKey: '123456__123456', name: 'test' };
renderComponent(
{
pubKey: pubKey.publicKey,
pubKeys: [pubKey],
} as VegaWalletContextShape,
false
);
expect(screen.getByTestId('proposal-submit')).toHaveTextContent(
'Submit proposal'
);
});
it('should display submitting button text if wallet is connected and submitting', () => {
mockConfig.store.setState({
pubKey: pubKey.publicKey,
keys: [pubKey],
});
renderComponent(true);
const pubKey = { publicKey: '123456__123456', name: 'test' };
renderComponent(
{
pubKey: pubKey.publicKey,
pubKeys: [pubKey],
} as VegaWalletContextShape,
true
);
expect(screen.getByTestId('proposal-submit')).toHaveTextContent(
'Submitting proposal'
);
@@ -1,6 +1,6 @@
import { useTranslation } from 'react-i18next';
import { Button } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { VegaWalletContainer } from '../../../../components/vega-wallet-container';
interface ProposalFormSubmitProps {
@@ -1,24 +1,19 @@
import {
VegaTransactionDialog,
getProposalDialogIcon,
getProposalDialogIntent,
useGetProposalDialogTitle,
} from '@vegaprotocol/proposals';
import type {
ProposalEventFieldsFragment,
VegaTxState,
} from '@vegaprotocol/proposals';
import type { ProposalEventFieldsFragment } from '@vegaprotocol/proposals';
import type { DialogProps } from '@vegaprotocol/proposals';
interface ProposalFormTransactionDialogProps {
finalizedProposal: ProposalEventFieldsFragment | null;
transaction: VegaTxState;
onChange: (open: boolean) => void;
TransactionDialog: (props: DialogProps) => JSX.Element;
}
export const ProposalFormTransactionDialog = ({
finalizedProposal,
transaction,
onChange,
TransactionDialog,
}: ProposalFormTransactionDialogProps) => {
const title = useGetProposalDialogTitle(finalizedProposal?.state);
// Render a custom complete UI if the proposal was rejected otherwise
@@ -29,16 +24,13 @@ export const ProposalFormTransactionDialog = ({
return (
<div data-testid="proposal-transaction-dialog">
<VegaTransactionDialog
<TransactionDialog
title={title}
intent={getProposalDialogIntent(finalizedProposal?.state)}
icon={getProposalDialogIcon(finalizedProposal?.state)}
content={{
Complete: completeContent,
}}
transaction={transaction}
isOpen={transaction.dialogOpen}
onChange={onChange}
/>
</div>
);
@@ -1,8 +1,10 @@
import { render, screen } from '@testing-library/react';
import { BrowserRouter as Router } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import {
lastWeek,
mockWalletContext,
networkParamsQueryMock,
nextWeek,
} from '../../test-helpers/mocks';
@@ -46,7 +48,9 @@ const renderComponent = (
render(
<Router>
<MockedProvider mocks={mocks}>
<VoteBreakdown proposal={proposal} />
<VegaWalletContext.Provider value={mockWalletContext}>
<VoteBreakdown proposal={proposal} />
</VegaWalletContext.Provider>
</MockedProvider>
</Router>
);
@@ -56,7 +60,6 @@ describe('VoteBreakdown', () => {
jest.useFakeTimers();
jest.setSystemTime(0);
});
afterAll(() => {
jest.useRealTimers();
});
@@ -17,7 +17,6 @@ import {
import { useBatchVoteInformation } from '../../hooks/use-vote-information';
import { MarketName } from '../proposal/market-name';
import { Indicator } from '../proposal/indicator';
import { type ProposalNode } from '../proposal/proposal-utils';
export const CompactVotes = ({ number }: { number: BigNumber }) => (
<CompactNumber
@@ -111,64 +110,24 @@ const Status = ({ reached, threshold, text, testId }: StatusProps) => {
export const VoteBreakdown = ({
proposal,
restData,
}: {
proposal: Proposal | BatchProposal;
restData?: ProposalNode | null;
}) => {
if (proposal.__typename === 'Proposal') {
return <VoteBreakdownNormal proposal={proposal} />;
}
if (proposal.__typename === 'BatchProposal') {
return <VoteBreakdownBatch proposal={proposal} restData={restData} />;
return <VoteBreakdownBatch proposal={proposal} />;
}
return null;
};
const VoteBreakdownBatch = ({
proposal,
restData,
}: {
proposal: BatchProposal;
restData?: ProposalNode | null;
}) => {
const VoteBreakdownBatch = ({ proposal }: { proposal: BatchProposal }) => {
const [fullBreakdown, setFullBreakdown] = useState(false);
const { t } = useTranslation();
const yesELS =
restData?.yes?.reduce((all, y) => {
if (y.elsPerMarket) {
y.elsPerMarket.forEach((item) => {
const share = Number(item.els);
if (all[item.marketId]) {
all[item.marketId].push(share);
} else {
all[item.marketId] = [share];
}
return all;
});
}
return all;
}, {} as Record<string, number[]>) || {};
const noELS =
restData?.no?.reduce((all, y) => {
if (y.elsPerMarket) {
y.elsPerMarket.forEach((item) => {
const share = Number(item.els);
if (all[item.marketId]) {
all[item.marketId].push(share);
} else {
all[item.marketId] = [share];
}
return all;
});
}
return all;
}, {} as Record<string, number[]>) || {};
const voteInfo = useBatchVoteInformation({
terms: compact(
proposal.subProposals ? proposal.subProposals.map((p) => p?.terms) : []
@@ -235,8 +194,6 @@ const VoteBreakdownBatch = ({
proposal={proposal}
votes={proposal.votes}
terms={p.terms}
yesELS={yesELS}
noELS={noELS}
/>
);
})}
@@ -297,8 +254,6 @@ const VoteBreakdownBatch = ({
proposal={proposal}
votes={proposal.votes}
terms={p.terms}
yesELS={yesELS}
noELS={noELS}
/>
);
})}
@@ -316,17 +271,17 @@ const VoteBreakdownBatchSubProposal = ({
votes,
terms,
indicator,
yesELS,
noELS,
}: {
proposal: BatchProposal;
votes: VoteFieldsFragment;
terms: ProposalTermsFieldsFragment;
indicator?: number;
yesELS: Record<string, number[]>;
noELS: Record<string, number[]>;
}) => {
const { t } = useTranslation();
const voteInfo = useVoteInformation({
votes,
terms,
});
const isProposalOpen = proposal?.state === ProposalState.STATE_OPEN;
const isUpdateMarket = terms?.change?.__typename === 'UpdateMarket';
@@ -339,15 +294,6 @@ const VoteBreakdownBatchSubProposal = ({
marketId = terms.change.market.id;
}
const voteInfo = useVoteInformation({
votes,
terms,
// yes votes ELS for this specific proposal (market)
yesELS: marketId ? yesELS[marketId] : undefined,
// no votes ELS for this specific proposal (market)
noELS: marketId ? noELS[marketId] : undefined,
});
const marketName = marketId ? (
<>
: <MarketName marketId={marketId} />
@@ -1,5 +1,5 @@
import { captureMessage } from '@sentry/minimal';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { VoteValue } from '@vegaprotocol/types';
import { useEffect, useState } from 'react';
import { useUserVoteQuery } from './__generated__/Vote';
@@ -1,19 +1,20 @@
import { useTranslation } from 'react-i18next';
import { Icon, ExternalLink } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { ProposalState } from '@vegaprotocol/types';
import { ConnectToVega } from '../../../../components/connect-to-vega';
import { VoteButtonsContainer } from './vote-buttons';
import { SubHeading } from '../../../../components/heading';
import { type VoteValue } from '@vegaprotocol/types';
import { type VegaTxState } from '@vegaprotocol/proposals';
import { type DialogProps, type VegaTxState } from '@vegaprotocol/proposals';
import { type VoteState } from './use-user-vote';
import { type Proposal, type BatchProposal } from '../../types';
interface UserVoteProps {
proposal: Proposal | BatchProposal;
transaction: VegaTxState;
transaction: VegaTxState | null;
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
dialog: (props: DialogProps) => JSX.Element;
voteState: VoteState | null;
voteDatetime: Date | null;
}
@@ -22,6 +23,7 @@ export const UserVote = ({
proposal,
submit,
transaction,
dialog,
voteState,
voteDatetime,
}: UserVoteProps) => {
@@ -54,6 +56,7 @@ export const UserVote = ({
className="flex"
submit={submit}
transaction={transaction}
dialog={dialog}
/>
)
) : (
@@ -2,20 +2,33 @@ import { render, screen } from '@testing-library/react';
import { VoteTransactionDialog } from './vote-transaction-dialog';
import { VoteState } from './use-user-vote';
import { VegaTxStatus } from '@vegaprotocol/proposals';
import { ConnectorErrors, unknownError } from '@vegaprotocol/wallet';
describe('VoteTransactionDialog', () => {
const mockTransactionDialog = jest.fn(({ title, content }) => (
<div>
<div>{title}</div>
<div>{content?.Complete}</div>
</div>
));
it('renders without crashing', () => {
render(
<VoteTransactionDialog
voteState={VoteState.Yes}
transaction={null}
TransactionDialog={mockTransactionDialog}
/>
);
expect(screen.getByTestId('vote-transaction-dialog')).toBeInTheDocument();
});
it('renders with txRequested title when voteState is Requested', () => {
render(
<VoteTransactionDialog
voteState={VoteState.Requested}
transaction={{
error: null,
txHash: null,
signature: null,
status: VegaTxStatus.Requested,
dialogOpen: true,
}}
transaction={null}
TransactionDialog={mockTransactionDialog}
/>
);
@@ -26,13 +39,8 @@ describe('VoteTransactionDialog', () => {
render(
<VoteTransactionDialog
voteState={VoteState.Pending}
transaction={{
error: null,
txHash: null,
signature: null,
status: VegaTxStatus.Pending,
dialogOpen: true,
}}
transaction={null}
TransactionDialog={mockTransactionDialog}
/>
);
@@ -43,13 +51,8 @@ describe('VoteTransactionDialog', () => {
render(
<VoteTransactionDialog
voteState={VoteState.Yes} // or any other state other than Requested or Pending
transaction={{
error: null,
txHash: null,
signature: null,
status: VegaTxStatus.Complete,
dialogOpen: true,
}}
transaction={null}
TransactionDialog={mockTransactionDialog}
/>
);
@@ -62,18 +65,17 @@ describe('VoteTransactionDialog', () => {
<VoteTransactionDialog
voteState={VoteState.Failed}
transaction={{
error: unknownError(),
error: { message: 'Custom error test message', name: 'blah' },
txHash: null,
signature: null,
status: VegaTxStatus.Error,
dialogOpen: true,
dialogOpen: false,
}}
TransactionDialog={mockTransactionDialog}
/>
);
expect(
screen.getByText(ConnectorErrors.unknown.message)
).toBeInTheDocument();
expect(screen.getByText('Custom error test message')).toBeInTheDocument();
});
it('renders default error message when voteState is failed and no error message exists on the tx', () => {
@@ -84,9 +86,10 @@ describe('VoteTransactionDialog', () => {
error: null,
txHash: null,
signature: null,
status: VegaTxStatus.Complete,
dialogOpen: true,
status: VegaTxStatus.Error,
dialogOpen: false,
}}
TransactionDialog={mockTransactionDialog}
/>
);
@@ -97,13 +100,8 @@ describe('VoteTransactionDialog', () => {
render(
<VoteTransactionDialog
voteState={VoteState.Yes}
transaction={{
error: null,
txHash: null,
signature: null,
status: VegaTxStatus.Default,
dialogOpen: true,
}}
transaction={null}
TransactionDialog={mockTransactionDialog}
/>
);
@@ -1,77 +1,120 @@
import { act, fireEvent, render, screen } from '@testing-library/react';
import { fireEvent, render, screen } from '@testing-library/react';
import BigNumber from 'bignumber.js';
import { VoteButtons, type VoteButtonsProps } from './vote-buttons';
import { VoteButtons } from './vote-buttons';
import { VoteState } from './use-user-vote';
import { ProposalState } from '@vegaprotocol/types';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { mockWalletContext } from '../../test-helpers/mocks';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { MockedProvider } from '@apollo/react-testing';
import { VegaTxStatus } from '@vegaprotocol/proposals';
import {
MockedWalletProvider,
mockConfig,
} from '@vegaprotocol/wallet-react/testing';
describe('Vote buttons', () => {
const key = { publicKey: '0x123', name: 'key 1' };
const transaction = {
status: VegaTxStatus.Default,
error: null,
txHash: null,
signature: null,
dialogOpen: false,
};
const props = {
voteState: VoteState.NotCast,
voteDatetime: null,
proposalState: ProposalState.STATE_OPEN,
proposalId: null,
minVoterBalance: null,
spamProtectionMinTokens: null,
currentStakeAvailable: new BigNumber(1),
submit: () => Promise.resolve(),
transaction,
};
const renderComponent = (testProps?: Partial<VoteButtonsProps>) => {
return render(
it('should render successfully', () => {
const { baseElement } = render(
<AppStateProvider>
<MockedProvider>
<MockedWalletProvider>
<VoteButtons {...props} {...testProps} />
</MockedWalletProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<VoteButtons
voteState={VoteState.NotCast}
voteDatetime={null}
proposalState={ProposalState.STATE_OPEN}
proposalId={null}
minVoterBalance={null}
spamProtectionMinTokens={null}
currentStakeAvailable={new BigNumber(1)}
dialog={() => <div>Blah</div>}
submit={() => Promise.resolve()}
transaction={null}
/>
</VegaWalletContext.Provider>
</MockedProvider>
</AppStateProvider>
);
};
beforeEach(() => {
mockConfig.store.setState({ pubKey: key.publicKey, keys: [key] });
});
afterEach(() => {
act(() => {
mockConfig.reset();
});
});
it('should render successfully', () => {
const { baseElement } = renderComponent();
expect(baseElement).toBeTruthy();
});
it('should explain that voting is closed if the proposal is not open', () => {
renderComponent({ proposalState: ProposalState.STATE_PASSED });
render(
<AppStateProvider>
<MockedProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<VoteButtons
voteState={VoteState.NotCast}
voteDatetime={null}
proposalState={ProposalState.STATE_PASSED}
proposalId={null}
minVoterBalance={null}
spamProtectionMinTokens={null}
currentStakeAvailable={new BigNumber(1)}
dialog={() => <div>Blah</div>}
submit={() => Promise.resolve()}
transaction={null}
/>
</VegaWalletContext.Provider>
</MockedProvider>
</AppStateProvider>
);
expect(screen.getByText('Voting has ended.')).toBeTruthy();
});
it('should provide a connect wallet prompt if no pubkey', () => {
mockConfig.reset();
renderComponent();
const mockWalletNoPubKeyContext = {
pubKey: null,
pubKeys: [],
isReadOnly: false,
sendTx: jest.fn().mockReturnValue(Promise.resolve(null)),
connect: jest.fn(),
disconnect: jest.fn(),
selectPubKey: jest.fn(),
connector: null,
} as unknown as VegaWalletContextShape;
render(
<AppStateProvider>
<MockedProvider>
<VegaWalletContext.Provider value={mockWalletNoPubKeyContext}>
<VoteButtons
voteState={VoteState.NotCast}
voteDatetime={null}
proposalState={ProposalState.STATE_OPEN}
proposalId={null}
minVoterBalance={null}
spamProtectionMinTokens={null}
currentStakeAvailable={new BigNumber(1)}
dialog={() => <div>Blah</div>}
submit={() => Promise.resolve()}
transaction={null}
/>
</VegaWalletContext.Provider>
</MockedProvider>
</AppStateProvider>
);
expect(screen.getByTestId('connect-wallet')).toBeTruthy();
});
it('should tell the user they need tokens if their current stake is 0', () => {
renderComponent({ currentStakeAvailable: new BigNumber(0) });
render(
<AppStateProvider>
<MockedProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<VoteButtons
voteState={VoteState.NotCast}
voteDatetime={null}
proposalState={ProposalState.STATE_OPEN}
proposalId={null}
minVoterBalance={null}
spamProtectionMinTokens={null}
currentStakeAvailable={new BigNumber(0)}
dialog={() => <div>Blah</div>}
submit={() => Promise.resolve()}
transaction={null}
/>
</VegaWalletContext.Provider>
</MockedProvider>
</AppStateProvider>
);
expect(
screen.getByText(
'You need some VEGA tokens to participate in governance.'
@@ -80,10 +123,26 @@ describe('Vote buttons', () => {
});
it('should tell the user of the minimum requirements if they have some, but not enough tokens', () => {
renderComponent({
minVoterBalance: '2000000000000000000',
spamProtectionMinTokens: '1000000000000000000',
});
render(
<AppStateProvider>
<MockedProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<VoteButtons
voteState={VoteState.NotCast}
voteDatetime={null}
proposalState={ProposalState.STATE_OPEN}
proposalId={null}
minVoterBalance="2000000000000000000"
spamProtectionMinTokens="1000000000000000000"
currentStakeAvailable={new BigNumber(1)}
dialog={() => <div>Blah</div>}
submit={() => Promise.resolve()}
transaction={null}
/>
</VegaWalletContext.Provider>
</MockedProvider>
</AppStateProvider>
);
expect(
screen.getByText(
'You must have at least 2 VEGA associated to vote on this proposal'
@@ -92,23 +151,51 @@ describe('Vote buttons', () => {
});
it('should show you voted if vote state is correct, and if the proposal is still open, it will display a change vote button', () => {
renderComponent({
voteState: VoteState.Yes,
minVoterBalance: '2000000000000000000',
spamProtectionMinTokens: '1000000000000000000',
currentStakeAvailable: new BigNumber(10),
});
render(
<AppStateProvider>
<MockedProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<VoteButtons
voteState={VoteState.Yes}
voteDatetime={null}
proposalState={ProposalState.STATE_OPEN}
proposalId={null}
minVoterBalance="2000000000000000000"
spamProtectionMinTokens="1000000000000000000"
currentStakeAvailable={new BigNumber(10)}
dialog={() => <div>Blah</div>}
submit={() => Promise.resolve()}
transaction={null}
/>
</VegaWalletContext.Provider>
</MockedProvider>
</AppStateProvider>
);
expect(screen.getByTestId('you-voted')).toBeInTheDocument();
expect(screen.getByTestId('change-vote-button')).toBeInTheDocument();
});
it('should allow you to change your vote', () => {
renderComponent({
voteState: VoteState.No,
minVoterBalance: '2000000000000000000',
spamProtectionMinTokens: '1000000000000000000',
currentStakeAvailable: new BigNumber(10),
});
render(
<AppStateProvider>
<MockedProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<VoteButtons
voteState={VoteState.No}
voteDatetime={null}
proposalState={ProposalState.STATE_OPEN}
proposalId={null}
minVoterBalance="2000000000000000000"
spamProtectionMinTokens="1000000000000000000"
currentStakeAvailable={new BigNumber(10)}
dialog={() => <div>Blah</div>}
submit={() => Promise.resolve()}
transaction={null}
/>
</VegaWalletContext.Provider>
</MockedProvider>
</AppStateProvider>
);
fireEvent.click(screen.getByTestId('change-vote-button'));
expect(screen.getByTestId('vote-buttons')).toBeInTheDocument();
});
@@ -1,7 +1,7 @@
import { format } from 'date-fns';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useVegaWallet, useDialogStore } from '@vegaprotocol/wallet-react';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import {
AsyncRenderer,
Button,
@@ -17,7 +17,7 @@ import { VoteState } from './use-user-vote';
import { ProposalMinRequirements, ProposalUserAction } from '../shared';
import { VoteTransactionDialog } from './vote-transaction-dialog';
import { useVoteButtonsQuery } from './__generated__/Stake';
import type { VegaTxState } from '@vegaprotocol/proposals';
import type { DialogProps, VegaTxState } from '@vegaprotocol/proposals';
import { filterAcceptableGraphqlErrors } from '../../../../lib/party';
import {
NetworkParams,
@@ -32,7 +32,8 @@ interface VoteButtonsContainerProps {
proposalId: string | null;
proposalState: ProposalState;
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
transaction: VegaTxState;
transaction: VegaTxState | null;
dialog: (props: DialogProps) => JSX.Element;
className?: string;
}
@@ -135,16 +136,17 @@ export const VoteButtonsContainer = (props: VoteButtonsContainerProps) => {
);
};
export interface VoteButtonsProps {
interface VoteButtonsProps {
voteState: VoteState | null;
voteDatetime: Date | null;
proposalState: ProposalState;
proposalId: string | null;
proposalState: ProposalState;
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
transaction: VegaTxState | null;
dialog: (props: DialogProps) => JSX.Element;
currentStakeAvailable: BigNumber;
minVoterBalance: string | null;
spamProtectionMinTokens: string | null;
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
transaction: VegaTxState;
}
export const VoteButtons = ({
@@ -157,10 +159,13 @@ export const VoteButtons = ({
spamProtectionMinTokens,
submit,
transaction,
dialog: Dialog,
}: VoteButtonsProps) => {
const { t } = useTranslation();
const { pubKey } = useVegaWallet();
const openVegaWalletDialog = useDialogStore((store) => store.open);
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
openVegaWalletDialog: store.openVegaWalletDialog,
}));
const [changeVote, setChangeVote] = React.useState(false);
const proposalVotable = useMemo(
() =>
@@ -179,7 +184,11 @@ export const VoteButtons = ({
if (!pubKey) {
return (
<div data-testid="connect-wallet">
<ButtonLink onClick={openVegaWalletDialog}>
<ButtonLink
onClick={() => {
openVegaWalletDialog();
}}
>
{t('connectVegaWallet')}
</ButtonLink>{' '}
{t('toVote')}
@@ -292,7 +301,11 @@ export const VoteButtons = ({
</p>
)
)}
<VoteTransactionDialog voteState={voteState} transaction={transaction} />
<VoteTransactionDialog
voteState={voteState}
transaction={transaction}
TransactionDialog={Dialog}
/>
</>
);
};
@@ -1,13 +1,11 @@
import { t } from '@vegaprotocol/i18n';
import { VoteState } from './use-user-vote';
import {
VegaTransactionDialog,
type VegaTxState,
} from '@vegaprotocol/proposals';
import type { DialogProps, VegaTxState } from '@vegaprotocol/proposals';
interface VoteTransactionDialogProps {
voteState: VoteState;
transaction: VegaTxState;
transaction: VegaTxState | null;
TransactionDialog: (props: DialogProps) => JSX.Element;
}
const dialogTitle = (voteState: VoteState): string | undefined => {
@@ -24,23 +22,22 @@ const dialogTitle = (voteState: VoteState): string | undefined => {
export const VoteTransactionDialog = ({
voteState,
transaction,
TransactionDialog,
}: VoteTransactionDialogProps) => {
// Render a custom message if the voting fails otherwise
// pass undefined so that the default vega transaction dialog UI gets used
const customMessage =
voteState === VoteState.Failed ? (
<p>{transaction.error?.message || t('voteError')}</p>
<p>{transaction?.error?.message || t('voteError')}</p>
) : undefined;
return (
<div data-testid="vote-transaction-dialog">
<VegaTransactionDialog
<TransactionDialog
title={dialogTitle(voteState)}
transaction={transaction}
content={{
Complete: customMessage,
}}
isOpen={transaction.dialogOpen}
/>
</div>
);
@@ -8,18 +8,13 @@ import {
type VoteFieldsFragment,
} from '../__generated__/Proposals';
import { type ProposalChangeType } from '../types';
import sum from 'lodash/sum';
export const useVoteInformation = ({
votes,
terms,
yesELS,
noELS,
}: {
votes: VoteFieldsFragment;
terms: ProposalTermsFieldsFragment;
yesELS?: number[];
noELS?: number[];
}) => {
const {
appState: { totalSupply, decimals },
@@ -36,9 +31,7 @@ export const useVoteInformation = ({
paramsForChange,
votes,
totalSupply,
decimals,
yesELS,
noELS
decimals
);
};
@@ -79,11 +72,7 @@ const getVoteData = (
},
votes: ProposalFieldsFragment['votes'],
totalSupply: BigNumber,
decimals: number,
/** A list of ELS yes votes */
yesELS?: number[],
/** A list if ELS no votes */
noELS?: number[]
decimals: number
) => {
const requiredMajorityPercentage = params.requiredMajority
? new BigNumber(params.requiredMajority).times(100)
@@ -97,31 +86,17 @@ const getVoteData = (
addDecimal(votes.no.totalTokens ?? 0, decimals)
);
let noEquityLikeShareWeight = !votes.no.totalEquityLikeShareWeight
const noEquityLikeShareWeight = !votes.no.totalEquityLikeShareWeight
? new BigNumber(0)
: new BigNumber(votes.no.totalEquityLikeShareWeight).times(100);
// there's no meaningful `totalEquityLikeShareWeight` in batch proposals,
// it has to be deduced from `elsPerMarket` of `no` votes of given proposal
// data. (by REST DATA)
if (noELS != null) {
const noTotalELS = sum(noELS);
noEquityLikeShareWeight = new BigNumber(noTotalELS).times(100);
}
const yesTokens = new BigNumber(
addDecimal(votes.yes.totalTokens ?? 0, decimals)
);
let yesEquityLikeShareWeight = !votes.yes.totalEquityLikeShareWeight
const yesEquityLikeShareWeight = !votes.yes.totalEquityLikeShareWeight
? new BigNumber(0)
: new BigNumber(votes.yes.totalEquityLikeShareWeight).times(100);
// there's no meaningful `totalEquityLikeShareWeight` in batch proposals,
// it has to be deduced from `elsPerMarket` of `yes` votes of given proposal
// data. (by REST DATA)
if (noELS != null) {
const yesTotalELS = sum(yesELS);
yesEquityLikeShareWeight = new BigNumber(yesTotalELS).times(100);
}
const totalTokensVoted = yesTokens.plus(noTokens);
@@ -1,12 +1,13 @@
import { render, screen } from '@testing-library/react';
import { ProposeFreeform } from './propose-freeform';
import { MockedProvider } from '@apollo/client/testing';
import { mockWalletContext } from '../../test-helpers/mocks';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { MemoryRouter as Router } from 'react-router-dom';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import type { MockedResponse } from '@apollo/client/testing';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'),
@@ -71,19 +72,18 @@ const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
},
};
const renderComponent = () => {
return render(
const renderComponent = () =>
render(
<Router>
<MockedProvider mocks={[updateMarketNetworkParamsQueryMock]}>
<MockedWalletProvider>
<AppStateProvider>
<AppStateProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<ProposeFreeform />
</AppStateProvider>
</MockedWalletProvider>
</VegaWalletContext.Provider>
</AppStateProvider>
</MockedProvider>
</Router>
);
};
// Note: form submission is tested in propose-raw.spec.tsx. Reusable form
// components are tested in their own directory.
@@ -51,8 +51,7 @@ export const ProposeFreeform = () => {
watch,
trigger,
} = useForm<FreeformProposalFormFields>();
const { finalizedProposal, transaction, submit, setTransaction } =
useProposalSubmit();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const assembleProposal = (fields: FreeformProposalFormFields) => {
const isVoteDeadlineAtMinimum =
@@ -170,8 +169,7 @@ export const ProposeFreeform = () => {
<ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
transaction={transaction}
onChange={(open) => setTransaction({ dialogOpen: open })}
TransactionDialog={Dialog}
/>
</form>
</div>
@@ -1,12 +1,13 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { ProposeNetworkParameter } from './propose-network-parameter';
import { MockedProvider } from '@apollo/client/testing';
import { mockWalletContext } from '../../test-helpers/mocks';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { MemoryRouter as Router } from 'react-router-dom';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import type { MockedResponse } from '@apollo/client/testing';
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'),
@@ -71,27 +72,26 @@ const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
},
};
const renderComponent = () => {
return render(
const renderComponent = () =>
render(
<Router>
<MockedProvider mocks={[updateMarketNetworkParamsQueryMock]}>
<MockedWalletProvider>
<AppStateProvider>
<AppStateProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<ProposeNetworkParameter />
</AppStateProvider>
</MockedWalletProvider>
</VegaWalletContext.Provider>
</AppStateProvider>
</MockedProvider>
</Router>
);
};
// Note: form submission is tested in propose-raw.spec.tsx. Reusable form
// components are tested in their own directory.
describe('Propose Network Parameter', () => {
it('should render successfully', () => {
it('should render successfully', async () => {
const { baseElement } = renderComponent();
expect(baseElement).toBeTruthy();
await expect(baseElement).toBeTruthy();
});
it('should render the correct title', async () => {
@@ -91,8 +91,7 @@ export const ProposeNetworkParameter = () => {
watch,
trigger,
} = useForm<NetworkParameterProposalFormFields>();
const { finalizedProposal, transaction, submit, setTransaction } =
useProposalSubmit();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const selectedParamEntry = params
? Object.entries(params).find(([key]) => key === selectedNetworkParam)
@@ -313,8 +312,7 @@ export const ProposeNetworkParameter = () => {
<ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
transaction={transaction}
onChange={(open) => setTransaction({ dialogOpen: open })}
TransactionDialog={Dialog}
/>
</form>
</div>
@@ -1,12 +1,13 @@
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter as Router } from 'react-router-dom';
import { render, screen } from '@testing-library/react';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { mockWalletContext } from '../../test-helpers/mocks';
import { ProposeNewAsset } from './propose-new-asset';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import type { MockedResponse } from '@apollo/client/testing';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'),
@@ -71,27 +72,26 @@ const newAssetNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
},
};
const renderComponent = () => {
return render(
const renderComponent = () =>
render(
<Router>
<MockedProvider mocks={[newAssetNetworkParamsQueryMock]}>
<MockedWalletProvider>
<AppStateProvider>
<AppStateProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<ProposeNewAsset />
</AppStateProvider>
</MockedWalletProvider>
</VegaWalletContext.Provider>
</AppStateProvider>
</MockedProvider>
</Router>
);
};
// Note: form submission is tested in propose-raw.spec.tsx. Reusable form
// components are tested in their own directory.
describe('Propose New Asset', () => {
it('should render successfully', () => {
it('should render successfully', async () => {
const { baseElement } = renderComponent();
expect(baseElement).toBeTruthy();
await expect(baseElement).toBeTruthy();
});
it('should render the title', async () => {
@@ -64,8 +64,7 @@ export const ProposeNewAsset = () => {
watch,
trigger,
} = useForm<NewAssetProposalFormFields>();
const { finalizedProposal, transaction, submit, setTransaction } =
useProposalSubmit();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const assembleProposal = (fields: NewAssetProposalFormFields) => {
const isVoteDeadlineAtMinimum = doesValueEquateToParam(
@@ -233,8 +232,7 @@ export const ProposeNewAsset = () => {
<ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
transaction={transaction}
onChange={(open) => setTransaction({ dialogOpen: open })}
TransactionDialog={Dialog}
/>
</form>
</div>
@@ -1,12 +1,13 @@
import { render, screen } from '@testing-library/react';
import { ProposeNewMarket } from './propose-new-market';
import { MockedProvider } from '@apollo/client/testing';
import { mockWalletContext } from '../../test-helpers/mocks';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { BrowserRouter as Router } from 'react-router-dom';
import type { MockedResponse } from '@apollo/client/testing';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'),
@@ -71,27 +72,26 @@ const newMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
},
};
const renderComponent = () => {
return render(
const renderComponent = () =>
render(
<Router>
<MockedProvider mocks={[newMarketNetworkParamsQueryMock]}>
<MockedWalletProvider>
<AppStateProvider>
<AppStateProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<ProposeNewMarket />
</AppStateProvider>
</MockedWalletProvider>
</VegaWalletContext.Provider>
</AppStateProvider>
</MockedProvider>
</Router>
);
};
// Note: form submission is tested in propose-raw.spec.tsx. Reusable form
// components are tested in their own directory.
describe('Propose New Market', () => {
it('should render successfully', () => {
it('should render successfully', async () => {
const { baseElement } = renderComponent();
expect(baseElement).toBeTruthy();
await expect(baseElement).toBeTruthy();
});
it('should render the form components', async () => {
@@ -62,8 +62,7 @@ export const ProposeNewMarket = () => {
watch,
trigger,
} = useForm<NewMarketProposalFormFields>();
const { finalizedProposal, transaction, submit, setTransaction } =
useProposalSubmit();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const assembleProposal = (fields: NewMarketProposalFormFields) => {
const isVoteDeadlineAtMinimum = doesValueEquateToParam(
@@ -215,8 +214,7 @@ export const ProposeNewMarket = () => {
<ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
transaction={transaction}
onChange={(open) => setTransaction({ dialogOpen: open })}
TransactionDialog={Dialog}
/>
</form>
</div>
@@ -3,6 +3,8 @@ import type { MockedResponse } from '@apollo/client/testing';
import { addHours, getTime } from 'date-fns';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { MockedProvider } from '@apollo/client/testing';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import * as Schema from '@vegaprotocol/types';
import { ProposeRaw } from './propose-raw';
import { ProposalEventDocument } from '@vegaprotocol/proposals';
@@ -10,11 +12,6 @@ import type { ProposalEventSubscription } from '@vegaprotocol/proposals';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import {
MockedWalletProvider,
mockConfig,
} from '@vegaprotocol/wallet-react/testing';
import { userRejectedError } from '@vegaprotocol/wallet';
const paramsDelay = 20;
@@ -106,15 +103,23 @@ describe('Raw proposal form', () => {
},
delay: 300,
};
const setup = () => {
const setup = (mockSendTx = jest.fn()) => {
return render(
<AppStateProvider>
<MockedProvider
mocks={[rawProposalNetworkParamsQueryMock, mockProposalEvent]}
>
<MockedWalletProvider>
<VegaWalletContext.Provider
value={
{
pubKey,
sendTx: mockSendTx,
links: { explorer: 'explorer' },
} as unknown as VegaWalletContextShape
}
>
<ProposeRaw />
</MockedWalletProvider>
</VegaWalletContext.Provider>
</MockedProvider>
</AppStateProvider>
);
@@ -122,22 +127,15 @@ describe('Raw proposal form', () => {
beforeAll(() => {
jest.useFakeTimers();
mockConfig.store.setState({ status: 'connected', pubKey: '0x123' });
});
afterAll(() => {
jest.useRealTimers();
mockConfig.reset();
});
afterEach(() => {
jest.clearAllMocks();
});
it('handles validation', async () => {
const mockSendTx = jest.spyOn(mockConfig, 'sendTransaction');
setup();
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve());
setup(mockSendTx);
expect(await screen.findByTestId('proposal-submit')).toBeTruthy();
await act(async () => {
@@ -164,25 +162,20 @@ describe('Raw proposal form', () => {
});
it('sends the transaction', async () => {
const mockSendTx = jest
.spyOn(mockConfig, 'sendTransaction')
.mockReturnValue(
new Promise((resolve) => {
setTimeout(
() =>
resolve({
transactionHash: 'tx-hash',
signature:
'cfe592d169f87d0671dd447751036d0dddc165b9c4b65e5a5060e2bbadd1aa726d4cbe9d3c3b327bcb0bff4f83999592619a2493f9bbd251fae99ce7ce766909',
sentAt: new Date().toISOString(),
receivedAt: new Date().toISOString(),
}),
100
);
})
);
setup();
const mockSendTx = jest.fn().mockReturnValue(
new Promise((resolve) => {
setTimeout(
() =>
resolve({
transactionHash: 'tx-hash',
signature:
'cfe592d169f87d0671dd447751036d0dddc165b9c4b65e5a5060e2bbadd1aa726d4cbe9d3c3b327bcb0bff4f83999592619a2493f9bbd251fae99ce7ce766909',
}),
100
);
})
);
setup(mockSendTx);
await act(async () => {
jest.advanceTimersByTime(paramsDelay);
@@ -213,12 +206,8 @@ describe('Raw proposal form', () => {
fireEvent.click(screen.getByTestId('proposal-submit'));
});
expect(mockSendTx).toHaveBeenCalledWith({
publicKey: pubKey,
sendingMode: 'TYPE_SYNC',
transaction: {
proposalSubmission: JSON.parse(inputJSON),
},
expect(mockSendTx).toHaveBeenCalledWith(pubKey, {
proposalSubmission: JSON.parse(inputJSON),
});
expect(screen.getByTestId('dialog-title')).toHaveTextContent(
@@ -243,12 +232,12 @@ describe('Raw proposal form', () => {
});
it('can be rejected by the user', async () => {
jest.spyOn(mockConfig, 'sendTransaction').mockReturnValue(
new Promise((_, reject) => {
setTimeout(() => reject(userRejectedError()), 100);
const mockSendTx = jest.fn().mockReturnValue(
new Promise((resolve) => {
setTimeout(() => resolve(null), 100);
})
);
setup();
setup(mockSendTx);
await act(async () => {
jest.advanceTimersByTime(paramsDelay);
@@ -52,8 +52,7 @@ export const ProposeRaw = () => {
handleSubmit,
formState: { isSubmitting, errors },
} = useForm<RawProposalFormFields>();
const { finalizedProposal, transaction, submit, setTransaction } =
useProposalSubmit();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const hasError = Boolean(errors.rawProposalData?.message);
@@ -153,8 +152,7 @@ export const ProposeRaw = () => {
<ProposalFormSubmit isSubmitting={isSubmitting} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
transaction={transaction}
onChange={(open) => setTransaction({ dialogOpen: open })}
TransactionDialog={Dialog}
/>
</form>
</div>
@@ -1,12 +1,13 @@
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter as Router } from 'react-router-dom';
import { render, screen } from '@testing-library/react';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { mockWalletContext } from '../../test-helpers/mocks';
import { ProposeUpdateAsset } from './propose-update-asset';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import type { MockedResponse } from '@apollo/client/testing';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'),
@@ -71,27 +72,26 @@ const updateAssetNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
},
};
const renderComponent = () => {
return render(
const renderComponent = () =>
render(
<Router>
<MockedProvider mocks={[updateAssetNetworkParamsQueryMock]}>
<MockedWalletProvider>
<AppStateProvider>
<AppStateProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<ProposeUpdateAsset />
</AppStateProvider>
</MockedWalletProvider>
</VegaWalletContext.Provider>
</AppStateProvider>
</MockedProvider>
</Router>
);
};
// Note: form submission is tested in propose-raw.spec.tsx. Reusable form
// components are tested in their own directory.
describe('Propose Update Asset', () => {
it('should render successfully', () => {
it('should render successfully', async () => {
const { baseElement } = renderComponent();
expect(baseElement).toBeTruthy();
await expect(baseElement).toBeTruthy();
});
it('should render the title', async () => {
@@ -62,8 +62,7 @@ export const ProposeUpdateAsset = () => {
watch,
trigger,
} = useForm<UpdateAssetProposalFormFields>();
const { finalizedProposal, transaction, submit, setTransaction } =
useProposalSubmit();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const assembleProposal = (fields: UpdateAssetProposalFormFields) => {
const isVoteDeadlineAtMinimum = doesValueEquateToParam(
@@ -219,8 +218,7 @@ export const ProposeUpdateAsset = () => {
<ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
transaction={transaction}
onChange={(open) => setTransaction({ dialogOpen: open })}
TransactionDialog={Dialog}
/>
</form>
</div>
@@ -2,14 +2,15 @@ import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter as Router } from 'react-router-dom';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { mockWalletContext } from '../../test-helpers/mocks';
import { ProposeUpdateMarket } from './propose-update-market';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import type { ProposalMarketsQueryQuery } from './__generated__/UpdateMarket';
import { ProposalMarketsQueryDocument } from './__generated__/UpdateMarket';
import { ProposalState } from '@vegaprotocol/types';
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
request: {
@@ -216,30 +217,29 @@ const marketQueryMock: MockedResponse<ProposalMarketsQueryQuery> = {
},
};
const renderComponent = () => {
return render(
const renderComponent = () =>
render(
<MockedProvider
mocks={[updateMarketNetworkParamsQueryMock, marketQueryMock]}
addTypename={false}
>
<Router>
<MockedWalletProvider>
<AppStateProvider>
<AppStateProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<ProposeUpdateMarket />
</AppStateProvider>
</MockedWalletProvider>
</VegaWalletContext.Provider>
</AppStateProvider>
</Router>
</MockedProvider>
);
};
// Note: form submission is tested in propose-raw.spec.tsx. Reusable form
// components are tested in their own directory.
describe('Propose Update Market', () => {
it('should render successfully', () => {
it('should render successfully', async () => {
const { baseElement } = renderComponent();
expect(baseElement).toBeTruthy();
await expect(baseElement).toBeTruthy();
});
it('should render the title', async () => {
@@ -109,8 +109,7 @@ export const ProposeUpdateMarket = () => {
watch,
trigger,
} = useForm<UpdateMarketProposalFormFields>();
const { finalizedProposal, transaction, submit, setTransaction } =
useProposalSubmit();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const assembleProposal = (fields: UpdateMarketProposalFormFields) => {
const isVoteDeadlineAtMinimum = doesValueEquateToParam(
@@ -324,8 +323,7 @@ export const ProposeUpdateMarket = () => {
<ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
transaction={transaction}
onChange={(open) => setTransaction({ dialogOpen: open })}
TransactionDialog={Dialog}
/>
</form>
</div>
@@ -1,17 +1,28 @@
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import type { MockedResponse } from '@apollo/client/testing';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import type { PubKey, VegaWalletContextShape } from '@vegaprotocol/wallet';
import type { VoteValue } from '@vegaprotocol/types';
import type { UserVoteQuery } from '../components/vote-details/__generated__/Vote';
import { UserVoteDocument } from '../components/vote-details/__generated__/Vote';
import faker from 'faker';
import { type Key } from '@vegaprotocol/wallet';
export const mockPubkey: Key = {
export const mockPubkey: PubKey = {
publicKey: '0x123',
name: 'test key 1',
};
export const mockWalletContext = {
pubKey: mockPubkey.publicKey,
pubKeys: [mockPubkey],
isReadOnly: false,
sendTx: jest.fn().mockReturnValue(Promise.resolve(null)),
connect: jest.fn(),
disconnect: jest.fn(),
selectPubKey: jest.fn(),
connector: null,
} as unknown as VegaWalletContextShape;
const mockEthereumConfig = {
network_id: '3',
chain_id: '3',
@@ -1,11 +1,13 @@
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { useDialogStore } from '@vegaprotocol/wallet-react';
import { useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { Button } from '@vegaprotocol/ui-toolkit';
import { SubHeading } from '../../components/heading';
export const ConnectToSeeRewards = () => {
const openVegaWalletDialog = useDialogStore((store) => store.open);
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
openVegaWalletDialog: store.openVegaWalletDialog,
}));
const { t } = useTranslation();
const classes = classNames(
@@ -1,10 +1,11 @@
import { useMemo, useState } from 'react';
import { useMemo, useEffect, useState, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { AsyncRenderer, Pagination } from '@vegaprotocol/ui-toolkit';
import { removePaginationWrapper } from '@vegaprotocol/utils';
import type { EpochFieldsFragment } from '../home/__generated__/Rewards';
import { useRewardsQuery } from '../home/__generated__/Rewards';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { ENV } from '../../../config';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { EpochIndividualRewardsTable } from './epoch-individual-rewards-table';
import { generateEpochIndividualRewardsList } from './generate-epoch-individual-rewards-list';
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
@@ -26,16 +27,22 @@ export const EpochIndividualRewards = ({
const [page, setPage] = useState(1);
const { t } = useTranslation();
const { pubKey } = useVegaWallet();
const { delegationsPagination } = ENV;
const { param: marketCreationQuantumMultiple } = useNetworkParam(
'rewards_marketCreationQuantumMultiple'
);
const { data, loading, error } = useRewardsQuery({
const { data, loading, error, refetch } = useRewardsQuery({
notifyOnNetworkStatusChange: true,
variables: {
partyId: pubKey || '',
...calculateEpochOffset({ epochId, page, size: EPOCHS_PAGE_SIZE }),
delegationsPagination: { first: 50 },
fromEpoch: epochId - EPOCHS_PAGE_SIZE,
toEpoch: epochId,
delegationsPagination: delegationsPagination
? {
first: Number(delegationsPagination),
}
: undefined,
},
skip: !pubKey,
});
@@ -63,6 +70,36 @@ export const EpochIndividualRewards = ({
});
}, [data?.party, epochId, epochRewardSummaries, page, rewards]);
const refetchData = useCallback(
async (toPage?: number) => {
const targetPage = toPage ?? page;
await refetch({
partyId: pubKey || '',
...calculateEpochOffset({ epochId, page, size: EPOCHS_PAGE_SIZE }),
delegationsPagination: delegationsPagination
? {
first: Number(delegationsPagination),
}
: undefined,
});
setPage(targetPage);
},
[epochId, page, refetch, delegationsPagination, pubKey]
);
const prevEpochIdRef = useRef<number | null>(null);
useEffect(() => {
if (prevEpochIdRef.current === null) {
prevEpochIdRef.current = epochId;
} else if (epochId !== prevEpochIdRef.current) {
// When the epoch changes, we want to refetch the data to update the current page
refetchData();
}
prevEpochIdRef.current = epochId;
}, [epochId, refetchData]);
// Workarounds for the error handling of AsyncRenderer
const filteredErrors = filterAcceptableGraphqlErrors(error);
const filteredData = data || [];
@@ -94,10 +131,10 @@ export const EpochIndividualRewards = ({
isLoading={loading}
hasPrevPage={page > 1}
hasNextPage={page < totalPages}
onBack={() => setPage((x) => x - 1)}
onNext={() => setPage((x) => x + 1)}
onFirst={() => setPage(1)}
onLast={() => setPage(totalPages)}
onBack={() => refetchData(page - 1)}
onNext={() => refetchData(page + 1)}
onFirst={() => refetchData(1)}
onLast={() => refetchData(totalPages)}
>
{t('Page')} {page}
</Pagination>
@@ -23,6 +23,36 @@ describe('generateEpochIndividualRewardsList', () => {
epoch: { id: '2' },
};
const reward3: RewardFieldsFragment = {
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '200',
percentageOfTotal: '0.2',
receivedAt: new Date(),
asset: { id: 'gbp', symbol: 'GBP', name: 'GBP', decimals: 7 },
party: { id: 'blah' },
epoch: { id: '2' },
};
const reward4: RewardFieldsFragment = {
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '100',
percentageOfTotal: '0.1',
receivedAt: new Date(),
asset: { id: 'usd', symbol: 'USD', name: 'USD', decimals: 6 },
party: { id: 'blah' },
epoch: { id: '1' },
};
const reward5: RewardFieldsFragment = {
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '150',
percentageOfTotal: '0.15',
receivedAt: new Date(),
asset: { id: 'usd', symbol: 'USD', name: 'USD', decimals: 6 },
party: { id: 'blah' },
epoch: { id: '3' },
};
const rewardWrongType: RewardFieldsFragment = {
rewardType: AccountType.ACCOUNT_TYPE_INSURANCE,
amount: '50',
@@ -90,10 +120,26 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: {
amount: '100',
percentageOfTotal: '0.1',
},
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: {
amount: '0',
percentageOfTotal: '0',
},
},
},
],
@@ -101,7 +147,7 @@ describe('generateEpochIndividualRewardsList', () => {
});
it('should return an array sorted by epoch descending', () => {
const rewards = [reward1, reward2];
const rewards = [reward1, reward2, reward3, reward4];
const result1 = generateEpochIndividualRewardsList({
rewards,
epochId: 2,
@@ -111,7 +157,7 @@ describe('generateEpochIndividualRewardsList', () => {
expect(result1[0].epoch).toEqual(2);
expect(result1[1].epoch).toEqual(1);
const reorderedRewards = [reward2, reward1];
const reorderedRewards = [reward4, reward3, reward2, reward1];
const result2 = generateEpochIndividualRewardsList({
rewards: reorderedRewards,
epochId: 2,
@@ -124,7 +170,7 @@ describe('generateEpochIndividualRewardsList', () => {
it('returns data in the expected shape', () => {
// Just sanity checking the whole structure here
const rewards = [reward1, reward2];
const rewards = [reward1, reward2, reward3, reward4];
const result = generateEpochIndividualRewardsList({
rewards,
epochId: 2,
@@ -135,6 +181,37 @@ describe('generateEpochIndividualRewardsList', () => {
{
epoch: 2,
rewards: [
{
asset: 'GBP',
totalAmount: '200',
decimals: 7,
rewardTypes: {
[AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
amount: '200',
percentageOfTotal: '0.2',
},
[AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: {
amount: '0',
percentageOfTotal: '0',
},
},
},
{
asset: 'EUR',
totalAmount: '50',
@@ -144,10 +221,26 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: {
amount: '50',
percentageOfTotal: '0.05',
},
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: {
amount: '0',
percentageOfTotal: '0',
},
},
},
],
@@ -157,17 +250,33 @@ describe('generateEpochIndividualRewardsList', () => {
rewards: [
{
asset: 'USD',
totalAmount: '100',
totalAmount: '200',
decimals: 6,
rewardTypes: {
[AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
amount: '100',
percentageOfTotal: '0.1',
},
[AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: {
amount: '100',
percentageOfTotal: '0.1',
},
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: {
amount: '0',
percentageOfTotal: '0',
},
},
},
],
@@ -176,7 +285,7 @@ describe('generateEpochIndividualRewardsList', () => {
});
it('returns data correctly for the requested epoch range', () => {
const rewards = [reward1, reward2];
const rewards = [reward1, reward2, reward3, reward4, reward5];
const resultPageOne = generateEpochIndividualRewardsList({
rewards,
epochId: 3,
@@ -188,11 +297,74 @@ describe('generateEpochIndividualRewardsList', () => {
expect(resultPageOne).toEqual([
{
epoch: 3,
rewards: [],
rewards: [
{
asset: 'USD',
decimals: 6,
totalAmount: '150',
rewardTypes: {
[AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
amount: '150',
percentageOfTotal: '0.15',
},
[AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: {
amount: '0',
percentageOfTotal: '0',
},
},
},
],
},
{
epoch: 2,
rewards: [
{
asset: 'GBP',
totalAmount: '200',
decimals: 7,
rewardTypes: {
[AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
amount: '200',
percentageOfTotal: '0.2',
},
[AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: {
amount: '0',
percentageOfTotal: '0',
},
},
},
{
asset: 'EUR',
totalAmount: '50',
@@ -202,10 +374,26 @@ describe('generateEpochIndividualRewardsList', () => {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: {
amount: '50',
percentageOfTotal: '0.05',
},
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: {
amount: '0',
percentageOfTotal: '0',
},
},
},
],
@@ -226,17 +414,33 @@ describe('generateEpochIndividualRewardsList', () => {
rewards: [
{
asset: 'USD',
totalAmount: '100',
totalAmount: '200',
decimals: 6,
rewardTypes: {
[AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
amount: '100',
percentageOfTotal: '0.1',
},
[AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: {
amount: '100',
percentageOfTotal: '0.1',
},
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: {
amount: '0',
percentageOfTotal: '0',
},
},
},
],
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { AsyncRenderer, Pagination } from '@vegaprotocol/ui-toolkit';
import type { EpochFieldsFragment } from '../home/__generated__/Rewards';
@@ -23,17 +23,37 @@ export const EpochTotalRewards = ({ currentEpoch }: EpochTotalRewardsProps) => {
'rewards_marketCreationQuantumMultiple'
);
const [page, setPage] = useState(1);
const { data, loading, error } = useEpochAssetsRewardsQuery({
const { data, loading, error, refetch } = useEpochAssetsRewardsQuery({
notifyOnNetworkStatusChange: true,
variables: {
epochRewardSummariesFilter: calculateEpochOffset({
epochId,
page: page,
size: EPOCHS_PAGE_SIZE,
}),
epochRewardSummariesFilter: {
fromEpoch: epochId - EPOCHS_PAGE_SIZE,
},
},
});
const refetchData = useCallback(
async (toPage?: number) => {
const targetPage = toPage ?? page;
await refetch({
epochRewardSummariesFilter: calculateEpochOffset({
epochId,
page: targetPage,
size: EPOCHS_PAGE_SIZE,
}),
});
setPage(targetPage);
},
[epochId, page, refetch]
);
useEffect(() => {
// when the epoch changes, we want to refetch the data to update the current page
if (data) {
refetchData();
}
}, [epochId, data, refetchData]);
const epochTotalRewardSummaries =
generateEpochTotalRewardsList({
data,
@@ -65,10 +85,10 @@ export const EpochTotalRewards = ({ currentEpoch }: EpochTotalRewardsProps) => {
isLoading={loading}
hasPrevPage={page > 1}
hasNextPage={page < totalPages}
onBack={() => setPage((x) => x - 1)}
onNext={() => setPage((x) => x + 1)}
onFirst={() => setPage(1)}
onLast={() => setPage(totalPages)}
onBack={() => refetchData(page - 1)}
onNext={() => refetchData(page + 1)}
onFirst={() => refetchData(1)}
onLast={() => refetchData(totalPages)}
>
{t('Page')} {page}
</Pagination>
@@ -149,6 +149,38 @@ describe('generateEpochAssetRewardsList', () => {
amount: '0',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
amount: '0',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
amount: '0',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '0',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
amount: '0',
},
],
]),
totalAmount: '123',
},
@@ -259,8 +291,40 @@ describe('generateEpochAssetRewardsList', () => {
amount: '100',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
amount: '123',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
amount: '0',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '0',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
amount: '0',
},
],
]),
totalAmount: '100',
totalAmount: '223',
},
],
]),
@@ -270,7 +334,66 @@ describe('generateEpochAssetRewardsList', () => {
'2',
{
epoch: 2,
assetRewards: new Map(),
assetRewards: new Map([
[
'1',
{
assetId: '1',
decimals: 18,
name: 'Asset 1',
rewards: new Map([
[
AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
{
rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
amount: '0',
},
],
[
AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
{
rewardType:
AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
amount: '0',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
amount: '0',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
amount: '0',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '5',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
amount: '0',
},
],
]),
totalAmount: '5',
},
],
]),
},
],
])
@@ -366,7 +489,66 @@ describe('generateEpochAssetRewardsList', () => {
'2',
{
epoch: 2,
assetRewards: new Map(),
assetRewards: new Map([
[
'1',
{
assetId: '1',
name: 'Asset 1',
decimals: 18,
rewards: new Map([
[
AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
{
rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
amount: '0',
},
],
[
AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
{
rewardType:
AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
amount: '0',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
amount: '0',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
amount: '0',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '33',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
amount: '0',
},
],
]),
totalAmount: '33',
},
],
]),
},
],
[
@@ -396,6 +578,38 @@ describe('generateEpochAssetRewardsList', () => {
amount: '15',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
amount: '0',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
amount: '0',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '0',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
amount: '0',
},
],
]),
totalAmount: '15',
},
@@ -442,8 +656,40 @@ describe('generateEpochAssetRewardsList', () => {
amount: '100',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
amount: '123',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
amount: '0',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
amount: '0',
},
],
[
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
{
rewardType:
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
amount: '0',
},
],
]),
totalAmount: '100',
totalAmount: '223',
},
],
]),
@@ -10,7 +10,7 @@ import {
Toggle,
ExternalLink,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import {
useNetworkParams,
NetworkParams,
@@ -18,6 +18,22 @@ export const RowAccountTypes = {
columnTitle: 'rewardsColInfraHeader',
description: 'rewardsColInfraTooltip',
},
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: {
columnTitle: 'rewardsColPriceTakingHeader',
description: 'rewardsColPriceTakingTooltip',
},
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: {
columnTitle: 'rewardsColPriceMakingHeader',
description: 'rewardsColPriceMakingTooltip',
},
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
columnTitle: 'rewardsColLiquidityProvisionHeader',
description: 'rewardsColLiquidityProvisionTooltip',
},
[AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: {
columnTitle: 'rewardsColMarketCreationHeader',
description: 'rewardsColMarketCreationTooltip',
},
};
interface ColumnHeaderProps {
@@ -39,7 +55,7 @@ export const rowGridItemStyles = (last = false) =>
});
const gridStyles = classNames(
'grid grid-cols-[repeat(4,minmax(100px,auto))] max-w-full overflow-auto',
'grid grid-cols-[repeat(8,minmax(100px,auto))] max-w-full overflow-auto',
`border-t border-vega-dark-200`,
'text-sm'
);
@@ -33,8 +33,8 @@ let mockVegaWalletHookValue: {
pubKey: null,
};
jest.mock('@vegaprotocol/wallet-react', () => ({
...jest.requireActual('@vegaprotocol/wallet-react'),
jest.mock('@vegaprotocol/wallet', () => ({
...jest.requireActual('@vegaprotocol/wallet'),
useVegaWallet: jest.fn(() => mockVegaWalletHookValue),
}));
@@ -2,7 +2,7 @@ import { useWeb3React } from '@web3-react/core';
import { useTranslation } from 'react-i18next';
import { EthConnectPrompt } from '../../../../../components/eth-connect-prompt';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { ConnectToVega } from '../../../../../components/connect-to-vega';
export const StakingWalletsContainer = ({
@@ -33,8 +33,8 @@ let mockVegaWalletHookValue: {
pubKey: null,
};
jest.mock('@vegaprotocol/wallet-react', () => ({
...jest.requireActual('@vegaprotocol/wallet-react'),
jest.mock('@vegaprotocol/wallet', () => ({
...jest.requireActual('@vegaprotocol/wallet'),
useVegaWallet: jest.fn(() => mockVegaWalletHookValue),
}));
@@ -1,4 +1,4 @@
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useWeb3React } from '@web3-react/core';
import { EthConnectPrompt } from '../../../components/eth-connect-prompt';
import { DisassociatePage } from './components/disassociate-page';
@@ -5,7 +5,8 @@ import { useStakingQuery } from '../__generated__/Staking';
import { usePreviousEpochQuery } from '../__generated__/PreviousEpoch';
import { ValidatorTables } from './validator-tables';
import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { ENV } from '../../../config';
import { getMultisigStatusInfo } from '../../../lib/get-multisig-status-info';
import { MultisigIncorrectNotice } from '../../../components/multisig-incorrect-notice';
@@ -19,6 +20,7 @@ export const EpochData = () => {
refetch: nodesRefetch,
} = useNodesQuery();
const { delegationsPagination } = ENV;
const {
data: userStakingData,
error: userStakingError,
@@ -27,9 +29,11 @@ export const EpochData = () => {
} = useStakingQuery({
variables: {
partyId: pubKey || '',
delegationsPagination: {
first: 50,
},
delegationsPagination: delegationsPagination
? {
first: Number(delegationsPagination),
}
: undefined,
},
});
const { data: previousEpochData } = usePreviousEpochQuery({
@@ -288,7 +288,7 @@ describe('Consensus validators table', () => {
expect(
grid.querySelector('[role="gridcell"][col-id="totalPenalties"]')
).toHaveTextContent('10.07%');
).toHaveTextContent('13.16%');
expect(
grid.querySelector('[role="gridcell"][col-id="normalisedVotingPower"]')
@@ -185,19 +185,15 @@ export const ConsensusValidatorsTable = ({
const { rawValidatorScore: previousEpochValidatorScore } =
getLastEpochScoreAndPerformance(previousEpochData, id);
const overstakingPenalty = calculateOverstakedPenalty(
const overstakingPenalty = calculateOverallPenalty(
id,
allNodesInPreviousEpoch
);
const totalPenalty = calculateOverallPenalty(
const totalPenalty = calculateOverstakedPenalty(
id,
allNodesInPreviousEpoch
);
const lastEpochDataForNode = allNodesInPreviousEpoch.find(
(node) => node.id === id
);
return {
id,
[ValidatorFields.RANKING_INDEX]: stakedTotalRanking,
@@ -243,12 +239,6 @@ export const ConsensusValidatorsTable = ({
: undefined,
[ValidatorFields.MULTISIG_ERROR]:
multisigStatus?.showMultisigStatusError,
[ValidatorFields.MULTISIG_PENALTY]: formatNumberPercentage(
new BigNumber(1)
.minus(lastEpochDataForNode?.rewardScore?.multisigScore ?? 1)
.times(100),
2
),
};
}
);
@@ -388,6 +378,7 @@ export const ConsensusValidatorsTable = ({
headerTooltip: t('StakeDescription').toString(),
cellRenderer: TotalStakeRenderer,
width: 120,
sort: 'desc',
},
{
field: ValidatorFields.PENDING_STAKE,
@@ -409,7 +400,6 @@ export const ConsensusValidatorsTable = ({
headerTooltip: t('NormalisedVotingPowerDescription').toString(),
cellRenderer: VotingPowerRenderer,
width: 120,
sort: 'desc',
},
{
field: ValidatorFields.TOTAL_PENALTIES,
@@ -40,7 +40,6 @@ export enum ValidatorFields {
PENDING_USER_STAKE = 'pendingUserStake',
USER_STAKE_SHARE = 'userStakeShare',
MULTISIG_ERROR = 'multisigError',
MULTISIG_PENALTY = 'multisigPenalty',
}
export const addUserDataToValidator = (
@@ -328,7 +327,7 @@ interface TotalPenaltiesRendererProps {
overstakedAmount: string;
overstakingPenalty: string;
totalPenalties: string;
multisigPenalty: string;
multisigError?: boolean;
};
}
@@ -347,9 +346,11 @@ export const TotalPenaltiesRenderer = ({
<div data-testid="overstaked-penalty-tooltip">
{t('overstakedPenalty')}: {data.overstakingPenalty}
</div>
<div data-testid="multisig-error-tooltip">
{t('multisigPenalty')}: {data.multisigPenalty}
</div>
{data.multisigError && (
<div data-testid="multisig-error-tooltip">
{t('multisigPenalty')}: 100%
</div>
)}
</>
}
>
@@ -2,7 +2,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import {
addDecimal,
removePaginationWrapper,
@@ -1,5 +1,6 @@
import { ENV } from '../../../config';
import { Callout, Intent, Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useTranslation } from 'react-i18next';
import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch';
import { SplashLoader } from '../../../components/splash-loader';
@@ -27,13 +28,16 @@ export const NodeContainer = ({
}) => {
const { t } = useTranslation();
const { pubKey } = useVegaWallet();
const { delegationsPagination } = ENV;
const { data, loading, error, refetch } = useStakingQuery({
variables: {
partyId: pubKey || '',
delegationsPagination: {
first: 50,
},
delegationsPagination: delegationsPagination
? {
first: Number(delegationsPagination),
}
: undefined,
},
errorPolicy: 'all',
});
@@ -0,0 +1,78 @@
import React from 'react';
import * as Sentry from '@sentry/react';
import { Button, Callout, Intent, Loader } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next';
import { useAppState } from '../../../contexts/app-state/app-state-context';
import type { BigNumber } from '../../../lib/bignumber';
import type { UndelegateSubmissionBody } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { removeDecimal } from '@vegaprotocol/utils';
interface PendingStakeProps {
pendingAmount: BigNumber;
nodeId: string;
pubKey: string;
}
enum FormState {
Default,
Pending,
Success,
Failure,
}
export const PendingStake = ({
pendingAmount,
nodeId,
pubKey,
}: PendingStakeProps) => {
const { t } = useTranslation();
const { sendTx } = useVegaWallet();
const { appState } = useAppState();
const [formState, setFormState] = React.useState(FormState.Default);
const removeStakeNow = async () => {
setFormState(FormState.Pending);
try {
const command: UndelegateSubmissionBody = {
undelegateSubmission: {
nodeId,
amount: removeDecimal(pendingAmount.toString(), appState.decimals),
method: 'METHOD_NOW',
},
};
await sendTx(pubKey, command);
} catch (err) {
setFormState(FormState.Failure);
Sentry.captureException(err);
}
};
if (formState === FormState.Failure) {
return (
<Callout
intent={Intent.Danger}
title={t('failedToRemovePendingStake', { pendingAmount })}
>
<p>{t('pleaseTryAgain')}</p>
</Callout>
);
} else if (formState === FormState.Pending) {
return (
<Callout
icon={<Loader size="small" />}
title={t('removingPendingStake', { pendingAmount })}
/>
);
}
return (
<div className="py-4">
<h2>{t('pendingNomination')}</h2>
<p>{t('pendingNominationNextEpoch', { pendingAmount })}</p>
<Button onClick={() => removeStakeNow()}>
{t('cancelPendingEpochNomination')}
</Button>
</div>
);
};
@@ -3,6 +3,7 @@ import * as Sentry from '@sentry/react';
import React, { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { ENV } from '../../../config';
import { usePartyDelegationsLazyQuery } from './__generated__/PartyDelegations';
import { TokenInput } from '../../../components/token-input';
import { useAppState } from '../../../contexts/app-state/app-state-context';
@@ -27,11 +28,11 @@ import {
NetworkParams,
} from '@vegaprotocol/network-parameters';
import { useBalances } from '../../../lib/balances/balances-store';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { SubHeading } from '../../../components/heading';
import {
type DelegateSubmissionBody,
type UndelegateSubmissionBody,
import type {
DelegateSubmissionBody,
UndelegateSubmissionBody,
} from '@vegaprotocol/wallet';
import Routes from '../../routes';
@@ -78,6 +79,7 @@ export const StakingForm = ({
const [error, setError] = useState<Error | null>(null);
const [isDialogVisible, setIsDialogVisible] = useState(false);
const { t } = useTranslation();
const { delegationsPagination } = ENV;
const [action, setAction] = React.useState<StakeAction>(
params.action as StakeAction
);
@@ -152,9 +154,11 @@ export const StakingForm = ({
const [delegationSearch, { data }] = usePartyDelegationsLazyQuery({
variables: {
partyId: pubKey,
delegationsPagination: {
first: 50,
},
delegationsPagination: delegationsPagination
? {
first: Number(delegationsPagination),
}
: undefined,
},
fetchPolicy: 'network-only',
});
@@ -37,6 +37,7 @@ import {
import type { ReactNode } from 'react';
import type { StakingNodeFieldsFragment } from '../__generated__/Staking';
import type { PreviousEpochQuery } from '../__generated__/PreviousEpoch';
import { getMultisigStatusInfo } from '../../../lib/get-multisig-status-info';
const statuses = {
[Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_ERSATZ]: 'status-ersatz',
@@ -104,10 +105,9 @@ export const ValidatorTable = ({
};
}, [node, previousEpochData?.epoch.validatorsConnection?.edges]);
const previousNodeData =
previousEpochData?.epoch.validatorsConnection?.edges?.find(
(e) => e?.node.id === node.id
);
const multisigStatus = previousEpochData
? getMultisigStatusInfo(previousEpochData)
: undefined;
return (
<>
@@ -293,15 +293,21 @@ export const ValidatorTable = ({
data-testid="multisig-penalty"
className="flex gap-2 items-baseline"
>
{multisigStatus?.zeroScoreNodes.find(
(n) => n.id === node.id
) ? (
<Tooltip
description={t('multisigPenaltyThisNodeIndicator')}
>
<span className="inline-block w-2 h-2 rounded-full bg-vega-red-500"></span>
</Tooltip>
) : null}
<Tooltip description={t('multisigPenaltyDescription')}>
<span>
{formatNumberPercentage(
new BigNumber(1)
.minus(
previousNodeData?.node.rewardScore?.multisigScore ??
1
)
.times(100),
BigNumber(
multisigStatus?.showMultisigStatusError ? 100 : 0
),
2
)}
</span>
@@ -9,7 +9,7 @@ import {
useWithdrawalDialog,
WithdrawalsTable,
} from '@vegaprotocol/withdraws';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { useDocumentTitle } from '../../hooks/use-document-title';
import type { RouteChildProps } from '../index';
+4 -2
View File
@@ -6,7 +6,7 @@ import {
VegaIconNames,
useToasts,
} from '@vegaprotocol/ui-toolkit';
import { useDialogStore } from '@vegaprotocol/wallet-react';
import { useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import {
useEthereumTransactionToasts,
useEthereumWithdrawApprovalsToasts,
@@ -19,7 +19,9 @@ import { useTranslation } from 'react-i18next';
const WalletDisconnectAdditionalContent = () => {
const { t } = useTranslation();
const { hideToast } = useWalletDisconnectToastActions();
const openVegaWalletDialog = useDialogStore((store) => store.open);
const openVegaWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
);
return (
<p className="mt-2">
<TradingButton
+3 -5
View File
@@ -1,11 +1,9 @@
const { join } = require('path');
const { createGlobPatternsForDependencies } = require('@nx/react/tailwind');
const { theme } = require('../../libs/tailwindcss-config/src/theme');
const {
vegaCustomClasses,
} = require('../../libs/tailwindcss-config/src/vega-custom-classes');
const theme = require('../../libs/tailwindcss-config/src/theme');
const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom-classes');
export default {
module.exports = {
content: [
join(__dirname, 'src/**/*.{js,ts,jsx,tsx}'),
'libs/ui-toolkit/src/utils/shared.ts',
@@ -0,0 +1,11 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic"
}
]
],
"plugins": []
}

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