Compare commits

...

34 Commits

Author SHA1 Message Date
88264d890d Remove '.git' from .dockerignore; the Vega build process uses git commands ('rev-parse') to navigate 2024-03-29 18:52:21 -07:00
Alessio
26b78f878b Add a simple path prefix for static assets 2024-03-29 11:12:26 -07:00
Art
054c0377b4
chore(governance): lp votes for batch proposal (#5914)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-03-11 13:10:34 +01:00
Matthew Russell
29bcbd06fb
fix(trading,types): fix default build and type generation commands (#5959) 2024-03-11 10:06:59 +00:00
Matthew Russell
1d721dc748
fix(trading): party alias snags (#5947)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-03-08 17:29:41 +00:00
Matthew Russell
1d71a839b3
fix(trading): manifest.json and woff preloading (#5950) 2024-03-08 17:27:36 +00:00
Art
4b917926c5
chore(trading): market's tick size (#5929)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-03-08 17:04:33 +00:00
Art
3a56678403
fix(trading): pick the best node to connect, slow notification notification, other node picking improvements (#5909) 2024-03-08 16:12:11 +00:00
Matthew Russell
bcb5351dfc
chore(types,utils,wallet): prep for publish, remove lp dashboard (#5948) 2024-03-08 13:20:03 +00:00
Ben
177e72dd16
chore(trading): Refactor test fixtures (#5913) 2024-03-07 23:08:31 +01:00
daro-maj
8221882346
chore(trading): fix CI for trading e2e tests (#5945) 2024-03-07 20:23:50 +00:00
Matthew Russell
b1a8473131
fix(wallet-react): fix typo in refresh text 2024-03-07 18:00:34 +00:00
Matthew Russell
f18216f70f
chore(trading,governance,explorer): back merge hotfixes (#5946) 2024-03-07 17:41:58 +00:00
Ben
de2b79416e
chore(trading): staking reward card e2e (#5915) 2024-03-07 14:11:14 +00:00
Matthew Russell
6504912284
fix(wallet): wallet connection and error fixes (#5927)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
Co-authored-by: bwallacee <ben@vega.xyz>
2024-03-07 14:01:46 +00:00
m.ray
93643f1737
feat(trading): take profit and stop loss (#5902)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-03-07 10:53:16 +00:00
Bartłomiej Głownia
464d5af6be
feat(trading): add additional intervals (#5916) 2024-03-07 10:40:33 +00:00
Ben
b2a62f16bc
chore(trading): fix banners test (#5933) 2024-03-07 09:39:25 +00:00
Ben
9ec03a04ea
chore(trading): skip market info volume test (#5935)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-03-06 15:03:03 +00:00
Art
2a0727ec4b
chore(trading): scoped to teams games (#5920)
Co-authored-by: bwallacee <ben@vega.xyz>
2024-03-06 11:44:33 +00:00
Art
0d39c2354c
fix(trading): add team id to games query (#5921) 2024-03-06 10:47:47 +00:00
Matthew Russell
ad6f0c5798
feat(trading): add and show key aliases (#5819) 2024-03-06 10:47:16 +00:00
m.ray
38d13085fb
chore(trading): remove view as pubkey banner (#5926) 2024-03-05 16:15:51 +00:00
daro-maj
c0f4278b81
fix(trading): fix ci cypress issue (#5922) 2024-03-05 16:09:14 +00:00
Ben
b2777043b4
chore(trading): update vega and market-sim (#5919) 2024-03-05 15:49:50 +00:00
Matthew Russell
4d19b55096
Merge pull request #5912 from vegaprotocol/main
chore(trading,governance): back merge hotfixes
2024-03-05 04:14:14 -05:00
daro-maj
e6b3ff456d
fix(trading): fix tests due to new wallet connection method (#5903) 2024-03-04 15:58:33 +01:00
Matthew Russell
00dbb7dd60
chore(trading): delete trading-e2e cypress tests (#5901) 2024-03-03 14:25:04 +00:00
Matthew Russell
89e2033556
chore(ci): remove unused release script (#5874) 2024-03-01 14:26:43 +00:00
Matthew Russell
2d821700bd
fix(trading): check for batch proposal in trading mode tooltip (#5895) 2024-03-01 14:26:29 +00:00
Matthew Russell
28b4593a1d
refactor(trading,governance,wallet): wallet rewrite (#5815)
Co-authored-by: bwallacee <ben@vega.xyz>
2024-03-01 14:25:56 +00:00
Matthew Russell
358c734e31
Merge pull request #5893 from vegaprotocol/chore/enable-isolated-margin-testnet
chore(trading): enable isolated margin on testnet
2024-03-01 04:35:00 -05:00
Matthew Russell
3dd7496a9a
chore: enable isolated margin on testnet 2024-03-01 09:12:13 +00:00
m.ray
80d576d484
chore(trading): show staking reward cards (#5875) 2024-03-01 06:30:12 +00:00
549 changed files with 8938 additions and 10867 deletions

View File

@ -4,6 +4,5 @@ tmp/*
.dockerignore .dockerignore
dockerfiles dockerfiles
node_modules node_modules
.git
.github .github
.vscode .vscode

View File

@ -196,9 +196,9 @@ jobs:
cypress: cypress:
needs: [build-sources, check-e2e-needed] needs: [build-sources, check-e2e-needed]
name: '(CI) cypress' name: '(CI) cypress'
if: ${{ needs.check-e2e-needed.outputs.run-tests == 'true' }}
uses: ./.github/workflows/cypress-run.yml uses: ./.github/workflows/cypress-run.yml
secrets: inherit 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: with:
projects: ${{ needs.build-sources.outputs.projects-e2e }} projects: ${{ needs.build-sources.outputs.projects-e2e }}
tags: '@smoke' tags: '@smoke'
@ -287,6 +287,7 @@ jobs:
steps: steps:
- run: | - run: |
result="${{ needs.cypress.result }}" result="${{ needs.cypress.result }}"
echo "Result: $result"
if [[ $result == "success" || $result == "skipped" ]]; then if [[ $result == "success" || $result == "skipped" ]]; then
exit 0 exit 0
else else

View File

@ -1,36 +0,0 @@
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 }}

View File

@ -12,7 +12,6 @@ on:
options: options:
- explorer-e2e - explorer-e2e
- governance-e2e - governance-e2e
- trading-e2e
tags: tags:
description: 'Test tags to run' description: 'Test tags to run'
required: true required: true

View File

@ -10,5 +10,5 @@ jobs:
uses: ./.github/workflows/cypress-run.yml uses: ./.github/workflows/cypress-run.yml
secrets: inherit secrets: inherit
with: with:
projects: '["explorer-e2e","governance-e2e","trading-e2e"]' projects: '["explorer-e2e","governance-e2e"]'
tags: '@smoke @regression @slow' tags: '@smoke @regression @slow'

28
.verdaccio/config.yml Normal file
View File

@ -0,0 +1,28 @@
# 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

View File

@ -1,7 +1,9 @@
const { join } = require('path'); const { join } = require('path');
const { createGlobPatternsForDependencies } = require('@nx/react/tailwind'); const { createGlobPatternsForDependencies } = require('@nx/react/tailwind');
const theme = require('../../libs/tailwindcss-config/src/theme'); const { theme } = require('../../libs/tailwindcss-config/src/theme');
const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom-classes'); const {
vegaCustomClasses,
} = require('../../libs/tailwindcss-config/src/vega-custom-classes');
module.exports = { module.exports = {
content: [ content: [

View File

@ -7,6 +7,7 @@ import {
navigateTo, navigateTo,
navigation, navigation,
turnTelemetryOff, turnTelemetryOff,
setRiskAccepted,
} from '../../support/common.functions'; } from '../../support/common.functions';
import { import {
clickOnValidatorFromList, clickOnValidatorFromList,
@ -57,6 +58,7 @@ context(
// 1002-STKE-002, 1002-STKE-032 // 1002-STKE-002, 1002-STKE-032
before('visit staking tab and connect vega wallet', function () { before('visit staking tab and connect vega wallet', function () {
cy.visit('/'); cy.visit('/');
setRiskAccepted();
ethereumWalletConnect(); ethereumWalletConnect();
cy.connectVegaWallet(); cy.connectVegaWallet();
vegaWalletSetSpecifiedApprovalAmount('1000'); vegaWalletSetSpecifiedApprovalAmount('1000');

View File

@ -5,6 +5,7 @@ import {
navigateTo, navigateTo,
navigation, navigation,
turnTelemetryOff, turnTelemetryOff,
setRiskAccepted,
} from '../../support/common.functions'; } from '../../support/common.functions';
import { import {
stakingPageAssociateTokens, stakingPageAssociateTokens,
@ -57,6 +58,7 @@ context(
function () { function () {
cy.clearLocalStorage(); cy.clearLocalStorage();
turnTelemetryOff(); turnTelemetryOff();
setRiskAccepted();
cy.mockChainId(); cy.mockChainId();
cy.reload(); cy.reload();
waitForSpinner(); waitForSpinner();

View File

@ -79,23 +79,23 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
}); });
}); });
it('should have information on active nodes', function () { it.skip('should have information on active nodes', function () {
cy.getByTestId('node-information') cy.getByTestId('node-information')
.first() .first()
.should('contain.text', '1') .should('contain.text', '2')
.and('contain.text', 'active nodes'); .and('contain.text', 'active nodes');
}); });
it('should have information on consensus nodes', function () { it.skip('should have information on consensus nodes', function () {
cy.getByTestId('node-information') cy.getByTestId('node-information')
.last() .last()
.should('contain.text', '1') .should('contain.text', '2')
.and('contain.text', 'consensus nodes'); .and('contain.text', 'consensus nodes');
}); });
it('should contain link to specific validators', function () { it.skip('should contain link to specific validators', function () {
cy.getByTestId('validators') cy.getByTestId('validators')
.should('have.length', '1') .should('have.length', '2')
.each(($validator) => { .each(($validator) => {
cy.wrap($validator).find('a').should('have.attr', 'href'); 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') .invoke('text')
.should('not.eq', currentBlockHeight); .should('not.eq', currentBlockHeight);
}); });
cy.getByTestId('subscription-cell').should('have.text', 'Yes'); cy.getByTestId('subscription-cell').should('be.be.visible');
}); });
cy.getByTestId('connect').should('be.disabled'); cy.getByTestId('connect').should('be.disabled');
cy.getByTestId('node-url-custom').click({ force: true }); cy.getByTestId('node-url-custom').click({ force: true });

View File

@ -34,12 +34,6 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
cy.connectPublicKey(vegaWalletPubKey); 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 () { it.skip('Able to connect public key via wallet and view assets in wallet', function () {
verifyConnectedToPubKey(); verifyConnectedToPubKey();
cy.getByTestId('currency-title', { timeout: 10000 }) cy.getByTestId('currency-title', { timeout: 10000 })

View File

@ -3,6 +3,7 @@
import { aliasGQLQuery } from '@vegaprotocol/cypress'; import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { import {
navigation, navigation,
setRiskAccepted,
verifyPageHeader, verifyPageHeader,
verifyTabHighlighted, verifyTabHighlighted,
} from '../../support/common.functions'; } from '../../support/common.functions';
@ -187,6 +188,7 @@ context('Validators Page - verify elements on page', function () {
before('connect wallets and click on validator', function () { before('connect wallets and click on validator', function () {
cy.mockChainId(); cy.mockChainId();
cy.visit('/validators'); cy.visit('/validators');
setRiskAccepted();
cy.connectVegaWallet(); cy.connectVegaWallet();
clickOnValidatorFromList(0); clickOnValidatorFromList(0);
}); });

View File

@ -1,5 +1,8 @@
import { truncateByChars } from '@vegaprotocol/utils'; import { truncateByChars } from '@vegaprotocol/utils';
import { waitForSpinner } from '../../support/common.functions'; import {
setRiskAccepted,
waitForSpinner,
} from '../../support/common.functions';
import { import {
vegaWalletFaucetAssetsWithoutCheck, vegaWalletFaucetAssetsWithoutCheck,
vegaWalletTeardown, vegaWalletTeardown,
@ -11,7 +14,6 @@ const connectButton = 'connect-vega-wallet';
const getVegaLink = 'link'; const getVegaLink = 'link';
const dialog = '[role="dialog"]:visible'; const dialog = '[role="dialog"]:visible';
const dialogHeader = 'dialog-title'; const dialogHeader = 'dialog-title';
const walletDialogHeader = 'wallet-dialog-title';
const connectorsList = 'connectors-list'; const connectorsList = 'connectors-list';
const dialogCloseBtn = 'dialog-close'; const dialogCloseBtn = 'dialog-close';
const accountNo = 'vega-account-truncated'; const accountNo = 'vega-account-truncated';
@ -34,6 +36,7 @@ context(
() => { () => {
before('visit token home page', () => { before('visit token home page', () => {
cy.visit('/'); cy.visit('/');
setRiskAccepted();
cy.get(walletContainer, { timeout: 60000 }).should('be.visible'); cy.get(walletContainer, { timeout: 60000 }).should('be.visible');
}); });
@ -63,17 +66,12 @@ context(
it('should have Connect Vega header visible', () => { it('should have Connect Vega header visible', () => {
cy.get(dialog).within(() => { cy.get(dialog).within(() => {
cy.getByTestId(walletDialogHeader) cy.getByTestId(connectorsList)
.should('be.visible') .should('be.visible')
.and('have.text', 'Get a Vega wallet'); .and(
}); 'have.text',
}); 'Get the Vega WalletGet MetaMask>_Command Line WalletView as public key'
);
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');
}); });
}); });
@ -88,7 +86,6 @@ context(
before('connect vega wallet', function () { before('connect vega wallet', function () {
cy.mockChainId(); cy.mockChainId();
cy.visit('/'); cy.visit('/');
cy.wait('@ChainId');
cy.connectVegaWallet(); cy.connectVegaWallet();
vegaWalletTeardown(); vegaWalletTeardown();
}); });

View File

@ -102,6 +102,12 @@ export function turnTelemetryOff() {
); );
} }
export function setRiskAccepted() {
cy.window().then((win) =>
win.localStorage.setItem('vega_wallet_risk_accepted', 'true')
);
}
export function dissociateFromSecondWalletKey() { export function dissociateFromSecondWalletKey() {
const secondWalletKey = Cypress.env('vegaWalletPublicKey2Short'); const secondWalletKey = Cypress.env('vegaWalletPublicKey2Short');
cy.getByTestId('vega-in-wallet') cy.getByTestId('vega-in-wallet')

View File

@ -1,7 +1,7 @@
import * as Sentry from '@sentry/react'; import * as Sentry from '@sentry/react';
import { toBigNum } from '@vegaprotocol/utils'; import { toBigNum } from '@vegaprotocol/utils';
import { Splash } from '@vegaprotocol/ui-toolkit'; import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet, useEagerConnect } from '@vegaprotocol/wallet'; import { useVegaWallet, useEagerConnect } from '@vegaprotocol/wallet-react';
import { useFeatureFlags, useEnvironment } from '@vegaprotocol/environment'; import { useFeatureFlags, useEnvironment } from '@vegaprotocol/environment';
import { useWeb3React } from '@web3-react/core'; import { useWeb3React } from '@web3-react/core';
import React, { Suspense } from 'react'; import React, { Suspense } from 'react';
@ -15,20 +15,6 @@ import {
} from './contexts/app-state/app-state-context'; } from './contexts/app-state/app-state-context';
import { useContracts } from './contexts/contracts/contracts-context'; import { useContracts } from './contexts/contracts/contracts-context';
import { useRefreshAssociatedBalances } from './hooks/use-refresh-associated-balances'; 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 }) => { export const AppLoader = ({ children }: { children: React.ReactElement }) => {
const featureFlags = useFeatureFlags((state) => state.flags); const featureFlags = useFeatureFlags((state) => state.flags);
@ -40,9 +26,9 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
const { token, staking, vesting } = useContracts(); const { token, staking, vesting } = useContracts();
const setAssociatedBalances = useRefreshAssociatedBalances(); const setAssociatedBalances = useRefreshAssociatedBalances();
const [balancesLoaded, setBalancesLoaded] = React.useState(false); const [balancesLoaded, setBalancesLoaded] = React.useState(false);
const vegaConnecting = useVegaWalletEagerConnect(); const vegaWalletStatus = useEagerConnect();
const loaded = balancesLoaded && !vegaConnecting; const loaded = balancesLoaded && vegaWalletStatus !== 'connecting';
React.useEffect(() => { React.useEffect(() => {
const run = async () => { const run = async () => {
@ -183,3 +169,5 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
} }
return <Suspense fallback={loading}>{children}</Suspense>; return <Suspense fallback={loading}>{children}</Suspense>;
}; };
AppLoader.displayName = 'AppLoader';

View File

@ -1,7 +1,6 @@
import './i18n'; import './i18n';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import * as Sentry from '@sentry/react';
import { BrowserRouter as Router, useLocation } from 'react-router-dom'; import { BrowserRouter as Router, useLocation } from 'react-router-dom';
import { AppLoader } from './app-loader'; import { AppLoader } from './app-loader';
import { NetworkInfo } from '@vegaprotocol/network-info'; import { NetworkInfo } from '@vegaprotocol/network-info';
@ -26,7 +25,7 @@ import {
} from '@vegaprotocol/web3'; } from '@vegaprotocol/web3';
import { Web3Provider } from '@vegaprotocol/web3'; import { Web3Provider } from '@vegaprotocol/web3';
import { VegaWalletDialogs } from './components/vega-wallet-dialogs'; import { VegaWalletDialogs } from './components/vega-wallet-dialogs';
import { VegaWalletProvider, useChainId } from '@vegaprotocol/wallet'; import { WalletProvider } from '@vegaprotocol/wallet-react';
import { import {
useVegaTransactionManager, useVegaTransactionManager,
useVegaTransactionUpdater, useVegaTransactionUpdater,
@ -36,26 +35,21 @@ import { useEthereumConfig } from '@vegaprotocol/web3';
import { import {
useEnvironment, useEnvironment,
NetworkLoader, NetworkLoader,
useInitializeEnv,
NodeGuard, NodeGuard,
NodeSwitcherDialog, NodeSwitcherDialog,
useNodeSwitcherStore, useNodeSwitcherStore,
DocsLinks,
NodeFailure, NodeFailure,
AppLoader as Loader, AppLoader as Loader,
useInitializeEnv,
} from '@vegaprotocol/environment'; } from '@vegaprotocol/environment';
import { ENV } from './config';
import type { InMemoryCacheConfig } from '@apollo/client'; import type { InMemoryCacheConfig } from '@apollo/client';
import { CreateWithdrawalDialog } from '@vegaprotocol/withdraws'; import { CreateWithdrawalDialog } from '@vegaprotocol/withdraws';
import { SplashLoader } from './components/splash-loader'; import { SplashLoader } from './components/splash-loader';
import { ToastsManager } from './toasts-manager'; import { ToastsManager } from './toasts-manager';
import { import { TelemetryDialog } from './components/telemetry-dialog/telemetry-dialog';
TelemetryDialog,
TELEMETRY_ON,
} from './components/telemetry-dialog/telemetry-dialog';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { isPartyNotFoundError } from './lib/party'; import { useSentryInit } from './hooks/use-sentry-init';
import { useVegaWalletConfig } from './hooks/use-vega-wallet-config';
const cache: InMemoryCacheConfig = { const cache: InMemoryCacheConfig = {
typePolicies: { typePolicies: {
@ -104,32 +98,12 @@ const Web3Container = ({
/** Ethereum provider url */ /** Ethereum provider url */
providerUrl: string; providerUrl: string;
}) => { }) => {
const InitializeHandlers = () => {
useVegaTransactionManager();
useVegaTransactionUpdater();
useEthTransactionManager();
useEthTransactionUpdater();
useEthWithdrawApprovalsManager();
return null;
};
const [connectors, initializeConnectors] = useWeb3ConnectStore((store) => [ const [connectors, initializeConnectors] = useWeb3ConnectStore((store) => [
store.connectors, store.connectors,
store.initialize, store.initialize,
]); ]);
const { const { ETHEREUM_PROVIDER_URL, ETH_LOCAL_PROVIDER_URL, ETH_WALLET_MNEMONIC } =
ETHEREUM_PROVIDER_URL, useEnvironment();
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(() => { useEffect(() => {
if (chainId) { if (chainId) {
@ -150,50 +124,31 @@ const Web3Container = ({
ETH_LOCAL_PROVIDER_URL, ETH_LOCAL_PROVIDER_URL,
ETH_WALLET_MNEMONIC, ETH_WALLET_MNEMONIC,
]); ]);
const sideBar = React.useMemo(() => {
return [<EthWallet />, <VegaWallet />];
}, []);
if (connectors.length === 0) { const vegaWalletConfig = useVegaWalletConfig();
if (!vegaWalletConfig || connectors.length === 0) {
// Prevent loading when the connectors are not initialized // Prevent loading when the connectors are not initialized
return <SplashLoader />; return <SplashLoader />;
} }
if (
!VEGA_URL ||
!VEGA_WALLET_URL ||
!VEGA_EXPLORER_URL ||
!DocsLinks ||
!CHROME_EXTENSION_URL ||
!MOZILLA_EXTENSION_URL ||
!vegaChainId
) {
return null;
}
return ( return (
<Web3Provider connectors={connectors}> <Web3Provider connectors={connectors}>
<Web3Connector connectors={connectors} chainId={Number(chainId)}> <Web3Connector connectors={connectors} chainId={Number(chainId)}>
<VegaWalletProvider <WalletProvider config={vegaWalletConfig}>
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> <ContractsProvider>
<AppLoader> <AppLoader>
<BalanceManager> <BalanceManager>
<> <>
<AppLayout> <AppLayout>
<TemplateSidebar sidebar={sideBar}> <TemplateSidebar
sidebar={
<>
<EthWallet />
<VegaWallet />
</>
}
>
<AppRouter /> <AppRouter />
</TemplateSidebar> </TemplateSidebar>
<footer className="p-4 break-all border-t border-neutral-700"> <footer className="p-4 break-all border-t border-neutral-700">
@ -211,7 +166,7 @@ const Web3Container = ({
</BalanceManager> </BalanceManager>
</AppLoader> </AppLoader>
</ContractsProvider> </ContractsProvider>
</VegaWalletProvider> </WalletProvider>
</Web3Connector> </Web3Connector>
</Web3Provider> </Web3Provider>
); );
@ -231,20 +186,9 @@ const ScrollToTop = () => {
return null; return null;
}; };
const removeQueryParams = (url: string) => {
return url.split('?')[0];
};
const AppContainer = () => { const AppContainer = () => {
const { config, loading, error } = useEthereumConfig(); const { config, loading, error } = useEthereumConfig();
const { const { VEGA_URL, ETHEREUM_PROVIDER_URL } = useEnvironment();
VEGA_ENV,
VEGA_URL,
GIT_COMMIT_HASH,
GIT_BRANCH,
ETHEREUM_PROVIDER_URL,
} = useEnvironment();
const [telemetryOn] = useLocalStorage(TELEMETRY_ON);
const { t } = useTranslation(); const { t } = useTranslation();
const [nodeSwitcherOpen, setNodeSwitcher] = useNodeSwitcherStore((store) => [ const [nodeSwitcherOpen, setNodeSwitcher] = useNodeSwitcherStore((store) => [
store.dialogOpen, store.dialogOpen,
@ -254,70 +198,7 @@ const AppContainer = () => {
// Hacky skip all the loading & web3 init for geo restricted users // Hacky skip all the loading & web3 init for geo restricted users
const isRestricted = document?.location?.pathname?.includes('/restricted'); const isRestricted = document?.location?.pathname?.includes('/restricted');
useEffect(() => { useSentryInit();
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) { if (isRestricted) {
return ( return (
@ -359,6 +240,15 @@ const AppContainer = () => {
); );
}; };
const InitializeHandlers = () => {
useVegaTransactionManager();
useVegaTransactionUpdater();
useEthTransactionManager();
useEthTransactionUpdater();
useEthWithdrawApprovalsManager();
return null;
};
function App() { function App() {
useInitializeEnv(); useInitializeEnv();

View File

@ -7,7 +7,7 @@ import { useGetAssociationBreakdown } from '../../hooks/use-get-association-brea
import { useGetUserBalances } from '../../hooks/use-get-user-balances'; import { useGetUserBalances } from '../../hooks/use-get-user-balances';
import { useBalances } from '../../lib/balances/balances-store'; import { useBalances } from '../../lib/balances/balances-store';
import type { ReactElement } from 'react'; import type { ReactElement } from 'react';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useListenForStakingEvents as useListenForAssociationEvents } from '../../hooks/use-listen-for-staking-events'; import { useListenForStakingEvents as useListenForAssociationEvents } from '../../hooks/use-listen-for-staking-events';
import { useTranches } from '../../lib/tranches/tranches-store'; import { useTranches } from '../../lib/tranches/tranches-store';
import { useUserTrancheBalances } from '../../routes/redemption/hooks'; import { useUserTrancheBalances } from '../../routes/redemption/hooks';

View File

@ -1,18 +1,13 @@
import { Button } from '@vegaprotocol/ui-toolkit'; import { Button } from '@vegaprotocol/ui-toolkit';
import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useVegaWalletDialogStore } from '@vegaprotocol/wallet'; import { useDialogStore } from '@vegaprotocol/wallet-react';
export const ConnectToVega = () => { export const ConnectToVega = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({ const openVegaWalletDialog = useDialogStore((store) => store.open);
openVegaWalletDialog: store.openVegaWalletDialog,
}));
return ( return (
<Button <Button
onClick={() => { onClick={openVegaWalletDialog}
openVegaWalletDialog();
}}
data-testid="connect-to-vega-wallet-btn" data-testid="connect-to-vega-wallet-btn"
variant="primary" variant="primary"
> >

View File

@ -6,8 +6,8 @@ import {
Intent, Intent,
Icon, Icon,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '../use-vega-wallet'; import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useT } from '../use-t'; import { useTranslation } from 'react-i18next';
export interface VegaManageDialogProps { export interface VegaManageDialogProps {
dialogOpen: boolean; dialogOpen: boolean;
@ -18,7 +18,7 @@ export const VegaManageDialog = ({
dialogOpen, dialogOpen,
setDialogOpen, setDialogOpen,
}: VegaManageDialogProps) => { }: VegaManageDialogProps) => {
const t = useT(); const { t } = useTranslation();
const { pubKey, pubKeys, selectPubKey, disconnect } = useVegaWallet(); const { pubKey, pubKeys, selectPubKey, disconnect } = useVegaWallet();
return ( return (
<Dialog <Dialog

View File

@ -1,5 +1,5 @@
import classNames from 'classnames'; import classNames from 'classnames';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet-react';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { AnnouncementBanner } from '@vegaprotocol/announcements'; import { AnnouncementBanner } from '@vegaprotocol/announcements';
import { Nav } from '../nav'; import { Nav } from '../nav';

View File

@ -1,8 +1,8 @@
import React from 'react'; import { Children, type ReactNode } from 'react';
export interface TemplateSidebarProps { export interface TemplateSidebarProps {
children: React.ReactNode; children: ReactNode;
sidebar: React.ReactNode[]; sidebar: ReactNode;
} }
export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) { export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
@ -12,9 +12,9 @@ export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
{children} {children}
</main> </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"> <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">
{sidebar.map((Component, i) => ( {Children.map(sidebar, (child, i) => (
<section className="mb-4 last:mb-0" key={i}> <section className="mb-4 last:mb-0" key={i}>
{Component} {child}
</section> </section>
))} ))}
</aside> </aside>

View File

@ -1,5 +1,5 @@
import { Button } from '@vegaprotocol/ui-toolkit'; import { Button } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet'; import { useVegaWallet, useDialogStore } from '@vegaprotocol/wallet-react';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -10,9 +10,7 @@ interface VegaWalletContainerProps {
export const VegaWalletContainer = ({ children }: VegaWalletContainerProps) => { export const VegaWalletContainer = ({ children }: VegaWalletContainerProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { pubKey } = useVegaWallet(); const { pubKey } = useVegaWallet();
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({ const openVegaWalletDialog = useDialogStore((store) => store.open);
openVegaWalletDialog: store.openVegaWalletDialog,
}));
if (!pubKey) { if (!pubKey) {
return ( return (

View File

@ -9,7 +9,7 @@ import Routes from '../../routes/routes';
export const RiskMessage = () => { export const RiskMessage = () => {
return ( return (
<> <>
<div className="bg-vega-light-100 dark:bg-vega-dark-100 p-6 mb-6"> <div className="bg-vega-light-100 dark:bg-vega-dark-100 p-6">
<ul className="list-[square] ml-4"> <ul className="list-[square] ml-4">
<li> <li>
{t( {t(
@ -23,7 +23,7 @@ export const RiskMessage = () => {
</li> </li>
</ul> </ul>
</div> </div>
<p className="mb-8"> <p>
{t( {t(
'By using the Vega Governance App, you acknowledge that you have read and understood the' 'By using the Vega Governance App, you acknowledge that you have read and understood the'
)}{' '} )}{' '}

View File

@ -1,25 +1,39 @@
import { import {
VegaConnectDialog, ConnectDialogWithRiskAck,
VegaManageDialog, useDialogStore,
ViewAsDialog, } from '@vegaprotocol/wallet-react';
} from '@vegaprotocol/wallet';
import { import {
AppStateActionType, AppStateActionType,
useAppState, useAppState,
} from '../../contexts/app-state/app-state-context'; } from '../../contexts/app-state/app-state-context';
import { useConnectors } from '../../lib/vega-connectors';
import { RiskMessage } from './risk-message'; 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 = () => { export const VegaWalletDialogs = () => {
const { VEGA_ENV } = useEnvironment();
const { appState, appDispatch } = useAppState(); const { appState, appDispatch } = useAppState();
const connectors = useConnectors(); const [riskAccepted, setRiskAccepted] = useLocalStorage(
'vega_wallet_risk_accepted'
);
const vegaWalletDialogOpen = useDialogStore((store) => store.isOpen);
const setVegaWalletDialog = useDialogStore((store) => store.set);
return ( return (
<> <>
<VegaConnectDialog <ConnectDialogWithRiskAck
connectors={connectors} open={vegaWalletDialogOpen}
riskMessage={<RiskMessage />} onChange={setVegaWalletDialog}
riskAccepted={
VEGA_ENV === Networks.TESTNET ? riskAccepted === 'true' : true
}
riskAckContent={<RiskMessage />}
onRiskAccepted={() => setRiskAccepted('true')}
onRiskRejected={() => {
setRiskAccepted('false');
setVegaWalletDialog(false);
}}
/> />
<VegaManageDialog <VegaManageDialog
dialogOpen={appState.vegaWalletManageOverlay} dialogOpen={appState.vegaWalletManageOverlay}
setDialogOpen={(open) => setDialogOpen={(open) =>
@ -29,8 +43,6 @@ export const VegaWalletDialogs = () => {
}) })
} }
/> />
<ViewAsDialog connector={connectors.view} />
</> </>
); );
}; };

View File

@ -10,7 +10,7 @@ import vegaBlack from '../../images/vega_black.png';
import vegaVesting from '../../images/vega_vesting.png'; import vegaVesting from '../../images/vega_vesting.png';
import { BigNumber } from '../../lib/bignumber'; import { BigNumber } from '../../lib/bignumber';
import { type WalletCardAssetProps } from '../wallet-card'; import { type WalletCardAssetProps } from '../wallet-card';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useContracts } from '../../contexts/contracts/contracts-context'; import { useContracts } from '../../contexts/contracts/contracts-context';
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
import { import {

View File

@ -1,11 +1,11 @@
import { ButtonLink, Link } from '@vegaprotocol/ui-toolkit'; import { ButtonLink, Link } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ExternalLinks } from '@vegaprotocol/environment'; import { ExternalLinks } from '@vegaprotocol/environment';
import { useViewAsDialog } from '@vegaprotocol/wallet'; import { useConnect } from '@vegaprotocol/wallet-react';
export const VegaWalletPrompt = () => { export const VegaWalletPrompt = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const setViewAsDialog = useViewAsDialog((state) => state.setOpen); const { connect } = useConnect();
return ( return (
<> <>
<h3 className="mt-4 mb-2">{t('getWallet')}</h3> <h3 className="mt-4 mb-2">{t('getWallet')}</h3>
@ -16,7 +16,7 @@ export const VegaWalletPrompt = () => {
<ButtonLink <ButtonLink
className="text-neutral-500" className="text-neutral-500"
data-testid="view-as-user" data-testid="view-as-user"
onClick={() => setViewAsDialog(true)} onClick={() => connect('viewParty')}
> >
{t('viewAsParty')} {t('viewAsParty')}
</ButtonLink> </ButtonLink>

View File

@ -25,7 +25,7 @@ import {
} from '../wallet-card'; } from '../wallet-card';
import { VegaWalletPrompt } from './vega-wallet-prompt'; import { VegaWalletPrompt } from './vega-wallet-prompt';
import { usePollForDelegations } from './hooks'; import { usePollForDelegations } from './hooks';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet'; import { useVegaWallet, useDialogStore } from '@vegaprotocol/wallet-react';
import { Button, ButtonLink } from '@vegaprotocol/ui-toolkit'; import { Button, ButtonLink } from '@vegaprotocol/ui-toolkit';
import { toBigNum } from '@vegaprotocol/utils'; import { toBigNum } from '@vegaprotocol/utils';
import { usePendingBalancesStore } from '../../hooks/use-pending-balances-manager'; import { usePendingBalancesStore } from '../../hooks/use-pending-balances-manager';
@ -34,16 +34,17 @@ import omit from 'lodash/omit';
export const VegaWallet = () => { export const VegaWallet = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { pubKey, pubKeys } = useVegaWallet(); const { status, pubKey, pubKeys } = useVegaWallet();
const pubKeyObj = useMemo(() => { const pubKeyObj = useMemo(() => {
return pubKeys?.find((pk) => pk.publicKey === pubKey); return pubKeys?.find((pk) => pk.publicKey === pubKey);
}, [pubKey, pubKeys]); }, [pubKey, pubKeys]);
const child = !pubKeys ? ( const child =
<VegaWalletNotConnected /> status === 'connected' ? (
) : ( <VegaWalletConnected vegaKeys={pubKeys.map((pk) => pk.publicKey)} />
<VegaWalletConnected vegaKeys={pubKeys.map((pk) => pk.publicKey)} /> ) : (
); <VegaWalletNotConnected />
);
return ( return (
<section className="vega-wallet" data-testid="vega-wallet"> <section className="vega-wallet" data-testid="vega-wallet">
@ -75,9 +76,7 @@ export const VegaWallet = () => {
const VegaWalletNotConnected = () => { const VegaWalletNotConnected = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({ const openVegaWalletDialog = useDialogStore((store) => store.open);
openVegaWalletDialog: store.openVegaWalletDialog,
}));
return ( return (
<> <>
<Button <Button

View File

@ -111,3 +111,4 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
</ContractsContext.Provider> </ContractsContext.Provider>
); );
}; };
ContractsProvider.displayName = 'ContractsProvider';

View File

@ -7,7 +7,7 @@ import { useWeb3React } from '@web3-react/core';
export const useListenForStakingEvents = ( export const useListenForStakingEvents = (
contract: Contract | undefined, contract: Contract | undefined,
vegaPublicKey: string | null, vegaPublicKey: string | undefined,
numberOfConfirmations: number numberOfConfirmations: number
) => { ) => {
const { account } = useWeb3React(); const { account } = useWeb3React();

View File

@ -1,6 +1,6 @@
import * as Sentry from '@sentry/react'; import * as Sentry from '@sentry/react';
import { toBigNum } from '@vegaprotocol/utils'; import { toBigNum } from '@vegaprotocol/utils';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useEthereumConfig } from '@vegaprotocol/web3'; import { useEthereumConfig } from '@vegaprotocol/web3';
import React from 'react'; import React from 'react';

View File

@ -0,0 +1,81 @@
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];
};

View File

@ -0,0 +1,41 @@
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]);
};

View File

@ -31,7 +31,7 @@ i18n
load: 'languageOnly', load: 'languageOnly',
debug: isInDev, debug: isInDev,
// have a common namespace used around the full app // have a common namespace used around the full app
ns: ['governance'], ns: ['governance', 'wallet', 'wallet-react'],
defaultNS: 'governance', defaultNS: 'governance',
keySeparator: false, // we use content as keys keySeparator: false, // we use content as keys
nsSeparator: false, nsSeparator: false,

View File

@ -1,30 +0,0 @@
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]
);
};

View File

@ -5,7 +5,6 @@ import {
ProposalState, ProposalState,
VoteValue, VoteValue,
} from '@vegaprotocol/types'; } from '@vegaprotocol/types';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider'; import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { import {
generateNoVotes, generateNoVotes,
@ -16,9 +15,7 @@ import { ProposalHeader, NewTransferSummary } from './proposal-header';
import { import {
lastWeek, lastWeek,
nextWeek, nextWeek,
mockWalletContext,
createUserVoteQueryMock, createUserVoteQueryMock,
networkParamsQueryMock,
} from '../../test-helpers/mocks'; } from '../../test-helpers/mocks';
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from 'react-router-dom';
import { VoteState } from '../vote-details/use-user-vote'; import { VoteState } from '../vote-details/use-user-vote';
@ -45,14 +42,12 @@ const renderComponent = (
render( render(
<AppStateProvider> <AppStateProvider>
<BrowserRouter> <BrowserRouter>
<MockedProvider mocks={[networkParamsQueryMock, ...mocks]}> <MockedProvider mocks={mocks}>
<VegaWalletContext.Provider value={mockWalletContext}> <ProposalHeader
<ProposalHeader proposal={proposal}
proposal={proposal} isListItem={isListItem}
isListItem={isListItem} voteState={voteState}
voteState={voteState} />
/>
</VegaWalletContext.Provider>
</MockedProvider> </MockedProvider>
</BrowserRouter> </BrowserRouter>
</AppStateProvider> </AppStateProvider>
@ -160,7 +155,7 @@ describe('Proposal header', () => {
screen.queryByTestId('proposal-description') screen.queryByTestId('proposal-description')
).not.toBeInTheDocument(); ).not.toBeInTheDocument();
expect(screen.getByTestId('proposal-details')).toHaveTextContent( expect(screen.getByTestId('proposal-details')).toHaveTextContent(
'Update to market: MarketId' /Update to market: MarketId/
); );
}); });

View File

@ -38,6 +38,7 @@ import { differenceInHours, format, formatDistanceToNowStrict } from 'date-fns';
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats'; import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
import { MarketName } from '../proposal/market-name'; import { MarketName } from '../proposal/market-name';
import { Indicator } from '../proposal/indicator'; import { Indicator } from '../proposal/indicator';
import { type ProposalNode } from '../proposal/proposal-utils';
const ProposalTypeTags = ({ const ProposalTypeTags = ({
proposal, proposal,
@ -540,10 +541,12 @@ const BatchProposalStateText = ({
export const ProposalHeader = ({ export const ProposalHeader = ({
proposal, proposal,
restData,
isListItem = true, isListItem = true,
voteState, voteState,
}: { }: {
proposal: Proposal | BatchProposal; proposal: Proposal | BatchProposal;
restData?: ProposalNode | null;
isListItem?: boolean; isListItem?: boolean;
voteState?: VoteState | null; voteState?: VoteState | null;
}) => { }) => {
@ -595,7 +598,7 @@ export const ProposalHeader = ({
)} )}
</div> </div>
<ProposalDetails proposal={proposal} /> <ProposalDetails proposal={proposal} />
<VoteBreakdown proposal={proposal} /> <VoteBreakdown proposal={proposal} restData={restData} />
</> </>
); );
}; };

View File

@ -91,6 +91,28 @@ export type ProposalNode = {
proposal: ProposalData; proposal: ProposalData;
proposalType: ProposalNodeType; proposalType: ProposalNodeType;
proposals: SubProposalData[]; proposals: SubProposalData[];
yes?: [
{
partyId: string;
elsPerMarket?: [
{
marketId: string;
els: string;
}
];
}
];
no?: [
{
partyId: string;
elsPerMarket?: [
{
marketId: string;
els: string;
}
];
}
];
}; };
type SingleProposalNode = ProposalNode & { type SingleProposalNode = ProposalNode & {

View File

@ -1,12 +1,12 @@
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing'; 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 { render, screen } from '@testing-library/react';
import { generateProposal } from '../../test-helpers/generate-proposals'; import { generateProposal } from '../../test-helpers/generate-proposals';
import { Proposal } from './proposal'; import { Proposal } from './proposal';
import { ProposalState } from '@vegaprotocol/types'; import { ProposalState } from '@vegaprotocol/types';
import { type Proposal as IProposal } from '../../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.mock('@vegaprotocol/network-parameters', () => ({
...jest.requireActual('@vegaprotocol/network-parameters'), ...jest.requireActual('@vegaprotocol/network-parameters'),
@ -24,45 +24,44 @@ jest.mock('@vegaprotocol/network-parameters', () => ({
error: null, error: null,
})), })),
})); }));
jest.mock('../proposal-detail-header/proposal-header', () => ({ jest.mock('../proposal-detail-header/proposal-header', () => ({
ProposalHeader: () => <div data-testid="proposal-header"></div>, ProposalHeader: () => <div data-testid="proposal-header"></div>,
})); }));
jest.mock('../proposal-change-table', () => ({ jest.mock('../proposal-change-table', () => ({
ProposalChangeTable: () => <div data-testid="proposal-change-table"></div>, ProposalChangeTable: () => <div data-testid="proposal-change-table"></div>,
})); }));
jest.mock('../proposal-json', () => ({ jest.mock('../proposal-json', () => ({
ProposalJson: () => <div data-testid="proposal-json"></div>, ProposalJson: () => <div data-testid="proposal-json"></div>,
})); }));
jest.mock('../list-asset', () => ({ jest.mock('../list-asset', () => ({
ListAsset: () => <div data-testid="proposal-list-asset"></div>, ListAsset: () => <div data-testid="proposal-list-asset"></div>,
})); }));
jest.mock('./proposal-change-details', () => ({ jest.mock('../list-asset', () => ({
ProposalChangeDetails: () => ( ListAsset: () => <div data-testid="proposal-list-asset"></div>,
<div data-testid="proposal-change-details"></div>
),
})); }));
const vegaWalletConfig: VegaWalletConfig = { jest.mock('../vote-details', () => ({
network: 'TESTNET', UserVote: () => <div data-testid="user-vote"></div>,
vegaUrl: 'https://vega.xyz', }));
vegaWalletServiceUrl: 'https://wallet.vega.xyz',
links: { jest.mock('./proposal-change-details', () => ({
explorer: 'explorer', ProposalChangeDetails: () => <div data-testid="proposal-change-details" />,
concepts: 'concepts', }));
chromeExtensionUrl: 'chrome',
mozillaExtensionUrl: 'mozilla',
},
chainId: 'VEGA_CHAIN_ID',
};
const renderComponent = (proposal: IProposal) => { const renderComponent = (proposal: IProposal) => {
render( return render(
<MemoryRouter> <MemoryRouter>
<MockedProvider> <MockedProvider>
<VegaWalletProvider config={vegaWalletConfig}> <MockedWalletProvider>
<Proposal restData={null} proposal={proposal} /> <AppStateProvider>
</VegaWalletProvider> <Proposal restData={null} proposal={proposal} />
</AppStateProvider>
</MockedWalletProvider>
</MockedProvider> </MockedProvider>
</MemoryRouter> </MemoryRouter>
); );

View File

@ -23,7 +23,7 @@ export interface ProposalProps {
export const Proposal = ({ proposal, restData }: ProposalProps) => { export const Proposal = ({ proposal, restData }: ProposalProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { submit, Dialog, finalizedVote, transaction } = useVoteSubmit(); const { submit, finalizedVote, transaction } = useVoteSubmit();
const { voteState, voteDatetime } = useUserVote(proposal?.id, finalizedVote); const { voteState, voteDatetime } = useUserVote(proposal?.id, finalizedVote);
return ( return (
@ -48,6 +48,7 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
<ProposalHeader <ProposalHeader
proposal={proposal} proposal={proposal}
restData={restData}
isListItem={false} isListItem={false}
voteState={voteState} voteState={voteState}
/> />
@ -88,7 +89,6 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
<UserVote <UserVote
proposal={proposal} proposal={proposal}
submit={submit} submit={submit}
dialog={Dialog}
transaction={transaction} transaction={transaction}
voteState={voteState} voteState={voteState}
voteDatetime={voteDatetime} voteDatetime={voteDatetime}

View File

@ -1,15 +1,14 @@
import { BrowserRouter as Router } from 'react-router-dom'; import { BrowserRouter as Router } from 'react-router-dom';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { ProposalsListItemDetails } from './proposals-list-item-details'; import { ProposalsListItemDetails } from './proposals-list-item-details';
import { mockWalletContext } from '../../test-helpers/mocks';
const renderComponent = (id: string) => const renderComponent = (id: string) =>
render( render(
<Router> <Router>
<VegaWalletContext.Provider value={mockWalletContext}> <AppStateProvider>
<ProposalsListItemDetails id={id} /> <ProposalsListItemDetails id={id} />
</VegaWalletContext.Provider> </AppStateProvider>
</Router> </Router>
); );

View File

@ -1,25 +1,32 @@
import { render, screen } from '@testing-library/react'; import { act, render, screen } from '@testing-library/react';
import { VegaWalletContext } from '@vegaprotocol/wallet'; import {
MockedWalletProvider,
mockConfig,
} from '@vegaprotocol/wallet-react/testing';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider'; import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { ProposalFormSubmit } from './proposal-form-submit'; import { ProposalFormSubmit } from './proposal-form-submit';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
const renderComponent = ( const renderComponent = (isSubmitting: boolean) => {
context: VegaWalletContextShape,
isSubmitting: boolean
) => {
render( render(
<AppStateProvider> <MockedWalletProvider>
<VegaWalletContext.Provider value={context}> <AppStateProvider>
<ProposalFormSubmit isSubmitting={isSubmitting} /> <ProposalFormSubmit isSubmitting={isSubmitting} />
</VegaWalletContext.Provider> </AppStateProvider>
</AppStateProvider> </MockedWalletProvider>
); );
}; };
describe('Proposal Form Submit', () => { 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', () => { it('should display connection message and button if wallet not connected', () => {
renderComponent({ pubKey: null } as VegaWalletContextShape, false); renderComponent(false);
expect( expect(
screen.getByText('Connect your wallet to submit a proposal') screen.getByText('Connect your wallet to submit a proposal')
@ -30,28 +37,22 @@ describe('Proposal Form Submit', () => {
}); });
it('should display submit button if wallet is connected', () => { it('should display submit button if wallet is connected', () => {
const pubKey = { publicKey: '123456__123456', name: 'test' }; mockConfig.store.setState({
renderComponent( pubKey: pubKey.publicKey,
{ keys: [pubKey],
pubKey: pubKey.publicKey, });
pubKeys: [pubKey], renderComponent(false);
} as VegaWalletContextShape,
false
);
expect(screen.getByTestId('proposal-submit')).toHaveTextContent( expect(screen.getByTestId('proposal-submit')).toHaveTextContent(
'Submit proposal' 'Submit proposal'
); );
}); });
it('should display submitting button text if wallet is connected and submitting', () => { it('should display submitting button text if wallet is connected and submitting', () => {
const pubKey = { publicKey: '123456__123456', name: 'test' }; mockConfig.store.setState({
renderComponent( pubKey: pubKey.publicKey,
{ keys: [pubKey],
pubKey: pubKey.publicKey, });
pubKeys: [pubKey], renderComponent(true);
} as VegaWalletContextShape,
true
);
expect(screen.getByTestId('proposal-submit')).toHaveTextContent( expect(screen.getByTestId('proposal-submit')).toHaveTextContent(
'Submitting proposal' 'Submitting proposal'
); );

View File

@ -1,6 +1,6 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button } from '@vegaprotocol/ui-toolkit'; import { Button } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { VegaWalletContainer } from '../../../../components/vega-wallet-container'; import { VegaWalletContainer } from '../../../../components/vega-wallet-container';
interface ProposalFormSubmitProps { interface ProposalFormSubmitProps {

View File

@ -1,19 +1,24 @@
import { import {
VegaTransactionDialog,
getProposalDialogIcon, getProposalDialogIcon,
getProposalDialogIntent, getProposalDialogIntent,
useGetProposalDialogTitle, useGetProposalDialogTitle,
} from '@vegaprotocol/proposals'; } from '@vegaprotocol/proposals';
import type { ProposalEventFieldsFragment } from '@vegaprotocol/proposals'; import type {
import type { DialogProps } from '@vegaprotocol/proposals'; ProposalEventFieldsFragment,
VegaTxState,
} from '@vegaprotocol/proposals';
interface ProposalFormTransactionDialogProps { interface ProposalFormTransactionDialogProps {
finalizedProposal: ProposalEventFieldsFragment | null; finalizedProposal: ProposalEventFieldsFragment | null;
TransactionDialog: (props: DialogProps) => JSX.Element; transaction: VegaTxState;
onChange: (open: boolean) => void;
} }
export const ProposalFormTransactionDialog = ({ export const ProposalFormTransactionDialog = ({
finalizedProposal, finalizedProposal,
TransactionDialog, transaction,
onChange,
}: ProposalFormTransactionDialogProps) => { }: ProposalFormTransactionDialogProps) => {
const title = useGetProposalDialogTitle(finalizedProposal?.state); const title = useGetProposalDialogTitle(finalizedProposal?.state);
// Render a custom complete UI if the proposal was rejected otherwise // Render a custom complete UI if the proposal was rejected otherwise
@ -24,13 +29,16 @@ export const ProposalFormTransactionDialog = ({
return ( return (
<div data-testid="proposal-transaction-dialog"> <div data-testid="proposal-transaction-dialog">
<TransactionDialog <VegaTransactionDialog
title={title} title={title}
intent={getProposalDialogIntent(finalizedProposal?.state)} intent={getProposalDialogIntent(finalizedProposal?.state)}
icon={getProposalDialogIcon(finalizedProposal?.state)} icon={getProposalDialogIcon(finalizedProposal?.state)}
content={{ content={{
Complete: completeContent, Complete: completeContent,
}} }}
transaction={transaction}
isOpen={transaction.dialogOpen}
onChange={onChange}
/> />
</div> </div>
); );

View File

@ -1,10 +1,8 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { BrowserRouter as Router } from 'react-router-dom'; import { BrowserRouter as Router } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { import {
lastWeek, lastWeek,
mockWalletContext,
networkParamsQueryMock, networkParamsQueryMock,
nextWeek, nextWeek,
} from '../../test-helpers/mocks'; } from '../../test-helpers/mocks';
@ -48,9 +46,7 @@ const renderComponent = (
render( render(
<Router> <Router>
<MockedProvider mocks={mocks}> <MockedProvider mocks={mocks}>
<VegaWalletContext.Provider value={mockWalletContext}> <VoteBreakdown proposal={proposal} />
<VoteBreakdown proposal={proposal} />
</VegaWalletContext.Provider>
</MockedProvider> </MockedProvider>
</Router> </Router>
); );
@ -60,6 +56,7 @@ describe('VoteBreakdown', () => {
jest.useFakeTimers(); jest.useFakeTimers();
jest.setSystemTime(0); jest.setSystemTime(0);
}); });
afterAll(() => { afterAll(() => {
jest.useRealTimers(); jest.useRealTimers();
}); });

View File

@ -17,6 +17,7 @@ import {
import { useBatchVoteInformation } from '../../hooks/use-vote-information'; import { useBatchVoteInformation } from '../../hooks/use-vote-information';
import { MarketName } from '../proposal/market-name'; import { MarketName } from '../proposal/market-name';
import { Indicator } from '../proposal/indicator'; import { Indicator } from '../proposal/indicator';
import { type ProposalNode } from '../proposal/proposal-utils';
export const CompactVotes = ({ number }: { number: BigNumber }) => ( export const CompactVotes = ({ number }: { number: BigNumber }) => (
<CompactNumber <CompactNumber
@ -110,24 +111,64 @@ const Status = ({ reached, threshold, text, testId }: StatusProps) => {
export const VoteBreakdown = ({ export const VoteBreakdown = ({
proposal, proposal,
restData,
}: { }: {
proposal: Proposal | BatchProposal; proposal: Proposal | BatchProposal;
restData?: ProposalNode | null;
}) => { }) => {
if (proposal.__typename === 'Proposal') { if (proposal.__typename === 'Proposal') {
return <VoteBreakdownNormal proposal={proposal} />; return <VoteBreakdownNormal proposal={proposal} />;
} }
if (proposal.__typename === 'BatchProposal') { if (proposal.__typename === 'BatchProposal') {
return <VoteBreakdownBatch proposal={proposal} />; return <VoteBreakdownBatch proposal={proposal} restData={restData} />;
} }
return null; return null;
}; };
const VoteBreakdownBatch = ({ proposal }: { proposal: BatchProposal }) => { const VoteBreakdownBatch = ({
proposal,
restData,
}: {
proposal: BatchProposal;
restData?: ProposalNode | null;
}) => {
const [fullBreakdown, setFullBreakdown] = useState(false); const [fullBreakdown, setFullBreakdown] = useState(false);
const { t } = useTranslation(); 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({ const voteInfo = useBatchVoteInformation({
terms: compact( terms: compact(
proposal.subProposals ? proposal.subProposals.map((p) => p?.terms) : [] proposal.subProposals ? proposal.subProposals.map((p) => p?.terms) : []
@ -194,6 +235,8 @@ const VoteBreakdownBatch = ({ proposal }: { proposal: BatchProposal }) => {
proposal={proposal} proposal={proposal}
votes={proposal.votes} votes={proposal.votes}
terms={p.terms} terms={p.terms}
yesELS={yesELS}
noELS={noELS}
/> />
); );
})} })}
@ -254,6 +297,8 @@ const VoteBreakdownBatch = ({ proposal }: { proposal: BatchProposal }) => {
proposal={proposal} proposal={proposal}
votes={proposal.votes} votes={proposal.votes}
terms={p.terms} terms={p.terms}
yesELS={yesELS}
noELS={noELS}
/> />
); );
})} })}
@ -271,17 +316,17 @@ const VoteBreakdownBatchSubProposal = ({
votes, votes,
terms, terms,
indicator, indicator,
yesELS,
noELS,
}: { }: {
proposal: BatchProposal; proposal: BatchProposal;
votes: VoteFieldsFragment; votes: VoteFieldsFragment;
terms: ProposalTermsFieldsFragment; terms: ProposalTermsFieldsFragment;
indicator?: number; indicator?: number;
yesELS: Record<string, number[]>;
noELS: Record<string, number[]>;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const voteInfo = useVoteInformation({
votes,
terms,
});
const isProposalOpen = proposal?.state === ProposalState.STATE_OPEN; const isProposalOpen = proposal?.state === ProposalState.STATE_OPEN;
const isUpdateMarket = terms?.change?.__typename === 'UpdateMarket'; const isUpdateMarket = terms?.change?.__typename === 'UpdateMarket';
@ -294,6 +339,15 @@ const VoteBreakdownBatchSubProposal = ({
marketId = terms.change.market.id; 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 ? ( const marketName = marketId ? (
<> <>
: <MarketName marketId={marketId} /> : <MarketName marketId={marketId} />

View File

@ -1,5 +1,5 @@
import { captureMessage } from '@sentry/minimal'; import { captureMessage } from '@sentry/minimal';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { VoteValue } from '@vegaprotocol/types'; import { VoteValue } from '@vegaprotocol/types';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useUserVoteQuery } from './__generated__/Vote'; import { useUserVoteQuery } from './__generated__/Vote';

View File

@ -1,20 +1,19 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Icon, ExternalLink } from '@vegaprotocol/ui-toolkit'; import { Icon, ExternalLink } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { ProposalState } from '@vegaprotocol/types'; import { ProposalState } from '@vegaprotocol/types';
import { ConnectToVega } from '../../../../components/connect-to-vega'; import { ConnectToVega } from '../../../../components/connect-to-vega';
import { VoteButtonsContainer } from './vote-buttons'; import { VoteButtonsContainer } from './vote-buttons';
import { SubHeading } from '../../../../components/heading'; import { SubHeading } from '../../../../components/heading';
import { type VoteValue } from '@vegaprotocol/types'; import { type VoteValue } from '@vegaprotocol/types';
import { type DialogProps, type VegaTxState } from '@vegaprotocol/proposals'; import { type VegaTxState } from '@vegaprotocol/proposals';
import { type VoteState } from './use-user-vote'; import { type VoteState } from './use-user-vote';
import { type Proposal, type BatchProposal } from '../../types'; import { type Proposal, type BatchProposal } from '../../types';
interface UserVoteProps { interface UserVoteProps {
proposal: Proposal | BatchProposal; proposal: Proposal | BatchProposal;
transaction: VegaTxState | null; transaction: VegaTxState;
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>; submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
dialog: (props: DialogProps) => JSX.Element;
voteState: VoteState | null; voteState: VoteState | null;
voteDatetime: Date | null; voteDatetime: Date | null;
} }
@ -23,7 +22,6 @@ export const UserVote = ({
proposal, proposal,
submit, submit,
transaction, transaction,
dialog,
voteState, voteState,
voteDatetime, voteDatetime,
}: UserVoteProps) => { }: UserVoteProps) => {
@ -56,7 +54,6 @@ export const UserVote = ({
className="flex" className="flex"
submit={submit} submit={submit}
transaction={transaction} transaction={transaction}
dialog={dialog}
/> />
) )
) : ( ) : (

View File

@ -2,33 +2,20 @@ import { render, screen } from '@testing-library/react';
import { VoteTransactionDialog } from './vote-transaction-dialog'; import { VoteTransactionDialog } from './vote-transaction-dialog';
import { VoteState } from './use-user-vote'; import { VoteState } from './use-user-vote';
import { VegaTxStatus } from '@vegaprotocol/proposals'; import { VegaTxStatus } from '@vegaprotocol/proposals';
import { ConnectorErrors, unknownError } from '@vegaprotocol/wallet';
describe('VoteTransactionDialog', () => { 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', () => { it('renders with txRequested title when voteState is Requested', () => {
render( render(
<VoteTransactionDialog <VoteTransactionDialog
voteState={VoteState.Requested} voteState={VoteState.Requested}
transaction={null} transaction={{
TransactionDialog={mockTransactionDialog} error: null,
txHash: null,
signature: null,
status: VegaTxStatus.Requested,
dialogOpen: true,
}}
/> />
); );
@ -39,8 +26,13 @@ describe('VoteTransactionDialog', () => {
render( render(
<VoteTransactionDialog <VoteTransactionDialog
voteState={VoteState.Pending} voteState={VoteState.Pending}
transaction={null} transaction={{
TransactionDialog={mockTransactionDialog} error: null,
txHash: null,
signature: null,
status: VegaTxStatus.Pending,
dialogOpen: true,
}}
/> />
); );
@ -51,8 +43,13 @@ describe('VoteTransactionDialog', () => {
render( render(
<VoteTransactionDialog <VoteTransactionDialog
voteState={VoteState.Yes} // or any other state other than Requested or Pending voteState={VoteState.Yes} // or any other state other than Requested or Pending
transaction={null} transaction={{
TransactionDialog={mockTransactionDialog} error: null,
txHash: null,
signature: null,
status: VegaTxStatus.Complete,
dialogOpen: true,
}}
/> />
); );
@ -65,17 +62,18 @@ describe('VoteTransactionDialog', () => {
<VoteTransactionDialog <VoteTransactionDialog
voteState={VoteState.Failed} voteState={VoteState.Failed}
transaction={{ transaction={{
error: { message: 'Custom error test message', name: 'blah' }, error: unknownError(),
txHash: null, txHash: null,
signature: null, signature: null,
status: VegaTxStatus.Error, status: VegaTxStatus.Error,
dialogOpen: false, dialogOpen: true,
}} }}
TransactionDialog={mockTransactionDialog}
/> />
); );
expect(screen.getByText('Custom error test message')).toBeInTheDocument(); expect(
screen.getByText(ConnectorErrors.unknown.message)
).toBeInTheDocument();
}); });
it('renders default error message when voteState is failed and no error message exists on the tx', () => { it('renders default error message when voteState is failed and no error message exists on the tx', () => {
@ -86,10 +84,9 @@ describe('VoteTransactionDialog', () => {
error: null, error: null,
txHash: null, txHash: null,
signature: null, signature: null,
status: VegaTxStatus.Error, status: VegaTxStatus.Complete,
dialogOpen: false, dialogOpen: true,
}} }}
TransactionDialog={mockTransactionDialog}
/> />
); );
@ -100,8 +97,13 @@ describe('VoteTransactionDialog', () => {
render( render(
<VoteTransactionDialog <VoteTransactionDialog
voteState={VoteState.Yes} voteState={VoteState.Yes}
transaction={null} transaction={{
TransactionDialog={mockTransactionDialog} error: null,
txHash: null,
signature: null,
status: VegaTxStatus.Default,
dialogOpen: true,
}}
/> />
); );

View File

@ -1,120 +1,77 @@
import { fireEvent, render, screen } from '@testing-library/react'; import { act, fireEvent, render, screen } from '@testing-library/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { VoteButtons } from './vote-buttons'; import { VoteButtons, type VoteButtonsProps } from './vote-buttons';
import { VoteState } from './use-user-vote'; import { VoteState } from './use-user-vote';
import { ProposalState } from '@vegaprotocol/types'; 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 { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { MockedProvider } from '@apollo/react-testing'; import { MockedProvider } from '@apollo/react-testing';
import { VegaTxStatus } from '@vegaprotocol/proposals';
import {
MockedWalletProvider,
mockConfig,
} from '@vegaprotocol/wallet-react/testing';
describe('Vote buttons', () => { describe('Vote buttons', () => {
it('should render successfully', () => { const key = { publicKey: '0x123', name: 'key 1' };
const { baseElement } = render( 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(
<AppStateProvider> <AppStateProvider>
<MockedProvider> <MockedProvider>
<VegaWalletContext.Provider value={mockWalletContext}> <MockedWalletProvider>
<VoteButtons <VoteButtons {...props} {...testProps} />
voteState={VoteState.NotCast} </MockedWalletProvider>
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> </MockedProvider>
</AppStateProvider> </AppStateProvider>
); );
};
beforeEach(() => {
mockConfig.store.setState({ pubKey: key.publicKey, keys: [key] });
});
afterEach(() => {
act(() => {
mockConfig.reset();
});
});
it('should render successfully', () => {
const { baseElement } = renderComponent();
expect(baseElement).toBeTruthy(); expect(baseElement).toBeTruthy();
}); });
it('should explain that voting is closed if the proposal is not open', () => { it('should explain that voting is closed if the proposal is not open', () => {
render( renderComponent({ proposalState: ProposalState.STATE_PASSED });
<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(); expect(screen.getByText('Voting has ended.')).toBeTruthy();
}); });
it('should provide a connect wallet prompt if no pubkey', () => { it('should provide a connect wallet prompt if no pubkey', () => {
const mockWalletNoPubKeyContext = { mockConfig.reset();
pubKey: null, renderComponent();
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(); expect(screen.getByTestId('connect-wallet')).toBeTruthy();
}); });
it('should tell the user they need tokens if their current stake is 0', () => { it('should tell the user they need tokens if their current stake is 0', () => {
render( renderComponent({ currentStakeAvailable: new BigNumber(0) });
<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( expect(
screen.getByText( screen.getByText(
'You need some VEGA tokens to participate in governance.' 'You need some VEGA tokens to participate in governance.'
@ -123,26 +80,10 @@ describe('Vote buttons', () => {
}); });
it('should tell the user of the minimum requirements if they have some, but not enough tokens', () => { it('should tell the user of the minimum requirements if they have some, but not enough tokens', () => {
render( renderComponent({
<AppStateProvider> minVoterBalance: '2000000000000000000',
<MockedProvider> spamProtectionMinTokens: '1000000000000000000',
<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( expect(
screen.getByText( screen.getByText(
'You must have at least 2 VEGA associated to vote on this proposal' 'You must have at least 2 VEGA associated to vote on this proposal'
@ -151,51 +92,23 @@ 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', () => { it('should show you voted if vote state is correct, and if the proposal is still open, it will display a change vote button', () => {
render( renderComponent({
<AppStateProvider> voteState: VoteState.Yes,
<MockedProvider> minVoterBalance: '2000000000000000000',
<VegaWalletContext.Provider value={mockWalletContext}> spamProtectionMinTokens: '1000000000000000000',
<VoteButtons currentStakeAvailable: new BigNumber(10),
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('you-voted')).toBeInTheDocument();
expect(screen.getByTestId('change-vote-button')).toBeInTheDocument(); expect(screen.getByTestId('change-vote-button')).toBeInTheDocument();
}); });
it('should allow you to change your vote', () => { it('should allow you to change your vote', () => {
render( renderComponent({
<AppStateProvider> voteState: VoteState.No,
<MockedProvider> minVoterBalance: '2000000000000000000',
<VegaWalletContext.Provider value={mockWalletContext}> spamProtectionMinTokens: '1000000000000000000',
<VoteButtons currentStakeAvailable: new BigNumber(10),
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')); fireEvent.click(screen.getByTestId('change-vote-button'));
expect(screen.getByTestId('vote-buttons')).toBeInTheDocument(); expect(screen.getByTestId('vote-buttons')).toBeInTheDocument();
}); });

View File

@ -1,7 +1,7 @@
import { format } from 'date-fns'; import { format } from 'date-fns';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet'; import { useVegaWallet, useDialogStore } from '@vegaprotocol/wallet-react';
import { import {
AsyncRenderer, AsyncRenderer,
Button, Button,
@ -17,7 +17,7 @@ import { VoteState } from './use-user-vote';
import { ProposalMinRequirements, ProposalUserAction } from '../shared'; import { ProposalMinRequirements, ProposalUserAction } from '../shared';
import { VoteTransactionDialog } from './vote-transaction-dialog'; import { VoteTransactionDialog } from './vote-transaction-dialog';
import { useVoteButtonsQuery } from './__generated__/Stake'; import { useVoteButtonsQuery } from './__generated__/Stake';
import type { DialogProps, VegaTxState } from '@vegaprotocol/proposals'; import type { VegaTxState } from '@vegaprotocol/proposals';
import { filterAcceptableGraphqlErrors } from '../../../../lib/party'; import { filterAcceptableGraphqlErrors } from '../../../../lib/party';
import { import {
NetworkParams, NetworkParams,
@ -32,8 +32,7 @@ interface VoteButtonsContainerProps {
proposalId: string | null; proposalId: string | null;
proposalState: ProposalState; proposalState: ProposalState;
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>; submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
transaction: VegaTxState | null; transaction: VegaTxState;
dialog: (props: DialogProps) => JSX.Element;
className?: string; className?: string;
} }
@ -136,17 +135,16 @@ export const VoteButtonsContainer = (props: VoteButtonsContainerProps) => {
); );
}; };
interface VoteButtonsProps { export interface VoteButtonsProps {
voteState: VoteState | null; voteState: VoteState | null;
voteDatetime: Date | null; voteDatetime: Date | null;
proposalId: string | null;
proposalState: ProposalState; proposalState: ProposalState;
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>; proposalId: string | null;
transaction: VegaTxState | null;
dialog: (props: DialogProps) => JSX.Element;
currentStakeAvailable: BigNumber; currentStakeAvailable: BigNumber;
minVoterBalance: string | null; minVoterBalance: string | null;
spamProtectionMinTokens: string | null; spamProtectionMinTokens: string | null;
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
transaction: VegaTxState;
} }
export const VoteButtons = ({ export const VoteButtons = ({
@ -159,13 +157,10 @@ export const VoteButtons = ({
spamProtectionMinTokens, spamProtectionMinTokens,
submit, submit,
transaction, transaction,
dialog: Dialog,
}: VoteButtonsProps) => { }: VoteButtonsProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { pubKey } = useVegaWallet(); const { pubKey } = useVegaWallet();
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({ const openVegaWalletDialog = useDialogStore((store) => store.open);
openVegaWalletDialog: store.openVegaWalletDialog,
}));
const [changeVote, setChangeVote] = React.useState(false); const [changeVote, setChangeVote] = React.useState(false);
const proposalVotable = useMemo( const proposalVotable = useMemo(
() => () =>
@ -184,11 +179,7 @@ export const VoteButtons = ({
if (!pubKey) { if (!pubKey) {
return ( return (
<div data-testid="connect-wallet"> <div data-testid="connect-wallet">
<ButtonLink <ButtonLink onClick={openVegaWalletDialog}>
onClick={() => {
openVegaWalletDialog();
}}
>
{t('connectVegaWallet')} {t('connectVegaWallet')}
</ButtonLink>{' '} </ButtonLink>{' '}
{t('toVote')} {t('toVote')}
@ -301,11 +292,7 @@ export const VoteButtons = ({
</p> </p>
) )
)} )}
<VoteTransactionDialog <VoteTransactionDialog voteState={voteState} transaction={transaction} />
voteState={voteState}
transaction={transaction}
TransactionDialog={Dialog}
/>
</> </>
); );
}; };

View File

@ -1,11 +1,13 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { VoteState } from './use-user-vote'; import { VoteState } from './use-user-vote';
import type { DialogProps, VegaTxState } from '@vegaprotocol/proposals'; import {
VegaTransactionDialog,
type VegaTxState,
} from '@vegaprotocol/proposals';
interface VoteTransactionDialogProps { interface VoteTransactionDialogProps {
voteState: VoteState; voteState: VoteState;
transaction: VegaTxState | null; transaction: VegaTxState;
TransactionDialog: (props: DialogProps) => JSX.Element;
} }
const dialogTitle = (voteState: VoteState): string | undefined => { const dialogTitle = (voteState: VoteState): string | undefined => {
@ -22,22 +24,23 @@ const dialogTitle = (voteState: VoteState): string | undefined => {
export const VoteTransactionDialog = ({ export const VoteTransactionDialog = ({
voteState, voteState,
transaction, transaction,
TransactionDialog,
}: VoteTransactionDialogProps) => { }: VoteTransactionDialogProps) => {
// Render a custom message if the voting fails otherwise // Render a custom message if the voting fails otherwise
// pass undefined so that the default vega transaction dialog UI gets used // pass undefined so that the default vega transaction dialog UI gets used
const customMessage = const customMessage =
voteState === VoteState.Failed ? ( voteState === VoteState.Failed ? (
<p>{transaction?.error?.message || t('voteError')}</p> <p>{transaction.error?.message || t('voteError')}</p>
) : undefined; ) : undefined;
return ( return (
<div data-testid="vote-transaction-dialog"> <div data-testid="vote-transaction-dialog">
<TransactionDialog <VegaTransactionDialog
title={dialogTitle(voteState)} title={dialogTitle(voteState)}
transaction={transaction}
content={{ content={{
Complete: customMessage, Complete: customMessage,
}} }}
isOpen={transaction.dialogOpen}
/> />
</div> </div>
); );

View File

@ -8,13 +8,18 @@ import {
type VoteFieldsFragment, type VoteFieldsFragment,
} from '../__generated__/Proposals'; } from '../__generated__/Proposals';
import { type ProposalChangeType } from '../types'; import { type ProposalChangeType } from '../types';
import sum from 'lodash/sum';
export const useVoteInformation = ({ export const useVoteInformation = ({
votes, votes,
terms, terms,
yesELS,
noELS,
}: { }: {
votes: VoteFieldsFragment; votes: VoteFieldsFragment;
terms: ProposalTermsFieldsFragment; terms: ProposalTermsFieldsFragment;
yesELS?: number[];
noELS?: number[];
}) => { }) => {
const { const {
appState: { totalSupply, decimals }, appState: { totalSupply, decimals },
@ -31,7 +36,9 @@ export const useVoteInformation = ({
paramsForChange, paramsForChange,
votes, votes,
totalSupply, totalSupply,
decimals decimals,
yesELS,
noELS
); );
}; };
@ -72,7 +79,11 @@ const getVoteData = (
}, },
votes: ProposalFieldsFragment['votes'], votes: ProposalFieldsFragment['votes'],
totalSupply: BigNumber, totalSupply: BigNumber,
decimals: number decimals: number,
/** A list of ELS yes votes */
yesELS?: number[],
/** A list if ELS no votes */
noELS?: number[]
) => { ) => {
const requiredMajorityPercentage = params.requiredMajority const requiredMajorityPercentage = params.requiredMajority
? new BigNumber(params.requiredMajority).times(100) ? new BigNumber(params.requiredMajority).times(100)
@ -86,17 +97,31 @@ const getVoteData = (
addDecimal(votes.no.totalTokens ?? 0, decimals) addDecimal(votes.no.totalTokens ?? 0, decimals)
); );
const noEquityLikeShareWeight = !votes.no.totalEquityLikeShareWeight let noEquityLikeShareWeight = !votes.no.totalEquityLikeShareWeight
? new BigNumber(0) ? new BigNumber(0)
: new BigNumber(votes.no.totalEquityLikeShareWeight).times(100); : 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( const yesTokens = new BigNumber(
addDecimal(votes.yes.totalTokens ?? 0, decimals) addDecimal(votes.yes.totalTokens ?? 0, decimals)
); );
const yesEquityLikeShareWeight = !votes.yes.totalEquityLikeShareWeight let yesEquityLikeShareWeight = !votes.yes.totalEquityLikeShareWeight
? new BigNumber(0) ? new BigNumber(0)
: new BigNumber(votes.yes.totalEquityLikeShareWeight).times(100); : 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); const totalTokensVoted = yesTokens.plus(noTokens);

View File

@ -1,13 +1,12 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { ProposeFreeform } from './propose-freeform'; import { ProposeFreeform } from './propose-freeform';
import { MockedProvider } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing';
import { mockWalletContext } from '../../test-helpers/mocks';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider'; import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { MemoryRouter as Router } from 'react-router-dom'; import { MemoryRouter as Router } from 'react-router-dom';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters'; import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import type { MockedResponse } from '@apollo/client/testing'; import type { MockedResponse } from '@apollo/client/testing';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'), ...jest.requireActual('@vegaprotocol/environment'),
@ -72,18 +71,19 @@ const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
}, },
}; };
const renderComponent = () => const renderComponent = () => {
render( return render(
<Router> <Router>
<MockedProvider mocks={[updateMarketNetworkParamsQueryMock]}> <MockedProvider mocks={[updateMarketNetworkParamsQueryMock]}>
<AppStateProvider> <MockedWalletProvider>
<VegaWalletContext.Provider value={mockWalletContext}> <AppStateProvider>
<ProposeFreeform /> <ProposeFreeform />
</VegaWalletContext.Provider> </AppStateProvider>
</AppStateProvider> </MockedWalletProvider>
</MockedProvider> </MockedProvider>
</Router> </Router>
); );
};
// Note: form submission is tested in propose-raw.spec.tsx. Reusable form // Note: form submission is tested in propose-raw.spec.tsx. Reusable form
// components are tested in their own directory. // components are tested in their own directory.

View File

@ -51,7 +51,8 @@ export const ProposeFreeform = () => {
watch, watch,
trigger, trigger,
} = useForm<FreeformProposalFormFields>(); } = useForm<FreeformProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit(); const { finalizedProposal, transaction, submit, setTransaction } =
useProposalSubmit();
const assembleProposal = (fields: FreeformProposalFormFields) => { const assembleProposal = (fields: FreeformProposalFormFields) => {
const isVoteDeadlineAtMinimum = const isVoteDeadlineAtMinimum =
@ -169,7 +170,8 @@ export const ProposeFreeform = () => {
<ProposalFormDownloadJson downloadJson={viewJson} /> <ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog <ProposalFormTransactionDialog
finalizedProposal={finalizedProposal} finalizedProposal={finalizedProposal}
TransactionDialog={Dialog} transaction={transaction}
onChange={(open) => setTransaction({ dialogOpen: open })}
/> />
</form> </form>
</div> </div>

View File

@ -1,13 +1,12 @@
import { fireEvent, render, screen } from '@testing-library/react'; import { fireEvent, render, screen } from '@testing-library/react';
import { ProposeNetworkParameter } from './propose-network-parameter'; import { ProposeNetworkParameter } from './propose-network-parameter';
import { MockedProvider } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing';
import { mockWalletContext } from '../../test-helpers/mocks';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider'; import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { MemoryRouter as Router } from 'react-router-dom'; import { MemoryRouter as Router } from 'react-router-dom';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters'; import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import type { MockedResponse } from '@apollo/client/testing'; import type { MockedResponse } from '@apollo/client/testing';
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'), ...jest.requireActual('@vegaprotocol/environment'),
@ -72,26 +71,27 @@ const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
}, },
}; };
const renderComponent = () => const renderComponent = () => {
render( return render(
<Router> <Router>
<MockedProvider mocks={[updateMarketNetworkParamsQueryMock]}> <MockedProvider mocks={[updateMarketNetworkParamsQueryMock]}>
<AppStateProvider> <MockedWalletProvider>
<VegaWalletContext.Provider value={mockWalletContext}> <AppStateProvider>
<ProposeNetworkParameter /> <ProposeNetworkParameter />
</VegaWalletContext.Provider> </AppStateProvider>
</AppStateProvider> </MockedWalletProvider>
</MockedProvider> </MockedProvider>
</Router> </Router>
); );
};
// Note: form submission is tested in propose-raw.spec.tsx. Reusable form // Note: form submission is tested in propose-raw.spec.tsx. Reusable form
// components are tested in their own directory. // components are tested in their own directory.
describe('Propose Network Parameter', () => { describe('Propose Network Parameter', () => {
it('should render successfully', async () => { it('should render successfully', () => {
const { baseElement } = renderComponent(); const { baseElement } = renderComponent();
await expect(baseElement).toBeTruthy(); expect(baseElement).toBeTruthy();
}); });
it('should render the correct title', async () => { it('should render the correct title', async () => {

View File

@ -91,7 +91,8 @@ export const ProposeNetworkParameter = () => {
watch, watch,
trigger, trigger,
} = useForm<NetworkParameterProposalFormFields>(); } = useForm<NetworkParameterProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit(); const { finalizedProposal, transaction, submit, setTransaction } =
useProposalSubmit();
const selectedParamEntry = params const selectedParamEntry = params
? Object.entries(params).find(([key]) => key === selectedNetworkParam) ? Object.entries(params).find(([key]) => key === selectedNetworkParam)
@ -312,7 +313,8 @@ export const ProposeNetworkParameter = () => {
<ProposalFormDownloadJson downloadJson={viewJson} /> <ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog <ProposalFormTransactionDialog
finalizedProposal={finalizedProposal} finalizedProposal={finalizedProposal}
TransactionDialog={Dialog} transaction={transaction}
onChange={(open) => setTransaction({ dialogOpen: open })}
/> />
</form> </form>
</div> </div>

View File

@ -1,13 +1,12 @@
import { MockedProvider } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter as Router } from 'react-router-dom'; import { MemoryRouter as Router } from 'react-router-dom';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider'; import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { mockWalletContext } from '../../test-helpers/mocks';
import { ProposeNewAsset } from './propose-new-asset'; import { ProposeNewAsset } from './propose-new-asset';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters'; import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import type { MockedResponse } from '@apollo/client/testing'; import type { MockedResponse } from '@apollo/client/testing';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'), ...jest.requireActual('@vegaprotocol/environment'),
@ -72,26 +71,27 @@ const newAssetNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
}, },
}; };
const renderComponent = () => const renderComponent = () => {
render( return render(
<Router> <Router>
<MockedProvider mocks={[newAssetNetworkParamsQueryMock]}> <MockedProvider mocks={[newAssetNetworkParamsQueryMock]}>
<AppStateProvider> <MockedWalletProvider>
<VegaWalletContext.Provider value={mockWalletContext}> <AppStateProvider>
<ProposeNewAsset /> <ProposeNewAsset />
</VegaWalletContext.Provider> </AppStateProvider>
</AppStateProvider> </MockedWalletProvider>
</MockedProvider> </MockedProvider>
</Router> </Router>
); );
};
// Note: form submission is tested in propose-raw.spec.tsx. Reusable form // Note: form submission is tested in propose-raw.spec.tsx. Reusable form
// components are tested in their own directory. // components are tested in their own directory.
describe('Propose New Asset', () => { describe('Propose New Asset', () => {
it('should render successfully', async () => { it('should render successfully', () => {
const { baseElement } = renderComponent(); const { baseElement } = renderComponent();
await expect(baseElement).toBeTruthy(); expect(baseElement).toBeTruthy();
}); });
it('should render the title', async () => { it('should render the title', async () => {

View File

@ -64,7 +64,8 @@ export const ProposeNewAsset = () => {
watch, watch,
trigger, trigger,
} = useForm<NewAssetProposalFormFields>(); } = useForm<NewAssetProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit(); const { finalizedProposal, transaction, submit, setTransaction } =
useProposalSubmit();
const assembleProposal = (fields: NewAssetProposalFormFields) => { const assembleProposal = (fields: NewAssetProposalFormFields) => {
const isVoteDeadlineAtMinimum = doesValueEquateToParam( const isVoteDeadlineAtMinimum = doesValueEquateToParam(
@ -232,7 +233,8 @@ export const ProposeNewAsset = () => {
<ProposalFormDownloadJson downloadJson={viewJson} /> <ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog <ProposalFormTransactionDialog
finalizedProposal={finalizedProposal} finalizedProposal={finalizedProposal}
TransactionDialog={Dialog} transaction={transaction}
onChange={(open) => setTransaction({ dialogOpen: open })}
/> />
</form> </form>
</div> </div>

View File

@ -1,13 +1,12 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { ProposeNewMarket } from './propose-new-market'; import { ProposeNewMarket } from './propose-new-market';
import { MockedProvider } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing';
import { mockWalletContext } from '../../test-helpers/mocks';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider'; import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { BrowserRouter as Router } from 'react-router-dom'; import { BrowserRouter as Router } from 'react-router-dom';
import type { MockedResponse } from '@apollo/client/testing'; import type { MockedResponse } from '@apollo/client/testing';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters'; import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'), ...jest.requireActual('@vegaprotocol/environment'),
@ -72,26 +71,27 @@ const newMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
}, },
}; };
const renderComponent = () => const renderComponent = () => {
render( return render(
<Router> <Router>
<MockedProvider mocks={[newMarketNetworkParamsQueryMock]}> <MockedProvider mocks={[newMarketNetworkParamsQueryMock]}>
<AppStateProvider> <MockedWalletProvider>
<VegaWalletContext.Provider value={mockWalletContext}> <AppStateProvider>
<ProposeNewMarket /> <ProposeNewMarket />
</VegaWalletContext.Provider> </AppStateProvider>
</AppStateProvider> </MockedWalletProvider>
</MockedProvider> </MockedProvider>
</Router> </Router>
); );
};
// Note: form submission is tested in propose-raw.spec.tsx. Reusable form // Note: form submission is tested in propose-raw.spec.tsx. Reusable form
// components are tested in their own directory. // components are tested in their own directory.
describe('Propose New Market', () => { describe('Propose New Market', () => {
it('should render successfully', async () => { it('should render successfully', () => {
const { baseElement } = renderComponent(); const { baseElement } = renderComponent();
await expect(baseElement).toBeTruthy(); expect(baseElement).toBeTruthy();
}); });
it('should render the form components', async () => { it('should render the form components', async () => {

View File

@ -62,7 +62,8 @@ export const ProposeNewMarket = () => {
watch, watch,
trigger, trigger,
} = useForm<NewMarketProposalFormFields>(); } = useForm<NewMarketProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit(); const { finalizedProposal, transaction, submit, setTransaction } =
useProposalSubmit();
const assembleProposal = (fields: NewMarketProposalFormFields) => { const assembleProposal = (fields: NewMarketProposalFormFields) => {
const isVoteDeadlineAtMinimum = doesValueEquateToParam( const isVoteDeadlineAtMinimum = doesValueEquateToParam(
@ -214,7 +215,8 @@ export const ProposeNewMarket = () => {
<ProposalFormDownloadJson downloadJson={viewJson} /> <ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog <ProposalFormTransactionDialog
finalizedProposal={finalizedProposal} finalizedProposal={finalizedProposal}
TransactionDialog={Dialog} transaction={transaction}
onChange={(open) => setTransaction({ dialogOpen: open })}
/> />
</form> </form>
</div> </div>

View File

@ -3,8 +3,6 @@ import type { MockedResponse } from '@apollo/client/testing';
import { addHours, getTime } from 'date-fns'; import { addHours, getTime } from 'date-fns';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider'; import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { MockedProvider } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
import { ProposeRaw } from './propose-raw'; import { ProposeRaw } from './propose-raw';
import { ProposalEventDocument } from '@vegaprotocol/proposals'; import { ProposalEventDocument } from '@vegaprotocol/proposals';
@ -12,6 +10,11 @@ import type { ProposalEventSubscription } from '@vegaprotocol/proposals';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters'; import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import { NetworkParamsDocument } 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; const paramsDelay = 20;
@ -103,23 +106,15 @@ describe('Raw proposal form', () => {
}, },
delay: 300, delay: 300,
}; };
const setup = (mockSendTx = jest.fn()) => { const setup = () => {
return render( return render(
<AppStateProvider> <AppStateProvider>
<MockedProvider <MockedProvider
mocks={[rawProposalNetworkParamsQueryMock, mockProposalEvent]} mocks={[rawProposalNetworkParamsQueryMock, mockProposalEvent]}
> >
<VegaWalletContext.Provider <MockedWalletProvider>
value={
{
pubKey,
sendTx: mockSendTx,
links: { explorer: 'explorer' },
} as unknown as VegaWalletContextShape
}
>
<ProposeRaw /> <ProposeRaw />
</VegaWalletContext.Provider> </MockedWalletProvider>
</MockedProvider> </MockedProvider>
</AppStateProvider> </AppStateProvider>
); );
@ -127,15 +122,22 @@ describe('Raw proposal form', () => {
beforeAll(() => { beforeAll(() => {
jest.useFakeTimers(); jest.useFakeTimers();
mockConfig.store.setState({ status: 'connected', pubKey: '0x123' });
}); });
afterAll(() => { afterAll(() => {
jest.useRealTimers(); jest.useRealTimers();
mockConfig.reset();
});
afterEach(() => {
jest.clearAllMocks();
}); });
it('handles validation', async () => { it('handles validation', async () => {
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve()); const mockSendTx = jest.spyOn(mockConfig, 'sendTransaction');
setup(mockSendTx);
setup();
expect(await screen.findByTestId('proposal-submit')).toBeTruthy(); expect(await screen.findByTestId('proposal-submit')).toBeTruthy();
await act(async () => { await act(async () => {
@ -162,20 +164,25 @@ describe('Raw proposal form', () => {
}); });
it('sends the transaction', async () => { it('sends the transaction', async () => {
const mockSendTx = jest.fn().mockReturnValue( const mockSendTx = jest
new Promise((resolve) => { .spyOn(mockConfig, 'sendTransaction')
setTimeout( .mockReturnValue(
() => new Promise((resolve) => {
resolve({ setTimeout(
transactionHash: 'tx-hash', () =>
signature: resolve({
'cfe592d169f87d0671dd447751036d0dddc165b9c4b65e5a5060e2bbadd1aa726d4cbe9d3c3b327bcb0bff4f83999592619a2493f9bbd251fae99ce7ce766909', transactionHash: 'tx-hash',
}), signature:
100 'cfe592d169f87d0671dd447751036d0dddc165b9c4b65e5a5060e2bbadd1aa726d4cbe9d3c3b327bcb0bff4f83999592619a2493f9bbd251fae99ce7ce766909',
); sentAt: new Date().toISOString(),
}) receivedAt: new Date().toISOString(),
); }),
setup(mockSendTx); 100
);
})
);
setup();
await act(async () => { await act(async () => {
jest.advanceTimersByTime(paramsDelay); jest.advanceTimersByTime(paramsDelay);
@ -206,8 +213,12 @@ describe('Raw proposal form', () => {
fireEvent.click(screen.getByTestId('proposal-submit')); fireEvent.click(screen.getByTestId('proposal-submit'));
}); });
expect(mockSendTx).toHaveBeenCalledWith(pubKey, { expect(mockSendTx).toHaveBeenCalledWith({
proposalSubmission: JSON.parse(inputJSON), publicKey: pubKey,
sendingMode: 'TYPE_SYNC',
transaction: {
proposalSubmission: JSON.parse(inputJSON),
},
}); });
expect(screen.getByTestId('dialog-title')).toHaveTextContent( expect(screen.getByTestId('dialog-title')).toHaveTextContent(
@ -232,12 +243,12 @@ describe('Raw proposal form', () => {
}); });
it('can be rejected by the user', async () => { it('can be rejected by the user', async () => {
const mockSendTx = jest.fn().mockReturnValue( jest.spyOn(mockConfig, 'sendTransaction').mockReturnValue(
new Promise((resolve) => { new Promise((_, reject) => {
setTimeout(() => resolve(null), 100); setTimeout(() => reject(userRejectedError()), 100);
}) })
); );
setup(mockSendTx); setup();
await act(async () => { await act(async () => {
jest.advanceTimersByTime(paramsDelay); jest.advanceTimersByTime(paramsDelay);

View File

@ -52,7 +52,8 @@ export const ProposeRaw = () => {
handleSubmit, handleSubmit,
formState: { isSubmitting, errors }, formState: { isSubmitting, errors },
} = useForm<RawProposalFormFields>(); } = useForm<RawProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit(); const { finalizedProposal, transaction, submit, setTransaction } =
useProposalSubmit();
const hasError = Boolean(errors.rawProposalData?.message); const hasError = Boolean(errors.rawProposalData?.message);
@ -152,7 +153,8 @@ export const ProposeRaw = () => {
<ProposalFormSubmit isSubmitting={isSubmitting} /> <ProposalFormSubmit isSubmitting={isSubmitting} />
<ProposalFormTransactionDialog <ProposalFormTransactionDialog
finalizedProposal={finalizedProposal} finalizedProposal={finalizedProposal}
TransactionDialog={Dialog} transaction={transaction}
onChange={(open) => setTransaction({ dialogOpen: open })}
/> />
</form> </form>
</div> </div>

View File

@ -1,13 +1,12 @@
import { MockedProvider } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter as Router } from 'react-router-dom'; import { MemoryRouter as Router } from 'react-router-dom';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider'; import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { mockWalletContext } from '../../test-helpers/mocks';
import { ProposeUpdateAsset } from './propose-update-asset'; import { ProposeUpdateAsset } from './propose-update-asset';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters'; import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import type { MockedResponse } from '@apollo/client/testing'; import type { MockedResponse } from '@apollo/client/testing';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'), ...jest.requireActual('@vegaprotocol/environment'),
@ -72,26 +71,27 @@ const updateAssetNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
}, },
}; };
const renderComponent = () => const renderComponent = () => {
render( return render(
<Router> <Router>
<MockedProvider mocks={[updateAssetNetworkParamsQueryMock]}> <MockedProvider mocks={[updateAssetNetworkParamsQueryMock]}>
<AppStateProvider> <MockedWalletProvider>
<VegaWalletContext.Provider value={mockWalletContext}> <AppStateProvider>
<ProposeUpdateAsset /> <ProposeUpdateAsset />
</VegaWalletContext.Provider> </AppStateProvider>
</AppStateProvider> </MockedWalletProvider>
</MockedProvider> </MockedProvider>
</Router> </Router>
); );
};
// Note: form submission is tested in propose-raw.spec.tsx. Reusable form // Note: form submission is tested in propose-raw.spec.tsx. Reusable form
// components are tested in their own directory. // components are tested in their own directory.
describe('Propose Update Asset', () => { describe('Propose Update Asset', () => {
it('should render successfully', async () => { it('should render successfully', () => {
const { baseElement } = renderComponent(); const { baseElement } = renderComponent();
await expect(baseElement).toBeTruthy(); expect(baseElement).toBeTruthy();
}); });
it('should render the title', async () => { it('should render the title', async () => {

View File

@ -62,7 +62,8 @@ export const ProposeUpdateAsset = () => {
watch, watch,
trigger, trigger,
} = useForm<UpdateAssetProposalFormFields>(); } = useForm<UpdateAssetProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit(); const { finalizedProposal, transaction, submit, setTransaction } =
useProposalSubmit();
const assembleProposal = (fields: UpdateAssetProposalFormFields) => { const assembleProposal = (fields: UpdateAssetProposalFormFields) => {
const isVoteDeadlineAtMinimum = doesValueEquateToParam( const isVoteDeadlineAtMinimum = doesValueEquateToParam(
@ -218,7 +219,8 @@ export const ProposeUpdateAsset = () => {
<ProposalFormDownloadJson downloadJson={viewJson} /> <ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog <ProposalFormTransactionDialog
finalizedProposal={finalizedProposal} finalizedProposal={finalizedProposal}
TransactionDialog={Dialog} transaction={transaction}
onChange={(open) => setTransaction({ dialogOpen: open })}
/> />
</form> </form>
</div> </div>

View File

@ -2,15 +2,14 @@ import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter as Router } from 'react-router-dom'; import { MemoryRouter as Router } from 'react-router-dom';
import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider'; import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { mockWalletContext } from '../../test-helpers/mocks';
import { ProposeUpdateMarket } from './propose-update-market'; import { ProposeUpdateMarket } from './propose-update-market';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters'; import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import type { ProposalMarketsQueryQuery } from './__generated__/UpdateMarket'; import type { ProposalMarketsQueryQuery } from './__generated__/UpdateMarket';
import { ProposalMarketsQueryDocument } from './__generated__/UpdateMarket'; import { ProposalMarketsQueryDocument } from './__generated__/UpdateMarket';
import { ProposalState } from '@vegaprotocol/types'; import { ProposalState } from '@vegaprotocol/types';
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = { const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
request: { request: {
@ -217,29 +216,30 @@ const marketQueryMock: MockedResponse<ProposalMarketsQueryQuery> = {
}, },
}; };
const renderComponent = () => const renderComponent = () => {
render( return render(
<MockedProvider <MockedProvider
mocks={[updateMarketNetworkParamsQueryMock, marketQueryMock]} mocks={[updateMarketNetworkParamsQueryMock, marketQueryMock]}
addTypename={false} addTypename={false}
> >
<Router> <Router>
<AppStateProvider> <MockedWalletProvider>
<VegaWalletContext.Provider value={mockWalletContext}> <AppStateProvider>
<ProposeUpdateMarket /> <ProposeUpdateMarket />
</VegaWalletContext.Provider> </AppStateProvider>
</AppStateProvider> </MockedWalletProvider>
</Router> </Router>
</MockedProvider> </MockedProvider>
); );
};
// Note: form submission is tested in propose-raw.spec.tsx. Reusable form // Note: form submission is tested in propose-raw.spec.tsx. Reusable form
// components are tested in their own directory. // components are tested in their own directory.
describe('Propose Update Market', () => { describe('Propose Update Market', () => {
it('should render successfully', async () => { it('should render successfully', () => {
const { baseElement } = renderComponent(); const { baseElement } = renderComponent();
await expect(baseElement).toBeTruthy(); expect(baseElement).toBeTruthy();
}); });
it('should render the title', async () => { it('should render the title', async () => {

View File

@ -109,7 +109,8 @@ export const ProposeUpdateMarket = () => {
watch, watch,
trigger, trigger,
} = useForm<UpdateMarketProposalFormFields>(); } = useForm<UpdateMarketProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit(); const { finalizedProposal, transaction, submit, setTransaction } =
useProposalSubmit();
const assembleProposal = (fields: UpdateMarketProposalFormFields) => { const assembleProposal = (fields: UpdateMarketProposalFormFields) => {
const isVoteDeadlineAtMinimum = doesValueEquateToParam( const isVoteDeadlineAtMinimum = doesValueEquateToParam(
@ -323,7 +324,8 @@ export const ProposeUpdateMarket = () => {
<ProposalFormDownloadJson downloadJson={viewJson} /> <ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog <ProposalFormTransactionDialog
finalizedProposal={finalizedProposal} finalizedProposal={finalizedProposal}
TransactionDialog={Dialog} transaction={transaction}
onChange={(open) => setTransaction({ dialogOpen: open })}
/> />
</form> </form>
</div> </div>

View File

@ -1,28 +1,17 @@
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import type { MockedResponse } from '@apollo/client/testing'; import type { MockedResponse } from '@apollo/client/testing';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters'; import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import type { PubKey, VegaWalletContextShape } from '@vegaprotocol/wallet';
import type { VoteValue } from '@vegaprotocol/types'; import type { VoteValue } from '@vegaprotocol/types';
import type { UserVoteQuery } from '../components/vote-details/__generated__/Vote'; import type { UserVoteQuery } from '../components/vote-details/__generated__/Vote';
import { UserVoteDocument } from '../components/vote-details/__generated__/Vote'; import { UserVoteDocument } from '../components/vote-details/__generated__/Vote';
import faker from 'faker'; import faker from 'faker';
import { type Key } from '@vegaprotocol/wallet';
export const mockPubkey: PubKey = { export const mockPubkey: Key = {
publicKey: '0x123', publicKey: '0x123',
name: 'test key 1', 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 = { const mockEthereumConfig = {
network_id: '3', network_id: '3',
chain_id: '3', chain_id: '3',

View File

@ -1,13 +1,11 @@
import classNames from 'classnames'; import classNames from 'classnames';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useVegaWalletDialogStore } from '@vegaprotocol/wallet'; import { useDialogStore } from '@vegaprotocol/wallet-react';
import { Button } from '@vegaprotocol/ui-toolkit'; import { Button } from '@vegaprotocol/ui-toolkit';
import { SubHeading } from '../../components/heading'; import { SubHeading } from '../../components/heading';
export const ConnectToSeeRewards = () => { export const ConnectToSeeRewards = () => {
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({ const openVegaWalletDialog = useDialogStore((store) => store.open);
openVegaWalletDialog: store.openVegaWalletDialog,
}));
const { t } = useTranslation(); const { t } = useTranslation();
const classes = classNames( const classes = classNames(

View File

@ -4,7 +4,7 @@ import { AsyncRenderer, Pagination } from '@vegaprotocol/ui-toolkit';
import { removePaginationWrapper } from '@vegaprotocol/utils'; import { removePaginationWrapper } from '@vegaprotocol/utils';
import type { EpochFieldsFragment } from '../home/__generated__/Rewards'; import type { EpochFieldsFragment } from '../home/__generated__/Rewards';
import { useRewardsQuery } from '../home/__generated__/Rewards'; import { useRewardsQuery } from '../home/__generated__/Rewards';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { EpochIndividualRewardsTable } from './epoch-individual-rewards-table'; import { EpochIndividualRewardsTable } from './epoch-individual-rewards-table';
import { generateEpochIndividualRewardsList } from './generate-epoch-individual-rewards-list'; import { generateEpochIndividualRewardsList } from './generate-epoch-individual-rewards-list';
import { calculateEpochOffset } from '../../../lib/epoch-pagination'; import { calculateEpochOffset } from '../../../lib/epoch-pagination';

View File

@ -10,7 +10,7 @@ import {
Toggle, Toggle,
ExternalLink, ExternalLink,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { import {
useNetworkParams, useNetworkParams,
NetworkParams, NetworkParams,

View File

@ -33,8 +33,8 @@ let mockVegaWalletHookValue: {
pubKey: null, pubKey: null,
}; };
jest.mock('@vegaprotocol/wallet', () => ({ jest.mock('@vegaprotocol/wallet-react', () => ({
...jest.requireActual('@vegaprotocol/wallet'), ...jest.requireActual('@vegaprotocol/wallet-react'),
useVegaWallet: jest.fn(() => mockVegaWalletHookValue), useVegaWallet: jest.fn(() => mockVegaWalletHookValue),
})); }));

View File

@ -2,7 +2,7 @@ import { useWeb3React } from '@web3-react/core';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { EthConnectPrompt } from '../../../../../components/eth-connect-prompt'; import { EthConnectPrompt } from '../../../../../components/eth-connect-prompt';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { ConnectToVega } from '../../../../../components/connect-to-vega'; import { ConnectToVega } from '../../../../../components/connect-to-vega';
export const StakingWalletsContainer = ({ export const StakingWalletsContainer = ({

View File

@ -33,8 +33,8 @@ let mockVegaWalletHookValue: {
pubKey: null, pubKey: null,
}; };
jest.mock('@vegaprotocol/wallet', () => ({ jest.mock('@vegaprotocol/wallet-react', () => ({
...jest.requireActual('@vegaprotocol/wallet'), ...jest.requireActual('@vegaprotocol/wallet-react'),
useVegaWallet: jest.fn(() => mockVegaWalletHookValue), useVegaWallet: jest.fn(() => mockVegaWalletHookValue),
})); }));

View File

@ -1,4 +1,4 @@
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useWeb3React } from '@web3-react/core'; import { useWeb3React } from '@web3-react/core';
import { EthConnectPrompt } from '../../../components/eth-connect-prompt'; import { EthConnectPrompt } from '../../../components/eth-connect-prompt';
import { DisassociatePage } from './components/disassociate-page'; import { DisassociatePage } from './components/disassociate-page';

View File

@ -5,7 +5,7 @@ import { useStakingQuery } from '../__generated__/Staking';
import { usePreviousEpochQuery } from '../__generated__/PreviousEpoch'; import { usePreviousEpochQuery } from '../__generated__/PreviousEpoch';
import { ValidatorTables } from './validator-tables'; import { ValidatorTables } from './validator-tables';
import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch'; import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { getMultisigStatusInfo } from '../../../lib/get-multisig-status-info'; import { getMultisigStatusInfo } from '../../../lib/get-multisig-status-info';
import { MultisigIncorrectNotice } from '../../../components/multisig-incorrect-notice'; import { MultisigIncorrectNotice } from '../../../components/multisig-incorrect-notice';

View File

@ -2,7 +2,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { import {
addDecimal, addDecimal,
removePaginationWrapper, removePaginationWrapper,

View File

@ -1,5 +1,5 @@
import { Callout, Intent, Splash } from '@vegaprotocol/ui-toolkit'; import { Callout, Intent, Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch'; import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch';
import { SplashLoader } from '../../../components/splash-loader'; import { SplashLoader } from '../../../components/splash-loader';

View File

@ -1,78 +0,0 @@
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>
);
};

View File

@ -27,11 +27,11 @@ import {
NetworkParams, NetworkParams,
} from '@vegaprotocol/network-parameters'; } from '@vegaprotocol/network-parameters';
import { useBalances } from '../../../lib/balances/balances-store'; import { useBalances } from '../../../lib/balances/balances-store';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { SubHeading } from '../../../components/heading'; import { SubHeading } from '../../../components/heading';
import type { import {
DelegateSubmissionBody, type DelegateSubmissionBody,
UndelegateSubmissionBody, type UndelegateSubmissionBody,
} from '@vegaprotocol/wallet'; } from '@vegaprotocol/wallet';
import Routes from '../../routes'; import Routes from '../../routes';

View File

@ -9,7 +9,7 @@ import {
useWithdrawalDialog, useWithdrawalDialog,
WithdrawalsTable, WithdrawalsTable,
} from '@vegaprotocol/withdraws'; } from '@vegaprotocol/withdraws';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useDataProvider } from '@vegaprotocol/data-provider'; import { useDataProvider } from '@vegaprotocol/data-provider';
import { useDocumentTitle } from '../../hooks/use-document-title'; import { useDocumentTitle } from '../../hooks/use-document-title';
import type { RouteChildProps } from '../index'; import type { RouteChildProps } from '../index';

View File

@ -6,7 +6,7 @@ import {
VegaIconNames, VegaIconNames,
useToasts, useToasts,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { useVegaWalletDialogStore } from '@vegaprotocol/wallet'; import { useDialogStore } from '@vegaprotocol/wallet-react';
import { import {
useEthereumTransactionToasts, useEthereumTransactionToasts,
useEthereumWithdrawApprovalsToasts, useEthereumWithdrawApprovalsToasts,
@ -19,9 +19,7 @@ import { useTranslation } from 'react-i18next';
const WalletDisconnectAdditionalContent = () => { const WalletDisconnectAdditionalContent = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { hideToast } = useWalletDisconnectToastActions(); const { hideToast } = useWalletDisconnectToastActions();
const openVegaWalletDialog = useVegaWalletDialogStore( const openVegaWalletDialog = useDialogStore((store) => store.open);
(store) => store.openVegaWalletDialog
);
return ( return (
<p className="mt-2"> <p className="mt-2">
<TradingButton <TradingButton

View File

@ -1,9 +1,11 @@
const { join } = require('path'); const { join } = require('path');
const { createGlobPatternsForDependencies } = require('@nx/react/tailwind'); const { createGlobPatternsForDependencies } = require('@nx/react/tailwind');
const theme = require('../../libs/tailwindcss-config/src/theme'); const { theme } = require('../../libs/tailwindcss-config/src/theme');
const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom-classes'); const {
vegaCustomClasses,
} = require('../../libs/tailwindcss-config/src/vega-custom-classes');
module.exports = { export default {
content: [ content: [
join(__dirname, 'src/**/*.{js,ts,jsx,tsx}'), join(__dirname, 'src/**/*.{js,ts,jsx,tsx}'),
'libs/ui-toolkit/src/utils/shared.ts', 'libs/ui-toolkit/src/utils/shared.ts',

View File

@ -1,11 +0,0 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic"
}
]
],
"plugins": []
}

View File

@ -1,16 +0,0 @@
# This file is used by:
# 1. autoprefixer to adjust CSS to support the below specified browsers
# 2. babel preset-env to adjust included polyfills
#
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
#
# If you need to support different browsers in production, you may tweak the list below.
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major version
last 2 iOS major versions
Firefox ESR
not IE 9-11 # For IE 9-11 support, remove 'not'.

View File

@ -1,28 +0,0 @@
# React Environment Variables
# https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#expanding-environment-variables-in-env
# Netlify Environment Variables
# https://www.netlify.com/docs/continuous-deployment/#environment-variables
NX_VERSION=\$npm_package_version
NX_REPOSITORY_URL=\$REPOSITORY_URL
NX_BRANCH=\$BRANCH
NX_PULL_REQUEST=\$PULL_REQUEST
NX_HEAD=\$HEAD
NX_COMMIT_REF=\$COMMIT_REF
NX_CONTEXT=\$CONTEXT
NX_REVIEW_ID=\$REVIEW_ID
NX_INCOMING_HOOK_TITLE=\$INCOMING_HOOK_TITLE
NX_INCOMING_HOOK_URL=\$INCOMING_HOOK_URL
NX_INCOMING_HOOK_BODY=\$INCOMING_HOOK_BODY
NX_URL=\$URL
NX_DEPLOY_URL=\$DEPLOY_URL
NX_DEPLOY_PRIME_URL=\$DEPLOY_PRIME_URL
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml
NX_VEGA_ENV = 'TESTNET'
NX_VEGA_URL="https://api.n07.testnet.vega.xyz/graphql"
NX_VEGA_WALLET_URL=http://localhost:1789
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf

View File

@ -1,3 +0,0 @@
# App configuration variables
NX_VEGA_URL=http://localhost:3008/graphql
NX_VEGA_ENV=LOCAL

View File

@ -1,8 +0,0 @@
# App configuration variables
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
NX_VEGA_URL=https://api.n04.d.vega.xyz/graphql
NX_VEGA_ENV=DEVNET
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_EXPLORER_URL=#

View File

@ -1,9 +0,0 @@
# App configuration variables
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_ENV=MAINNET
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\"}
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://etherscan.io
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz
NX_VEGA_CONSOLE_URL=https://console.vega.xyz

View File

@ -1,9 +0,0 @@
# App configuration variables
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
NX_VEGA_URL=https://api.n00.stagnet1.vega.xyz/graphql
NX_VEGA_ENV=STAGNET1
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}

View File

@ -1,9 +0,0 @@
# App configuration variables
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml
NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
NX_VEGA_ENV=TESTNET
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\"}
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf

View File

@ -1,10 +0,0 @@
const { join } = require('path');
module.exports = {
plugins: {
tailwindcss: {
config: join(__dirname, 'tailwind.config.js'),
},
autoprefixer: {},
},
};

View File

@ -1,94 +0,0 @@
{
"name": "liquidity-provision-dashboard",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/liquidity-provision-dashboard/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"compiler": "babel",
"outputPath": "dist/apps/liquidity-provision-dashboard",
"index": "apps/liquidity-provision-dashboard/src/index.html",
"baseHref": "/",
"main": "apps/liquidity-provision-dashboard/src/main.tsx",
"polyfills": "apps/liquidity-provision-dashboard/src/polyfills.ts",
"tsConfig": "apps/liquidity-provision-dashboard/tsconfig.app.json",
"assets": [
"apps/liquidity-provision-dashboard/src/favicon.ico",
"apps/liquidity-provision-dashboard/src/assets"
],
"styles": ["apps/liquidity-provision-dashboard/src/styles.scss"],
"scripts": [],
"webpackConfig": "@nx/react/plugins/webpack"
},
"configurations": {
"development": {
"extractLicenses": false,
"optimization": false,
"sourceMap": true,
"vendorChunk": true
},
"production": {
"fileReplacements": [
{
"replace": "apps/liquidity-provision-dashboard/src/environments/environment.ts",
"with": "apps/liquidity-provision-dashboard/src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false
}
}
},
"serve": {
"executor": "@nx/webpack:dev-server",
"options": {
"buildTarget": "liquidity-provision-dashboard:build",
"hmr": true,
"port": 4201
},
"configurations": {
"development": {
"buildTarget": "liquidity-provision-dashboard:build:development"
},
"production": {
"buildTarget": "liquidity-provision-dashboard:build:production",
"hmr": false
}
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": [
"apps/liquidity-provision-dashboard/**/*.{ts,tsx,js,jsx}"
]
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": [
"{workspaceRoot}/coverage/apps/liquidity-provision-dashboard"
],
"options": {
"jestConfig": "apps/liquidity-provision-dashboard/jest.config.ts"
}
},
"build-spec": {
"executor": "nx:run-commands",
"outputs": [],
"options": {
"command": "yarn tsc --project ./apps/liquidity-provision-dashboard/tsconfig.spec.json"
}
}
},
"tags": []
}

View File

@ -1,47 +0,0 @@
import type { InMemoryCacheConfig } from '@apollo/client';
import { NetworkLoader, useInitializeEnv } from '@vegaprotocol/environment';
import { useRoutes } from 'react-router-dom';
import '../styles.scss';
import { Navbar } from './components/navbar';
import { routerConfig } from './routes/router-config';
const cache: InMemoryCacheConfig = {
typePolicies: {
Market: {
merge: true,
},
Party: {
merge: true,
},
Query: {},
Account: {
keyFields: false,
fields: {
balanceFormatted: {},
},
},
Node: {
keyFields: false,
},
Instrument: {
keyFields: false,
},
},
};
const AppRouter = () => useRoutes(routerConfig);
export function App() {
useInitializeEnv();
return (
<NetworkLoader cache={cache}>
<div className="max-h-full min-h-full bg-white">
<Navbar />
<AppRouter />
</div>
</NetworkLoader>
);
}
export default App;

View File

@ -1,25 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { Intro } from './intro';
import { MarketList } from './market-list';
export function Dashboard() {
return (
<>
<div className="px-16 pt-20 pb-12 bg-greys-light-100">
<div className="max-w-screen-xl mx-auto">
<h1 className="font-alpha calt uppercase text-5xl mb-8">
{t('Top liquidity opportunities')}
</h1>
<Intro />
</div>
</div>
<div className="px-16 py-6">
<div className="max-w-screen-xl mx-auto">
<MarketList />
</div>
</div>
</>
);
}

View File

@ -1 +0,0 @@
export * from './dashboard';

View File

@ -1 +0,0 @@
export * from './intro';

View File

@ -1,53 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
// TODO: add mainnet links once docs have been updated
const LINKS = {
testnet: [
{
label: 'Learn about liquidity fees',
url: 'https://docs.vega.xyz/testnet/tutorials/providing-liquidity#resources',
},
{
label: 'Provide liquidity',
url: 'https://docs.vega.xyz/testnet/tutorials/providing-liquidity#overview',
},
{
label: 'View your liquidity provisions',
url: 'https://docs.vega.xyz/testnet/tutorials/providing-liquidity#viewing-existing-liquidity-provisions',
},
{
label: 'Amend or remove liquidity',
url: 'https://docs.vega.xyz/testnet/tutorials/providing-liquidity#amending-a-liquidity-commitment',
},
],
mainnet: [],
};
// TODO: update this when network switcher is added
type Network = 'testnet' | 'mainnet';
export const Intro = ({ network = 'testnet' }: { network?: Network }) => {
return (
<div>
<p className="font-alpha calt text-2xl font-medium mb-2">
{t(
'Become a liquidity provider and earn a cut of the fees paid during trading.'
)}
</p>
<div>
<ul className="flex flex-wrap">
{LINKS[network].map(
({ label, url }: { label: string; url: string }) => (
<li key={url} className="mr-6">
<ExternalLink href={url} rel="noreferrer">
{t(label)}
</ExternalLink>
</li>
)
)}
</ul>
</div>
</div>
);
};

View File

@ -1 +0,0 @@
export * from './market-list';

View File

@ -1,296 +0,0 @@
import { DApp, useLinks } from '@vegaprotocol/environment';
import { type Market } from '@vegaprotocol/liquidity';
import {
displayChange,
formatWithAsset,
useMarketsLiquidity,
} from '@vegaprotocol/liquidity';
import {
addDecimalsFormatNumber,
formatNumberPercentage,
getExpiryDate,
toBigNum,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { type VegaValueFormatterParams } from '@vegaprotocol/datagrid';
import { PriceChangeCell } from '@vegaprotocol/datagrid';
import type * as Schema from '@vegaprotocol/types';
import {
AsyncRenderer,
Icon,
HealthBar,
TooltipCellComponent,
} from '@vegaprotocol/ui-toolkit';
import {
type GetRowIdParams,
type RowClickedEvent,
type ColDef,
} from 'ag-grid-community';
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import { useCallback, useState, useMemo } from 'react';
import { Grid } from '../../grid';
import { HealthDialog } from '../../health-dialog';
import { Status } from '../../status';
import { intentForStatus } from '../../../lib/utils';
import { formatDistanceToNow } from 'date-fns';
import { getAsset } from '@vegaprotocol/markets';
export const MarketList = () => {
const { data, error, loading } = useMarketsLiquidity();
const [isHealthDialogOpen, setIsHealthDialogOpen] = useState(false);
const consoleLink = useLinks(DApp.Console);
const getRowId = useCallback(({ data }: GetRowIdParams) => data.id, []);
const columnDefs = useMemo<ColDef[]>(
() => [
{
headerName: t('Market (futures)'),
field: 'tradableInstrument.instrument.name',
cellRenderer: ({ value, data }: { value: string; data: Market }) => {
return (
<>
<span className="leading-3">{value}</span>
<span className="leading-3">{getAsset(data).symbol}</span>
</>
);
},
minWidth: 100,
flex: 1,
headerTooltip: t('The market name and settlement asset'),
},
{
headerName: t('Market Code'),
headerTooltip: t(
'The market code is a unique identifier for this market'
),
field: 'tradableInstrument.instrument.code',
},
{
headerName: t('Type'),
headerTooltip: t('Type'),
field: 'tradableInstrument.instrument.product.__typename',
},
{
headerName: t('Last Price'),
headerTooltip: t('Latest price for this market'),
field: 'data.markPrice',
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<Market, 'data.markPrice'>) =>
value && data ? formatWithAsset(value, getAsset(data)) : '-',
},
{
headerName: t('Change (24h)'),
headerTooltip: t('Change in price over the last 24h'),
cellRenderer: ({
data,
}: VegaValueFormatterParams<Market, 'data.candles'>) => {
if (data && data.candles) {
const prices = data.candles.map((candle) => candle.close);
return (
<PriceChangeCell
candles={prices}
decimalPlaces={data?.decimalPlaces}
/>
);
} else return <div>{t('-')}</div>;
},
},
{
headerName: t('Volume (24h)'),
field: 'dayVolume',
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<Market, 'dayVolume'>) =>
value && data
? `${addDecimalsFormatNumber(
value,
getAsset(data).decimals || 0
)} (${displayChange(data.volumeChange)})`
: '-',
headerTooltip: t('The trade volume over the last 24h'),
},
{
headerName: t('Total staked by LPs'),
field: 'liquidityCommitted',
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<Market, 'liquidityCommitted'>) =>
data && value
? formatWithAsset(value.toString(), getAsset(data))
: '-',
headerTooltip: t('The amount of funds allocated to provide liquidity'),
},
{
headerName: t('Target stake'),
field: 'target',
valueFormatter: ({
value,
data,
}: VegaValueFormatterParams<Market, 'target'>) =>
data && value ? formatWithAsset(value, getAsset(data)) : '-',
headerTooltip: t(
'The ideal committed liquidity to operate the market. If total commitment currently below this level then LPs can set the fee level with new commitment.'
),
},
{
headerName: t('% Target stake met'),
valueFormatter: ({ data }: VegaValueFormatterParams<Market, ''>) => {
if (data) {
const roundedPercentage =
parseInt(
(data.liquidityCommitted / parseFloat(data.target)).toFixed(0)
) * 100;
const display = Number.isNaN(roundedPercentage)
? 'N/A'
: formatNumberPercentage(toBigNum(roundedPercentage, 0), 0);
return display;
} else return '-';
},
headerTooltip: t('% Target stake met'),
},
{
headerName: t('Fee levels'),
field: 'fees',
valueFormatter: ({ value }: VegaValueFormatterParams<Market, 'fees'>) =>
value ? `${value.factors.liquidityFee}%` : '-',
headerTooltip: t('Fee level for this market'),
},
{
headerName: t('Status'),
field: 'tradingMode',
cellRenderer: ({
value,
data,
}: {
value: Schema.MarketTradingMode;
data: Market;
}) => {
return <Status trigger={data.data?.trigger} tradingMode={value} />;
},
headerTooltip: t(
'The current market status - those below the target stake mark are most in need of liquidity'
),
},
{
headerComponent: () => {
return (
<div>
<span>{t('Health')}</span>{' '}
<button
onClick={() => setIsHealthDialogOpen(true)}
aria-label={t('open tooltip')}
>
<Icon name="info-sign" />
</button>
</div>
);
},
field: 'tradingMode',
cellRenderer: ({
value,
data,
}: {
value: Schema.MarketTradingMode;
data: Market;
}) => (
<HealthBar
target={data.target}
decimals={getAsset(data).decimals || 0}
levels={data.feeLevels}
intent={intentForStatus(value)}
/>
),
sortable: false,
cellStyle: { overflow: 'unset' },
},
{
headerName: t('Age'),
field: 'marketTimestamps.open',
headerTooltip: t('Age of the market'),
valueFormatter: ({
value,
}: VegaValueFormatterParams<Market, 'marketTimestamps.open'>) => {
return value ? formatDistanceToNow(new Date(value)) : '-';
},
},
{
headerName: t('Closing Time'),
field: 'tradableInstrument.instrument.metadata.tags',
headerTooltip: t('Closing time of the market'),
valueFormatter: ({ data }: VegaValueFormatterParams<Market, ''>) => {
let expiry;
if (data?.tradableInstrument.instrument.metadata.tags) {
expiry = getExpiryDate(
data?.tradableInstrument.instrument.metadata.tags,
data?.marketTimestamps.close,
data?.state
);
}
return expiry ? expiry : '-';
},
},
],
[]
);
return (
<AsyncRenderer loading={loading} error={error} data={data}>
<div
className="w-full grow"
style={{ minHeight: 500, overflow: 'hidden' }}
>
<Grid
gridOptions={{
onRowClicked: ({ data }: RowClickedEvent) => {
window.open(
liquidityDetailsConsoleLink(data.id, consoleLink),
'_blank',
'noopener,noreferrer'
);
},
}}
rowData={data}
defaultColDef={{
resizable: true,
sortable: true,
unSortIcon: true,
cellClass: ['flex', 'flex-col', 'justify-center'],
tooltipComponent: TooltipCellComponent,
}}
columnDefs={columnDefs}
getRowId={getRowId}
isRowClickable
tooltipShowDelay={500}
/>
<HealthDialog
isOpen={isHealthDialogOpen}
onChange={() => {
setIsHealthDialogOpen(!isHealthDialogOpen);
}}
/>
</div>
</AsyncRenderer>
);
};
const liquidityDetailsConsoleLink = (
marketId: string,
consoleLink: (url: string | undefined) => string
) => consoleLink(`/#/liquidity/${marketId}`);

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