Compare commits

..
Author SHA1 Message Date
asiaznik 8a30cc6237 fix(ui-toolkit): test toast configuration 2023-05-22 16:55:03 +02:00
Maciek fff8fdd78b feat(trading): move trade grid panel - fix id of store (#3872) 2023-05-22 14:09:37 +00:00
m.rayandmaciek 079d8bc48d feat(trading): move deal ticket panel to the left (#3842)
Co-authored-by: maciek <maciek@vegaprotocol.io>
2023-05-22 15:40:42 +02:00
Eddandm.ray 9658363b56 chore(explorer): update be env for validator testnet (#3867)
Co-authored-by: m.ray <16125548+MadalinaRaicu@users.noreply.github.com>
2023-05-22 13:38:51 +00:00
dexturr e24289330e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-22 12:08:25 +00:00
Mikołaj Młodzikowski 0fbd2bfa72 feat(ci): adjust environment name 2023-05-22 11:42:09 +02:00
Mikołaj MłodzikowskiandMikołaj Młodzikowski 43ef4fad6f feat(ci): fix affected resolution (#3849)
Co-authored-by: Mikołaj Młodzikowski <mikolaj.mlodzikowski@github.com>
2023-05-22 11:16:33 +02:00
Matthew Russell 828dcf6e93 chore(governance,explorer,trading): move sentry dsn to env files (#3860) 2023-05-22 08:54:15 +02:00
dexturr 4a583ccf09 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-22 06:07:44 +00:00
dexturr 8bcad28f50 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-22 00:16:28 +00:00
dexturr b375681076 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-21 18:07:08 +00:00
dexturr 8a2db407cf chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-21 12:10:41 +00:00
dexturr 491844d3fd chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-21 06:09:53 +00:00
dexturr f1f51c5ecd chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-21 00:15:03 +00:00
dexturr 9bc78f8ccd chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-20 18:06:53 +00:00
dexturr a03031a283 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-20 12:10:14 +00:00
Matthew Russell b499c293e3 fix(trading): fix proposals not showing (#3859) 2023-05-20 10:55:52 +01:00
dexturr 65747e8d26 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-20 06:09:41 +00:00
dexturr 9a84eda21b chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-20 00:14:21 +00:00
Maciek fba98f2fd0 feat(trading,governance): improve flow of risk disclaimer presentation (#3772) 2023-05-19 21:27:45 +00:00
Matthew Russell 3e32d0f396 chore(governance,trading): update mainnet envs with new ethereum provider url (#3857) 2023-05-19 13:31:49 -07:00
Matthew Russell b75d189054 fix(trading): settlement price of closed markets (#3831) 2023-05-19 13:29:24 -07:00
Sam Keen afa627864c fix(governance): homepage proposals ordering fixed (#3856) 2023-05-19 18:12:14 +00:00
dexturr b13a866b2a chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-19 18:07:12 +00:00
daro-maj c65f07eee6 test(trading): skip failing node tests (#3854) 2023-05-19 19:54:56 +02:00
Sam Keen 5bd18cc127 feat(governance): avoid showing untitled proposals (#3851) 2023-05-19 16:19:28 +01:00
Art ff3519279d fix(governance): penalties calculation (#3850) 2023-05-19 16:19:07 +01:00
78 changed files with 5061 additions and 867 deletions
+17 -16
View File
@@ -98,36 +98,37 @@ jobs:
# See affected apps # See affected apps
- name: See affected apps - name: See affected apps
run: | run: |
affected="$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects)"
branch_slug="$(echo '${{ github.head_ref || github.ref_name }}' | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | cut -c 1-50 )"
echo ">>>> debug" echo ">>>> debug"
echo "NX_BASE: ${{ env.NX_BASE }}" echo "NX_BASE: ${{ env.NX_BASE }}"
echo "NX_HEAD: ${{ env.NX_HEAD }}" echo "NX_HEAD: ${{ env.NX_HEAD }}"
echo "Affected: ${affected}"
echo "Branch slug: ${branch_slug}"
echo ">>>> eof debug" echo ">>>> eof debug"
affected="$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects)"
branch_slug="$(echo '${{ github.head_ref || github.ref_name }}' | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | cut -c 1-50 )"
projects_e2e="" projects_e2e=""
preview_governance="not deployed" preview_governance="not deployed"
preview_trading="not deployed" preview_trading="not deployed"
preview_explorer="not deployed" preview_explorer="not deployed"
if [[ $affected == *"governance"* ]]; then
projects_e2e+='"governance-e2e" '
preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug")
fi
if [[ $affected == *"trading"* ]]; then
projects_e2e+='"trading-e2e" '
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
fi
if [[ $affected == *"explorer"* ]]; then
projects_e2e+='"explorer-e2e" '
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
fi
if [[ -z "$projects_e2e" ]]; then if [[ -z "$projects_e2e" ]]; then
projects_e2e+='"governance-e2e" "trading-e2e" "explorer-e2e" ' projects_e2e+='"governance-e2e" "trading-e2e" "explorer-e2e" '
preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug") preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug")
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug") preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug") preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
else
if [[ $affected == *"governance"* ]]; then
projects_e2e+='"governance-e2e" '
preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug")
fi
if [[ $affected == *"trading"* ]]; then
projects_e2e+='"trading-e2e" '
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
fi
if [[ $affected == *"explorer"* ]]; then
projects_e2e+='"explorer-e2e" '
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
fi
fi fi
projects_e2e=${projects_e2e%?} projects_e2e=${projects_e2e%?}
projects_e2e=[${projects_e2e// /,}] projects_e2e=[${projects_e2e// /,}]
+1
View File
@@ -1,6 +1,7 @@
# App configuration variables # App configuration variables
NX_TENDERMINT_URL=https://be.vega.community NX_TENDERMINT_URL=https://be.vega.community
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
NX_SENTRY_DSN=https://b3a56b03eda842faad731f3ea9dfd1bc@o286262.ingest.sentry.io/6242427
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_URL=https://api.vega.community/graphql NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_ENV=MAINNET NX_VEGA_ENV=MAINNET
@@ -8,7 +8,7 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_GOVERNANCE_URL=https://validator-testnet.governance.vega.xyz NX_VEGA_GOVERNANCE_URL=https://validator-testnet.governance.vega.xyz
NX_TENDERMINT_URL=https://tm-be.validators-testnet.vega.rocks NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.rocks
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/rest NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/rest
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket
NX_VEGA_EXPLORER_URL=https://validator-testnet.explorer.vega.xyz/ NX_VEGA_EXPLORER_URL=https://explorer.validators-testnet.vega.rocks/
+1
View File
@@ -12,3 +12,4 @@ NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-devnet1-k8s.ops.vega.xyz NX_TRANCHES_SERVICE_URL=https://tranches-devnet1-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_REST_URL=https://api.n00.devnet1.vega.xyz/api/v2/ NX_VEGA_REST_URL=https://api.n00.devnet1.vega.xyz/api/v2/
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
+2 -1
View File
@@ -3,9 +3,10 @@ NX_VEGA_ENV=MAINNET
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_URL=https://api.vega.community/graphql NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.governance.vega.xyz","TESTNET":"https://governance.fairground.wtf","MAINNET":"https://governance.vega.xyz"}' NX_VEGA_NETWORKS='{"DEVNET":"https://dev.governance.vega.xyz","TESTNET":"https://governance.fairground.wtf","MAINNET":"https://governance.vega.xyz"}'
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_ETHEREUM_PROVIDER_URL=https://eth-mainnet.gateway.pokt.network/v1/lb/6684b914570bbfa533ba9324
NX_ETHERSCAN_URL=https://etherscan.io NX_ETHERSCAN_URL=https://etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40:87edc2605e544f888305d7fc4a9141bd@o286262.ingest.sentry.io/5882996 NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40:87edc2605e544f888305d7fc4a9141bd@o286262.ingest.sentry.io/5882996
+2 -1
View File
@@ -12,4 +12,5 @@ NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_DELEGATIONS_PAGINATION=50 NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-testnet-k8s.ops.vega.xyz NX_TRANCHES_SERVICE_URL=https://tranches-testnet-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_REST_URL=https://api.n07.testnet.vega.xyz/api/v2/ NX_VEGA_REST_URL=https://api.n07.testnet.vega.xyz/api/v2/
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
@@ -9,4 +9,5 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_REST_URL=https://api-validators-testnet.vega.rocks/api/v2/ NX_VEGA_REST_URL=https://api-validators-testnet.vega.rocks/api/v2/
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
@@ -0,0 +1,40 @@
import { t } from '@vegaprotocol/i18n';
import {
ExternalLink,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import Routes from '../../routes/routes';
export const RiskMessage = () => {
return (
<>
<div className="bg-vega-light-100 dark:bg-vega-dark-100 p-6 mb-6">
<ul className="list-[square] ml-4">
<li>
{t(
'You may encounter bugs, loss of functionality or loss of assets using the App.'
)}
</li>
<li>
{t(
'No party accepts any liability for any losses whatsoever related to its use.'
)}
</li>
</ul>
</div>
<p className="mb-8">
{t(
'By using the Vega Governance App, you acknowledge that you have read and understood the'
)}{' '}
<ExternalLink href={Routes.DISCLAIMER} className="underline">
<span className="flex items-center gap-1">
<span>{t('Vega Governance Disclaimer')}</span>
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
</span>
</ExternalLink>
.
</p>
</>
);
};
@@ -4,9 +4,11 @@ import {
useAppState, useAppState,
} from '../../contexts/app-state/app-state-context'; } from '../../contexts/app-state/app-state-context';
import { Connectors } from '../../lib/vega-connectors'; import { Connectors } from '../../lib/vega-connectors';
import { RiskMessage } from './risk-message';
export const VegaWalletDialogs = () => { export const VegaWalletDialogs = () => {
const { appState, appDispatch } = useAppState(); const { appState, appDispatch } = useAppState();
return ( return (
<> <>
<VegaConnectDialog <VegaConnectDialog
@@ -17,6 +19,7 @@ export const VegaWalletDialogs = () => {
isOpen: open, isOpen: open,
}) })
} }
riskMessage={<RiskMessage />}
/> />
<VegaManageDialog <VegaManageDialog
@@ -815,5 +815,11 @@
"NoThanks": "No thanks", "NoThanks": "No thanks",
"ShareData": "Share data", "ShareData": "Share data",
"ContinueSharingData": "Continue sharing data", "ContinueSharingData": "Continue sharing data",
"NodeUnsuitable": "Node: {{url}} is unsuitable" "NodeUnsuitable": "Node: {{url}} is unsuitable",
"Disclaimer": "Disclaimer",
"disclaimer1": "The Vega Governance App allows the Vega network to arrive at on-chain decisions, where tokenholders can create proposals that other tokenholders can vote to approve or reject. Vega supports on-chain proposals for creating markets and assets, and changing network parameters, markets and assets. Vega also supports freeform proposals for community suggestions that will not be enacted on-chain.",
"disclaimer2": "The Vega Governance App is free, public and open source software. Software upgrades may contain bugs or security vulnerabilities that might result in loss of functionality.",
"disclaimer3": "The Vega Governance App uses data obtained from nodes on the Vega Blockchain. The developers of the Vega Governance App do not operate or run the Vega Blockchain or any other blockchain.",
"disclaimer4": "The Vega Governance App is provided “as is”. The developers of the Vega Governance App make no representations or warranties of any kind, whether express or implied, statutory or otherwise regarding the Vega Governance App. They disclaim all warranties of merchantability, quality, fitness for purpose. They disclaim all warranties that the Vega Governance App is free of harmful components or errors.",
"disclaimer5": "No developer of the Vega Governance App accepts any responsibility for, or liability to users in connection with their use of the Vega Governance App."
} }
@@ -0,0 +1,21 @@
import type { RouteChildProps } from '..';
import { useDocumentTitle } from '../../hooks/use-document-title';
import { Heading } from '../../components/heading';
import { useTranslation } from 'react-i18next';
const Disclaimer = ({ name }: RouteChildProps) => {
useDocumentTitle(name);
const { t } = useTranslation();
return (
<>
<Heading title={t('Disclaimer')} />
<p className="mb-6 mt-10">{t('disclaimer1')}</p>
<p className="mb-6">{t('disclaimer2')}</p>
<p className="mb-6">{t('disclaimer3')}</p>
<p className="mb-8">{t('disclaimer4')}</p>
<p className="mb-8">{t('disclaimer5')}</p>
</>
);
};
export default Disclaimer;
+4 -1
View File
@@ -209,7 +209,10 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
[proposalsData] [proposalsData]
); );
const sortedProposals = useMemo(() => orderByDate(proposals), [proposals]); const sortedProposals = useMemo(
() => orderByDate(proposals).reverse(),
[proposals]
);
const protocolUpgradeProposals = useMemo( const protocolUpgradeProposals = useMemo(
() => () =>
@@ -124,7 +124,7 @@ describe('Proposal header', () => {
}) })
); );
expect(screen.getByTestId('proposal-title')).toHaveTextContent( expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'Unknown proposal' 'New asset proposal'
); );
expect(screen.getByTestId('proposal-type')).toHaveTextContent('New asset'); expect(screen.getByTestId('proposal-type')).toHaveTextContent('New asset');
expect(screen.getByTestId('proposal-details')).toHaveTextContent( expect(screen.getByTestId('proposal-details')).toHaveTextContent(
@@ -21,6 +21,7 @@ export const ProposalHeader = ({
let details: ReactNode; let details: ReactNode;
let proposalType = ''; let proposalType = '';
let fallbackTitle = '';
const title = proposal?.rationale.title.trim(); const title = proposal?.rationale.title.trim();
@@ -29,6 +30,7 @@ export const ProposalHeader = ({
switch (change?.__typename) { switch (change?.__typename) {
case 'NewMarket': { case 'NewMarket': {
proposalType = 'NewMarket'; proposalType = 'NewMarket';
fallbackTitle = t('NewMarketProposal');
details = ( details = (
<> <>
<span> <span>
@@ -50,6 +52,7 @@ export const ProposalHeader = ({
} }
case 'UpdateMarket': { case 'UpdateMarket': {
proposalType = 'UpdateMarket'; proposalType = 'UpdateMarket';
fallbackTitle = t('UpdateMarketProposal');
details = ( details = (
<> <>
<span>{t('Market change')}:</span>{' '} <span>{t('Market change')}:</span>{' '}
@@ -60,6 +63,7 @@ export const ProposalHeader = ({
} }
case 'NewAsset': { case 'NewAsset': {
proposalType = 'NewAsset'; proposalType = 'NewAsset';
fallbackTitle = t('NewAssetProposal');
details = ( details = (
<> <>
<span>{t('Symbol')}:</span> <Lozenge>{change.symbol}.</Lozenge>{' '} <span>{t('Symbol')}:</span> <Lozenge>{change.symbol}.</Lozenge>{' '}
@@ -81,6 +85,7 @@ export const ProposalHeader = ({
} }
case 'UpdateNetworkParameter': { case 'UpdateNetworkParameter': {
proposalType = 'NetworkParameter'; proposalType = 'NetworkParameter';
fallbackTitle = t('NetworkParameterProposal');
details = ( details = (
<> <>
<span>{t('Change')}:</span>{' '} <span>{t('Change')}:</span>{' '}
@@ -95,11 +100,13 @@ export const ProposalHeader = ({
} }
case 'NewFreeform': { case 'NewFreeform': {
proposalType = 'Freeform'; proposalType = 'Freeform';
fallbackTitle = t('FreeformProposal');
details = <span />; details = <span />;
break; break;
} }
case 'UpdateAsset': { case 'UpdateAsset': {
proposalType = 'UpdateAsset'; proposalType = 'UpdateAsset';
fallbackTitle = t('UpdateAssetProposal');
details = ( details = (
<> <>
<span>{t('AssetID')}:</span>{' '} <span>{t('AssetID')}:</span>{' '}
@@ -115,10 +122,14 @@ export const ProposalHeader = ({
<div data-testid="proposal-title"> <div data-testid="proposal-title">
{isListItem ? ( {isListItem ? (
<header> <header>
<SubHeading title={titleContent || t('Unknown proposal')} /> <SubHeading
title={titleContent || fallbackTitle || t('Unknown proposal')}
/>
</header> </header>
) : ( ) : (
<Heading title={titleContent || t('Unknown proposal')} /> <Heading
title={titleContent || fallbackTitle || t('Unknown proposal')}
/>
)} )}
</div> </div>
@@ -49,6 +49,7 @@ export const ProposeFreeform = () => {
formState: { errors }, formState: { errors },
setValue, setValue,
watch, watch,
trigger,
} = useForm<FreeformProposalFormFields>(); } = useForm<FreeformProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit(); const { finalizedProposal, submit, Dialog } = useProposalSubmit();
@@ -85,7 +86,13 @@ export const ProposeFreeform = () => {
await submit(assembleProposal(fields)); await submit(assembleProposal(fields));
}; };
const viewJson = () => { const viewJson = async () => {
const isValid = await trigger();
if (!isValid) {
return;
}
const formData = watch(); const formData = watch();
downloadJson( downloadJson(
JSON.stringify(assembleProposal(formData)), JSON.stringify(assembleProposal(formData)),
@@ -89,6 +89,7 @@ export const ProposeNetworkParameter = () => {
formState: { errors }, formState: { errors },
setValue, setValue,
watch, watch,
trigger,
} = useForm<NetworkParameterProposalFormFields>(); } = useForm<NetworkParameterProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit(); const { finalizedProposal, submit, Dialog } = useProposalSubmit();
@@ -148,7 +149,13 @@ export const ProposeNetworkParameter = () => {
await submit(assembleProposal(fields)); await submit(assembleProposal(fields));
}; };
const viewJson = () => { const viewJson = async () => {
const isValid = await trigger();
if (!isValid) {
return;
}
const formData = watch(); const formData = watch();
downloadJson( downloadJson(
JSON.stringify(assembleProposal(formData)), JSON.stringify(assembleProposal(formData)),
@@ -61,6 +61,7 @@ export const ProposeNewAsset = () => {
formState: { errors }, formState: { errors },
setValue, setValue,
watch, watch,
trigger,
} = useForm<NewAssetProposalFormFields>(); } = useForm<NewAssetProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit(); const { finalizedProposal, submit, Dialog } = useProposalSubmit();
@@ -117,7 +118,13 @@ export const ProposeNewAsset = () => {
await submit(assembleProposal(fields)); await submit(assembleProposal(fields));
}; };
const viewJson = () => { const viewJson = async () => {
const isValid = await trigger();
if (!isValid) {
return;
}
const formData = watch(); const formData = watch();
downloadJson( downloadJson(
JSON.stringify(assembleProposal(formData)), JSON.stringify(assembleProposal(formData)),
@@ -59,6 +59,7 @@ export const ProposeNewMarket = () => {
formState: { errors }, formState: { errors },
setValue, setValue,
watch, watch,
trigger,
} = useForm<NewMarketProposalFormFields>(); } = useForm<NewMarketProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit(); const { finalizedProposal, submit, Dialog } = useProposalSubmit();
@@ -107,7 +108,13 @@ export const ProposeNewMarket = () => {
await submit(assembleProposal(fields)); await submit(assembleProposal(fields));
}; };
const viewJson = () => { const viewJson = async () => {
const isValid = await trigger();
if (!isValid) {
return;
}
const formData = watch(); const formData = watch();
downloadJson( downloadJson(
JSON.stringify(assembleProposal(formData)), JSON.stringify(assembleProposal(formData)),
@@ -59,6 +59,7 @@ export const ProposeUpdateAsset = () => {
formState: { errors }, formState: { errors },
setValue, setValue,
watch, watch,
trigger,
} = useForm<UpdateAssetProposalFormFields>(); } = useForm<UpdateAssetProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit(); const { finalizedProposal, submit, Dialog } = useProposalSubmit();
@@ -107,7 +108,13 @@ export const ProposeUpdateAsset = () => {
await submit(assembleProposal(fields)); await submit(assembleProposal(fields));
}; };
const viewJson = () => { const viewJson = async () => {
const isValid = await trigger();
if (!isValid) {
return;
}
const formData = watch(); const formData = watch();
downloadJson( downloadJson(
JSON.stringify(assembleProposal(formData)), JSON.stringify(assembleProposal(formData)),
@@ -106,6 +106,7 @@ export const ProposeUpdateMarket = () => {
formState: { errors }, formState: { errors },
setValue, setValue,
watch, watch,
trigger,
} = useForm<UpdateMarketProposalFormFields>(); } = useForm<UpdateMarketProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit(); const { finalizedProposal, submit, Dialog } = useProposalSubmit();
@@ -157,7 +158,13 @@ export const ProposeUpdateMarket = () => {
await submit(assembleProposal(fields)); await submit(assembleProposal(fields));
}; };
const viewJson = () => { const viewJson = async () => {
const isValid = await trigger();
if (!isValid) {
return;
}
const formData = watch(); const formData = watch();
downloadJson( downloadJson(
JSON.stringify(assembleProposal(formData)), JSON.stringify(assembleProposal(formData)),
@@ -207,6 +207,13 @@ const LazyWithdrawals = React.lazy(
) )
); );
const LazyDisclaimer = React.lazy(
() =>
import(
/* webpackChunkName: "route-disclaimer", webpackPrefetch: true */ './disclaimer'
)
);
const redirects = [ const redirects = [
{ {
path: Routes.VALIDATORS, path: Routes.VALIDATORS,
@@ -349,6 +356,10 @@ const routerConfig = [
path: Routes.CONTRACTS, path: Routes.CONTRACTS,
element: <LazyContracts name="Contracts" />, element: <LazyContracts name="Contracts" />,
}, },
{
path: Routes.DISCLAIMER,
element: <LazyDisclaimer name="Disclaimer" />,
},
{ {
path: '*', path: '*',
// Not lazy as loaded when a user first hits the site // Not lazy as loaded when a user first hits the site
+1
View File
@@ -15,6 +15,7 @@ const Routes = {
SUPPLY: '/token/tranches', SUPPLY: '/token/tranches',
ASSOCIATE: '/token/associate', ASSOCIATE: '/token/associate',
DISASSOCIATE: '/token/disassociate', DISASSOCIATE: '/token/disassociate',
DISCLAIMER: '/disclaimer',
}; };
export default Routes; export default Routes;
@@ -5,12 +5,22 @@ query PreviousEpoch($epochId: ID) {
edges { edges {
node { node {
id id
stakedTotal
rewardScore { rewardScore {
rawValidatorScore rawValidatorScore
performanceScore performanceScore
multisigScore
validatorScore
normalisedScore
validatorStatus
} }
rankingScore { rankingScore {
status
previousStatus
rankingScore
stakeScore stakeScore
performanceScore
votingPower
} }
} }
} }
@@ -8,7 +8,7 @@ export type PreviousEpochQueryVariables = Types.Exact<{
}>; }>;
export type PreviousEpochQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string, validatorsConnection?: { __typename?: 'NodesConnection', edges?: Array<{ __typename?: 'NodeEdge', node: { __typename?: 'Node', id: string, rewardScore?: { __typename?: 'RewardScore', rawValidatorScore: string, performanceScore: string } | null, rankingScore: { __typename?: 'RankingScore', stakeScore: string } } } | null> | null } | null } }; export type PreviousEpochQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string, validatorsConnection?: { __typename?: 'NodesConnection', edges?: Array<{ __typename?: 'NodeEdge', node: { __typename?: 'Node', id: string, stakedTotal: string, rewardScore?: { __typename?: 'RewardScore', rawValidatorScore: string, performanceScore: string, multisigScore: string, validatorScore: string, normalisedScore: string, validatorStatus: Types.ValidatorStatus } | null, rankingScore: { __typename?: 'RankingScore', status: Types.ValidatorStatus, previousStatus: Types.ValidatorStatus, rankingScore: string, stakeScore: string, performanceScore: string, votingPower: string } } } | null> | null } | null } };
export const PreviousEpochDocument = gql` export const PreviousEpochDocument = gql`
@@ -19,12 +19,22 @@ export const PreviousEpochDocument = gql`
edges { edges {
node { node {
id id
stakedTotal
rewardScore { rewardScore {
rawValidatorScore rawValidatorScore
performanceScore performanceScore
multisigScore
validatorScore
normalisedScore
validatorStatus
} }
rankingScore { rankingScore {
status
previousStatus
rankingScore
stakeScore stakeScore
performanceScore
votingPower
} }
} }
} }
@@ -79,36 +79,72 @@ const MOCK_PREVIOUS_EPOCH: PreviousEpochQuery = {
{ {
node: { node: {
id: 'ccc022b7e63a4d0a6d3a193c3940c88574060e58a184964c994998d86835a1b4', id: 'ccc022b7e63a4d0a6d3a193c3940c88574060e58a184964c994998d86835a1b4',
stakedTotal: '14182454495731682635157',
rewardScore: { rewardScore: {
rawValidatorScore: '0.25', rawValidatorScore: '0.25',
performanceScore: '0.9998677767864936', performanceScore: '0.9998677767864936',
multisigScore: '',
validatorScore: '',
normalisedScore: '',
validatorStatus:
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
}, },
rankingScore: { rankingScore: {
stakeScore: '0.2499583402766206', stakeScore: '0.2499583402766206',
performanceScore: '0.9998677767864936',
status: Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
previousStatus:
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
rankingScore: '',
votingPower: '',
}, },
}, },
}, },
{ {
node: { node: {
id: '966438c6bffac737cfb08173ffcb3f393c4692b099ad80cb45a82e2dc0a8cf99', id: '966438c6bffac737cfb08173ffcb3f393c4692b099ad80cb45a82e2dc0a8cf99',
stakedTotal: '9618711883996159534058',
rewardScore: { rewardScore: {
rawValidatorScore: '0.3', rawValidatorScore: '0.3',
performanceScore: '1', performanceScore: '1',
multisigScore: '',
validatorScore: '0.31067',
normalisedScore: '',
validatorStatus:
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
}, },
rankingScore: { rankingScore: {
stakeScore: '0.25', stakeScore: '0.25',
performanceScore: '0.9998677767864936',
status: Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
previousStatus:
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
rankingScore: '',
votingPower: '',
}, },
}, },
}, },
{ {
node: { node: {
id: '12c81b738e8051152e1afe44376ec37bca9216466e6d44cdd772194bad0ada81', id: '12c81b738e8051152e1afe44376ec37bca9216466e6d44cdd772194bad0ada81',
stakedTotal: '4041343338923442976709',
rewardScore: { rewardScore: {
rawValidatorScore: '0.35', rawValidatorScore: '0.35',
performanceScore: '0.999629748500531', performanceScore: '0.999629748500531',
multisigScore: '',
validatorScore: '',
normalisedScore: '',
validatorStatus:
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
}, },
rankingScore: { rankingScore: {
stakeScore: '0.2312', stakeScore: '0.2312',
performanceScore: '0.9998677767864936',
status: Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
previousStatus:
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
rankingScore: '',
votingPower: '',
}, },
}, },
}, },
@@ -7,12 +7,12 @@ import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { useAppState } from '../../../../contexts/app-state/app-state-context'; import { useAppState } from '../../../../contexts/app-state/app-state-context';
import { BigNumber } from '../../../../lib/bignumber'; import { BigNumber } from '../../../../lib/bignumber';
import { import {
calculateOverallPenalty,
calculateOverstakedPenalty,
calculatesPerformancePenalty,
getFormattedPerformanceScore, getFormattedPerformanceScore,
getLastEpochScoreAndPerformance, getLastEpochScoreAndPerformance,
getNormalisedVotingPower, getNormalisedVotingPower,
getOverstakingPenalty,
getPerformancePenalty,
getTotalPenalties,
getUnnormalisedVotingPower, getUnnormalisedVotingPower,
} from '../../shared'; } from '../../shared';
import { import {
@@ -32,6 +32,7 @@ import type { ValidatorsTableProps } from './shared';
import { import {
formatNumber, formatNumber,
formatNumberPercentage, formatNumberPercentage,
removePaginationWrapper,
toBigNum, toBigNum,
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
import { VALIDATOR_LOGO_MAP } from './logo-map'; import { VALIDATOR_LOGO_MAP } from './logo-map';
@@ -136,6 +137,10 @@ export const ConsensusValidatorsTable = ({
[totalStake] [totalStake]
); );
const allNodesInPreviousEpoch = removePaginationWrapper(
previousEpochData?.epoch.validatorsConnection?.edges
);
const nodes = useMemo(() => { const nodes = useMemo(() => {
if (!data) return []; if (!data) return [];
let canonisedNodes = data let canonisedNodes = data
@@ -160,7 +165,7 @@ export const ConsensusValidatorsTable = ({
stakedByDelegates, stakedByDelegates,
stakedByOperator, stakedByOperator,
stakedTotal, stakedTotal,
rankingScore: { stakeScore, votingPower }, rankingScore: { stakeScore, votingPower, performanceScore },
pendingStake, pendingStake,
stakedTotalRanking, stakedTotalRanking,
stakedByUser, stakedByUser,
@@ -172,11 +177,8 @@ export const ConsensusValidatorsTable = ({
: avatarUrl : avatarUrl
? avatarUrl ? avatarUrl
: null; : null;
const { const { rawValidatorScore: previousEpochValidatorScore } =
rawValidatorScore: previousEpochValidatorScore, getLastEpochScoreAndPerformance(previousEpochData, id);
performanceScore: previousEpochPerformanceScore,
stakeScore: previousEpochStakeScore,
} = getLastEpochScoreAndPerformance(previousEpochData, id);
return { return {
id, id,
@@ -199,21 +201,19 @@ export const ConsensusValidatorsTable = ({
toBigNum(stakedByOperator, decimals), toBigNum(stakedByOperator, decimals),
2 2
), ),
[ValidatorFields.PERFORMANCE_SCORE]: getFormattedPerformanceScore( [ValidatorFields.PERFORMANCE_SCORE]:
previousEpochPerformanceScore getFormattedPerformanceScore(performanceScore).toString(),
).toString(), [ValidatorFields.PERFORMANCE_PENALTY]: formatNumberPercentage(
[ValidatorFields.PERFORMANCE_PENALTY]: getPerformancePenalty( calculatesPerformancePenalty(performanceScore),
previousEpochPerformanceScore 2
), ),
[ValidatorFields.OVERSTAKING_PENALTY]: getOverstakingPenalty( [ValidatorFields.OVERSTAKING_PENALTY]: formatNumberPercentage(
previousEpochValidatorScore, calculateOverstakedPenalty(id, allNodesInPreviousEpoch),
previousEpochStakeScore 2
), ),
[ValidatorFields.TOTAL_PENALTIES]: getTotalPenalties( [ValidatorFields.TOTAL_PENALTIES]: formatNumberPercentage(
previousEpochValidatorScore, calculateOverallPenalty(id, allNodesInPreviousEpoch),
previousEpochPerformanceScore, 2
stakedTotal,
totalStake
), ),
[ValidatorFields.PENDING_STAKE]: pendingStake, [ValidatorFields.PENDING_STAKE]: pendingStake,
[ValidatorFields.STAKED_BY_USER]: stakedByUser [ValidatorFields.STAKED_BY_USER]: stakedByUser
@@ -328,12 +328,12 @@ export const ConsensusValidatorsTable = ({
...remaining, ...remaining,
]; ];
}, [ }, [
allNodesInPreviousEpoch,
data, data,
decimals, decimals,
hideTopThird, hideTopThird,
previousEpochData, previousEpochData,
thirdOfTotalStake, thirdOfTotalStake,
totalStake,
validatorsView, validatorsView,
]); ]);
@@ -5,15 +5,14 @@ import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { useAppState } from '../../../../contexts/app-state/app-state-context'; import { useAppState } from '../../../../contexts/app-state/app-state-context';
import { BigNumber } from '../../../../lib/bignumber'; import { BigNumber } from '../../../../lib/bignumber';
import { import {
calculatesPerformancePenalty,
calculateOverallPenalty,
calculateOverstakedPenalty,
getFormattedPerformanceScore, getFormattedPerformanceScore,
getLastEpochScoreAndPerformance, getLastEpochScoreAndPerformance,
getOverstakingPenalty,
getPerformancePenalty,
getTotalPenalties,
} from '../../shared'; } from '../../shared';
import { import {
defaultColDef, defaultColDef,
StakeNeededForPromotionRenderer,
stakedTotalPercentage, stakedTotalPercentage,
ValidatorFields, ValidatorFields,
ValidatorRenderer, ValidatorRenderer,
@@ -28,6 +27,7 @@ import type { ValidatorsTableProps } from './shared';
import { import {
formatNumber, formatNumber,
formatNumberPercentage, formatNumberPercentage,
removePaginationWrapper,
toBigNum, toBigNum,
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
@@ -52,6 +52,10 @@ export const StandbyPendingValidatorsTable = ({
const gridRef = useRef<AgGridReact | null>(null); const gridRef = useRef<AgGridReact | null>(null);
const allNodesInPreviousEpoch = removePaginationWrapper(
previousEpochData?.epoch.validatorsConnection?.edges
);
let nodes = useMemo(() => { let nodes = useMemo(() => {
if (!data) return []; if (!data) return [];
@@ -77,18 +81,15 @@ export const StandbyPendingValidatorsTable = ({
stakedByDelegates, stakedByDelegates,
stakedByOperator, stakedByOperator,
stakedTotal, stakedTotal,
rankingScore: { stakeScore }, rankingScore: { stakeScore, performanceScore },
pendingStake, pendingStake,
stakedTotalRanking, stakedTotalRanking,
stakedByUser, stakedByUser,
pendingUserStake, pendingUserStake,
userStakeShare, userStakeShare,
}) => { }) => {
const { const { performanceScore: previousEpochPerformanceScore } =
rawValidatorScore: previousEpochValidatorScore, getLastEpochScoreAndPerformance(previousEpochData, id);
performanceScore: previousEpochPerformanceScore,
stakeScore: previousEpochStakeScore,
} = getLastEpochScoreAndPerformance(previousEpochData, id);
let individualStakeNeededForPromotion, let individualStakeNeededForPromotion,
individualStakeNeededForPromotionDescription; individualStakeNeededForPromotionDescription;
@@ -144,21 +145,19 @@ export const StandbyPendingValidatorsTable = ({
toBigNum(stakedByOperator, decimals), toBigNum(stakedByOperator, decimals),
2 2
), ),
[ValidatorFields.PERFORMANCE_SCORE]: getFormattedPerformanceScore( [ValidatorFields.PERFORMANCE_SCORE]:
previousEpochPerformanceScore getFormattedPerformanceScore(performanceScore).toString(),
).toString(), [ValidatorFields.PERFORMANCE_PENALTY]: formatNumberPercentage(
[ValidatorFields.PERFORMANCE_PENALTY]: getPerformancePenalty( calculatesPerformancePenalty(performanceScore),
previousEpochPerformanceScore 2
), ),
[ValidatorFields.OVERSTAKING_PENALTY]: getOverstakingPenalty( [ValidatorFields.OVERSTAKING_PENALTY]: formatNumberPercentage(
previousEpochValidatorScore, calculateOverstakedPenalty(id, allNodesInPreviousEpoch),
previousEpochStakeScore 2
), ),
[ValidatorFields.TOTAL_PENALTIES]: getTotalPenalties( [ValidatorFields.TOTAL_PENALTIES]: formatNumberPercentage(
previousEpochValidatorScore, calculateOverallPenalty(id, allNodesInPreviousEpoch),
previousEpochPerformanceScore, 2
stakedTotal,
totalStake
), ),
[ValidatorFields.PENDING_STAKE]: pendingStake, [ValidatorFields.PENDING_STAKE]: pendingStake,
[ValidatorFields.STAKED_BY_USER]: stakedByUser [ValidatorFields.STAKED_BY_USER]: stakedByUser
@@ -172,13 +171,13 @@ export const StandbyPendingValidatorsTable = ({
} }
); );
}, [ }, [
allNodesInPreviousEpoch,
data, data,
decimals, decimals,
previousEpochData, previousEpochData,
stakeNeededForPromotion, stakeNeededForPromotion,
stakeNeededForPromotionDescription, stakeNeededForPromotionDescription,
t, t,
totalStake,
]); ]);
if (validatorsView === 'myStake') { if (validatorsView === 'myStake') {
@@ -226,21 +225,21 @@ export const StandbyPendingValidatorsTable = ({
cellRenderer: StakeShareRenderer, cellRenderer: StakeShareRenderer,
width: 100, width: 100,
}, },
{ // {
field: ValidatorFields.STAKE_NEEDED_FOR_PROMOTION, // field: ValidatorFields.STAKE_NEEDED_FOR_PROMOTION,
headerName: t(ValidatorFields.STAKE_NEEDED_FOR_PROMOTION).toString(), // headerName: t(ValidatorFields.STAKE_NEEDED_FOR_PROMOTION).toString(),
headerTooltip: t(stakeNeededForPromotionDescription, { // headerTooltip: t(stakeNeededForPromotionDescription, {
prefix: t('The'), // prefix: t('The'),
}), // }),
cellRenderer: StakeNeededForPromotionRenderer, // cellRenderer: StakeNeededForPromotionRenderer,
width: 210, // width: 210,
}, // },
{ {
field: ValidatorFields.TOTAL_PENALTIES, field: ValidatorFields.TOTAL_PENALTIES,
headerName: t(ValidatorFields.TOTAL_PENALTIES).toString(), headerName: t(ValidatorFields.TOTAL_PENALTIES).toString(),
headerTooltip: t('TotalPenaltiesDescription').toString(), headerTooltip: t('TotalPenaltiesDescription').toString(),
cellRenderer: TotalPenaltiesRenderer, cellRenderer: TotalPenaltiesRenderer,
width: 120, width: 120 + 210,
}, },
], ],
[] []
@@ -1,11 +1,15 @@
import React, { useMemo } from 'react'; import { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
useEnvironment, useEnvironment,
DocsLinks, DocsLinks,
ExternalLinks, ExternalLinks,
} from '@vegaprotocol/environment'; } from '@vegaprotocol/environment';
import { toBigNum } from '@vegaprotocol/utils'; import {
formatNumberPercentage,
removePaginationWrapper,
toBigNum,
} from '@vegaprotocol/utils';
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
import { import {
Link as UTLink, Link as UTLink,
@@ -24,11 +28,11 @@ import { SubHeading } from '../../../components/heading';
import { import {
getLastEpochScoreAndPerformance, getLastEpochScoreAndPerformance,
getNormalisedVotingPower, getNormalisedVotingPower,
getOverstakingPenalty,
getPerformancePenalty,
getTotalPenalties,
getUnnormalisedVotingPower, getUnnormalisedVotingPower,
getStakePercentage, getStakePercentage,
calculatesPerformancePenalty,
calculateOverstakedPenalty,
calculateOverallPenalty,
} from '../shared'; } from '../shared';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import type { StakingNodeFieldsFragment } from '../__generated__/Staking'; import type { StakingNodeFieldsFragment } from '../__generated__/Staking';
@@ -78,17 +82,27 @@ export const ValidatorTable = ({
const stakedOnNode = toBigNum(node.stakedTotal, decimals); const stakedOnNode = toBigNum(node.stakedTotal, decimals);
const { rawValidatorScore, performanceScore, stakeScore } = const { rawValidatorScore } = getLastEpochScoreAndPerformance(
getLastEpochScoreAndPerformance(previousEpochData, node.id); previousEpochData,
node.id
);
const stakePercentage = getStakePercentage(total, stakedOnNode); const stakePercentage = getStakePercentage(total, stakedOnNode);
const totalPenaltiesAmount = getTotalPenalties( const penalties = useMemo(() => {
rawValidatorScore, const allNodesInPreviousEpoch = removePaginationWrapper(
performanceScore, previousEpochData?.epoch.validatorsConnection?.edges
stakedOnNode.toString(), );
total.toString() return {
); // current epoch
performance: calculatesPerformancePenalty(
node.rankingScore.performanceScore
),
// previous epoch
overstaked: calculateOverstakedPenalty(node.id, allNodesInPreviousEpoch),
overall: calculateOverallPenalty(node.id, allNodesInPreviousEpoch),
};
}, [node, previousEpochData?.epoch.validatorsConnection?.edges]);
return ( return (
<> <>
@@ -242,7 +256,7 @@ export const ValidatorTable = ({
<Tooltip description={t('OverstakedPenaltyDescription')}> <Tooltip description={t('OverstakedPenaltyDescription')}>
<span data-testid="overstaking-penalty"> <span data-testid="overstaking-penalty">
{getOverstakingPenalty(rawValidatorScore, stakeScore)} {formatNumberPercentage(penalties.overstaked, 2)}
</span> </span>
</Tooltip> </Tooltip>
</KeyValueTableRow> </KeyValueTableRow>
@@ -251,7 +265,7 @@ export const ValidatorTable = ({
<Tooltip description={t('PerformancePenaltyDescription')}> <Tooltip description={t('PerformancePenaltyDescription')}>
<span data-testid="performance-penalty"> <span data-testid="performance-penalty">
{getPerformancePenalty(performanceScore)} {formatNumberPercentage(penalties.performance, 2)}
</span> </span>
</Tooltip> </Tooltip>
</KeyValueTableRow> </KeyValueTableRow>
@@ -260,7 +274,7 @@ export const ValidatorTable = ({
<strong>{t('TOTAL PENALTIES')}</strong> <strong>{t('TOTAL PENALTIES')}</strong>
</span> </span>
<span data-testid="total-penalties"> <span data-testid="total-penalties">
<strong>{totalPenaltiesAmount}</strong> <strong>{formatNumberPercentage(penalties.overall, 2)}</strong>
</span> </span>
</KeyValueTableRow> </KeyValueTableRow>
</KeyValueTable> </KeyValueTable>
@@ -9,6 +9,7 @@ import {
getTotalPenalties, getTotalPenalties,
getStakePercentage, getStakePercentage,
} from './shared'; } from './shared';
import * as Schema from '@vegaprotocol/types';
describe('getLastEpochScoreAndPerformance', () => { describe('getLastEpochScoreAndPerformance', () => {
const mockPreviousEpochData = { const mockPreviousEpochData = {
@@ -19,24 +20,48 @@ describe('getLastEpochScoreAndPerformance', () => {
{ {
node: { node: {
id: '0x123', id: '0x123',
stakedTotal: '',
rewardScore: { rewardScore: {
rawValidatorScore: '0.25', rawValidatorScore: '0.25',
performanceScore: '0.75', performanceScore: '0.75',
multisigScore: '',
validatorScore: '',
normalisedScore: '',
validatorStatus:
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
}, },
rankingScore: { rankingScore: {
stakeScore: '0.25', stakeScore: '0.25',
performanceScore: '0.75',
status: Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
previousStatus:
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
rankingScore: '',
votingPower: '',
}, },
}, },
}, },
{ {
node: { node: {
id: '0x234', id: '0x234',
stakedTotal: '',
rewardScore: { rewardScore: {
rawValidatorScore: '0.35', rawValidatorScore: '0.35',
performanceScore: '0.85', performanceScore: '0.85',
multisigScore: '',
validatorScore: '',
normalisedScore: '',
validatorStatus:
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
}, },
rankingScore: { rankingScore: {
stakeScore: '0.25', stakeScore: '0.25',
performanceScore: '0.85',
status: Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
previousStatus:
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
rankingScore: '',
votingPower: '',
}, },
}, },
}, },
+89 -1
View File
@@ -4,6 +4,94 @@ import {
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
import type { PreviousEpochQuery } from './__generated__/PreviousEpoch'; import type { PreviousEpochQuery } from './__generated__/PreviousEpoch';
import { BigNumber } from '../../lib/bignumber'; import { BigNumber } from '../../lib/bignumber';
import type { LastArrayElement } from 'type-fest';
type Node = NonNullable<
LastArrayElement<
NonNullable<
NonNullable<PreviousEpochQuery['epoch']['validatorsConnection']>['edges']
>
>
>['node'];
/**
* Calculates theoretical stake score for a given node
* @param nodeId Id of a node for which a score is calculated
* @param nodes A collection of all nodes
* @returns Theoretical stake score for given node based on the staked total
* of all node of the same type (status)
*/
const calculateTheoreticalStakeScore = (nodeId: string, nodes: Node[]) => {
const node = nodes.find((n) => n.id === nodeId);
if (!node) {
return new BigNumber(0);
}
const all = nodes
.filter((n) => n.rankingScore.status === node.rankingScore.status)
.map((n) => new BigNumber(n.stakedTotal));
const sumOfSameType = all.reduce((acc, a) => acc.plus(a), new BigNumber(0));
if (sumOfSameType.isZero()) {
return new BigNumber(0);
}
return new BigNumber(node.stakedTotal).dividedBy(sumOfSameType);
};
/**
* Calculates overall penalty for a given node
* @param nodeId Id of a node for which a penalty is calculated
* @param nodes A collection of all nodes - needed to calculate theoretical stake score
* @returns %
*/
export const calculateOverallPenalty = (nodeId: string, nodes: Node[]) => {
const node = nodes.find((n) => n.id === nodeId);
const tts = calculateTheoreticalStakeScore(nodeId, nodes);
if (!node || tts.isZero()) {
return new BigNumber(0);
}
const penalty = new BigNumber(1)
.minus(new BigNumber(node.rewardScore?.validatorScore || 0).dividedBy(tts))
.times(100);
return penalty.isLessThan(0) ? new BigNumber(0) : penalty;
};
/**
* Calculates over-staked penalty for a given node
* @param nodeId Id of a node for which a penalty is calculated
* @param nodes A collection of all nodes - needed to calculate theoretical stake score
* @returns %
*/
export const calculateOverstakedPenalty = (nodeId: string, nodes: Node[]) => {
const node = nodes.find((n) => n.id === nodeId);
const tts = calculateTheoreticalStakeScore(nodeId, nodes);
if (!node || tts.isZero()) {
return new BigNumber(0);
}
const penalty = new BigNumber(1)
.minus(
new BigNumber(node.rewardScore?.rawValidatorScore || 0).dividedBy(tts)
)
.times(100);
console.log(
nodeId,
new BigNumber(node.rewardScore?.rawValidatorScore || 0).toString(),
tts.toString(),
new BigNumber(node.rewardScore?.rawValidatorScore || 0)
.dividedBy(tts)
.toString()
);
return penalty.isLessThan(0) ? new BigNumber(0) : penalty;
};
/**
* Calculates performance penalty based on the given performance score.
* @returns %
*/
export const calculatesPerformancePenalty = (performanceScore: string) => {
const penalty = new BigNumber(1)
.minus(new BigNumber(performanceScore))
.times(100);
return penalty.isLessThan(0) ? new BigNumber(0) : penalty;
};
export const getLastEpochScoreAndPerformance = ( export const getLastEpochScoreAndPerformance = (
previousEpochData: PreviousEpochQuery | undefined, previousEpochData: PreviousEpochQuery | undefined,
@@ -15,7 +103,7 @@ export const getLastEpochScoreAndPerformance = (
return { return {
rawValidatorScore: validator?.rewardScore?.rawValidatorScore, rawValidatorScore: validator?.rewardScore?.rawValidatorScore,
performanceScore: validator?.rewardScore?.performanceScore, performanceScore: validator?.rankingScore?.performanceScore,
stakeScore: validator?.rankingScore?.stakeScore, stakeScore: validator?.rankingScore?.stakeScore,
}; };
}; };
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,10 @@
import { aliasGQLQuery } from '@vegaprotocol/cypress'; import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { MarketState, MarketStateMapping } from '@vegaprotocol/types'; import type { DataSourceDefinition } from '@vegaprotocol/types';
import {
MarketState,
MarketStateMapping,
PropertyKeyType,
} from '@vegaprotocol/types';
import { addDays, subDays } from 'date-fns'; import { addDays, subDays } from 'date-fns';
import { import {
chainIdQuery, chainIdQuery,
@@ -20,6 +25,25 @@ import {
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
describe('Closed markets', { tags: '@smoke' }, () => { describe('Closed markets', { tags: '@smoke' }, () => {
const settlementDataProperty = 'settlement-data-property';
const settlementDataPropertyKey = {
__typename: 'PropertyKey' as const,
name: settlementDataProperty,
type: PropertyKeyType.TYPE_INTEGER,
numberDecimalPlaces: 2,
};
const settlementDataSourceData: DataSourceDefinition = {
sourceType: {
sourceType: {
filters: [
{
__typename: 'Filter',
key: settlementDataPropertyKey,
},
],
},
},
};
const rowSelector = const rowSelector =
'[data-testid="tab-closed-markets"] .ag-center-cols-container .ag-row'; '[data-testid="tab-closed-markets"] .ag-center-cols-container .ag-row';
@@ -37,11 +61,15 @@ describe('Closed markets', { tags: '@smoke' }, () => {
tradableInstrument: { tradableInstrument: {
instrument: { instrument: {
product: { product: {
dataSourceSpecBinding: {
settlementDataProperty,
},
dataSourceSpecForTradingTermination: { dataSourceSpecForTradingTermination: {
id: 'market-1-trading-termination-oracle-id', id: 'market-1-trading-termination-oracle-id',
}, },
dataSourceSpecForSettlementData: { dataSourceSpecForSettlementData: {
id: 'market-1-settlement-data-oracle-id', id: 'market-1-settlement-data-oracle-id',
data: settlementDataSourceData,
}, },
settlementAsset, settlementAsset,
}, },
@@ -63,6 +91,15 @@ describe('Closed markets', { tags: '@smoke' }, () => {
`settlement-expiry-date:${addDays(new Date(), 4).toISOString()}`, `settlement-expiry-date:${addDays(new Date(), 4).toISOString()}`,
], ],
}, },
product: {
dataSourceSpecBinding: {
settlementDataProperty,
},
dataSourceSpecForSettlementData: {
id: 'market-1-settlement-data-oracle-id',
data: settlementDataSourceData,
},
},
}, },
}, },
}); });
@@ -81,6 +118,15 @@ describe('Closed markets', { tags: '@smoke' }, () => {
`settlement-expiry-date:${subDays(new Date(), 2).toISOString()}`, `settlement-expiry-date:${subDays(new Date(), 2).toISOString()}`,
], ],
}, },
product: {
dataSourceSpecBinding: {
settlementDataProperty,
},
dataSourceSpecForSettlementData: {
id: 'market-1-settlement-data-oracle-id',
data: settlementDataSourceData,
},
},
}, },
}, },
}); });
@@ -301,7 +347,7 @@ describe('Closed markets', { tags: '@smoke' }, () => {
addDecimalsFormatNumber( addDecimalsFormatNumber(
// @ts-ignore cannot deep un-partial // @ts-ignore cannot deep un-partial
specDataConnection.externalData.data.data[0].value, specDataConnection.externalData.data.data[0].value,
settledMarket.decimalPlaces settlementDataPropertyKey.numberDecimalPlaces
) )
); );
+13 -1
View File
@@ -1,7 +1,7 @@
import { mockConnectWallet } from '@vegaprotocol/cypress'; import { mockConnectWallet } from '@vegaprotocol/cypress';
describe('Navbar', { tags: '@smoke' }, () => { describe('Navbar', { tags: '@smoke' }, () => {
before(() => { beforeEach(() => {
cy.clearAllLocalStorage(); cy.clearAllLocalStorage();
cy.mockTradingPage(); cy.mockTradingPage();
cy.mockSubscription(); cy.mockSubscription();
@@ -47,6 +47,18 @@ describe('Navbar', { tags: '@smoke' }, () => {
}); });
}); });
}); });
it('Disclaimer should be presented after choosing from menu', () => {
cy.get('nav')
.find('ul li:contains(Resources)')
.contains('Resources')
.click();
cy.getByTestId('Disclaimer').eq(0).click();
cy.location('hash').should('equal', '#/disclaimer');
cy.get('p').contains(
'Vega is a decentralised peer-to-peer protocol that can be used to trade derivatives with cryptoassets.'
);
});
}); });
describe('mobile view', () => { describe('mobile view', () => {
+1
View File
@@ -2,6 +2,7 @@ NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9
NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
NX_VEGA_ENV=DEVNET NX_VEGA_ENV=DEVNET
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
+2 -1
View File
@@ -1,6 +1,7 @@
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_ETHEREUM_PROVIDER_URL=https://eth-mainnet.gateway.pokt.network/v1/lb/af6a2d529a11f8158bc8ca2a
NX_ETHERSCAN_URL=https://etherscan.io NX_ETHERSCAN_URL=https://etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_ENV=MAINNET NX_VEGA_ENV=MAINNET
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz
+1
View File
@@ -2,6 +2,7 @@ NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9
NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
NX_VEGA_ENV=STAGNET1 NX_VEGA_ENV=STAGNET1
NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz
+1
View File
@@ -2,6 +2,7 @@ NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9
NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml
NX_VEGA_ENV=TESTNET NX_VEGA_ENV=TESTNET
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
@@ -2,6 +2,7 @@ NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9
NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml
NX_VEGA_ENV=VALIDATOR_TESTNET NX_VEGA_ENV=VALIDATOR_TESTNET
NX_VEGA_EXPLORER_URL=https://validator-testnet.explorer.vega.xyz NX_VEGA_EXPLORER_URL=https://validator-testnet.explorer.vega.xyz
@@ -0,0 +1,48 @@
import { t } from '@vegaprotocol/i18n';
export const Disclaimer = () => {
return (
<div className="py-16 px-8 flex w-full justify-center">
<div className="lg:min-w-[700px] min-w-[300px] max-w-[700px]">
<h1 className="text-4xl xl:text-5xl uppercase font-alpha calt">
{t('Disclaimer')}
</h1>
<p className="mb-6 mt-10">
{t(
'Vega is a decentralised peer-to-peer protocol that can be used to trade derivatives with cryptoassets. The Vega Protocol is an implementation layer (layer one) protocol made of free, public, open-source or source-available software. Use of the Vega Protocol involves various risks, including but not limited to, losses while digital assets are supplied to the Vega Protocol and losses due to the fluctuation of prices of assets.'
)}
</p>
<p className="mb-6">
{t(
'Before using the Vega Protocol, review the relevant documentation at docs.vega.xyz to make sure that you understand how it works. Conduct your own due diligence and consult your financial advisor before making any investment decisions.'
)}
</p>
<p className="mb-6">
{t(
'As described in the Vega Protocol core license, the Vega Protocol is provided “as is”, at your own risk, and without warranties of any kind. Although Gobalsky Labs Limited developed much of the initial code for the Vega Protocol, it does not provide or control the Vega Protocol, which is run by third parties deploying it on a bespoke blockchain. Upgrades and modifications to the Vega Protocol are managed in a community-driven way by holders of the VEGA governance token.'
)}
</p>
<p className="mb-8">
{t(
'No developer or entity involved in creating the Vega Protocol will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the Vega Protocol, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or legal costs, or loss of profits, cryptoassets, tokens or anything else of value.'
)}
</p>
<p className="mb-8">
{t(
'This website is hosted on a decentralised network, the Interplanetary File System (“IPFS”). The IPFS decentralised web is made up of all the computers (nodes) connected to it. Data is therefore stored on many different computers.'
)}
</p>
<p className="mb-8">
{t(
"The information provided on this website does not constitute investment advice, financial advice, trading advice, or any other sort of advice and you should not treat any of the website's content as such. No party recommends that any cryptoasset should be bought, sold, or held by you via this website. No party ensures the accuracy of information listed on this website or holds any responsibility for any missing or wrong information. You understand that you are using any and all information available here at your own risk."
)}
</p>
<p className="mb-8">
{t(
'Additionally, just as you can access email protocols such as SMTP through multiple email clients, you can potentially access the Vega Protocol through many web or mobile interfaces. You are responsible for doing your own diligence on those interfaces to understand the associated risks and any fees.'
)}
</p>
</div>
</div>
);
};
@@ -0,0 +1,3 @@
import { Disclaimer } from './disclaimer';
export default Disclaimer;
+20 -23
View File
@@ -247,7 +247,7 @@ const MainGrid = memo(
const navigate = useNavigate(); const navigate = useNavigate();
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'top' }); const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'top' });
const [sizesMiddle, handleOnMiddleLayoutChange] = usePaneLayout({ const [sizesMiddle, handleOnMiddleLayoutChange] = usePaneLayout({
id: 'middle', id: 'middle-1',
}); });
return ( return (
@@ -259,27 +259,7 @@ const MainGrid = memo(
onChange={handleOnMiddleLayoutChange} onChange={handleOnMiddleLayoutChange}
> >
<ResizableGridPanel <ResizableGridPanel
priority={LayoutPriority.High} preferredSize={sizesMiddle[0] || 330}
minSize={200}
preferredSize={sizesMiddle[0] || '50%'}
>
<TradeGridChild>
<Tabs storageKey="console-trade-grid-main-left">
<Tab id="chart" name={t('Chart')}>
<TradingViews.candles.component marketId={marketId} />
</Tab>
<Tab id="depth" name={t('Depth')}>
<TradingViews.depth.component marketId={marketId} />
</Tab>
<Tab id="liquidity" name={t('Liquidity')}>
<TradingViews.liquidity.component marketId={marketId} />
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize={sizesMiddle[1] || 330}
minSize={300} minSize={300}
> >
<TradeGridChild> <TradeGridChild>
@@ -297,7 +277,24 @@ const MainGrid = memo(
</TradeGridChild> </TradeGridChild>
</ResizableGridPanel> </ResizableGridPanel>
<ResizableGridPanel <ResizableGridPanel
priority={LayoutPriority.Low} minSize={200}
preferredSize={sizesMiddle[1] || '50%'}
>
<TradeGridChild>
<Tabs storageKey="console-trade-grid-main-left">
<Tab id="chart" name={t('Chart')}>
<TradingViews.candles.component marketId={marketId} />
</Tab>
<Tab id="depth" name={t('Depth')}>
<TradingViews.depth.component marketId={marketId} />
</Tab>
<Tab id="liquidity" name={t('Liquidity')}>
<TradingViews.liquidity.component marketId={marketId} />
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
<ResizableGridPanel
preferredSize={sizesMiddle[2] || 430} preferredSize={sizesMiddle[2] || 430}
minSize={200} minSize={200}
> >
@@ -1,6 +1,6 @@
import { act, render, screen, within } from '@testing-library/react'; import { act, render, screen, within } from '@testing-library/react';
import { Closed } from './closed'; import { Closed } from './closed';
import { MarketStateMapping } from '@vegaprotocol/types'; import { MarketStateMapping, PropertyKeyType } from '@vegaprotocol/types';
import { PositionStatus } from '@vegaprotocol/types'; import { PositionStatus } from '@vegaprotocol/types';
import { MarketState } from '@vegaprotocol/types'; import { MarketState } from '@vegaprotocol/types';
import { subDays } from 'date-fns'; import { subDays } from 'date-fns';
@@ -55,6 +55,23 @@ describe('Closed', () => {
product: { product: {
dataSourceSpecForSettlementData: { dataSourceSpecForSettlementData: {
id: settlementDataId, id: settlementDataId,
data: {
sourceType: {
sourceType: {
filters: [
{
__typename: 'Filter',
key: {
__typename: 'PropertyKey',
name: settlementDataProperty,
type: PropertyKeyType.TYPE_INTEGER,
numberDecimalPlaces: 5,
},
},
],
},
},
},
}, },
dataSourceSpecBinding: { dataSourceSpecBinding: {
settlementDataProperty, settlementDataProperty,
+30 -14
View File
@@ -13,7 +13,10 @@ import {
getMarketExpiryDate, getMarketExpiryDate,
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
import { usePositionsQuery } from '@vegaprotocol/positions'; import { usePositionsQuery } from '@vegaprotocol/positions';
import type { MarketMaybeWithData } from '@vegaprotocol/markets'; import type {
DataSourceFilterFragment,
MarketMaybeWithData,
} from '@vegaprotocol/markets';
import { import {
MarketTableActions, MarketTableActions,
closedMarketsWithDataProvider, closedMarketsWithDataProvider,
@@ -42,6 +45,7 @@ interface Row {
markPrice: string | undefined; markPrice: string | undefined;
settlementDataOracleId: string; settlementDataOracleId: string;
settlementDataSpecBinding: string; settlementDataSpecBinding: string;
setlementDataSourceFilter: DataSourceFilterFragment | undefined;
tradingTerminationOracleId: string; tradingTerminationOracleId: string;
settlementAsset: SettlementAsset; settlementAsset: SettlementAsset;
realisedPNL: string | undefined; realisedPNL: string | undefined;
@@ -74,28 +78,40 @@ export const Closed = () => {
} }
); );
const instrument = market.tradableInstrument.instrument;
const spec =
instrument.product.dataSourceSpecForSettlementData.data.sourceType
.__typename === 'DataSourceDefinitionExternal'
? instrument.product.dataSourceSpecForSettlementData.data.sourceType
.sourceType
: undefined;
const filters = spec?.filters || [];
const settlementDataSpecBinding =
instrument.product.dataSourceSpecBinding.settlementDataProperty;
const filter = filters?.find((filter) => {
return filter.key.name === settlementDataSpecBinding;
});
const row: Row = { const row: Row = {
id: market.id, id: market.id,
code: market.tradableInstrument.instrument.code, code: instrument.code,
name: market.tradableInstrument.instrument.name, name: instrument.name,
decimalPlaces: market.decimalPlaces, decimalPlaces: market.decimalPlaces,
state: market.state, state: market.state,
metadata: market.tradableInstrument.instrument.metadata.tags ?? [], metadata: instrument.metadata.tags ?? [],
closeTimestamp: market.marketTimestamps.close, closeTimestamp: market.marketTimestamps.close,
bestBidPrice: market.data?.bestBidPrice, bestBidPrice: market.data?.bestBidPrice,
bestOfferPrice: market.data?.bestOfferPrice, bestOfferPrice: market.data?.bestOfferPrice,
markPrice: market.data?.markPrice, markPrice: market.data?.markPrice,
settlementDataOracleId: settlementDataOracleId:
market.tradableInstrument.instrument.product instrument.product.dataSourceSpecForSettlementData.id,
.dataSourceSpecForSettlementData.id, settlementDataSpecBinding,
settlementDataSpecBinding: setlementDataSourceFilter: filter,
market.tradableInstrument.instrument.product.dataSourceSpecBinding
.settlementDataProperty,
tradingTerminationOracleId: tradingTerminationOracleId:
market.tradableInstrument.instrument.product instrument.product.dataSourceSpecForTradingTermination.id,
.dataSourceSpecForTradingTermination.id, settlementAsset: instrument.product.settlementAsset,
settlementAsset:
market.tradableInstrument.instrument.product.settlementAsset,
realisedPNL: position?.node.realisedPNL, realisedPNL: position?.node.realisedPNL,
}; };
@@ -238,8 +254,8 @@ const ClosedMarketsDataGrid = ({ rowData }: { rowData: Row[] }) => {
}: VegaICellRendererParams<Row, 'settlementDataOracleId'>) => ( }: VegaICellRendererParams<Row, 'settlementDataOracleId'>) => (
<SettlementPriceCell <SettlementPriceCell
oracleSpecId={value} oracleSpecId={value}
decimalPlaces={data?.decimalPlaces ?? 0}
settlementDataSpecBinding={data?.settlementDataSpecBinding} settlementDataSpecBinding={data?.settlementDataSpecBinding}
filter={data?.setlementDataSourceFilter}
/> />
), ),
}, },
@@ -12,7 +12,7 @@ export const Proposed = () => {
const externalLink = tokenLink(TOKEN_NEW_MARKET_PROPOSAL); const externalLink = tokenLink(TOKEN_NEW_MARKET_PROPOSAL);
return ( return (
<> <>
<div className="min-h-[200px]"> <div className="h-[400px]">
<ProposalsList /> <ProposalsList />
</div> </div>
<ExternalLink className="py-4 px-[11px] text-sm" href={externalLink}> <ExternalLink className="py-4 px-[11px] text-sm" href={externalLink}>
@@ -1,13 +1,17 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import type { Property } from '@vegaprotocol/types'; import type { Property } from '@vegaprotocol/types';
import { PropertyKeyType } from '@vegaprotocol/types';
import type { MockedResponse } from '@apollo/client/testing'; import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing';
import type { OracleSpecDataConnectionQuery } from '@vegaprotocol/markets'; import type { OracleSpecDataConnectionQuery } from '@vegaprotocol/markets';
import { OracleSpecDataConnectionDocument } from '@vegaprotocol/markets'; import { OracleSpecDataConnectionDocument } from '@vegaprotocol/markets';
import type { SettlementPriceCellProps } from './settlement-price-cell'; import type { SettlementPriceCellProps } from './settlement-price-cell';
import { SettlementPriceCell } from './settlement-price-cell'; import { SettlementPriceCell } from './settlement-price-cell';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
describe('SettlementPriceCell', () => { describe('SettlementPriceCell', () => {
const settlementDataSpecBinding = 'settlement-data-spec-binding';
const createMock = ( const createMock = (
id: string, id: string,
property: Property property: Property
@@ -40,11 +44,19 @@ describe('SettlementPriceCell', () => {
}, },
}; };
}; };
const createProps = (): SettlementPriceCellProps => { const createProps = (
filterKey = {
name: settlementDataSpecBinding,
type: PropertyKeyType.TYPE_INTEGER,
numberDecimalPlaces: 2,
}
): SettlementPriceCellProps => {
return { return {
oracleSpecId: 'oracle-spec-id', oracleSpecId: 'oracle-spec-id',
decimalPlaces: 2, filter: {
settlementDataSpecBinding: 'settlement-data-spec-binding', key: filterKey,
},
settlementDataSpecBinding,
}; };
}; };
it('renders fetches and renders the settlment data value', async () => { it('renders fetches and renders the settlment data value', async () => {
@@ -65,14 +77,19 @@ describe('SettlementPriceCell', () => {
expect(screen.getByText('-')).toBeInTheDocument(); expect(screen.getByText('-')).toBeInTheDocument();
const link = await screen.findByRole('link'); const link = await screen.findByRole('link');
expect(link).toHaveTextContent('12.34'); expect(link).toHaveTextContent(
addDecimalsFormatNumber(
property.value,
props.filter?.key.numberDecimalPlaces || 0
)
);
expect(link).toHaveAttribute( expect(link).toHaveAttribute(
'href', 'href',
expect.stringContaining(`/oracles/${props.oracleSpecId}`) expect.stringContaining(`/oracles/${props.oracleSpecId}`)
); );
}); });
it('renders "-" if no spec value is found', async () => { it('renders "Unknown" if no spec value is found', async () => {
const props = createProps(); const props = createProps();
const property = { const property = {
__typename: 'Property' as const, __typename: 'Property' as const,
@@ -90,7 +107,7 @@ describe('SettlementPriceCell', () => {
expect(screen.getByText('-')).toBeInTheDocument(); expect(screen.getByText('-')).toBeInTheDocument();
const link = await screen.findByRole('link'); const link = await screen.findByRole('link');
expect(link).toHaveTextContent('-'); expect(link).toHaveTextContent('Unknown');
expect(link).toHaveAttribute( expect(link).toHaveAttribute(
'href', 'href',
expect.stringContaining(`/oracles/${props.oracleSpecId}`) expect.stringContaining(`/oracles/${props.oracleSpecId}`)
@@ -110,4 +127,33 @@ describe('SettlementPriceCell', () => {
expect(screen.getByText('-')).toBeInTheDocument(); expect(screen.getByText('-')).toBeInTheDocument();
expect(screen.queryByRole('link')).not.toBeInTheDocument(); expect(screen.queryByRole('link')).not.toBeInTheDocument();
}); });
it('does not format value if property key type is not integer', async () => {
const props = createProps({
name: settlementDataSpecBinding,
type: PropertyKeyType.TYPE_TIMESTAMP,
numberDecimalPlaces: 2,
});
const property = {
__typename: 'Property' as const,
name: props.settlementDataSpecBinding as string,
value: '1234',
};
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const mock = createMock(props.oracleSpecId!, property);
render(
<MockedProvider mocks={[mock]}>
<SettlementPriceCell {...props} />
</MockedProvider>
);
expect(screen.getByText('-')).toBeInTheDocument();
const link = await screen.findByRole('link');
expect(link).toHaveTextContent(property.value);
expect(link).toHaveAttribute(
'href',
expect.stringContaining(`/oracles/${props.oracleSpecId}`)
);
});
}); });
@@ -1,19 +1,21 @@
import { DApp, EXPLORER_ORACLE, useLinks } from '@vegaprotocol/environment'; import { DApp, EXPLORER_ORACLE, useLinks } from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { DataSourceFilterFragment } from '@vegaprotocol/markets';
import { useOracleSpecBindingData } from '@vegaprotocol/markets'; import { useOracleSpecBindingData } from '@vegaprotocol/markets';
import { PropertyKeyType } from '@vegaprotocol/types';
import { Link } from '@vegaprotocol/ui-toolkit'; import { Link } from '@vegaprotocol/ui-toolkit';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils'; import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
export interface SettlementPriceCellProps { export interface SettlementPriceCellProps {
oracleSpecId: string | undefined; oracleSpecId: string | undefined;
decimalPlaces: number;
settlementDataSpecBinding: string | undefined; settlementDataSpecBinding: string | undefined;
filter: DataSourceFilterFragment | undefined;
} }
export const SettlementPriceCell = ({ export const SettlementPriceCell = ({
oracleSpecId, oracleSpecId,
decimalPlaces,
settlementDataSpecBinding, settlementDataSpecBinding,
filter,
}: SettlementPriceCellProps) => { }: SettlementPriceCellProps) => {
const linkCreator = useLinks(DApp.Explorer); const linkCreator = useLinks(DApp.Explorer);
const { property, loading } = useOracleSpecBindingData( const { property, loading } = useOracleSpecBindingData(
@@ -25,15 +27,32 @@ export const SettlementPriceCell = ({
return <span>-</span>; return <span>-</span>;
} }
const renderText = () => {
if (!property || !filter) {
return t('Unknown');
}
if (
filter.key.type === PropertyKeyType.TYPE_INTEGER &&
filter.key.numberDecimalPlaces !== null &&
filter.key.numberDecimalPlaces !== undefined
) {
return addDecimalsFormatNumber(
property.value,
filter.key.numberDecimalPlaces
);
}
return property.value;
};
return ( return (
<Link <Link
href={linkCreator(EXPLORER_ORACLE.replace(':id', oracleSpecId))} href={linkCreator(EXPLORER_ORACLE.replace(':id', oracleSpecId))}
className="underline font-mono" className="underline font-mono"
target="_blank" target="_blank"
> >
{property {renderText()}
? addDecimalsFormatNumber(property.value, decimalPlaces)
: t('-')}
</Link> </Link>
); );
}; };
+10 -3
View File
@@ -36,9 +36,8 @@ export const Navbar = ({
}) => { }) => {
const { GITHUB_FEEDBACK_URL } = useEnvironment(); const { GITHUB_FEEDBACK_URL } = useEnvironment();
const tokenLink = useLinks(DApp.Token); const tokenLink = useLinks(DApp.Token);
const { marketId } = useGlobalStore((store) => ({ const marketId = useGlobalStore((store) => store.marketId);
marketId: store.marketId,
}));
const tradingPath = marketId const tradingPath = marketId
? Links[Routes.MARKET](marketId) ? Links[Routes.MARKET](marketId)
: Links[Routes.MARKET](); : Links[Routes.MARKET]();
@@ -107,6 +106,14 @@ export const Navbar = ({
{t('Give Feedback')} {t('Give Feedback')}
</NavExternalLink> </NavExternalLink>
</NavigationItem> </NavigationItem>
<NavigationItem>
<NavigationLink
data-testid="Disclaimer"
to={Links[Routes.DISCLAIMER]()}
>
{t('Disclaimer')}
</NavigationLink>
</NavigationItem>
</NavigationList> </NavigationList>
</NavigationContent> </NavigationContent>
</NavigationItem> </NavigationItem>
@@ -1 +1,2 @@
export * from './welcome-dialog'; export * from './welcome-dialog';
export * from './risk-message';
@@ -0,0 +1,45 @@
import { t } from '@vegaprotocol/i18n';
import {
ExternalLink,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { Links, Routes } from '../../pages/client-router';
export const RiskMessage = () => {
return (
<>
<div className="bg-vega-light-100 dark:bg-vega-dark-100 p-6 mb-6">
<ul className="list-[square] ml-4">
<li className="mb-1">
{t(
'Conduct your own due diligence and consult your financial advisor before making any investment decisions.'
)}
</li>
<li className="mb-1">
{t(
'You may encounter bugs, loss of functionality or loss of assets.'
)}
</li>
<li>
{t('No party accepts any liability for any losses whatsoever.')}
</li>
</ul>
</div>
<p className="mb-8">
{t(
'By using the Vega Console, you acknowledge that you have read and understood the'
)}{' '}
<ExternalLink
href={`/#/${Links[Routes.DISCLAIMER]()}`}
className="underline"
>
<span className="flex items-center gap-1">
<span>{t('Vega Console Disclaimer')}</span>
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
</span>
</ExternalLink>
</p>
</>
);
};
@@ -47,25 +47,4 @@ describe('Risk notice dialog', () => {
expect(mockOnClose).toHaveBeenCalled(); expect(mockOnClose).toHaveBeenCalled();
} }
); );
it('displays a risk message for mainnet', () => {
const introText = 'Regulation may apply to use of this app';
const network = Networks.MAINNET;
// @ts-ignore ignore mock implementation
useEnvironment.mockImplementation(() => ({
...mockEnvDefinitions,
VEGA_ENV: network,
}));
render(<RiskNoticeDialog onClose={mockOnClose} network={network} />);
expect(screen.getByText(introText)).toBeInTheDocument();
const button = screen.getByRole('button', {
name: 'I understand, Continue',
});
fireEvent.click(button);
expect(mockOnClose).toHaveBeenCalled();
});
}); });
@@ -7,8 +7,8 @@ import {
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { RISK_ACCEPTED_KEY } from '../constants'; import { RISK_ACCEPTED_KEY } from '../constants';
import { TelemetryApproval } from './telemetry-approval'; import { TelemetryApproval } from './telemetry-approval';
import type { Networks } from '@vegaprotocol/environment';
import { import {
Networks,
useEnvironment, useEnvironment,
DocsLinks, DocsLinks,
ExternalLinks, ExternalLinks,
@@ -28,29 +28,17 @@ export const RiskNoticeDialog = ({ onClose, network }: Props) => {
}; };
return ( return (
<> <TestnetContent network={network} handleAcceptRisk={handleAcceptRisk} />
{network === Networks.MAINNET ? (
<MainnetContent />
) : (
<TestnetContent network={network} />
)}
<div className="my-4">
<TelemetryApproval
helpText={t(
'Help identify bugs and improve the service by sharing anonymous usage data. You can change this in your settings at any time.'
)}
/>
</div>
<Button onClick={handleAcceptRisk}>
{network === Networks.MAINNET
? t('I understand, Continue')
: t('Continue')}
</Button>
</>
); );
}; };
const TestnetContent = ({ network }: { network: Networks }) => { const TestnetContent = ({
network,
handleAcceptRisk,
}: {
network: Networks;
handleAcceptRisk: () => void;
}) => {
const { GITHUB_FEEDBACK_URL } = useEnvironment(); const { GITHUB_FEEDBACK_URL } = useEnvironment();
return ( return (
<> <>
@@ -88,29 +76,14 @@ const TestnetContent = ({ network }: { network: Networks }) => {
</li> </li>
</ul> </ul>
)} )}
</> <div className="my-4">
); <TelemetryApproval
}; helpText={t(
'Help identify bugs and improve the service by sharing anonymous usage data. You can change this in your settings at any time.'
const MainnetContent = () => { )}
return ( />
<> </div>
<h4 className="text-xl mb-2 mt-4"> <Button onClick={handleAcceptRisk}>{t('Continue')}</Button>
{t('Regulation may apply to use of this app')}
</h4>
<p className="mb-6">
{t(
'This decentralised application allows you to connect to and use publicly available blockchain services operated by third parties that may include trading, financial products, or other services that may be subject to legal and regulatory restrictions in your jurisdiction. This application is a front end only and does not hold any funds or provide any products or services. It is available to anyone with an internet connection via IPFS and other methods, and the ability to access it does not imply any right to use any services or that it is legal for you to do so. By using this application you accept that it is your responsibility to ensure that your use of the application and any blockchain services accessed through it is compliant with applicable laws and regulations in your jusrisdiction.'
)}
</p>
<h4 className="text-xl mb-2">
{t('Technical and financial risk of loss')}
</h4>
<p className="mb-8">
{t(
'The public blockchain services accessible via this decentralised application are operated by third parties and may carry significant risks including the potential loss of all funds that you deposit or hold with these services. Technical risks include the risk of loss in the event of the failure or compromise of the public blockchain infrastructure or smart contracts that provide any services you use. Financial risks include but are not limited to losses due to volatility, excessive leverage, low liquidity, and your own lack of understanding of the services you use. By using this decentralised application you accept that it is your responsibility to ensure that you understand any services you use and the technical and financial risks inherent in your use. Do not risk what you cannot afford to lose.'
)}
</p>
</> </>
); );
}; };
@@ -5,7 +5,7 @@ import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
export const TelemetryApproval = ({ helpText }: { helpText: string }) => { export const TelemetryApproval = ({ helpText }: { helpText: string }) => {
const [isApproved, setIsApproved] = useTelemetryApproval(); const [isApproved, setIsApproved] = useTelemetryApproval();
return ( return (
<div className="flex flex-col px-2 py-3"> <div className="flex flex-col py-3">
<div className="mr-4" role="form"> <div className="mr-4" role="form">
<Checkbox <Checkbox
label={<span className="text-lg pl-1">{t('Share usage data')}</span>} label={<span className="text-lg pl-1">{t('Share usage data')}</span>}
@@ -30,17 +30,24 @@ export const WelcomeDialog = () => {
const shouldDisplayWelcomeDialog = useGlobalStore( const shouldDisplayWelcomeDialog = useGlobalStore(
(store) => store.shouldDisplayWelcomeDialog (store) => store.shouldDisplayWelcomeDialog
); );
const isRiskDialogNeeded = riskAccepted !== 'true' && !('Cypress' in window);
const isWelcomeDialogNeeded = pathname === '/' || shouldDisplayWelcomeDialog;
const onCloseDialog = useCallback(() => {
update({ shouldDisplayWelcomeDialog: isRiskDialogNeeded });
}, [update, isRiskDialogNeeded]);
const isRiskDialogNeeded =
riskAccepted !== 'true' &&
VEGA_ENV !== Networks.MAINNET &&
!('Cypress' in window);
const isWelcomeDialogNeeded = pathname === '/' || shouldDisplayWelcomeDialog;
const onCloseDialog = useCallback(() => {
update({
shouldDisplayWelcomeDialog: isRiskDialogNeeded,
});
}, [update, isRiskDialogNeeded]);
if (isRiskDialogNeeded) { if (isRiskDialogNeeded) {
dialogContent = ( dialogContent = (
<RiskNoticeDialog onClose={onCloseDialog} network={VEGA_ENV} /> <RiskNoticeDialog onClose={onCloseDialog} network={VEGA_ENV} />
); );
title = VEGA_ENV === Networks.MAINNET ? t('WARNING') : t('Vega Console'); title = t('Vega Console');
size = 'medium'; size = 'medium';
} else if (isWelcomeDialogNeeded && data?.length === 0) { } else if (isWelcomeDialogNeeded && data?.length === 0) {
dialogContent = <WelcomeNoticeDialog />; dialogContent = <WelcomeNoticeDialog />;
@@ -16,6 +16,8 @@ import { Link } from 'react-router-dom';
import { ProposedMarkets } from './proposed-markets'; import { ProposedMarkets } from './proposed-markets';
import { Links, Routes } from '../../pages/client-router'; import { Links, Routes } from '../../pages/client-router';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler'; import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
import { TelemetryApproval } from './telemetry-approval';
import { Networks, useEnvironment } from '@vegaprotocol/environment';
export const SelectMarketLandingTable = ({ export const SelectMarketLandingTable = ({
markets, markets,
@@ -94,6 +96,8 @@ export const WelcomeLandingDialog = ({
onClose, onClose,
}: LandingDialogContainerProps) => { }: LandingDialogContainerProps) => {
const { data, loading, error } = useMarketList(); const { data, loading, error } = useMarketList();
const { VEGA_ENV } = useEnvironment();
const isMainnet = VEGA_ENV === Networks.MAINNET;
if (error) { if (error) {
return ( return (
<div className="flex justify-center items-center"> <div className="flex justify-center items-center">
@@ -114,6 +118,13 @@ export const WelcomeLandingDialog = ({
<> <>
<WelcomeDialogHeader /> <WelcomeDialogHeader />
<SelectMarketLandingTable markets={data} onClose={onClose} /> <SelectMarketLandingTable markets={data} onClose={onClose} />
{isMainnet && (
<TelemetryApproval
helpText={t(
'Help identify bugs and improve the service by sharing anonymous usage data. You can change this in your settings at any time.'
)}
/>
)}
</> </>
); );
}; };
@@ -10,6 +10,7 @@ import {
ExternalLinks, ExternalLinks,
} from '@vegaprotocol/environment'; } from '@vegaprotocol/environment';
import { ProposedMarkets } from './proposed-markets'; import { ProposedMarkets } from './proposed-markets';
import { TelemetryApproval } from './telemetry-approval';
export const WelcomeNoticeDialog = () => { export const WelcomeNoticeDialog = () => {
const { VEGA_ENV } = useEnvironment(); const { VEGA_ENV } = useEnvironment();
@@ -20,30 +21,30 @@ export const WelcomeNoticeDialog = () => {
<> <>
<h1 <h1
data-testid="welcome-notice-title" data-testid="welcome-notice-title"
className="mb-6 p-4 text-center text-2xl" className="text-2xl uppercase mb-6 text-center font-alpha calt"
> >
{t('Welcome to Console')} {t('Welcome to Console')}
</h1> </h1>
<p className="leading-6 mb-7"> <p className="leading-6 mb-4">
{t( {t(
'There are no markets to trade on right now. Trading on Vega is now live, but markets need to pass a governance vote before they can be traded on. In the meantime:' 'There are no markets to trade on right now. Trading on Vega is now live, but markets need to pass a governance vote before they can be traded on. In the meantime:'
)} )}
</p> </p>
<ul className="list-[square] pl-7"> <ul className="list-[square] pl-4 mb-4">
{isMainnet && ( {isMainnet && (
<li> <li className="mb-1">
<ExternalLink target="_blank" href={consoleFairgroundLink()}> <ExternalLink target="_blank" href={consoleFairgroundLink()}>
{t('Try out Console')} {t('Try out Console')}
</ExternalLink> </ExternalLink>
{t(' on Fairground, our Testnet')} {t(' on Fairground, our Testnet')}
</li> </li>
)} )}
<li> <li className="mb-1">
<ExternalLink target="_blank" href={tokenLink(TOKEN_PROPOSALS)}> <ExternalLink target="_blank" href={tokenLink(TOKEN_PROPOSALS)}>
{t('View and vote for proposed markets')} {t('View and vote for proposed markets')}
</ExternalLink> </ExternalLink>
</li> </li>
<li> <li className="mb-1">
<ExternalLink <ExternalLink
target="_blank" target="_blank"
href={tokenLink(TOKEN_NEW_MARKET_PROPOSAL)} href={tokenLink(TOKEN_NEW_MARKET_PROPOSAL)}
@@ -51,12 +52,19 @@ export const WelcomeNoticeDialog = () => {
{t('Propose a market')} {t('Propose a market')}
</ExternalLink> </ExternalLink>
</li> </li>
<li> <li className="mb-1">
<ExternalLink target="_blank" href={ExternalLinks.BLOG}> <ExternalLink target="_blank" href={ExternalLinks.BLOG}>
{t('Read about the mainnet launch')} {t('Read about the mainnet launch')}
</ExternalLink> </ExternalLink>
</li> </li>
</ul> </ul>
{isMainnet && (
<TelemetryApproval
helpText={t(
'Help identify bugs and improve the service by sharing anonymous usage data. You can change this in your settings at any time.'
)}
/>
)}
<ProposedMarkets /> <ProposedMarkets />
</> </>
); );
+10
View File
@@ -30,6 +30,10 @@ const LazySettings = dynamic(() => import('../client-pages/settings'), {
ssr: false, ssr: false,
}); });
const LazyDisclaimer = dynamic(() => import('../client-pages/disclaimer'), {
ssr: false,
});
export enum Routes { export enum Routes {
HOME = '/', HOME = '/',
MARKET = '/markets', MARKET = '/markets',
@@ -37,6 +41,7 @@ export enum Routes {
PORTFOLIO = '/portfolio', PORTFOLIO = '/portfolio',
LIQUIDITY = 'liquidity/:marketId', LIQUIDITY = 'liquidity/:marketId',
SETTINGS = 'settings', SETTINGS = 'settings',
DISCLAIMER = 'disclaimer',
} }
type ConsoleLinks = { [r in Routes]: (...args: string[]) => string }; type ConsoleLinks = { [r in Routes]: (...args: string[]) => string };
@@ -51,6 +56,7 @@ export const Links: ConsoleLinks = {
? trimEnd(`${Routes.LIQUIDITY}/${marketId}`, '/') ? trimEnd(`${Routes.LIQUIDITY}/${marketId}`, '/')
: Routes.LIQUIDITY, : Routes.LIQUIDITY,
[Routes.SETTINGS]: () => Routes.SETTINGS, [Routes.SETTINGS]: () => Routes.SETTINGS,
[Routes.DISCLAIMER]: () => Routes.DISCLAIMER,
}; };
const routerConfig: RouteObject[] = [ const routerConfig: RouteObject[] = [
@@ -97,6 +103,10 @@ const routerConfig: RouteObject[] = [
path: Routes.SETTINGS, path: Routes.SETTINGS,
element: <LazySettings />, element: <LazySettings />,
}, },
{
path: Routes.DISCLAIMER,
element: <LazyDisclaimer />,
},
{ {
path: '*', path: '*',
element: ( element: (
+5 -2
View File
@@ -12,13 +12,16 @@ import {
} from '@vegaprotocol/web3'; } from '@vegaprotocol/web3';
import { WelcomeDialog } from '../components/welcome-dialog'; import { WelcomeDialog } from '../components/welcome-dialog';
import { TransferDialog } from '@vegaprotocol/accounts'; import { TransferDialog } from '@vegaprotocol/accounts';
import { RiskMessage } from '../components/welcome-dialog';
const DialogsContainer = () => { const DialogsContainer = () => {
const { isOpen, id, trigger, setOpen } = useAssetDetailsDialogStore(); const { isOpen, id, trigger, setOpen } = useAssetDetailsDialogStore();
return ( return (
<> <>
<VegaConnectDialog connectors={Connectors} /> <VegaConnectDialog
connectors={Connectors}
riskMessage={<RiskMessage />}
/>
<AssetDetailsDialog <AssetDetailsDialog
assetId={id} assetId={id}
trigger={trigger || null} trigger={trigger || null}
@@ -60,6 +60,7 @@ export function addVegaWalletConnect() {
export function addSetVegaWallet() { export function addSetVegaWallet() {
Cypress.Commands.add('setVegaWallet', () => { Cypress.Commands.add('setVegaWallet', () => {
cy.window().then((win) => { cy.window().then((win) => {
win.localStorage.setItem('vega_risk_accepted', 'true');
win.localStorage.setItem( win.localStorage.setItem(
'vega_wallet_config', 'vega_wallet_config',
JSON.stringify({ JSON.stringify({
@@ -31,27 +31,6 @@ fragment OracleMarketSpecFields on Market {
} }
} }
fragment DataSourceSpec on DataSourceDefinition {
sourceType {
... on DataSourceDefinitionExternal {
sourceType {
... on DataSourceSpecConfiguration {
signers {
signer {
... on PubKey {
key
}
... on ETHAddress {
address
}
}
}
}
}
}
}
}
query OracleMarketsSpec { query OracleMarketsSpec {
marketsConnection { marketsConnection {
edges { edges {
+3 -26
View File
@@ -1,39 +1,16 @@
import * as Types from '@vegaprotocol/types'; import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
import { DataSourceSpecFragmentDoc } from './markets';
import * as Apollo from '@apollo/client'; import * as Apollo from '@apollo/client';
const defaultOptions = {} as const; const defaultOptions = {} as const;
export type OracleMarketSpecFieldsFragment = { __typename?: 'Market', id: string, state: Types.MarketState, tradingMode: Types.MarketTradingMode, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } } }; export type OracleMarketSpecFieldsFragment = { __typename?: 'Market', id: string, state: Types.MarketState, tradingMode: Types.MarketTradingMode, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } } };
export type DataSourceSpecFragment = { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } };
export type OracleMarketsSpecQueryVariables = Types.Exact<{ [key: string]: never; }>; export type OracleMarketsSpecQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type OracleMarketsSpecQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, state: Types.MarketState, tradingMode: Types.MarketTradingMode, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } } } }> } | null }; export type OracleMarketsSpecQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, state: Types.MarketState, tradingMode: Types.MarketTradingMode, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } } } }> } | null };
export const DataSourceSpecFragmentDoc = gql`
fragment DataSourceSpec on DataSourceDefinition {
sourceType {
... on DataSourceDefinitionExternal {
sourceType {
... on DataSourceSpecConfiguration {
signers {
signer {
... on PubKey {
key
}
... on ETHAddress {
address
}
}
}
}
}
}
}
}
`;
export const OracleMarketSpecFieldsFragmentDoc = gql` export const OracleMarketSpecFieldsFragmentDoc = gql`
fragment OracleMarketSpecFields on Market { fragment OracleMarketSpecFields on Market {
id id
+40 -3
View File
@@ -1,16 +1,53 @@
import * as Types from '@vegaprotocol/types'; import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
import { DataSourceSpecFragmentDoc } from './OracleMarketsSpec';
import * as Apollo from '@apollo/client'; import * as Apollo from '@apollo/client';
const defaultOptions = {} as const; const defaultOptions = {} as const;
export type MarketFieldsFragment = { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } }; export type DataSourceFilterFragment = { __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } };
export type DataSourceSpecFragment = { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } };
export type MarketFieldsFragment = { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } };
export type MarketsQueryVariables = Types.Exact<{ [key: string]: never; }>; export type MarketsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type MarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } } }> } | null }; export type MarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } } }> } | null };
export const DataSourceFilterFragmentDoc = gql`
fragment DataSourceFilter on Filter {
key {
name
type
numberDecimalPlaces
}
}
`;
export const DataSourceSpecFragmentDoc = gql`
fragment DataSourceSpec on DataSourceDefinition {
sourceType {
... on DataSourceDefinitionExternal {
sourceType {
... on DataSourceSpecConfiguration {
signers {
signer {
... on PubKey {
key
}
... on ETHAddress {
address
}
}
}
filters {
...DataSourceFilter
}
}
}
}
}
}
${DataSourceFilterFragmentDoc}`;
export const MarketFieldsFragmentDoc = gql` export const MarketFieldsFragmentDoc = gql`
fragment MarketFields on Market { fragment MarketFields on Market {
id id
@@ -4,7 +4,7 @@ import { useMarket } from '../markets-provider';
import { useMemo } from 'react'; import { useMemo } from 'react';
import type { Provider } from '../oracle-schema'; import type { Provider } from '../oracle-schema';
import type { DataSourceSpecFragment } from '../__generated__/OracleMarketsSpec'; import type { DataSourceSpecFragment } from '../__generated__';
export const getMatchingOracleProvider = ( export const getMatchingOracleProvider = (
dataSourceSpec: DataSourceSpecFragment, dataSourceSpec: DataSourceSpecFragment,
+11
View File
@@ -1,3 +1,11 @@
fragment DataSourceFilter on Filter {
key {
name
type
numberDecimalPlaces
}
}
fragment DataSourceSpec on DataSourceDefinition { fragment DataSourceSpec on DataSourceDefinition {
sourceType { sourceType {
... on DataSourceDefinitionExternal { ... on DataSourceDefinitionExternal {
@@ -13,6 +21,9 @@ fragment DataSourceSpec on DataSourceDefinition {
} }
} }
} }
filters {
...DataSourceFilter
}
} }
} }
} }
+22
View File
@@ -80,6 +80,17 @@ export const createMarketFragment = (
}, },
}, },
], ],
filters: [
{
__typename: 'Filter',
key: {
__typename: 'PropertyKey',
name: 'settlement-data-property',
type: Schema.PropertyKeyType.TYPE_TIMESTAMP,
numberDecimalPlaces: null,
},
},
],
}, },
}, },
}, },
@@ -102,6 +113,17 @@ export const createMarketFragment = (
}, },
}, },
], ],
filters: [
{
__typename: 'Filter',
key: {
__typename: 'PropertyKey',
name: 'settlement-data-property',
type: Schema.PropertyKeyType.TYPE_INTEGER,
numberDecimalPlaces: 2,
},
},
],
}, },
}, },
}, },
@@ -5,57 +5,74 @@ import {
waitFor, waitFor,
getAllByRole, getAllByRole,
} from '@testing-library/react'; } from '@testing-library/react';
import merge from 'lodash/merge';
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing';
import { ProposalsList } from './proposals-list'; import { ProposalsList } from './proposals-list';
import type { ProposalListFieldsFragment } from '../../lib/proposals-data-provider';
import * as Types from '@vegaprotocol/types'; import * as Types from '@vegaprotocol/types';
import { createProposalListFieldsFragment } from '../../lib/proposals-data-provider/proposals.mock';
const votesMock = { import type { ProposalsListQuery } from '../../lib';
yes: { import { ProposalsListDocument } from '../../lib';
totalTokens: '5000', import type { PartialDeep } from 'type-fest';
},
no: {
totalTokens: '2000',
},
};
let marketsProposalMock: ProposalListFieldsFragment[] | null = [
{
id: 'id-1',
state: Types.ProposalState.STATE_OPEN,
votes: { ...votesMock },
},
{
id: 'id-2',
state: Types.ProposalState.STATE_PASSED,
votes: { ...votesMock },
},
{
id: 'id-3',
state: Types.ProposalState.STATE_WAITING_FOR_NODE_VOTE,
votes: { ...votesMock },
},
] as ProposalListFieldsFragment[];
const useDataProvider = () => {
return {
data: marketsProposalMock,
loading: false,
error: false,
};
};
jest.mock('@vegaprotocol/data-provider', () => ({
...jest.requireActual('@vegaprotocol/data-provider'),
useDataProvider: jest.fn(() => useDataProvider()),
}));
describe('ProposalsList', () => { describe('ProposalsList', () => {
beforeEach(() => { const createProposalsMock = (override?: PartialDeep<ProposalsListQuery>) => {
jest.clearAllMocks(); const defaultProposalEdges = [
}); {
__typename: 'ProposalEdge' as const,
node: createProposalListFieldsFragment({
id: 'id-1',
state: Types.ProposalState.STATE_OPEN,
}),
},
{
__typename: 'ProposalEdge' as const,
node: createProposalListFieldsFragment({
id: 'id-2',
state: Types.ProposalState.STATE_PASSED,
}),
},
{
__typename: 'ProposalEdge' as const,
node: createProposalListFieldsFragment({
id: 'id-3',
state: Types.ProposalState.STATE_WAITING_FOR_NODE_VOTE,
}),
},
];
const data = merge(
{
proposalsConnection: {
__typename: 'ProposalsConnection' as const,
edges: defaultProposalEdges,
},
},
override
);
const mock: MockedResponse<ProposalsListQuery> = {
request: {
query: ProposalsListDocument,
variables: {
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
},
},
result: {
data,
},
};
return mock;
};
it('should be properly rendered', async () => { it('should be properly rendered', async () => {
const mock = createProposalsMock();
await act(() => { await act(() => {
render(<ProposalsList />, { wrapper: MockedProvider }); render(
<MockedProvider mocks={[mock]}>
<ProposalsList />
</MockedProvider>
);
}); });
const container = document.querySelector('.ag-center-cols-container'); const container = document.querySelector('.ag-center-cols-container');
await waitFor(() => { await waitFor(() => {
@@ -65,15 +82,25 @@ describe('ProposalsList', () => {
}); });
it('some of states should be filtered out', async () => { it('some of states should be filtered out', async () => {
marketsProposalMock = [ const mock = createProposalsMock({
{ proposalsConnection: {
...(marketsProposalMock as ProposalListFieldsFragment[])[0], edges: [
state: Types.ProposalState.STATE_ENACTED, {
__typename: 'ProposalEdge',
node: createProposalListFieldsFragment({
id: 'id-1',
state: Types.ProposalState.STATE_ENACTED,
}),
},
],
}, },
...(marketsProposalMock as ProposalListFieldsFragment[]).slice(1), });
];
await act(() => { await act(() => {
render(<ProposalsList />, { wrapper: MockedProvider }); render(
<MockedProvider mocks={[mock]}>
<ProposalsList />
</MockedProvider>
);
}); });
const container = document.querySelector('.ag-center-cols-container'); const container = document.querySelector('.ag-center-cols-container');
await waitFor(() => { await waitFor(() => {
@@ -83,9 +110,12 @@ describe('ProposalsList', () => {
}); });
it('empty response should causes no data message display', async () => { it('empty response should causes no data message display', async () => {
marketsProposalMock = null;
await act(() => { await act(() => {
render(<ProposalsList />, { wrapper: MockedProvider }); render(
<MockedProvider>
<ProposalsList />
</MockedProvider>
);
}); });
const container = document.querySelector('.ag-center-cols-container'); const container = document.querySelector('.ag-center-cols-container');
await waitFor(() => { await waitFor(() => {
@@ -1,13 +1,13 @@
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid'; import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import * as Types from '@vegaprotocol/types'; import * as Types from '@vegaprotocol/types';
import { proposalsDataProvider } from '../../lib/proposals-data-provider';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import { useColumnDefs } from './use-column-defs'; import { useColumnDefs } from './use-column-defs';
import type { ProposalListFieldsFragment } from '../../lib/proposals-data-provider/__generated__/Proposals'; import type { ProposalListFieldsFragment } from '../../lib/proposals-data-provider/__generated__/Proposals';
import { useProposalsListQuery } from '../../lib/proposals-data-provider/__generated__/Proposals';
import { removePaginationWrapper } from '@vegaprotocol/utils';
export const getNewMarketProposals = (data: ProposalListFieldsFragment[]) => export const getNewMarketProposals = (data: ProposalListFieldsFragment[]) =>
data.filter((proposal) => data.filter((proposal) =>
@@ -21,13 +21,15 @@ export const getNewMarketProposals = (data: ProposalListFieldsFragment[]) =>
export const ProposalsList = () => { export const ProposalsList = () => {
const gridRef = useRef<AgGridReact | null>(null); const gridRef = useRef<AgGridReact | null>(null);
const [dataCount, setDataCount] = useState(0); const [dataCount, setDataCount] = useState(0);
const { data, loading, error, reload } = useDataProvider({ const { data, loading, error, refetch } = useProposalsListQuery({
dataProvider: proposalsDataProvider,
variables: { variables: {
proposalType: Types.ProposalType.TYPE_NEW_MARKET, proposalType: Types.ProposalType.TYPE_NEW_MARKET,
}, },
errorPolicy: 'all', // currently there are some proposals failing due to proposals existing without settlement asset ids
}); });
const filteredData = getNewMarketProposals(data || []); const filteredData = getNewMarketProposals(
removePaginationWrapper(data?.proposalsConnection?.edges)
);
const { columnDefs, defaultColDef } = useColumnDefs(); const { columnDefs, defaultColDef } = useColumnDefs();
useEffect(() => { useEffect(() => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0); setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
@@ -36,11 +38,10 @@ export const ProposalsList = () => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0); setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, []); }, []);
return ( return (
<div className="relative"> <div className="relative h-full">
<AgGrid <AgGrid
ref={gridRef} ref={gridRef}
className="w-full h-full" className="w-full h-full"
domLayout="autoHeight"
columnDefs={columnDefs} columnDefs={columnDefs}
rowData={filteredData} rowData={filteredData}
defaultColDef={defaultColDef} defaultColDef={defaultColDef}
@@ -48,6 +49,8 @@ export const ProposalsList = () => {
suppressNoRowsOverlay suppressNoRowsOverlay
onFilterChanged={onFilterChanged} onFilterChanged={onFilterChanged}
storeKey="proposedMarkets" storeKey="proposedMarkets"
getRowId={({ data }) => data.id}
style={{ width: '100%', height: '100%' }}
/> />
<div className="pointer-events-none absolute inset-0"> <div className="pointer-events-none absolute inset-0">
<AsyncRenderer <AsyncRenderer
@@ -56,7 +59,7 @@ export const ProposalsList = () => {
data={filteredData} data={filteredData}
noDataMessage={t('No markets')} noDataMessage={t('No markets')}
noDataCondition={() => !dataCount} noDataCondition={() => !dataCount}
reload={reload} reload={refetch}
/> />
</div> </div>
</div> </div>
@@ -11,6 +11,7 @@ export const proposalListQuery = (
): ProposalsListQuery => { ): ProposalsListQuery => {
const defaultResult: ProposalsListQuery = { const defaultResult: ProposalsListQuery = {
proposalsConnection: { proposalsConnection: {
__typename: 'ProposalsConnection',
edges: proposalListFields.map((node) => ({ edges: proposalListFields.map((node) => ({
__typename: 'ProposalEdge', __typename: 'ProposalEdge',
node, node,
@@ -104,8 +105,10 @@ export const marketUpdateProposal: ProposalListFieldsFragment = {
}, },
}; };
const proposalListFields: ProposalListFieldsFragment[] = [ export const createProposalListFieldsFragment = (
{ override?: PartialDeep<ProposalListFieldsFragment>
): ProposalListFieldsFragment => {
const defaultProposal: ProposalListFieldsFragment = {
id: 'e9ec6d5c46a7e7bcabf9ba7a893fa5a5eeeec08b731f06f7a6eb7bf0e605b829', id: 'e9ec6d5c46a7e7bcabf9ba7a893fa5a5eeeec08b731f06f7a6eb7bf0e605b829',
reference: 'injected_at_runtime', reference: 'injected_at_runtime',
state: Schema.ProposalState.STATE_OPEN, state: Schema.ProposalState.STATE_OPEN,
@@ -194,7 +197,12 @@ const proposalListFields: ProposalListFieldsFragment[] = [
__typename: 'ProposalTerms', __typename: 'ProposalTerms',
}, },
__typename: 'Proposal', __typename: 'Proposal',
}, };
return merge(defaultProposal, override);
};
const proposalListFields: ProposalListFieldsFragment[] = [
createProposalListFieldsFragment(),
{ {
id: '1cd1deb532b97fbeb9262fe94499ecec5835e60ae564b7c5af530c90a13c29cb', id: '1cd1deb532b97fbeb9262fe94499ecec5835e60ae564b7c5af530c90a13c29cb',
reference: 'injected_at_runtime', reference: 'injected_at_runtime',
@@ -7,10 +7,11 @@ import { ToastPosition, useToastsConfiguration, useToasts } from './use-toasts';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { Intent } from '../../utils/intent'; import { Intent } from '../../utils/intent';
const testToast = { const TEST_TOAST = {
id: 'test-toast', id: 'test-toast',
intent: Intent.Primary, intent: Intent.Primary,
content: <>{t('This is an example of a toast notification')}</>, content: <>{t('This is an example of a toast notification')}</>,
onClose: () => useToasts.getState().remove('test-toast'),
}; };
export const ToastPositionSetter = () => { export const ToastPositionSetter = () => {
@@ -20,7 +21,7 @@ export const ToastPositionSetter = () => {
const handleChange = useCallback( const handleChange = useCallback(
(position: ToastPosition) => { (position: ToastPosition) => {
setPostion(position); setPostion(position);
setToast(testToast); setToast(TEST_TOAST);
}, },
[setToast, setPostion] [setToast, setPostion]
); );
@@ -109,12 +109,12 @@ export enum ToastPosition {
BottomCenter, BottomCenter,
} }
type ToastConfiguration = { type ToastsConfiguration = {
position: ToastPosition; position: ToastPosition;
setPosition: (position: ToastPosition) => void; setPosition: (position: ToastPosition) => void;
}; };
export const useToastsConfiguration = create<ToastConfiguration>()( export const useToastsConfiguration = create<ToastsConfiguration>()(
persist( persist(
immer((set) => ({ immer((set) => ({
position: ToastPosition.BottomRight, position: ToastPosition.BottomRight,
@@ -30,6 +30,7 @@ import type { Status } from '../use-json-rpc-connect';
import { useJsonRpcConnect } from '../use-json-rpc-connect'; import { useJsonRpcConnect } from '../use-json-rpc-connect';
import { ViewConnectorForm } from './view-connector-form'; import { ViewConnectorForm } from './view-connector-form';
import { useChainIdQuery } from './__generated__/ChainId'; import { useChainIdQuery } from './__generated__/ChainId';
import { useVegaWallet } from '../use-vega-wallet';
export const CLOSE_DELAY = 1700; export const CLOSE_DELAY = 1700;
type Connectors = { [key: string]: VegaConnector }; type Connectors = { [key: string]: VegaConnector };
@@ -38,6 +39,7 @@ type WalletType = 'jsonRpc' | 'hosted' | 'view';
export interface VegaConnectDialogProps { export interface VegaConnectDialogProps {
connectors: Connectors; connectors: Connectors;
onChangeOpen?: (open: boolean) => void; onChangeOpen?: (open: boolean) => void;
riskMessage?: React.ReactNode;
} }
export const useVegaWalletDialogStore = create<VegaWalletDialogStore>()( export const useVegaWalletDialogStore = create<VegaWalletDialogStore>()(
@@ -60,25 +62,30 @@ export interface VegaWalletDialogStore {
export const VegaConnectDialog = ({ export const VegaConnectDialog = ({
connectors, connectors,
onChangeOpen, onChangeOpen,
riskMessage,
}: VegaConnectDialogProps) => { }: VegaConnectDialogProps) => {
const vegaWalletDialogOpen = useVegaWalletDialogStore( const vegaWalletDialogOpen = useVegaWalletDialogStore(
(store) => store.vegaWalletDialogOpen (store) => store.vegaWalletDialogOpen
); );
const updateVegaWalletDialog = useVegaWalletDialogStore((store) => const updateVegaWalletDialog = useVegaWalletDialogStore(
onChangeOpen (store) => (open: boolean) => {
? (open: boolean) => { store.updateVegaWalletDialog(open);
store.updateVegaWalletDialog(open); onChangeOpen?.(open);
onChangeOpen(open); }
}
: store.updateVegaWalletDialog
); );
const closeVegaWalletDialog = useVegaWalletDialogStore((store) => const closeVegaWalletDialog = useVegaWalletDialogStore((store) => () => {
onChangeOpen store.closeVegaWalletDialog();
? () => { onChangeOpen?.(false);
store.closeVegaWalletDialog(); });
onChangeOpen(false); const { disconnect, acknowledgeNeeded } = useVegaWallet();
} const onVegaWalletDialogChange = useCallback(
: store.closeVegaWalletDialog (open: boolean) => {
updateVegaWalletDialog(open);
if (!open && acknowledgeNeeded) {
disconnect();
}
},
[updateVegaWalletDialog, acknowledgeNeeded, disconnect]
); );
const { data, error, loading } = useChainIdQuery(); const { data, error, loading } = useChainIdQuery();
@@ -112,6 +119,7 @@ export const VegaConnectDialog = ({
connectors={connectors} connectors={connectors}
closeDialog={closeVegaWalletDialog} closeDialog={closeVegaWalletDialog}
appChainId={data.statistics.chainId} appChainId={data.statistics.chainId}
riskMessage={riskMessage}
/> />
); );
}; };
@@ -120,7 +128,7 @@ export const VegaConnectDialog = ({
<Dialog <Dialog
open={vegaWalletDialogOpen} open={vegaWalletDialogOpen}
size="small" size="small"
onChange={updateVegaWalletDialog} onChange={onVegaWalletDialogChange}
> >
{renderContent()} {renderContent()}
</Dialog> </Dialog>
@@ -131,16 +139,17 @@ const ConnectDialogContainer = ({
connectors, connectors,
closeDialog, closeDialog,
appChainId, appChainId,
riskMessage,
}: { }: {
connectors: Connectors; connectors: Connectors;
closeDialog: () => void; closeDialog: () => void;
appChainId: string; appChainId: string;
riskMessage?: React.ReactNode;
}) => { }) => {
const { VEGA_WALLET_URL, VEGA_ENV, HOSTED_WALLET_URL } = useEnvironment(); const { VEGA_WALLET_URL, VEGA_ENV, HOSTED_WALLET_URL } = useEnvironment();
const [selectedConnector, setSelectedConnector] = useState<VegaConnector>(); const [selectedConnector, setSelectedConnector] = useState<VegaConnector>();
const [walletUrl, setWalletUrl] = useState(VEGA_WALLET_URL || ''); const [walletUrl, setWalletUrl] = useState(VEGA_WALLET_URL || '');
const [walletType, setWalletType] = useState<WalletType>(); const [walletType, setWalletType] = useState<WalletType>();
const reset = useCallback(() => { const reset = useCallback(() => {
setSelectedConnector(undefined); setSelectedConnector(undefined);
setWalletType(undefined); setWalletType(undefined);
@@ -189,6 +198,7 @@ const ConnectDialogContainer = ({
onConnect={closeDialog} onConnect={closeDialog}
appChainId={appChainId} appChainId={appChainId}
reset={reset} reset={reset}
riskMessage={riskMessage}
/> />
) : ( ) : (
<ConnectorList <ConnectorList
@@ -255,6 +265,7 @@ const SelectedForm = ({
jsonRpcState, jsonRpcState,
reset, reset,
onConnect, onConnect,
riskMessage,
}: { }: {
type: WalletType; type: WalletType;
connector: VegaConnector; connector: VegaConnector;
@@ -265,6 +276,7 @@ const SelectedForm = ({
}; };
reset: () => void; reset: () => void;
onConnect: () => void; onConnect: () => void;
riskMessage?: React.ReactNode;
}) => { }) => {
if (connector instanceof RestConnector) { if (connector instanceof RestConnector) {
return ( return (
@@ -317,6 +329,7 @@ const SelectedForm = ({
onConnect={onConnect} onConnect={onConnect}
appChainId={appChainId} appChainId={appChainId}
reset={reset} reset={reset}
riskMessage={riskMessage}
/> />
</ConnectDialogContent> </ConnectDialogContent>
<ConnectDialogFooter /> <ConnectDialogFooter />
@@ -1,6 +1,7 @@
import capitalize from 'lodash/capitalize'; import capitalize from 'lodash/capitalize';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { import {
Button,
ButtonLink, ButtonLink,
Diamond, Diamond,
Link, Link,
@@ -14,6 +15,8 @@ import { ClientErrors } from '../connectors';
import { ConnectDialogTitle } from './connect-dialog-elements'; import { ConnectDialogTitle } from './connect-dialog-elements';
import { Status } from '../use-json-rpc-connect'; import { Status } from '../use-json-rpc-connect';
import { DocsLinks } from '@vegaprotocol/environment'; import { DocsLinks } from '@vegaprotocol/environment';
import { useVegaWallet } from '../use-vega-wallet';
import { setAcknowledged } from '../storage';
export const ServiceErrors = { export const ServiceErrors = {
NO_HEALTHY_NODE: 1000, NO_HEALTHY_NODE: 1000,
@@ -26,6 +29,8 @@ export const JsonRpcConnectorForm = ({
status, status,
error, error,
reset, reset,
onConnect,
riskMessage,
}: { }: {
connector: JsonRpcConnector; connector: JsonRpcConnector;
appChainId: string; appChainId: string;
@@ -33,35 +38,13 @@ export const JsonRpcConnectorForm = ({
error: WalletClientError | null; error: WalletClientError | null;
onConnect: () => void; onConnect: () => void;
reset: () => void; reset: () => void;
riskMessage?: React.ReactNode;
}) => { }) => {
const { disconnect } = useVegaWallet();
if (status === Status.Idle) { if (status === Status.Idle) {
return null; return null;
} }
return (
<Connecting
status={status}
error={error}
connector={connector}
appChainId={appChainId}
reset={reset}
/>
);
};
const Connecting = ({
status,
error,
connector,
appChainId,
reset,
}: {
status: Status;
error: WalletClientError | null;
connector: JsonRpcConnector;
appChainId: string;
reset: () => void;
}) => {
if (status === Status.Error) { if (status === Status.Error) {
return ( return (
<Error <Error
@@ -125,6 +108,34 @@ const Connecting = ({
); );
} }
if (status === Status.AcknowledgeNeeded) {
const setConnection = () => {
setAcknowledged();
onConnect();
};
const handleDisagree = () => {
disconnect();
onConnect(); // this is dialog closing
};
return (
<>
<ConnectDialogTitle>{t('Understand the risk')}</ConnectDialogTitle>
{riskMessage}
<div className="grid grid-cols-2 gap-5">
<div>
<Button onClick={handleDisagree} fill>
{t('Cancel')}
</Button>
</div>
<div>
<Button onClick={setConnection} variant="primary" fill>
{t('I agree')}
</Button>
</div>
</div>
</>
);
}
return null; return null;
}; };
+3
View File
@@ -34,6 +34,9 @@ export interface VegaWalletContextShape {
/** Fetch public keys */ /** Fetch public keys */
fetchPubKeys?: () => Promise<PubKey[] | null>; fetchPubKeys?: () => Promise<PubKey[] | null>;
/** Acknowledge disclaimer */
acknowledgeNeeded?: boolean;
} }
export const VegaWalletContext = createContext< export const VegaWalletContext = createContext<
+29
View File
@@ -7,6 +7,8 @@ import { VegaWalletProvider } from './provider';
import { LocalStorage } from '@vegaprotocol/utils'; import { LocalStorage } from '@vegaprotocol/utils';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { WALLET_KEY } from './storage'; import { WALLET_KEY } from './storage';
import * as Environment from '@vegaprotocol/environment';
import * as ReactHelpers from '@vegaprotocol/react-helpers';
const restConnector = new RestConnector(); const restConnector = new RestConnector();
const viewConnector = new ViewConnector(); const viewConnector = new ViewConnector();
@@ -43,6 +45,7 @@ describe('VegaWalletProvider', () => {
// Default state // Default state
expect(result.current).toEqual({ expect(result.current).toEqual({
acknowledgeNeeded: false,
pubKey: null, pubKey: null,
pubKeys: null, pubKeys: null,
isReadOnly: false, isReadOnly: false,
@@ -83,6 +86,7 @@ describe('VegaWalletProvider', () => {
// Default state // Default state
expect(result.current).toEqual({ expect(result.current).toEqual({
acknowledgeNeeded: false,
pubKey: null, pubKey: null,
pubKeys: null, pubKeys: null,
isReadOnly: false, isReadOnly: false,
@@ -142,4 +146,29 @@ describe('VegaWalletProvider', () => {
}); });
expect(result.current.isReadOnly).toBe(true); expect(result.current.isReadOnly).toBe(true);
}); });
it('acknowledgeNeeded will set on', async () => {
jest
.spyOn(Environment, 'useEnvironment')
.mockReturnValue({ VEGA_ENV: 'MAINNET' });
jest.spyOn(ReactHelpers, 'useLocalStorage').mockImplementation(() => [
'',
() => {
/**/
},
() => {
/**/
},
]);
jest
.spyOn(viewConnector, 'connect')
.mockImplementation(() => Promise.resolve(mockPubKeys));
const { result } = setup();
expect(result.current.acknowledgeNeeded).toBe(true);
await act(async () => {
result.current.connect(viewConnector);
});
expect(result.current.isReadOnly).toBe(true);
});
}); });
+10 -1
View File
@@ -9,8 +9,10 @@ import type {
VegaConnector, VegaConnector,
} from './connectors/vega-connector'; } from './connectors/vega-connector';
import { VegaWalletContext } from './context'; import { VegaWalletContext } from './context';
import { WALLET_KEY } from './storage'; import { WALLET_KEY, WALLET_RISK_ACCEPTED_KEY } from './storage';
import { ViewConnector } from './connectors'; import { ViewConnector } from './connectors';
import { useEnvironment, Networks } from '@vegaprotocol/environment';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
interface VegaWalletProviderProps { interface VegaWalletProviderProps {
children: ReactNode; children: ReactNode;
@@ -108,6 +110,11 @@ export const VegaWalletProvider = ({ children }: VegaWalletProviderProps) => {
return connector.current.sendTx(pubkey, transaction); return connector.current.sendTx(pubkey, transaction);
}, []); }, []);
const { VEGA_ENV } = useEnvironment();
const [riskAcceptedValue] = useLocalStorage(WALLET_RISK_ACCEPTED_KEY);
const acknowledgeNeeded =
VEGA_ENV === Networks.MAINNET && riskAcceptedValue !== 'true';
const contextValue = useMemo<VegaWalletContextShape>(() => { const contextValue = useMemo<VegaWalletContextShape>(() => {
return { return {
isReadOnly, isReadOnly,
@@ -118,6 +125,7 @@ export const VegaWalletProvider = ({ children }: VegaWalletProviderProps) => {
disconnect, disconnect,
sendTx, sendTx,
fetchPubKeys, fetchPubKeys,
acknowledgeNeeded,
}; };
}, [ }, [
isReadOnly, isReadOnly,
@@ -128,6 +136,7 @@ export const VegaWalletProvider = ({ children }: VegaWalletProviderProps) => {
disconnect, disconnect,
sendTx, sendTx,
fetchPubKeys, fetchPubKeys,
acknowledgeNeeded,
]); ]);
return ( return (
+9
View File
@@ -8,6 +8,7 @@ interface ConnectorConfig {
export const WALLET_CONFIG = 'vega_wallet_config'; export const WALLET_CONFIG = 'vega_wallet_config';
export const WALLET_KEY = 'vega_wallet_key'; export const WALLET_KEY = 'vega_wallet_key';
export const WALLET_RISK_ACCEPTED_KEY = 'vega_wallet_risk_accepted';
export function setConfig(cfg: ConnectorConfig) { export function setConfig(cfg: ConnectorConfig) {
LocalStorage.setItem(WALLET_CONFIG, JSON.stringify(cfg)); LocalStorage.setItem(WALLET_CONFIG, JSON.stringify(cfg));
@@ -29,3 +30,11 @@ export function getConfig(): ConnectorConfig | null {
export function clearConfig() { export function clearConfig() {
LocalStorage.removeItem(WALLET_CONFIG); LocalStorage.removeItem(WALLET_CONFIG);
} }
export function getAcknowledged() {
return LocalStorage.getItem(WALLET_RISK_ACCEPTED_KEY) === 'true';
}
export function setAcknowledged() {
return LocalStorage.setItem(WALLET_RISK_ACCEPTED_KEY, 'true');
}
+4 -4
View File
@@ -7,13 +7,13 @@ export function useEagerConnect(Connectors: {
[connector: string]: VegaConnector; [connector: string]: VegaConnector;
}) { }) {
const [connecting, setConnecting] = useState(true); const [connecting, setConnecting] = useState(true);
const { connect } = useVegaWallet(); const { connect, acknowledgeNeeded } = useVegaWallet();
useEffect(() => { useEffect(() => {
const attemptConnect = async () => { const attemptConnect = async () => {
const cfg = getConfig(); const cfg = getConfig();
// No stored config, or config was malformed // No stored config, or config was malformed or no risk accepted
if (!cfg || !cfg.connector) { if (!cfg || !cfg.connector || acknowledgeNeeded) {
setConnecting(false); setConnecting(false);
return; return;
} }
@@ -42,7 +42,7 @@ export function useEagerConnect(Connectors: {
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
attemptConnect(); attemptConnect();
} }
}, [connect, Connectors]); }, [connect, Connectors, acknowledgeNeeded]);
return connecting; return connecting;
} }
+9 -5
View File
@@ -13,10 +13,11 @@ export enum Status {
ListingKeys = 'ListingKeys', ListingKeys = 'ListingKeys',
Connected = 'Connected', Connected = 'Connected',
Error = 'Error', Error = 'Error',
AcknowledgeNeeded = 'AcknowledgeNeeded',
} }
export const useJsonRpcConnect = (onConnect: () => void) => { export const useJsonRpcConnect = (onConnect: () => void) => {
const { connect } = useVegaWallet(); const { connect, acknowledgeNeeded } = useVegaWallet();
const [status, setStatus] = useState(Status.Idle); const [status, setStatus] = useState(Status.Idle);
const [error, setError] = useState<WalletClientError | null>(null); const [error, setError] = useState<WalletClientError | null>(null);
@@ -52,9 +53,12 @@ export const useJsonRpcConnect = (onConnect: () => void) => {
// Call connect in the wallet provider. The connector will be stored for // Call connect in the wallet provider. The connector will be stored for
// future actions such as sending transactions // future actions such as sending transactions
await connect(connector); await connect(connector);
if (acknowledgeNeeded) {
setStatus(Status.Connected); setStatus(Status.AcknowledgeNeeded);
onConnect(); } else {
setStatus(Status.Connected);
onConnect();
}
} catch (err) { } catch (err) {
if (err instanceof WalletClientError) { if (err instanceof WalletClientError) {
setError(err); setError(err);
@@ -62,7 +66,7 @@ export const useJsonRpcConnect = (onConnect: () => void) => {
setStatus(Status.Error); setStatus(Status.Error);
} }
}, },
[onConnect, connect] [onConnect, connect, acknowledgeNeeded]
); );
return { return {