Compare commits
119
Commits
main
...
urbit-build
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73049215dd | ||
|
|
cc782b5eae | ||
|
|
703792fbda | ||
|
|
029357b5ab | ||
|
|
954b4b2755 | ||
|
|
d238662c9d | ||
|
|
88251fae4d | ||
|
|
17946ae149 | ||
|
|
ebb0db3b37 | ||
|
|
df21996707 | ||
|
|
bb2464ddc2 | ||
|
|
a77bb06b2f | ||
|
|
1e15032c06 | ||
|
|
e28cdb2463 | ||
|
|
840ca63464 | ||
|
|
64fc075f7a | ||
|
|
bfa6be4c1c | ||
|
|
5f8c725c5b | ||
|
|
6d4d0d548e | ||
|
|
cd4b0e508e | ||
|
|
ed9cd3e0dd | ||
|
|
91d00a0520 | ||
|
|
0ada3a2f8d | ||
|
|
2efe0a7aa1 | ||
|
|
678ba9c4f7 | ||
|
|
4d566f5a5f | ||
|
|
f13307aa77 | ||
|
|
aed45e419d | ||
|
|
01e87443ee | ||
|
|
891e0d3d2f | ||
|
|
dc81334aec | ||
|
|
0a70c0ea49 | ||
|
|
05debbd777 | ||
|
|
8c383d8756 | ||
|
|
3e61018d89 | ||
|
|
77b1306f25 | ||
|
|
ff3cddc21b | ||
|
|
1d2ca52a25 | ||
|
|
62ecaaa9ce | ||
|
|
204871b81c | ||
|
|
ecdd917977 | ||
|
|
c0396db2b1 | ||
|
|
1fd1013353 | ||
|
|
e389579537 | ||
|
|
74f0f7bb3d | ||
|
|
3e78d55c0e | ||
|
|
1df8ba0972 | ||
|
|
e515928d87 | ||
|
|
2928181884 | ||
|
|
f6f3fb43eb | ||
|
|
1511772849 | ||
|
|
ab6c67fd37 | ||
|
|
f4212724d0 | ||
|
|
7b162104b6 | ||
|
|
88f99031d1 | ||
|
|
72df08851c | ||
|
|
d9292b9be2 | ||
|
|
be8de04686 | ||
|
|
bd14c2a39a | ||
|
|
8e2ec8ab67 | ||
|
|
0636d19b0d | ||
|
|
0dd58b68cf | ||
|
|
49fdd0f68a | ||
|
|
3e2e3c3970 | ||
|
|
b89523efb2 | ||
|
|
be9a5a3437 | ||
|
|
475b52bcaf | ||
|
|
7620f0b67d | ||
|
|
0f5b712034 | ||
|
|
424061d64c | ||
|
|
a49f5f0dd1 | ||
|
|
bf094288fd | ||
|
|
bc8a427788 | ||
|
|
447dc254d7 | ||
|
|
cebbdb4ee7 | ||
|
|
fd69f6078c | ||
|
|
917bdde0bb | ||
|
|
2153367258 | ||
|
|
c0942d56a1 | ||
|
|
e57a6de765 | ||
|
|
9eee76e5a6 | ||
|
|
aa71e608f0 | ||
|
|
20ad18c8a0 | ||
|
|
457422b5ac | ||
|
|
990c894e2f | ||
|
|
2e28a175ce | ||
|
|
a77858c1c0 | ||
|
|
054c0377b4 | ||
|
|
29bcbd06fb | ||
|
|
1d721dc748 | ||
|
|
1d71a839b3 | ||
|
|
4b917926c5 | ||
|
|
3a56678403 | ||
|
|
bcb5351dfc | ||
|
|
177e72dd16 | ||
|
|
8221882346 | ||
|
|
b1a8473131 | ||
|
|
f18216f70f | ||
|
|
de2b79416e | ||
|
|
6504912284 | ||
|
|
93643f1737 | ||
|
|
464d5af6be | ||
|
|
b2a62f16bc | ||
|
|
9ec03a04ea | ||
|
|
2a0727ec4b | ||
|
|
0d39c2354c | ||
|
|
ad6f0c5798 | ||
|
|
38d13085fb | ||
|
|
c0f4278b81 | ||
|
|
b2777043b4 | ||
|
|
4d19b55096 | ||
|
|
e6b3ff456d | ||
|
|
00dbb7dd60 | ||
|
|
89e2033556 | ||
|
|
2d821700bd | ||
|
|
28b4593a1d | ||
|
|
358c734e31 | ||
|
|
3dd7496a9a | ||
|
|
80d576d484 |
@@ -4,6 +4,5 @@ tmp/*
|
||||
.dockerignore
|
||||
dockerfiles
|
||||
node_modules
|
||||
.git
|
||||
.github
|
||||
.vscode
|
||||
|
||||
@@ -196,9 +196,9 @@ jobs:
|
||||
cypress:
|
||||
needs: [build-sources, check-e2e-needed]
|
||||
name: '(CI) cypress'
|
||||
if: ${{ needs.check-e2e-needed.outputs.run-tests == 'true' }}
|
||||
uses: ./.github/workflows/cypress-run.yml
|
||||
secrets: inherit
|
||||
if: needs.check-e2e-needed.outputs.run-tests == 'true' && (contains(needs.build-sources.outputs.projects, 'governance') || contains(needs.build-sources.outputs.projects, 'explorer'))
|
||||
with:
|
||||
projects: ${{ needs.build-sources.outputs.projects-e2e }}
|
||||
tags: '@smoke'
|
||||
@@ -287,6 +287,7 @@ jobs:
|
||||
steps:
|
||||
- run: |
|
||||
result="${{ needs.cypress.result }}"
|
||||
echo "Result: $result"
|
||||
if [[ $result == "success" || $result == "skipped" ]]; then
|
||||
exit 0
|
||||
else
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
inputs:
|
||||
console-test-branch:
|
||||
type: choice
|
||||
description: 'main: v0.73.13, develop: v0.74.0'
|
||||
description: 'main: v0.74.10, develop: v0.75.5'
|
||||
options:
|
||||
- main
|
||||
- develop
|
||||
@@ -57,15 +57,14 @@ jobs:
|
||||
#----------------------------------------------
|
||||
- name: Build trading app
|
||||
run: |
|
||||
yarn env-cmd -f ./apps/trading/.env.stagnet1 yarn nx export trading
|
||||
ENV_NAME="${{ needs.console-test-branch.outputs.console-branch == 'main' && 'mainnet' || 'stagnet1' }}"
|
||||
yarn env-cmd -f ./apps/trading/.env.$ENV_NAME yarn nx export trading
|
||||
DIST_LOCATION=dist/apps/trading/exported
|
||||
mv $DIST_LOCATION dist-result
|
||||
tree dist-result
|
||||
|
||||
#----------------------------------------------
|
||||
# export trading app docker image
|
||||
#----------------------------------------------
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
@@ -78,7 +77,7 @@ jobs:
|
||||
load: true
|
||||
build-args: |
|
||||
APP=trading
|
||||
ENV_NAME=stagnet1
|
||||
ENV_NAME=${{ needs.console-test-branch.outputs.console-branch == 'main' && 'mainnet' || 'stagnet1' }}
|
||||
tags: ci/trading:local
|
||||
outputs: type=docker,dest=/tmp/console-image.tar
|
||||
|
||||
@@ -182,12 +181,22 @@ jobs:
|
||||
virtualenvs-create: true
|
||||
virtualenvs-in-project: true
|
||||
virtualenvs-path: .venv
|
||||
|
||||
#----------------------------------------------
|
||||
# Set up pyproject.toml based on branch
|
||||
#----------------------------------------------
|
||||
- name: Create pyproject.toml based on branch
|
||||
run: |
|
||||
if [ "${{ needs.console-test-branch.outputs.console-branch }}" = "main" ]; then
|
||||
mv pyproject.main.toml pyproject.toml
|
||||
elif [ "${{ needs.console-test-branch.outputs.console-branch }}" = "develop" ]; then
|
||||
mv pyproject.develop.toml pyproject.toml
|
||||
fi
|
||||
working-directory: apps/trading/e2e
|
||||
#----------------------------------------------
|
||||
# install python dependencies
|
||||
#----------------------------------------------
|
||||
- name: Install dependencies
|
||||
run: poetry install --no-interaction --no-root
|
||||
run: poetry lock && poetry install --no-interaction --no-root
|
||||
working-directory: apps/trading/e2e
|
||||
#----------------------------------------------
|
||||
# install vega binaries
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
name: Cypress Console tests -- live environment
|
||||
|
||||
# This workflow runs using provided url
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
url:
|
||||
description: 'Url'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
cypress-run:
|
||||
name: Run Cypress Trading tests -- live environment
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js 20
|
||||
id: Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Run Cypress tests
|
||||
uses: cypress-io/github-action@v4
|
||||
with:
|
||||
browser: chrome
|
||||
record: true
|
||||
project: ./apps/trading-e2e
|
||||
config: baseUrl=${{ github.event.inputs.url }}
|
||||
env: grepTags=@live
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -12,7 +12,6 @@ on:
|
||||
options:
|
||||
- explorer-e2e
|
||||
- governance-e2e
|
||||
- trading-e2e
|
||||
tags:
|
||||
description: 'Test tags to run'
|
||||
required: true
|
||||
|
||||
@@ -10,5 +10,5 @@ jobs:
|
||||
uses: ./.github/workflows/cypress-run.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
projects: '["explorer-e2e","governance-e2e","trading-e2e"]'
|
||||
projects: '["explorer-e2e","governance-e2e"]'
|
||||
tags: '@smoke @regression @slow'
|
||||
|
||||
@@ -58,5 +58,6 @@ __pycache__/
|
||||
apps/trading/e2e/logs/
|
||||
apps/trading/e2e/.pytest_cache/
|
||||
apps/trading/e2e/traces/
|
||||
apps/trading/e2e/pyproject.toml
|
||||
|
||||
.nx/
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# path to a directory with all packages
|
||||
storage: ../tmp/local-registry/storage
|
||||
|
||||
# a list of other known repositories we can talk to
|
||||
uplinks:
|
||||
npmjs:
|
||||
url: https://registry.yarnpkg.com
|
||||
maxage: 60m
|
||||
|
||||
packages:
|
||||
'**':
|
||||
# give all users (including non-authenticated users) full access
|
||||
# because it is a local registry
|
||||
access: $all
|
||||
publish: $all
|
||||
unpublish: $all
|
||||
|
||||
# if package is not available locally, proxy requests to npm registry
|
||||
proxy: npmjs
|
||||
|
||||
# log settings
|
||||
logs:
|
||||
type: stdout
|
||||
format: pretty
|
||||
level: warn
|
||||
|
||||
publish:
|
||||
allow_offline: true # set offline to true to allow publish offline
|
||||
@@ -1,5 +1,5 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_ENV=VALIDATOR_TESTNET
|
||||
NX_VEGA_ENV=VALIDATORS_TESTNET
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml
|
||||
NX_VEGA_URL=https://api-validators-testnet.vega.rocks/graphql
|
||||
NX_VEGA_REST=https://api-validators-testnet.vega.rocks/
|
||||
|
||||
@@ -8,12 +8,14 @@ import EpochMissingOverview from './epoch-missing';
|
||||
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import type { IconProps } from '@vegaprotocol/ui-toolkit';
|
||||
import isPast from 'date-fns/isPast';
|
||||
import { EpochSymbol } from '../links/block-link/block-link';
|
||||
|
||||
const borderClass =
|
||||
'border-solid border-2 border-vega-dark-200 border-collapse';
|
||||
|
||||
export type EpochOverviewProps = {
|
||||
id?: string;
|
||||
icon?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -24,7 +26,7 @@ export type EpochOverviewProps = {
|
||||
*
|
||||
* The details are hidden in a tooltip, behind the epoch number
|
||||
*/
|
||||
const EpochOverview = ({ id }: EpochOverviewProps) => {
|
||||
const EpochOverview = ({ id, icon = true }: EpochOverviewProps) => {
|
||||
const { data, error, loading } = useExplorerEpochQuery({
|
||||
variables: { id: id || '' },
|
||||
});
|
||||
@@ -38,7 +40,12 @@ const EpochOverview = ({ id }: EpochOverviewProps) => {
|
||||
}
|
||||
|
||||
if (!ti || loading || error) {
|
||||
return <span>{id}</span>;
|
||||
return (
|
||||
<span>
|
||||
<EpochSymbol />
|
||||
{id}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
const description = (
|
||||
@@ -90,7 +97,11 @@ const EpochOverview = ({ id }: EpochOverviewProps) => {
|
||||
return (
|
||||
<Tooltip description={description}>
|
||||
<p>
|
||||
<IconForEpoch start={ti.start} end={ti.end} />
|
||||
{icon ? (
|
||||
<IconForEpoch start={ti.start} end={ti.end} />
|
||||
) : (
|
||||
<EpochSymbol />
|
||||
)}
|
||||
{id}
|
||||
</p>
|
||||
</Tooltip>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
query ExplorerEpochForBlock($block: String!) {
|
||||
epoch(block: $block) {
|
||||
id
|
||||
timestamps {
|
||||
start
|
||||
end
|
||||
lastBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerEpochForBlockQueryVariables = Types.Exact<{
|
||||
block: Types.Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerEpochForBlockQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: any | null, end?: any | null, lastBlock?: string | null } } };
|
||||
|
||||
|
||||
export const ExplorerEpochForBlockDocument = gql`
|
||||
query ExplorerEpochForBlock($block: String!) {
|
||||
epoch(block: $block) {
|
||||
id
|
||||
timestamps {
|
||||
start
|
||||
end
|
||||
lastBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerEpochForBlockQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerEpochForBlockQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerEpochForBlockQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useExplorerEpochForBlockQuery({
|
||||
* variables: {
|
||||
* block: // value for 'block'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerEpochForBlockQuery(baseOptions: Apollo.QueryHookOptions<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>(ExplorerEpochForBlockDocument, options);
|
||||
}
|
||||
export function useExplorerEpochForBlockLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>(ExplorerEpochForBlockDocument, options);
|
||||
}
|
||||
export type ExplorerEpochForBlockQueryHookResult = ReturnType<typeof useExplorerEpochForBlockQuery>;
|
||||
export type ExplorerEpochForBlockLazyQueryHookResult = ReturnType<typeof useExplorerEpochForBlockLazyQuery>;
|
||||
export type ExplorerEpochForBlockQueryResult = Apollo.QueryResult<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>;
|
||||
@@ -4,17 +4,56 @@ import { Link } from 'react-router-dom';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import Hash from '../hash';
|
||||
import { useExplorerEpochForBlockQuery } from './__generated__/EpochByBlock';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export type BlockLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
height: string;
|
||||
showEpoch?: boolean;
|
||||
};
|
||||
|
||||
const BlockLink = ({ height, ...props }: BlockLinkProps) => {
|
||||
const BlockLink = ({ height, showEpoch = false, ...props }: BlockLinkProps) => {
|
||||
return (
|
||||
<Link className="underline" {...props} to={`/${Routes.BLOCKS}/${height}`}>
|
||||
<Hash text={height} />
|
||||
</Link>
|
||||
<>
|
||||
<Link className="underline" {...props} to={`/${Routes.BLOCKS}/${height}`}>
|
||||
<Hash text={height} />
|
||||
</Link>
|
||||
{showEpoch && <EpochForBlock block={height} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export function EpochForBlock(props: { block: string }) {
|
||||
const { error, data, loading } = useExplorerEpochForBlockQuery({
|
||||
errorPolicy: 'ignore',
|
||||
variables: { block: props.block },
|
||||
});
|
||||
|
||||
// NOTE: 0.73.x & <0.74.2 can error showing epoch, so for now we hide loading
|
||||
// or error states and only display if we get usable data
|
||||
if (error || loading || !data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="ml-2" title={t('Epoch')}>
|
||||
<EpochSymbol />
|
||||
{data.epoch.id}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export const EPOCH_SYMBOL = 'ⓔ';
|
||||
|
||||
export function EpochSymbol() {
|
||||
return (
|
||||
<em
|
||||
title={t('Epoch')}
|
||||
className="mr-1 cursor-default text-xl leading-none align-text-bottom not-italic"
|
||||
>
|
||||
{EPOCH_SYMBOL}
|
||||
</em>
|
||||
);
|
||||
}
|
||||
|
||||
export default BlockLink;
|
||||
|
||||
+5
-2
@@ -1,5 +1,8 @@
|
||||
import type { ChainIdMapping } from './external-chain';
|
||||
import { SUPPORTED_CHAIN_IDS, SUPPORTED_CHAIN_LABELS } from './external-chain';
|
||||
import type { ChainIdMapping } from '@vegaprotocol/environment';
|
||||
import {
|
||||
SUPPORTED_CHAIN_IDS,
|
||||
SUPPORTED_CHAIN_LABELS,
|
||||
} from '@vegaprotocol/environment';
|
||||
|
||||
export const SUPPORTED_CHAIN_ICON_URLS: ChainIdMapping = {
|
||||
'1': '/assets/chain-eth-logo.svg',
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import Hash from '../hash';
|
||||
import { getExternalExplorerLink } from './external-chain';
|
||||
import { getExternalExplorerLink } from '@vegaprotocol/environment';
|
||||
import { ExternalChainIcon } from './external-chain-icon';
|
||||
|
||||
export enum EthExplorerLinkTypes {
|
||||
@@ -23,7 +23,7 @@ export const ExternalExplorerLink = ({
|
||||
code = false,
|
||||
...props
|
||||
}: ExternalExplorerLinkProps) => {
|
||||
const link = `${getExternalExplorerLink(chain, type)}/${type}/${id}${
|
||||
const link = `${getExternalExplorerLink(chain)}/${type}/${id}${
|
||||
code ? '#code' : ''
|
||||
}`;
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import GovernanceLink from './governance-link';
|
||||
|
||||
describe('GovernanceLink', () => {
|
||||
it('renders the link with the correct text', () => {
|
||||
render(<GovernanceLink text="Governance internet website" />);
|
||||
const linkElement = screen.getByText('Governance internet website');
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the link with the correct href and sensible default text', () => {
|
||||
render(<GovernanceLink />);
|
||||
const linkElement = screen.getByText('Governance');
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { ENV } from '../../../config/env';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export type GovernanceLinkProps = {
|
||||
text?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Just a link to the governance page, with optional text
|
||||
*/
|
||||
const GovernanceLink = ({ text = t('Governance') }: GovernanceLinkProps) => {
|
||||
const base = ENV.dataSources.governanceUrl;
|
||||
|
||||
return <ExternalLink href={base}>{text}</ExternalLink>;
|
||||
};
|
||||
|
||||
export default GovernanceLink;
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
LiquiditySLAParametersInfoPanel,
|
||||
MarginScalingFactorsPanel,
|
||||
PriceMonitoringBoundsInfoPanel,
|
||||
PriceMonitoringSettingsInfoPanel,
|
||||
SuccessionLineInfoPanel,
|
||||
getDataSourceSpecForSettlementData,
|
||||
getDataSourceSpecForTradingTermination,
|
||||
@@ -21,7 +22,6 @@ import {
|
||||
RiskModelInfoPanel,
|
||||
SettlementAssetInfoPanel,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { MarketInfoTable } from '@vegaprotocol/markets';
|
||||
import type { DataSourceFragment } from '@vegaprotocol/markets';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
@@ -74,27 +74,14 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
|
||||
<MarginScalingFactorsPanel market={market} />
|
||||
<h2 className={headerClassName}>{t('Risk factors')}</h2>
|
||||
<RiskFactorsInfoPanel market={market} />
|
||||
{(market.data?.priceMonitoringBounds || []).map((trigger, i) => (
|
||||
<>
|
||||
<h2 className={headerClassName}>
|
||||
{t('Price monitoring bounds %s', [(i + 1).toString()])}
|
||||
</h2>
|
||||
<PriceMonitoringBoundsInfoPanel
|
||||
market={market}
|
||||
triggerIndex={i + 1}
|
||||
/>
|
||||
</>
|
||||
))}
|
||||
{(market.priceMonitoringSettings?.parameters?.triggers || []).map(
|
||||
(trigger, i) => (
|
||||
<>
|
||||
<h2 className={headerClassName}>
|
||||
{t('Price monitoring settings %s', [(i + 1).toString()])}
|
||||
</h2>
|
||||
<MarketInfoTable data={trigger} key={i} />
|
||||
</>
|
||||
)
|
||||
)}
|
||||
<h2 className={headerClassName}>{t('Price monitoring bounds')}</h2>
|
||||
<div className="mt-3">
|
||||
<PriceMonitoringBoundsInfoPanel market={market} />
|
||||
</div>
|
||||
<h2 className={headerClassName}>{t('Price monitoring settings')}</h2>
|
||||
<div className="mt-3">
|
||||
<PriceMonitoringSettingsInfoPanel market={market} />
|
||||
</div>
|
||||
<h2 className={headerClassName}>{t('Liquidation strategy')}</h2>
|
||||
<LiquidationStrategyInfoPanel market={market} />
|
||||
<h2 className={headerClassName}>{t('Liquidity monitoring')}</h2>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo } from 'react';
|
||||
import { getAsset, type MarketFieldsFragment } from '@vegaprotocol/markets';
|
||||
import { getAsset, type MarketMaybeWithData } from '@vegaprotocol/markets';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { type AgGridReact } from 'ag-grid-react';
|
||||
@@ -17,7 +17,7 @@ import { type RowClickedEvent } from 'ag-grid-community';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
|
||||
type MarketsTableProps = {
|
||||
data: MarketFieldsFragment[] | null;
|
||||
data: MarketMaybeWithData[] | null;
|
||||
};
|
||||
export const MarketsTable = ({ data }: MarketsTableProps) => {
|
||||
const openAssetDetailsDialog = useAssetDetailsDialogStore(
|
||||
@@ -56,10 +56,10 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
|
||||
headerName: t('Status'),
|
||||
field: 'state',
|
||||
hide: window.innerWidth <= BREAKPOINT_MD,
|
||||
valueGetter: ({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketFieldsFragment>) => {
|
||||
return data?.state ? MarketStateMapping[data?.state] : '-';
|
||||
valueGetter: ({ data }: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||
return data?.data?.marketState
|
||||
? MarketStateMapping[data?.data.marketState]
|
||||
: '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -70,7 +70,7 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<
|
||||
MarketFieldsFragment,
|
||||
MarketMaybeWithData,
|
||||
'tradableInstrument.instrument.product.settlementAsset.symbol'
|
||||
>) => {
|
||||
const value = data && getAsset(data);
|
||||
@@ -99,7 +99,7 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
|
||||
field: 'id',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaICellRendererParams<MarketFieldsFragment, 'id'>) =>
|
||||
}: VegaICellRendererParams<MarketMaybeWithData, 'id'>) =>
|
||||
value ? (
|
||||
<Link className="underline" to={value}>
|
||||
{t('View details')}
|
||||
@@ -116,7 +116,7 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
|
||||
<AgGrid
|
||||
ref={gridRef}
|
||||
rowData={data}
|
||||
getRowId={({ data }: { data: MarketFieldsFragment }) => data.id}
|
||||
getRowId={({ data }: { data: MarketMaybeWithData }) => data.id}
|
||||
overlayNoRowsTemplate={t('This chain has no markets')}
|
||||
domLayout="autoHeight"
|
||||
defaultColDef={{
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
import compact from 'lodash/compact';
|
||||
import { MarketLink } from '../links';
|
||||
import { type MarketState, MarketStateMapping } from '@vegaprotocol/types';
|
||||
import OracleLink from '../links/oracle-link/oracle-link';
|
||||
import type {
|
||||
ExplorerOracleForMarketQuery,
|
||||
ExplorerOracleFormMarketsQuery,
|
||||
} from '../../routes/oracles/__generated__/OraclesForMarkets';
|
||||
import { useState } from 'react';
|
||||
|
||||
export type OraclesTableProps = {
|
||||
data?: ExplorerOracleFormMarketsQuery | ExplorerOracleForMarketQuery;
|
||||
};
|
||||
|
||||
const cellSpacing = 'px-3';
|
||||
|
||||
export function OraclesTable({ data }: OraclesTableProps) {
|
||||
const [hoveredOracle, setHoveredOracle] = useState('');
|
||||
|
||||
return (
|
||||
<table className="text-left">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className={cellSpacing}>Market</th>
|
||||
<th className={cellSpacing}>Type</th>
|
||||
<th className={cellSpacing}>State</th>
|
||||
<th className={cellSpacing}>Settlement</th>
|
||||
<th className={cellSpacing}>Termination</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data?.marketsConnection?.edges
|
||||
? data.marketsConnection.edges.map((o) => {
|
||||
let hasSeenOracleReports = false;
|
||||
let settlementOracle = '-';
|
||||
let settlementOracleStatus = '-';
|
||||
let terminationOracle = '-';
|
||||
let terminationOracleStatus = '-';
|
||||
|
||||
const id = o?.node.id;
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
o.node.tradableInstrument.instrument.product.__typename ===
|
||||
'Future'
|
||||
) {
|
||||
settlementOracle =
|
||||
o.node.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForSettlementData.id;
|
||||
terminationOracle =
|
||||
o.node.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForTradingTermination.id;
|
||||
settlementOracleStatus =
|
||||
o.node.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForSettlementData.status;
|
||||
terminationOracleStatus =
|
||||
o.node.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForTradingTermination.status;
|
||||
} else if (
|
||||
o.node.tradableInstrument.instrument.product.__typename ===
|
||||
'Perpetual'
|
||||
) {
|
||||
settlementOracle =
|
||||
o.node.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForSettlementData.id;
|
||||
terminationOracle =
|
||||
o.node.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForSettlementSchedule.id;
|
||||
settlementOracleStatus =
|
||||
o.node.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForSettlementData.status;
|
||||
terminationOracleStatus =
|
||||
o.node.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForSettlementSchedule.status;
|
||||
}
|
||||
const oracleInformationUnfiltered =
|
||||
data?.oracleSpecsConnection?.edges?.map((e) =>
|
||||
e && e.node ? e.node : undefined
|
||||
) || [];
|
||||
|
||||
const oracleInformation = compact(oracleInformationUnfiltered)
|
||||
.filter(
|
||||
(o) =>
|
||||
o.dataConnection.edges &&
|
||||
o.dataConnection.edges.length > 0 &&
|
||||
(o.dataSourceSpec.spec.id === settlementOracle ||
|
||||
o.dataSourceSpec.spec.id === terminationOracle)
|
||||
)
|
||||
.at(0);
|
||||
if (oracleInformation) {
|
||||
hasSeenOracleReports = true;
|
||||
}
|
||||
|
||||
const oracleList = `${settlementOracle} ${terminationOracle}`;
|
||||
|
||||
return (
|
||||
<tr
|
||||
id={id}
|
||||
key={id}
|
||||
className={
|
||||
hoveredOracle.length > 0 &&
|
||||
oracleList.indexOf(hoveredOracle) > -1
|
||||
? 'bg-gray-100 dark:bg-gray-800'
|
||||
: ''
|
||||
}
|
||||
data-testid="oracle-details"
|
||||
data-oracles={oracleList}
|
||||
>
|
||||
<td className={cellSpacing}>
|
||||
<MarketLink id={id} />
|
||||
</td>
|
||||
<td className={cellSpacing}>
|
||||
{o.node.tradableInstrument.instrument.product.__typename}
|
||||
</td>
|
||||
<td className={cellSpacing}>
|
||||
{MarketStateMapping[o.node.state as MarketState]}
|
||||
</td>
|
||||
<td
|
||||
className={
|
||||
hoveredOracle.length > 0 &&
|
||||
hoveredOracle === settlementOracle
|
||||
? `indent-1 ${cellSpacing}`
|
||||
: cellSpacing
|
||||
}
|
||||
>
|
||||
<OracleLink
|
||||
id={settlementOracle}
|
||||
status={settlementOracleStatus}
|
||||
hasSeenOracleReports={hasSeenOracleReports}
|
||||
onMouseOver={() => setHoveredOracle(settlementOracle)}
|
||||
onMouseOut={() => setHoveredOracle('')}
|
||||
/>
|
||||
</td>
|
||||
<td
|
||||
className={
|
||||
hoveredOracle.length > 0 &&
|
||||
hoveredOracle === terminationOracle
|
||||
? `indent-1 ${cellSpacing}`
|
||||
: cellSpacing
|
||||
}
|
||||
>
|
||||
<OracleLink
|
||||
id={terminationOracle}
|
||||
status={terminationOracleStatus}
|
||||
hasSeenOracleReports={hasSeenOracleReports}
|
||||
onMouseOver={() => setHoveredOracle(terminationOracle)}
|
||||
onMouseOut={() => setHoveredOracle('')}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
@@ -33,10 +33,10 @@ const SizeInAsset = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<p>
|
||||
<span>
|
||||
<span>{label}</span>
|
||||
<AssetLink assetId={assetId} showAssetSymbol={true} asDialog={true} />
|
||||
</p>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
ExternalExplorerLink,
|
||||
EthExplorerLinkTypes,
|
||||
} from '../../../links/external-explorer-link/external-explorer-link';
|
||||
import { getExternalChainLabel } from '../../../links/external-explorer-link/external-chain';
|
||||
import { getExternalChainLabel } from '@vegaprotocol/environment';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import { defaultAbiCoder, base64 } from 'ethers/lib/utils';
|
||||
import { BigNumber } from 'ethers';
|
||||
|
||||
@@ -10,6 +10,8 @@ import { ChainResponseCode } from '../chain-response-code/chain-reponse.code';
|
||||
import { TxDataView } from '../../tx-data-view';
|
||||
import Hash from '../../../links/hash';
|
||||
import { Signature } from '../../../signature/signature';
|
||||
import { useExplorerEpochForBlockQuery } from '../../../links/block-link/__generated__/EpochByBlock';
|
||||
import EpochOverview from '../../../epoch-overview/epoch';
|
||||
|
||||
interface TxDetailsSharedProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -44,6 +46,11 @@ export const TxDetailsShared = ({
|
||||
blockData,
|
||||
hideTypeRow = false,
|
||||
}: TxDetailsSharedProps) => {
|
||||
const { data } = useExplorerEpochForBlockQuery({
|
||||
errorPolicy: 'ignore',
|
||||
variables: { block: txData?.block?.toString() || '' },
|
||||
});
|
||||
|
||||
if (!txData) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
@@ -74,7 +81,7 @@ export const TxDetailsShared = ({
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Block')}</TableCell>
|
||||
<TableCell>
|
||||
<BlockLink height={height} />
|
||||
<BlockLink height={height} showEpoch={false} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
@@ -83,6 +90,7 @@ export const TxDetailsShared = ({
|
||||
<Signature signature={txData.signature} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Time')}</TableCell>
|
||||
<TableCell>
|
||||
@@ -100,6 +108,14 @@ export const TxDetailsShared = ({
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{data && data.epoch && (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell scope="row">{t('Epoch')}</TableCell>
|
||||
<TableCell modifier="bordered">
|
||||
<EpochOverview id={data.epoch.id} icon={false} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Response code')}</TableCell>
|
||||
<TableCell>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
query ExplorerTransferStatus($id: ID!) {
|
||||
transfer(id: $id) {
|
||||
fees {
|
||||
amount
|
||||
epoch
|
||||
}
|
||||
transfer {
|
||||
reference
|
||||
timestamp
|
||||
|
||||
+5
-1
@@ -8,12 +8,16 @@ export type ExplorerTransferStatusQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerTransferStatusQuery = { __typename?: 'Query', transfer?: { __typename?: 'TransferNode', transfer: { __typename?: 'Transfer', reference?: string | null, timestamp: any, status: Types.TransferStatus, reason?: string | null, fromAccountType: Types.AccountType, from: string, to: string, toAccountType: Types.AccountType, amount: string, asset?: { __typename?: 'Asset', id: string } | null } } | null };
|
||||
export type ExplorerTransferStatusQuery = { __typename?: 'Query', transfer?: { __typename?: 'TransferNode', fees?: Array<{ __typename?: 'TransferFee', amount: string, epoch: number } | null> | null, transfer: { __typename?: 'Transfer', reference?: string | null, timestamp: any, status: Types.TransferStatus, reason?: string | null, fromAccountType: Types.AccountType, from: string, to: string, toAccountType: Types.AccountType, amount: string, asset?: { __typename?: 'Asset', id: string } | null } } | null };
|
||||
|
||||
|
||||
export const ExplorerTransferStatusDocument = gql`
|
||||
query ExplorerTransferStatus($id: ID!) {
|
||||
transfer(id: $id) {
|
||||
fees {
|
||||
amount
|
||||
epoch
|
||||
}
|
||||
transfer {
|
||||
reference
|
||||
timestamp
|
||||
|
||||
+45
-4
@@ -44,8 +44,14 @@ const AccountType: Record<AccountTypes, string> = {
|
||||
ACCOUNT_TYPE_ORDER_MARGIN: 'Order Margin',
|
||||
};
|
||||
|
||||
export type TransferFee = {
|
||||
amount?: string;
|
||||
epoch?: number;
|
||||
};
|
||||
|
||||
interface TransferParticipantsProps {
|
||||
transfer: Transfer;
|
||||
fees?: TransferFee[] | null;
|
||||
from: string;
|
||||
}
|
||||
|
||||
@@ -60,6 +66,7 @@ interface TransferParticipantsProps {
|
||||
export function TransferParticipants({
|
||||
transfer,
|
||||
from,
|
||||
fees,
|
||||
}: TransferParticipantsProps) {
|
||||
// This mapping is required as the global account types require a type to be set, while
|
||||
// the underlying protobufs allow for every field to be undefined.
|
||||
@@ -104,6 +111,9 @@ export function TransferParticipants({
|
||||
{transfer.asset ? (
|
||||
<SizeInAsset assetId={transfer.asset} size={transfer.amount} />
|
||||
) : null}
|
||||
{transfer.asset && fees && (
|
||||
<TransferFees assetId={transfer.asset} fees={fees} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Empty divs for the top arrow and the bottom arrow of the transfer inset */}
|
||||
@@ -125,10 +135,6 @@ export function TransferParticipants({
|
||||
<path d="M0,0L8,9l8,-9Z" />
|
||||
</svg>
|
||||
</div>
|
||||
{/*
|
||||
<div className="z-10 absolute top-0 left-1/2 transform -translate-x-1/2 -translate-y-1/2 rotate-45 w-4 h-4 dark:border-vega-dark-200 border-vega-light-200 bg-white dark:bg-black border-r border-b"></div>
|
||||
<div className="z-10 absolute bottom-0 left-1/2 transform -translate-x-1/2 translate-y-1/2 rotate-45 w-4 h-4 border-vega-light-200 dark:border-vega-dark-200 bg-vega-light-200 dark:bg-vega-dark-200 border-r border-b"></div>
|
||||
*/}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -169,3 +175,38 @@ export function TransferRecurringRecipient({
|
||||
// Fallback should not happen
|
||||
return null;
|
||||
}
|
||||
|
||||
export function TransferFees({
|
||||
assetId,
|
||||
fees,
|
||||
}: {
|
||||
assetId: string;
|
||||
fees: TransferFee[];
|
||||
}) {
|
||||
// A recurring transfer that is rejected or cancelled will have an array of fees of 0 length
|
||||
if (assetId && fees && fees.length > 0) {
|
||||
if (fees.length === 1) {
|
||||
return (
|
||||
<p className="mt-2">
|
||||
Fee: <SizeInAsset assetId={assetId} size={fees[0].amount} />
|
||||
</p>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<details className="cursor-pointer mt-2">
|
||||
<summary>{t('Fees')}</summary>
|
||||
<ul>
|
||||
{fees.map((fee) => (
|
||||
<li className="text-nowrap leading-normal">
|
||||
<SizeInAsset assetId={assetId} size={fee.amount} />{' '}
|
||||
{t('in epoch')} {fee.epoch}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -41,9 +41,16 @@ export function TransferDetails({ transfer, from, id }: TransferDetailsProps) {
|
||||
? TransferStatus.STATUS_REJECTED
|
||||
: data?.transfer?.transfer.status;
|
||||
|
||||
const fees = data?.transfer?.fees?.map((fee) => {
|
||||
return {
|
||||
amount: fee?.amount ? fee.amount : '0',
|
||||
epoch: fee?.epoch ? fee.epoch : 0,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap">
|
||||
<TransferParticipants from={from} transfer={transfer} />
|
||||
<TransferParticipants from={from} transfer={transfer} fees={fees} />
|
||||
{recurring ? <TransferRepeat recurring={transfer.recurring} /> : null}
|
||||
<TransferStatusView status={status} error={error} loading={loading} />
|
||||
{recurring && recurring.dispatchStrategy ? (
|
||||
|
||||
@@ -113,13 +113,16 @@ export const TxDetailsTransfer = ({
|
||||
|
||||
/**
|
||||
* Gets a string description of this transfer
|
||||
* @param txData A full transfer
|
||||
* @param tx A full transfer
|
||||
* @returns string Transfer label
|
||||
*/
|
||||
export function getTypeLabelForTransfer(tx: Transfer) {
|
||||
if (tx.to === SPECIAL_CASE_NETWORK || tx.to === SPECIAL_CASE_NETWORK_ID) {
|
||||
if (tx.toAccountType === 'ACCOUNT_TYPE_NETWORK_TREASURY') {
|
||||
return 'Treasury transfer';
|
||||
}
|
||||
if (tx.recurring && tx.recurring.dispatchStrategy) {
|
||||
return 'Reward top up transfer';
|
||||
return 'Reward transfer';
|
||||
}
|
||||
// Else: we don't know that it's a reward transfer, so let's not guess
|
||||
} else if (tx.recurring) {
|
||||
|
||||
@@ -16,12 +16,12 @@ import { FilterLabel } from './tx-filter-label';
|
||||
|
||||
// All possible transaction types. Should be generated.
|
||||
export type FilterOption =
|
||||
| 'Amend LiquidityProvision Order'
|
||||
| 'Amend Liquidity Provision Order'
|
||||
| 'Amend Order'
|
||||
| 'Apply Referral Code'
|
||||
| 'Batch Market Instructions'
|
||||
| 'Batch Proposal'
|
||||
| 'Cancel LiquidityProvision Order'
|
||||
| 'Cancel Liquidity Provision Order'
|
||||
| 'Cancel Order'
|
||||
| 'Cancel Transfer Funds'
|
||||
| 'Chain Event'
|
||||
@@ -53,10 +53,10 @@ export type FilterOption =
|
||||
|
||||
export const filterOptions: Record<string, FilterOption[]> = {
|
||||
'Market Instructions': [
|
||||
'Amend LiquidityProvision Order',
|
||||
'Amend Liquidity Provision Order',
|
||||
'Amend Order',
|
||||
'Batch Market Instructions',
|
||||
'Cancel LiquidityProvision Order',
|
||||
'Cancel Liquidity Provision Order',
|
||||
'Cancel Order',
|
||||
'Liquidity Provision Order',
|
||||
'Stop Orders Submission',
|
||||
|
||||
@@ -2,6 +2,7 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import type { components } from '../../../types/explorer';
|
||||
import { VoteIcon } from '../vote-icon/vote-icon';
|
||||
import { ExternalChainIcon } from '../links/external-explorer-link/external-chain-icon';
|
||||
import { getTypeLabelForTransfer } from './details/tx-transfer';
|
||||
|
||||
interface TxOrderTypeProps {
|
||||
orderType: string;
|
||||
@@ -95,7 +96,7 @@ export function getLabelForOrderType(
|
||||
|
||||
/**
|
||||
* Given a proposal, will return a specific label
|
||||
* @param chainEvent
|
||||
* @param proposal
|
||||
* @returns
|
||||
*/
|
||||
export function getLabelForProposal(
|
||||
@@ -142,6 +143,36 @@ export function getLabelForProposal(
|
||||
}
|
||||
}
|
||||
|
||||
type label = {
|
||||
type: string;
|
||||
colours: string;
|
||||
};
|
||||
|
||||
export function getLabelForTransfer(
|
||||
transfer: components['schemas']['commandsv1Transfer']
|
||||
): label {
|
||||
const type = getTypeLabelForTransfer(transfer);
|
||||
|
||||
if (transfer.toAccountType === 'ACCOUNT_TYPE_NETWORK_TREASURY') {
|
||||
return {
|
||||
type,
|
||||
colours:
|
||||
'text-vega-green dark:text-green bg-vega-dark-150 dark:bg-vega-dark-250',
|
||||
};
|
||||
} else if (transfer.recurring) {
|
||||
return {
|
||||
type,
|
||||
colours:
|
||||
'text-vega-yellow dark:text-yellow bg-vega-dark-150 dark:bg-vega-dark-250',
|
||||
};
|
||||
}
|
||||
return {
|
||||
type,
|
||||
colours:
|
||||
'text-white dark:text-white bg-vega-dark-150 dark:bg-vega-dark-250',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a chain event, will try to provide a more useful label
|
||||
* @param chainEvent
|
||||
@@ -225,9 +256,10 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
|
||||
if (type === 'Chain Event' && !!command?.chainEvent) {
|
||||
type = getLabelForChainEvent(command.chainEvent);
|
||||
colours = 'text-white dark-text-white bg-vega-pink dark:bg-vega-pink';
|
||||
} else if (type === 'Validator Heartbeat') {
|
||||
colours =
|
||||
'text-white dark-text-white bg-vega-light-200 dark:bg-vega-dark-100';
|
||||
} else if (type === 'Transfer Funds' && command?.transfer) {
|
||||
const res = getLabelForTransfer(command.transfer);
|
||||
type = res.type;
|
||||
colours = res.colours;
|
||||
} else if (type === 'Proposal' || type === 'Governance Proposal') {
|
||||
if (command && !!command.proposalSubmission) {
|
||||
type = getLabelForProposal(command.proposalSubmission);
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('TX: Transfer: getLabelForTransfer', () => {
|
||||
},
|
||||
};
|
||||
|
||||
expect(getTypeLabelForTransfer(mock)).toEqual('Reward top up transfer');
|
||||
expect(getTypeLabelForTransfer(mock)).toEqual('Reward transfer');
|
||||
});
|
||||
|
||||
it('renders reward top up label if the TO party is network', () => {
|
||||
@@ -32,7 +32,7 @@ describe('TX: Transfer: getLabelForTransfer', () => {
|
||||
},
|
||||
};
|
||||
|
||||
expect(getTypeLabelForTransfer(mock)).toEqual('Reward top up transfer');
|
||||
expect(getTypeLabelForTransfer(mock)).toEqual('Reward transfer');
|
||||
});
|
||||
|
||||
it('renders recurring label if the tx has a recurring property', () => {
|
||||
@@ -81,6 +81,7 @@ describe('TxDetailsTransfer', () => {
|
||||
hash: 'test',
|
||||
submitter:
|
||||
'e1943eea46fed576cf2be42972f3c5515ad3d0ac7ac013f56677c12a53a1b3ed',
|
||||
block: '100',
|
||||
command: {
|
||||
nonce: '5188810881378065222',
|
||||
blockHeight: '14951513',
|
||||
|
||||
@@ -16,6 +16,8 @@ import { useBlockInfo } from '@vegaprotocol/tendermint';
|
||||
import { NodeLink } from '../../../components/links';
|
||||
import { useDocumentTitle } from '../../../hooks/use-document-title';
|
||||
import EmptyList from '../../../components/empty-list/empty-list';
|
||||
import { useExplorerEpochForBlockQuery } from '../../../components/links/block-link/__generated__/EpochByBlock';
|
||||
import EpochOverview from '../../../components/epoch-overview/epoch';
|
||||
|
||||
type Params = { block: string };
|
||||
|
||||
@@ -26,6 +28,11 @@ const Block = () => {
|
||||
state: { data: blockData, loading, error },
|
||||
} = useBlockInfo(Number(block));
|
||||
|
||||
const { data } = useExplorerEpochForBlockQuery({
|
||||
errorPolicy: 'ignore',
|
||||
variables: { block: block?.toString() || '' },
|
||||
});
|
||||
|
||||
return (
|
||||
<section>
|
||||
<RouteTitle data-testid="block-header">{t(`BLOCK ${block}`)}</RouteTitle>
|
||||
@@ -75,6 +82,7 @@ const Block = () => {
|
||||
<code>{blockData.result.block.header.consensus_hash}</code>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">Mined by</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
@@ -97,6 +105,14 @@ const Block = () => {
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{data && data.epoch && (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell scope="row">{t('Epoch')}</TableCell>
|
||||
<TableCell modifier="bordered">
|
||||
<EpochOverview id={data.epoch.id} icon={false} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">Transactions</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useScrollToLocation } from '../../hooks/scroll-to-location';
|
||||
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||
import { PageTitle } from '../../components/page-helpers/page-title';
|
||||
import { useExplorerOracleForMarketQuery } from '../oracles/__generated__/OraclesForMarkets';
|
||||
import { OraclesTable } from '../../components/oracle-table';
|
||||
|
||||
type Params = { marketId: string };
|
||||
|
||||
export const MarketOraclesPage = () => {
|
||||
useScrollToLocation();
|
||||
|
||||
const { marketId } = useParams<Params>();
|
||||
const { data, error, loading } = useExplorerOracleForMarketQuery({
|
||||
errorPolicy: 'ignore',
|
||||
variables: {
|
||||
id: marketId || '1',
|
||||
},
|
||||
});
|
||||
|
||||
useDocumentTitle([marketId ? marketId : 'market', 'Oracles for Market']);
|
||||
|
||||
return (
|
||||
<section className="relative">
|
||||
<PageTitle
|
||||
data-testid="markets-heading"
|
||||
title={t('Oracles for market')}
|
||||
/>
|
||||
<AsyncRenderer
|
||||
noDataMessage={t('This chain has no markets')}
|
||||
errorMessage={t('Could not fetch market') + ' ' + marketId}
|
||||
data={data}
|
||||
loading={loading}
|
||||
error={error}
|
||||
>
|
||||
<OraclesTable data={data} />
|
||||
</AsyncRenderer>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useScrollToLocation } from '../../hooks/scroll-to-location';
|
||||
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||
import { marketsProvider } from '@vegaprotocol/markets';
|
||||
import { marketsWithDataProvider } from '@vegaprotocol/markets';
|
||||
import { RouteTitle } from '../../components/route-title';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
@@ -12,7 +12,7 @@ export const MarketsPage = () => {
|
||||
useScrollToLocation();
|
||||
|
||||
const { data, loading, error } = useDataProvider({
|
||||
dataProvider: marketsProvider,
|
||||
dataProvider: marketsWithDataProvider,
|
||||
variables: undefined,
|
||||
skipUpdates: true,
|
||||
});
|
||||
|
||||
@@ -89,7 +89,40 @@ fragment ExplorerOracleDataSourceSpec on ExternalDataSourceSpec {
|
||||
}
|
||||
|
||||
query ExplorerOracleFormMarkets {
|
||||
marketsConnection {
|
||||
marketsConnection(includeSettled: false, pagination: { first: 20 }) {
|
||||
edges {
|
||||
node {
|
||||
...ExplorerOracleForMarketsMarket
|
||||
}
|
||||
}
|
||||
}
|
||||
oracleSpecsConnection {
|
||||
edges {
|
||||
node {
|
||||
dataSourceSpec {
|
||||
...ExplorerOracleDataSourceSpec
|
||||
}
|
||||
dataConnection(pagination: { first: 1 }) {
|
||||
edges {
|
||||
node {
|
||||
externalData {
|
||||
data {
|
||||
data {
|
||||
name
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query ExplorerOracleForMarket($id: ID!) {
|
||||
marketsConnection(id: $id) {
|
||||
edges {
|
||||
node {
|
||||
...ExplorerOracleForMarketsMarket
|
||||
|
||||
@@ -16,6 +16,13 @@ export type ExplorerOracleFormMarketsQueryVariables = Types.Exact<{ [key: string
|
||||
|
||||
export type ExplorerOracleFormMarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Spot' } } } } }> } | null, oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus, 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?: 'EthCallSpec', address: string, sourceChainId: number } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
|
||||
|
||||
export type ExplorerOracleForMarketQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerOracleForMarketQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Spot' } } } } }> } | null, oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus, 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?: 'EthCallSpec', address: string, sourceChainId: number } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
|
||||
|
||||
export const ExplorerOracleFutureFragmentDoc = gql`
|
||||
fragment ExplorerOracleFuture on Future {
|
||||
dataSourceSpecForSettlementData {
|
||||
@@ -113,7 +120,7 @@ export const ExplorerOracleDataSourceSpecFragmentDoc = gql`
|
||||
`;
|
||||
export const ExplorerOracleFormMarketsDocument = gql`
|
||||
query ExplorerOracleFormMarkets {
|
||||
marketsConnection {
|
||||
marketsConnection(includeSettled: false, pagination: {first: 20}) {
|
||||
edges {
|
||||
node {
|
||||
...ExplorerOracleForMarketsMarket
|
||||
@@ -126,7 +133,7 @@ export const ExplorerOracleFormMarketsDocument = gql`
|
||||
dataSourceSpec {
|
||||
...ExplorerOracleDataSourceSpec
|
||||
}
|
||||
dataConnection(pagination: {last: 1}) {
|
||||
dataConnection(pagination: {first: 1}) {
|
||||
edges {
|
||||
node {
|
||||
externalData {
|
||||
@@ -172,4 +179,67 @@ export function useExplorerOracleFormMarketsLazyQuery(baseOptions?: Apollo.LazyQ
|
||||
}
|
||||
export type ExplorerOracleFormMarketsQueryHookResult = ReturnType<typeof useExplorerOracleFormMarketsQuery>;
|
||||
export type ExplorerOracleFormMarketsLazyQueryHookResult = ReturnType<typeof useExplorerOracleFormMarketsLazyQuery>;
|
||||
export type ExplorerOracleFormMarketsQueryResult = Apollo.QueryResult<ExplorerOracleFormMarketsQuery, ExplorerOracleFormMarketsQueryVariables>;
|
||||
export type ExplorerOracleFormMarketsQueryResult = Apollo.QueryResult<ExplorerOracleFormMarketsQuery, ExplorerOracleFormMarketsQueryVariables>;
|
||||
export const ExplorerOracleForMarketDocument = gql`
|
||||
query ExplorerOracleForMarket($id: ID!) {
|
||||
marketsConnection(id: $id) {
|
||||
edges {
|
||||
node {
|
||||
...ExplorerOracleForMarketsMarket
|
||||
}
|
||||
}
|
||||
}
|
||||
oracleSpecsConnection {
|
||||
edges {
|
||||
node {
|
||||
dataSourceSpec {
|
||||
...ExplorerOracleDataSourceSpec
|
||||
}
|
||||
dataConnection(pagination: {last: 1}) {
|
||||
edges {
|
||||
node {
|
||||
externalData {
|
||||
data {
|
||||
data {
|
||||
name
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${ExplorerOracleForMarketsMarketFragmentDoc}
|
||||
${ExplorerOracleDataSourceSpecFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useExplorerOracleForMarketQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerOracleForMarketQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerOracleForMarketQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useExplorerOracleForMarketQuery({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerOracleForMarketQuery(baseOptions: Apollo.QueryHookOptions<ExplorerOracleForMarketQuery, ExplorerOracleForMarketQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerOracleForMarketQuery, ExplorerOracleForMarketQueryVariables>(ExplorerOracleForMarketDocument, options);
|
||||
}
|
||||
export function useExplorerOracleForMarketLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerOracleForMarketQuery, ExplorerOracleForMarketQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerOracleForMarketQuery, ExplorerOracleForMarketQueryVariables>(ExplorerOracleForMarketDocument, options);
|
||||
}
|
||||
export type ExplorerOracleForMarketQueryHookResult = ReturnType<typeof useExplorerOracleForMarketQuery>;
|
||||
export type ExplorerOracleForMarketLazyQueryHookResult = ReturnType<typeof useExplorerOracleForMarketLazyQuery>;
|
||||
export type ExplorerOracleForMarketQueryResult = Apollo.QueryResult<ExplorerOracleForMarketQuery, ExplorerOracleForMarketQueryVariables>;
|
||||
@@ -4,8 +4,13 @@ import {
|
||||
ExternalExplorerLink,
|
||||
EthExplorerLinkTypes,
|
||||
} from '../../../components/links/external-explorer-link/external-explorer-link';
|
||||
import { getExternalChainLabel } from '../../../components/links/external-explorer-link/external-chain';
|
||||
import { getExternalChainLabel } from '@vegaprotocol/environment';
|
||||
import { t } from 'i18next';
|
||||
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import isArray from 'lodash/isArray';
|
||||
import type { components } from '../../../../types/explorer';
|
||||
|
||||
type Normalisers = components['schemas']['vegaNormaliser'][];
|
||||
|
||||
interface OracleDetailsEthSourceProps {
|
||||
sourceType: SourceType;
|
||||
@@ -34,21 +39,117 @@ export function OracleEthSource({
|
||||
|
||||
const chainLabel = getExternalChainLabel(chain);
|
||||
|
||||
const abi = prepareOracleSpecField(sourceType?.sourceType?.abi);
|
||||
const args = prepareOracleSpecField(sourceType?.sourceType?.args);
|
||||
const normalisers = serialiseNormalisers(sourceType.sourceType.normalisers);
|
||||
|
||||
return (
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">
|
||||
<TableHeader scope="row" className="pt-1 align-text-top">
|
||||
{chainLabel} {t('Contract')}
|
||||
</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
<ExternalExplorerLink
|
||||
chain={chain}
|
||||
id={address}
|
||||
type={EthExplorerLinkTypes.address}
|
||||
code={true}
|
||||
/>
|
||||
<span className="mx-3">⇒</span>
|
||||
<code>{sourceType.sourceType.method}</code>
|
||||
<details>
|
||||
<summary className="cursor-pointer">
|
||||
<ExternalExplorerLink
|
||||
chain={chain}
|
||||
id={address}
|
||||
type={EthExplorerLinkTypes.address}
|
||||
code={true}
|
||||
/>
|
||||
<span className="mx-3">⇒</span>
|
||||
<code>{sourceType.sourceType.method}</code>
|
||||
</summary>
|
||||
|
||||
{args && (
|
||||
<>
|
||||
<h2 className={'mt-5 mb-1 text-xl'}>{t('Arguments')}</h2>
|
||||
<div className="max-w-3">
|
||||
<SyntaxHighlighter
|
||||
data={JSON.parse(
|
||||
sourceType.sourceType.args as unknown as string
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{abi && (
|
||||
<>
|
||||
<h2 className={'mt-5 mb-1 text-xl'}>{t('ABI')}</h2>
|
||||
<div className="max-w-3">
|
||||
<SyntaxHighlighter data={abi} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{normalisers && (
|
||||
<>
|
||||
<h2 className={'mt-5 mb-1 text-xl'}>{t('Normalisers')}</h2>
|
||||
<div className="max-w-3 mb-3">
|
||||
<SyntaxHighlighter data={normalisers} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</details>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
// Constant to define the absence of a valid string from the Oracle Spec fields
|
||||
const NO_DATA = false;
|
||||
|
||||
/**
|
||||
* The ABI and args are stored as either a (JSON escaped, probably) string
|
||||
* or array of strings. Given that OracleEthSource is simply throwing the
|
||||
* data in to a SyntaxHighlighter, we don't really care about the format,
|
||||
* so this function will just try to parse the data and return it as a string.
|
||||
*
|
||||
* @param abi
|
||||
* @returns
|
||||
*/
|
||||
export function prepareOracleSpecField(
|
||||
specField?: string[] | null
|
||||
): string | false {
|
||||
if (!specField) {
|
||||
return NO_DATA;
|
||||
}
|
||||
|
||||
try {
|
||||
if (isArray(specField)) {
|
||||
return JSON.parse(specField.join(''));
|
||||
} else {
|
||||
return JSON.parse(specField);
|
||||
}
|
||||
} catch (e) {
|
||||
return NO_DATA;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to prepareOracleSpecField above, but processes an array of normaliser objects
|
||||
* removing the __typename and returning a serialised array of normalisers for
|
||||
* SyntaxHighlighter
|
||||
*
|
||||
* @param normalisers
|
||||
* @returns
|
||||
*/
|
||||
export function serialiseNormalisers(
|
||||
normalisers?: Normalisers | null
|
||||
): Normalisers | false {
|
||||
if (!normalisers) {
|
||||
return NO_DATA;
|
||||
}
|
||||
|
||||
try {
|
||||
return normalisers.map((normaliser) => {
|
||||
return {
|
||||
name: normaliser.name,
|
||||
expression: normaliser.expression,
|
||||
};
|
||||
});
|
||||
} catch (e) {
|
||||
return NO_DATA;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,9 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { OracleFilter } from './oracle-filter';
|
||||
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
|
||||
import {
|
||||
ConditionOperator,
|
||||
DataSourceSpecStatus,
|
||||
PropertyKeyType,
|
||||
} from '@vegaprotocol/types';
|
||||
import { ConditionOperator, DataSourceSpecStatus } from '@vegaprotocol/types';
|
||||
import type { Condition } from '@vegaprotocol/types';
|
||||
|
||||
type Spec =
|
||||
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType'];
|
||||
|
||||
const mockExternalSpec: Spec = {
|
||||
sourceType: {
|
||||
__typename: 'DataSourceSpecConfiguration',
|
||||
filters: [
|
||||
{
|
||||
__typename: 'Filter',
|
||||
key: {
|
||||
type: PropertyKeyType.TYPE_INTEGER,
|
||||
name: 'testKey',
|
||||
},
|
||||
conditions: [
|
||||
{
|
||||
__typename: 'Condition',
|
||||
value: 'testValue',
|
||||
operator: ConditionOperator.OPERATOR_EQUALS,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
function renderComponent(data: ExplorerOracleDataSourceFragment) {
|
||||
return <OracleFilter data={data} />;
|
||||
}
|
||||
@@ -50,31 +21,6 @@ describe('Oracle Filter view', () => {
|
||||
expect(res.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Renders filters if type is DataSourceSpecConfiguration', () => {
|
||||
const res = render(
|
||||
renderComponent({
|
||||
dataSourceSpec: {
|
||||
spec: {
|
||||
id: 'irrelevant-test-data',
|
||||
createdAt: 'irrelevant-test-data',
|
||||
status: DataSourceSpecStatus.STATUS_ACTIVE,
|
||||
data: {
|
||||
sourceType: mockExternalSpec,
|
||||
},
|
||||
},
|
||||
},
|
||||
dataConnection: {
|
||||
edges: [],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Renders a comprehensible summary of key = value
|
||||
expect(res.getByText('testKey')).toBeInTheDocument();
|
||||
expect(res.getByText('=')).toBeInTheDocument();
|
||||
expect(res.getByText('testValue')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders conditions if type is DataSourceSpecConfigurationTime', () => {
|
||||
const res = render(
|
||||
renderComponent({
|
||||
@@ -136,7 +82,7 @@ describe('Oracle Filter view', () => {
|
||||
})
|
||||
);
|
||||
|
||||
// This should never happen, but for coverage sake we test that it does this
|
||||
// This should never happen, but for coverage we test that it does this
|
||||
const ul = res.getByRole('list');
|
||||
expect(ul).toBeInTheDocument();
|
||||
expect(ul).toBeEmptyDOMElement();
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
|
||||
import { OracleSpecInternalTimeTrigger } from './oracle-spec/internal-time-trigger';
|
||||
import {
|
||||
OracleSpecInternalTimeTrigger,
|
||||
TimeTrigger,
|
||||
} from './oracle-spec/internal-time-trigger';
|
||||
import { OracleSpecCondition } from './oracle-spec/condition';
|
||||
import { getCharacterForOperator } from './oracle-spec/operator';
|
||||
|
||||
@@ -11,7 +14,7 @@ interface OracleFilterProps {
|
||||
* Shows the conditions that this oracle is using to filter
|
||||
* data sources, as a list.
|
||||
*
|
||||
* Renders nothing if there is no data (which will frequently)
|
||||
* Renders nothing if there is no data (which will frequently
|
||||
* be the case) and if there is data, currently renders a simple
|
||||
* JSON view.
|
||||
*/
|
||||
@@ -21,6 +24,7 @@ export function OracleFilter({ data }: OracleFilterProps) {
|
||||
}
|
||||
|
||||
const s = data.dataSourceSpec.spec.data.sourceType.sourceType;
|
||||
|
||||
if (s.__typename === 'DataSourceSpecConfigurationTime' && s.conditions) {
|
||||
return (
|
||||
<ul>
|
||||
@@ -41,30 +45,30 @@ export function OracleFilter({ data }: OracleFilterProps) {
|
||||
s.triggers
|
||||
) {
|
||||
return <OracleSpecInternalTimeTrigger data={s} />;
|
||||
} else if (
|
||||
s.__typename === 'EthCallSpec' ||
|
||||
s.__typename === 'DataSourceSpecConfiguration'
|
||||
) {
|
||||
} else if (s.__typename === 'EthCallSpec') {
|
||||
if (s.filters !== null && s.filters && 'filters' in s) {
|
||||
return (
|
||||
<ul>
|
||||
{s.filters.map((f) => {
|
||||
const prop = <code title={f.key.type}>{f.key.name}</code>;
|
||||
<div>
|
||||
<ul>
|
||||
{s.filters.map((f) => {
|
||||
const prop = <code title={f.key.type}>{f.key.name}</code>;
|
||||
|
||||
if (!f.conditions || f.conditions.length === 0) {
|
||||
return prop;
|
||||
} else {
|
||||
return f.conditions.map((c) => {
|
||||
return (
|
||||
<li key={`${prop}${c.value}`}>
|
||||
{prop} {getCharacterForOperator(c.operator)}{' '}
|
||||
<code>{c.value ? c.value : '-'}</code>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
}
|
||||
})}
|
||||
</ul>
|
||||
if (!f.conditions || f.conditions.length === 0) {
|
||||
return prop;
|
||||
} else {
|
||||
return f.conditions.map((c) => {
|
||||
return (
|
||||
<li key={`${prop}${c.value}`}>
|
||||
{prop} {getCharacterForOperator(c.operator)}{' '}
|
||||
<code>{c.value ? c.value : '-'}</code>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
}
|
||||
})}
|
||||
</ul>
|
||||
{s.trigger && <TimeTrigger data={s.trigger.trigger} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+60
-27
@@ -1,5 +1,10 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { DataSourceSpecConfigurationTimeTrigger } from '@vegaprotocol/types';
|
||||
import type {
|
||||
DataSourceSpecConfigurationTimeTrigger,
|
||||
EthTimeTrigger,
|
||||
InternalTimeTrigger,
|
||||
Maybe,
|
||||
} from '@vegaprotocol/types';
|
||||
import secondsToMinutes from 'date-fns/secondsToMinutes';
|
||||
import fromUnixTime from 'date-fns/fromUnixTime';
|
||||
|
||||
@@ -13,32 +18,60 @@ export function OracleSpecInternalTimeTrigger({
|
||||
return (
|
||||
<div>
|
||||
<span>{t('Time')}</span>,
|
||||
{data.triggers.map((tr) => {
|
||||
return (
|
||||
<span>
|
||||
{tr?.initial ? (
|
||||
<span title={`${tr.initial}`}>
|
||||
<strong>{t('starting at')}</strong>{' '}
|
||||
<em className="not-italic underline decoration-dotted">
|
||||
{fromUnixTime(tr.initial).toLocaleString()}
|
||||
</em>
|
||||
</span>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{tr?.every ? (
|
||||
<span title={`${tr.every} ${t('seconds')}`}>
|
||||
, <strong>{t('every')}</strong>{' '}
|
||||
<em className="not-italic underline decoration-dotted">
|
||||
{secondsToMinutes(tr.every)} {t('minutes')}
|
||||
</em>{' '}
|
||||
</span>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
{data.triggers.map((tr) => (
|
||||
<TimeTrigger data={tr} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export interface TimeTriggerProps {
|
||||
data: Maybe<InternalTimeTrigger> | Maybe<EthTimeTrigger>;
|
||||
}
|
||||
|
||||
export function TimeTrigger({ data }: TimeTriggerProps) {
|
||||
const d = parseDate(data?.initial);
|
||||
|
||||
return (
|
||||
<span key={JSON.stringify(data)}>
|
||||
{data?.initial ? (
|
||||
<span title={`${data.initial}`}>
|
||||
<strong>{t('starting at')}</strong>{' '}
|
||||
<em className="not-italic underline decoration-dotted">{d}</em>
|
||||
</span>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{data?.every ? (
|
||||
<span title={`${data.every} ${t('seconds')}`}>
|
||||
, <strong>{t('every')}</strong>{' '}
|
||||
<em className="not-italic underline decor</em>ation-dotted">
|
||||
{secondsToMinutes(data.every)} {t('minutes')}
|
||||
</em>{' '}
|
||||
</span>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dates in oracle triggers can be (or maybe were previously) Unix Time or timestamps
|
||||
* depending on type. This function handles both cases and returns a nicely formatted date.
|
||||
*
|
||||
* @param date
|
||||
* @returns string Localestring for date
|
||||
*/
|
||||
export function parseDate(date?: string | number): string {
|
||||
if (!date) {
|
||||
return 'Invalid date';
|
||||
}
|
||||
const d = fromUnixTime(+date).toLocaleString();
|
||||
|
||||
if (d === 'Invalid Date') {
|
||||
return new Date(date).toLocaleString();
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
@@ -48,6 +48,11 @@ export const OracleDetails = ({
|
||||
? dataSource.dataSourceSpec.spec.data.sourceType.sourceType.sourceChainId.toString()
|
||||
: undefined;
|
||||
|
||||
const requiredConfirmations =
|
||||
(sourceType.sourceType.__typename === 'EthCallSpec' &&
|
||||
sourceType.sourceType.requiredConfirmations) ||
|
||||
'';
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TableWithTbody className="mb-2">
|
||||
@@ -64,15 +69,23 @@ export const OracleDetails = ({
|
||||
{getStatusString(dataSource.dataSourceSpec.spec.status)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<OracleMarkets id={id} />
|
||||
<OracleSigners sourceType={sourceType} />
|
||||
<OracleEthSource sourceType={sourceType} chain={chain} />
|
||||
<OracleMarkets id={id} />
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">{t('Filter')}</TableHeader>
|
||||
<TableHeader scope="row" className="pt-1 align-text-top">
|
||||
{t('Filter')}
|
||||
</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
<OracleFilter data={dataSource} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{requiredConfirmations && requiredConfirmations > 0 && (
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">{t('Required Confirmations')}</TableHeader>
|
||||
<TableCell modifier="bordered">{requiredConfirmations}</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableWithTbody>
|
||||
{dataConnection ? <OracleData data={dataConnection} /> : null}
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
import compact from 'lodash/compact';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { RouteTitle } from '../../../components/route-title';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useDocumentTitle } from '../../../hooks/use-document-title';
|
||||
import { useScrollToLocation } from '../../../hooks/scroll-to-location';
|
||||
import { useExplorerOracleFormMarketsQuery } from '../__generated__/OraclesForMarkets';
|
||||
import { MarketLink } from '../../../components/links';
|
||||
import { OracleLink } from '../../../components/links/oracle-link/oracle-link';
|
||||
import { useState } from 'react';
|
||||
import { MarketStateMapping } from '@vegaprotocol/types';
|
||||
import type { MarketState } from '@vegaprotocol/types';
|
||||
|
||||
const cellSpacing = 'px-3';
|
||||
import { OraclesTable } from '../../../components/oracle-table';
|
||||
|
||||
const Oracles = () => {
|
||||
const { data, loading, error } = useExplorerOracleFormMarketsQuery({
|
||||
@@ -21,8 +14,6 @@ const Oracles = () => {
|
||||
useDocumentTitle(['Oracles']);
|
||||
useScrollToLocation();
|
||||
|
||||
const [hoveredOracle, setHoveredOracle] = useState('');
|
||||
|
||||
return (
|
||||
<section>
|
||||
<RouteTitle data-testid="oracle-specs-heading">{t('Oracles')}</RouteTitle>
|
||||
@@ -38,148 +29,7 @@ const Oracles = () => {
|
||||
data.oracleSpecsConnection.edges?.length === 0
|
||||
}
|
||||
>
|
||||
<table className="text-left">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className={cellSpacing}>Market</th>
|
||||
<th className={cellSpacing}>Type</th>
|
||||
<th className={cellSpacing}>State</th>
|
||||
<th className={cellSpacing}>Settlement</th>
|
||||
<th className={cellSpacing}>Termination</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data?.marketsConnection?.edges
|
||||
? data.marketsConnection.edges.map((o) => {
|
||||
let hasSeenOracleReports = false;
|
||||
let settlementOracle = '-';
|
||||
let settlementOracleStatus = '-';
|
||||
let terminationOracle = '-';
|
||||
let terminationOracleStatus = '-';
|
||||
|
||||
const id = o?.node.id;
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
o.node.tradableInstrument.instrument.product.__typename ===
|
||||
'Future'
|
||||
) {
|
||||
settlementOracle =
|
||||
o.node.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForSettlementData.id;
|
||||
terminationOracle =
|
||||
o.node.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForTradingTermination.id;
|
||||
settlementOracleStatus =
|
||||
o.node.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForSettlementData.status;
|
||||
terminationOracleStatus =
|
||||
o.node.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForTradingTermination.status;
|
||||
} else if (
|
||||
o.node.tradableInstrument.instrument.product.__typename ===
|
||||
'Perpetual'
|
||||
) {
|
||||
settlementOracle =
|
||||
o.node.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForSettlementData.id;
|
||||
terminationOracle =
|
||||
o.node.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForSettlementSchedule.id;
|
||||
settlementOracleStatus =
|
||||
o.node.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForSettlementData.status;
|
||||
terminationOracleStatus =
|
||||
o.node.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForSettlementSchedule.status;
|
||||
}
|
||||
const oracleInformationUnfiltered =
|
||||
data?.oracleSpecsConnection?.edges?.map((e) =>
|
||||
e && e.node ? e.node : undefined
|
||||
) || [];
|
||||
|
||||
const oracleInformation = compact(oracleInformationUnfiltered)
|
||||
.filter(
|
||||
(o) =>
|
||||
o.dataConnection.edges &&
|
||||
o.dataConnection.edges.length > 0 &&
|
||||
(o.dataSourceSpec.spec.id === settlementOracle ||
|
||||
o.dataSourceSpec.spec.id === terminationOracle)
|
||||
)
|
||||
.at(0);
|
||||
if (oracleInformation) {
|
||||
hasSeenOracleReports = true;
|
||||
}
|
||||
|
||||
const oracleList = `${settlementOracle} ${terminationOracle}`;
|
||||
|
||||
return (
|
||||
<tr
|
||||
id={id}
|
||||
key={id}
|
||||
className={
|
||||
hoveredOracle.length > 0 &&
|
||||
oracleList.indexOf(hoveredOracle) > -1
|
||||
? 'bg-gray-100 dark:bg-gray-800'
|
||||
: ''
|
||||
}
|
||||
data-testid="oracle-details"
|
||||
data-oracles={oracleList}
|
||||
>
|
||||
<td className={cellSpacing}>
|
||||
<MarketLink id={id} />
|
||||
</td>
|
||||
<td className={cellSpacing}>
|
||||
{
|
||||
o.node.tradableInstrument.instrument.product
|
||||
.__typename
|
||||
}
|
||||
</td>
|
||||
<td className={cellSpacing}>
|
||||
{MarketStateMapping[o.node.state as MarketState]}
|
||||
</td>
|
||||
<td
|
||||
className={
|
||||
hoveredOracle.length > 0 &&
|
||||
hoveredOracle === settlementOracle
|
||||
? `indent-1 ${cellSpacing}`
|
||||
: cellSpacing
|
||||
}
|
||||
>
|
||||
<OracleLink
|
||||
id={settlementOracle}
|
||||
status={settlementOracleStatus}
|
||||
hasSeenOracleReports={hasSeenOracleReports}
|
||||
onMouseOver={() => setHoveredOracle(settlementOracle)}
|
||||
onMouseOut={() => setHoveredOracle('')}
|
||||
/>
|
||||
</td>
|
||||
<td
|
||||
className={
|
||||
hoveredOracle.length > 0 &&
|
||||
hoveredOracle === terminationOracle
|
||||
? `indent-1 ${cellSpacing}`
|
||||
: cellSpacing
|
||||
}
|
||||
>
|
||||
<OracleLink
|
||||
id={terminationOracle}
|
||||
status={terminationOracleStatus}
|
||||
hasSeenOracleReports={hasSeenOracleReports}
|
||||
onMouseOver={() =>
|
||||
setHoveredOracle(terminationOracle)
|
||||
}
|
||||
onMouseOut={() => setHoveredOracle('')}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
<OraclesTable data={data} />
|
||||
</AsyncRenderer>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useMemo, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { SubHeading } from '../../../components/sub-heading';
|
||||
import { toNonHex } from '../../../components/search/detect-search';
|
||||
import { useTxsData } from '../../../hooks/use-txs-data';
|
||||
import { getInitialFilters, useTxsData } from '../../../hooks/use-txs-data';
|
||||
import { TxsInfiniteList } from '../../../components/txs';
|
||||
import { PageHeader } from '../../../components/page-header';
|
||||
import { useDocumentTitle } from '../../../hooks/use-document-title';
|
||||
@@ -15,16 +15,18 @@ import { PartyBlockAccounts } from './components/party-block-accounts';
|
||||
import { isValidPartyId } from './components/party-id-error';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { TxsListNavigation } from '../../../components/txs/tx-list-navigation';
|
||||
import type { FilterOption } from '../../../components/txs/tx-filter';
|
||||
import { AllFilterOptions, TxsFilter } from '../../../components/txs/tx-filter';
|
||||
import {
|
||||
TxsFilter,
|
||||
type FilterOption,
|
||||
} from '../../../components/txs/tx-filter';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
type Params = { party: string };
|
||||
|
||||
const Party = () => {
|
||||
const [params] = useSearchParams();
|
||||
const [filters, setFilters] = useState(getInitialFilters(params));
|
||||
|
||||
const [filters, setFilters] = useState(new Set(AllFilterOptions));
|
||||
const { party } = useParams<Params>();
|
||||
|
||||
useDocumentTitle(['Public keys', party || '-']);
|
||||
|
||||
@@ -5,9 +5,8 @@ import Home from './home';
|
||||
import OraclePage from './oracles';
|
||||
import Oracles from './oracles/home';
|
||||
import { Oracle } from './oracles/id';
|
||||
import Party from './parties';
|
||||
import { Parties } from './parties/home';
|
||||
import { Party as PartySingle } from './parties/id';
|
||||
import { Party } from './parties/id';
|
||||
import { ValidatorsPage } from './validators';
|
||||
import Genesis from './genesis';
|
||||
import { Block } from './blocks/id';
|
||||
@@ -17,6 +16,7 @@ import { TxsList } from './txs/home';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Routes } from './route-names';
|
||||
import { NetworkParameters } from './network-parameters';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import type { Params, RouteObject } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { MarketPage, MarketsPage } from './markets';
|
||||
@@ -31,6 +31,7 @@ import { Disclaimer } from './pages/disclaimer';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import RestrictedPage from './restricted';
|
||||
import { NetworkTreasury } from './treasury';
|
||||
import { MarketOraclesPage } from './markets/market-oracles-page';
|
||||
|
||||
export type Navigable = {
|
||||
path: string;
|
||||
@@ -67,7 +68,7 @@ export const useRouterConfig = () => {
|
||||
? [
|
||||
{
|
||||
path: Routes.PARTIES,
|
||||
element: <Party />,
|
||||
element: <Outlet />,
|
||||
handle: {
|
||||
name: t('Parties'),
|
||||
text: t('Parties'),
|
||||
@@ -80,12 +81,12 @@ export const useRouterConfig = () => {
|
||||
},
|
||||
{
|
||||
path: ':party',
|
||||
element: <Party />,
|
||||
element: <Outlet />,
|
||||
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <PartySingle />,
|
||||
element: <Party />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.PARTIES, params.party)}>
|
||||
@@ -96,7 +97,7 @@ export const useRouterConfig = () => {
|
||||
},
|
||||
{
|
||||
path: 'assets',
|
||||
element: <Party />,
|
||||
element: <Outlet />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.PARTIES, params.party)}>
|
||||
@@ -199,12 +200,36 @@ export const useRouterConfig = () => {
|
||||
},
|
||||
{
|
||||
path: ':marketId',
|
||||
element: <MarketPage />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<MarketLink id={params.marketId as string} />
|
||||
),
|
||||
},
|
||||
element: <Outlet />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <MarketPage />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<MarketLink id={params.marketId as string} />
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'oracles',
|
||||
element: <Outlet />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<MarketLink id={params.marketId as string} />
|
||||
),
|
||||
},
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <MarketOraclesPage />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => t('Oracles'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -33,7 +33,10 @@ export const NetworkAccountsTable = () => {
|
||||
return (
|
||||
<section className="md:flex md:flex-row flex-wrap">
|
||||
{c.map((a) => (
|
||||
<div className="basis-1/2 md:basis-1/4">
|
||||
<div
|
||||
className="basis-1/2 md:basis-1/4"
|
||||
key={`${a.assetId}-${a.balance}`}
|
||||
>
|
||||
<div className="bg-white rounded overflow-hidden shadow-lg dark:bg-black dark:border-slate-500 dark:border">
|
||||
<div className="text-center p-6 bg-gray-100 dark:bg-slate-900 border-b dark:border-slate-500">
|
||||
<p className="flex justify-center">
|
||||
|
||||
@@ -16,19 +16,21 @@ import type { DeepPartial } from '@apollo/client/utilities';
|
||||
|
||||
describe('typeLabel', () => {
|
||||
it('should return "Transfer" for "OneOffTransfer" kind', () => {
|
||||
expect(typeLabel('OneOffTransfer')).toBe('Transfer');
|
||||
expect(typeLabel('OneOffTransfer')).toBe('Transfer - one time');
|
||||
});
|
||||
|
||||
it('should return "Transfer" for "RecurringTransfer" kind', () => {
|
||||
expect(typeLabel('RecurringTransfer')).toBe('Transfer');
|
||||
expect(typeLabel('RecurringTransfer')).toBe('Transfer - repeating');
|
||||
});
|
||||
|
||||
it('should return "Governance" for "OneOffGovernanceTransfer" kind', () => {
|
||||
expect(typeLabel('OneOffGovernanceTransfer')).toBe('Governance');
|
||||
expect(typeLabel('OneOffGovernanceTransfer')).toBe('Governance - one time');
|
||||
});
|
||||
|
||||
it('should return "Governance" for "RecurringGovernanceTransfer" kind', () => {
|
||||
expect(typeLabel('RecurringGovernanceTransfer')).toBe('Governance');
|
||||
expect(typeLabel('RecurringGovernanceTransfer')).toBe(
|
||||
'Governance - repeating'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return "Unknown" for unknown kind', () => {
|
||||
@@ -256,7 +258,7 @@ describe('NetworkTransfersTable', () => {
|
||||
expect(screen.getByTestId('from-account').textContent).toEqual('Treasury');
|
||||
expect(screen.getByTestId('to-account').textContent).toEqual('7100…97a0');
|
||||
expect(screen.getByTestId('transfer-kind').textContent).toEqual(
|
||||
'Governance'
|
||||
'Governance - one time'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import { useMemo } from 'react';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
import ProposalLink from '../../../components/links/proposal-link/proposal-link';
|
||||
|
||||
export const colours = {
|
||||
INCOMING: '!fill-vega-green-600 text-vega-green-600 mr-2',
|
||||
@@ -50,14 +51,24 @@ export function getToAccountTypeLabel(type?: AccountType): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function isGovernanceTransfer(kind?: string): boolean {
|
||||
if (kind && kind.includes('Governance')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function typeLabel(kind?: string): string {
|
||||
switch (kind) {
|
||||
case 'OneOffTransfer':
|
||||
return t('Transfer - one time');
|
||||
case 'RecurringTransfer':
|
||||
return t('Transfer');
|
||||
return t('Transfer - repeating');
|
||||
case 'OneOffGovernanceTransfer':
|
||||
return t('Governance - one time');
|
||||
case 'RecurringGovernanceTransfer':
|
||||
return t('Governance');
|
||||
return t('Governance - repeating');
|
||||
default:
|
||||
return t('Unknown');
|
||||
}
|
||||
@@ -239,6 +250,11 @@ export const NetworkTransfersTable = () => {
|
||||
>
|
||||
{a && typeLabel(a.kind.__typename)}
|
||||
</span>
|
||||
{isGovernanceTransfer(a?.kind.__typename) && a?.id && (
|
||||
<span className="ml-4">
|
||||
<ProposalLink id={a?.id} text="View" />
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import { RouteTitle } from '../../components/route-title';
|
||||
import { NetworkAccountsTable } from './components/network-accounts-table';
|
||||
import { NetworkTransfersTable } from './components/network-transfers-table';
|
||||
import GovernanceLink from '../../components/links/governance-link/governance-link';
|
||||
|
||||
export type NonZeroAccount = {
|
||||
assetId: string;
|
||||
@@ -16,7 +17,33 @@ export const NetworkTreasury = () => {
|
||||
return (
|
||||
<section>
|
||||
<RouteTitle data-testid="block-header">{t(`Treasury`)}</RouteTitle>
|
||||
<div>
|
||||
<details className="w-full md:w-3/5 cursor-pointer shadow-lg p-5 dark:border-l-2 dark:border-vega-green">
|
||||
<summary>{t('About the Network Treasury')}</summary>
|
||||
<section className="mt-4 b-1 border-grey">
|
||||
<p className="mb-2">
|
||||
The network treasury can hold funds from any active settlement asset
|
||||
on the network. It is funded periodically by transfers from Gobalsky
|
||||
as part of the Community Adoption Fund (CAF), but in future may
|
||||
receive funds from any sources.
|
||||
</p>
|
||||
<p className="mb-2">
|
||||
Funds in the network treasury can be used by creating governance
|
||||
initiated transfers via{' '}
|
||||
<GovernanceLink text={t('community governance')} />. These transfers
|
||||
can be initiated by anyone and be used to fund reward pools, or can
|
||||
be used to fund other activities the{' '}
|
||||
<abbr className="decoration-dotted" title="Community Adoption Fund">
|
||||
CAF
|
||||
</abbr>{' '}
|
||||
is exploring.
|
||||
</p>
|
||||
<p>
|
||||
This page shows details of the balances in the treasury, pending
|
||||
transfers, and historic transfer movements to and from the treasury.
|
||||
</p>
|
||||
</section>
|
||||
</details>
|
||||
<div className="mt-6">
|
||||
<NetworkAccountsTable />
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
const { join } = require('path');
|
||||
const { createGlobPatternsForDependencies } = require('@nx/react/tailwind');
|
||||
const theme = require('../../libs/tailwindcss-config/src/theme');
|
||||
const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom-classes');
|
||||
const { theme } = require('../../libs/tailwindcss-config/src/theme');
|
||||
const {
|
||||
vegaCustomClasses,
|
||||
} = require('../../libs/tailwindcss-config/src/vega-custom-classes');
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
navigateTo,
|
||||
navigation,
|
||||
turnTelemetryOff,
|
||||
setRiskAccepted,
|
||||
} from '../../support/common.functions';
|
||||
import {
|
||||
clickOnValidatorFromList,
|
||||
@@ -57,6 +58,7 @@ context(
|
||||
// 1002-STKE-002, 1002-STKE-032
|
||||
before('visit staking tab and connect vega wallet', function () {
|
||||
cy.visit('/');
|
||||
setRiskAccepted();
|
||||
ethereumWalletConnect();
|
||||
cy.connectVegaWallet();
|
||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
navigateTo,
|
||||
navigation,
|
||||
turnTelemetryOff,
|
||||
setRiskAccepted,
|
||||
} from '../../support/common.functions';
|
||||
import {
|
||||
stakingPageAssociateTokens,
|
||||
@@ -57,6 +58,7 @@ context(
|
||||
function () {
|
||||
cy.clearLocalStorage();
|
||||
turnTelemetryOff();
|
||||
setRiskAccepted();
|
||||
cy.mockChainId();
|
||||
cy.reload();
|
||||
waitForSpinner();
|
||||
|
||||
@@ -79,23 +79,23 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have information on active nodes', function () {
|
||||
it.skip('should have information on active nodes', function () {
|
||||
cy.getByTestId('node-information')
|
||||
.first()
|
||||
.should('contain.text', '1')
|
||||
.should('contain.text', '2')
|
||||
.and('contain.text', 'active nodes');
|
||||
});
|
||||
|
||||
it('should have information on consensus nodes', function () {
|
||||
it.skip('should have information on consensus nodes', function () {
|
||||
cy.getByTestId('node-information')
|
||||
.last()
|
||||
.should('contain.text', '1')
|
||||
.should('contain.text', '2')
|
||||
.and('contain.text', 'consensus nodes');
|
||||
});
|
||||
|
||||
it('should contain link to specific validators', function () {
|
||||
it.skip('should contain link to specific validators', function () {
|
||||
cy.getByTestId('validators')
|
||||
.should('have.length', '1')
|
||||
.should('have.length', '2')
|
||||
.each(($validator) => {
|
||||
cy.wrap($validator).find('a').should('have.attr', 'href');
|
||||
});
|
||||
@@ -153,7 +153,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
.invoke('text')
|
||||
.should('not.eq', currentBlockHeight);
|
||||
});
|
||||
cy.getByTestId('subscription-cell').should('have.text', 'Yes');
|
||||
cy.getByTestId('subscription-cell').should('be.be.visible');
|
||||
});
|
||||
cy.getByTestId('connect').should('be.disabled');
|
||||
cy.getByTestId('node-url-custom').click({ force: true });
|
||||
|
||||
@@ -34,12 +34,6 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
|
||||
cy.connectPublicKey(vegaWalletPubKey);
|
||||
});
|
||||
|
||||
it('Able to connect public key using url', function () {
|
||||
cy.getByTestId('exit-view').click();
|
||||
cy.visit(`/?address=${vegaWalletPubKey}`);
|
||||
verifyConnectedToPubKey();
|
||||
});
|
||||
|
||||
it.skip('Able to connect public key via wallet and view assets in wallet', function () {
|
||||
verifyConnectedToPubKey();
|
||||
cy.getByTestId('currency-title', { timeout: 10000 })
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import {
|
||||
navigation,
|
||||
setRiskAccepted,
|
||||
verifyPageHeader,
|
||||
verifyTabHighlighted,
|
||||
} from '../../support/common.functions';
|
||||
@@ -187,6 +188,7 @@ context('Validators Page - verify elements on page', function () {
|
||||
before('connect wallets and click on validator', function () {
|
||||
cy.mockChainId();
|
||||
cy.visit('/validators');
|
||||
setRiskAccepted();
|
||||
cy.connectVegaWallet();
|
||||
clickOnValidatorFromList(0);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { truncateByChars } from '@vegaprotocol/utils';
|
||||
import { waitForSpinner } from '../../support/common.functions';
|
||||
import {
|
||||
setRiskAccepted,
|
||||
waitForSpinner,
|
||||
} from '../../support/common.functions';
|
||||
import {
|
||||
vegaWalletFaucetAssetsWithoutCheck,
|
||||
vegaWalletTeardown,
|
||||
@@ -11,7 +14,6 @@ const connectButton = 'connect-vega-wallet';
|
||||
const getVegaLink = 'link';
|
||||
const dialog = '[role="dialog"]:visible';
|
||||
const dialogHeader = 'dialog-title';
|
||||
const walletDialogHeader = 'wallet-dialog-title';
|
||||
const connectorsList = 'connectors-list';
|
||||
const dialogCloseBtn = 'dialog-close';
|
||||
const accountNo = 'vega-account-truncated';
|
||||
@@ -34,6 +36,7 @@ context(
|
||||
() => {
|
||||
before('visit token home page', () => {
|
||||
cy.visit('/');
|
||||
setRiskAccepted();
|
||||
cy.get(walletContainer, { timeout: 60000 }).should('be.visible');
|
||||
});
|
||||
|
||||
@@ -63,17 +66,12 @@ context(
|
||||
|
||||
it('should have Connect Vega header visible', () => {
|
||||
cy.get(dialog).within(() => {
|
||||
cy.getByTestId(walletDialogHeader)
|
||||
cy.getByTestId(connectorsList)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Get a Vega wallet');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have jsonRpc and hosted connection options visible on list', function () {
|
||||
cy.getByTestId(connectorsList).within(() => {
|
||||
cy.getByTestId('connector-jsonRpc')
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Use the Desktop App/CLI');
|
||||
.and(
|
||||
'have.text',
|
||||
'Get the Vega WalletGet MetaMask>_Command Line WalletView as public key'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,7 +86,6 @@ context(
|
||||
before('connect vega wallet', function () {
|
||||
cy.mockChainId();
|
||||
cy.visit('/');
|
||||
cy.wait('@ChainId');
|
||||
cy.connectVegaWallet();
|
||||
vegaWalletTeardown();
|
||||
});
|
||||
|
||||
@@ -102,6 +102,12 @@ export function turnTelemetryOff() {
|
||||
);
|
||||
}
|
||||
|
||||
export function setRiskAccepted() {
|
||||
cy.window().then((win) =>
|
||||
win.localStorage.setItem('vega_wallet_risk_accepted', 'true')
|
||||
);
|
||||
}
|
||||
|
||||
export function dissociateFromSecondWalletKey() {
|
||||
const secondWalletKey = Cypress.env('vegaWalletPublicKey2Short');
|
||||
cy.getByTestId('vega-in-wallet')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_ENV=VALIDATOR_TESTNET
|
||||
NX_VEGA_ENV=VALIDATORS_TESTNET
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml
|
||||
NX_VEGA_URL=https://api-validators-testnet.vega.rocks/graphql
|
||||
NX_VEGA_REST=https://api-validators-testnet.vega.rocks/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { toBigNum } from '@vegaprotocol/utils';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet, useEagerConnect } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet, useEagerConnect } from '@vegaprotocol/wallet-react';
|
||||
import { useFeatureFlags, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import React, { Suspense } from 'react';
|
||||
@@ -15,20 +15,6 @@ import {
|
||||
} from './contexts/app-state/app-state-context';
|
||||
import { useContracts } from './contexts/contracts/contracts-context';
|
||||
import { useRefreshAssociatedBalances } from './hooks/use-refresh-associated-balances';
|
||||
import { useConnectors } from './lib/vega-connectors';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
const useVegaWalletEagerConnect = () => {
|
||||
const connectors = useConnectors();
|
||||
const vegaConnecting = useEagerConnect(connectors);
|
||||
const { pubKey, connect } = useVegaWallet();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [query] = React.useState(searchParams.get('address'));
|
||||
if (query && !pubKey) {
|
||||
connect(connectors.view);
|
||||
}
|
||||
return vegaConnecting;
|
||||
};
|
||||
|
||||
export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
@@ -40,9 +26,9 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||
const { token, staking, vesting } = useContracts();
|
||||
const setAssociatedBalances = useRefreshAssociatedBalances();
|
||||
const [balancesLoaded, setBalancesLoaded] = React.useState(false);
|
||||
const vegaConnecting = useVegaWalletEagerConnect();
|
||||
const vegaWalletStatus = useEagerConnect();
|
||||
|
||||
const loaded = balancesLoaded && !vegaConnecting;
|
||||
const loaded = balancesLoaded && vegaWalletStatus !== 'connecting';
|
||||
|
||||
React.useEffect(() => {
|
||||
const run = async () => {
|
||||
@@ -183,3 +169,5 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||
}
|
||||
return <Suspense fallback={loading}>{children}</Suspense>;
|
||||
};
|
||||
|
||||
AppLoader.displayName = 'AppLoader';
|
||||
|
||||
+31
-141
@@ -1,7 +1,6 @@
|
||||
import './i18n';
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { BrowserRouter as Router, useLocation } from 'react-router-dom';
|
||||
import { AppLoader } from './app-loader';
|
||||
import { NetworkInfo } from '@vegaprotocol/network-info';
|
||||
@@ -26,7 +25,7 @@ import {
|
||||
} from '@vegaprotocol/web3';
|
||||
import { Web3Provider } from '@vegaprotocol/web3';
|
||||
import { VegaWalletDialogs } from './components/vega-wallet-dialogs';
|
||||
import { VegaWalletProvider, useChainId } from '@vegaprotocol/wallet';
|
||||
import { WalletProvider } from '@vegaprotocol/wallet-react';
|
||||
import {
|
||||
useVegaTransactionManager,
|
||||
useVegaTransactionUpdater,
|
||||
@@ -36,26 +35,21 @@ import { useEthereumConfig } from '@vegaprotocol/web3';
|
||||
import {
|
||||
useEnvironment,
|
||||
NetworkLoader,
|
||||
useInitializeEnv,
|
||||
NodeGuard,
|
||||
NodeSwitcherDialog,
|
||||
useNodeSwitcherStore,
|
||||
DocsLinks,
|
||||
NodeFailure,
|
||||
AppLoader as Loader,
|
||||
useInitializeEnv,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { ENV } from './config';
|
||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||
import { CreateWithdrawalDialog } from '@vegaprotocol/withdraws';
|
||||
import { SplashLoader } from './components/splash-loader';
|
||||
import { ToastsManager } from './toasts-manager';
|
||||
import {
|
||||
TelemetryDialog,
|
||||
TELEMETRY_ON,
|
||||
} from './components/telemetry-dialog/telemetry-dialog';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { TelemetryDialog } from './components/telemetry-dialog/telemetry-dialog';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { isPartyNotFoundError } from './lib/party';
|
||||
import { useSentryInit } from './hooks/use-sentry-init';
|
||||
import { useVegaWalletConfig } from './hooks/use-vega-wallet-config';
|
||||
|
||||
const cache: InMemoryCacheConfig = {
|
||||
typePolicies: {
|
||||
@@ -104,32 +98,12 @@ const Web3Container = ({
|
||||
/** Ethereum provider url */
|
||||
providerUrl: string;
|
||||
}) => {
|
||||
const InitializeHandlers = () => {
|
||||
useVegaTransactionManager();
|
||||
useVegaTransactionUpdater();
|
||||
useEthTransactionManager();
|
||||
useEthTransactionUpdater();
|
||||
useEthWithdrawApprovalsManager();
|
||||
return null;
|
||||
};
|
||||
|
||||
const [connectors, initializeConnectors] = useWeb3ConnectStore((store) => [
|
||||
store.connectors,
|
||||
store.initialize,
|
||||
]);
|
||||
const {
|
||||
ETHEREUM_PROVIDER_URL,
|
||||
ETH_LOCAL_PROVIDER_URL,
|
||||
ETH_WALLET_MNEMONIC,
|
||||
VEGA_ENV,
|
||||
VEGA_URL,
|
||||
VEGA_EXPLORER_URL,
|
||||
CHROME_EXTENSION_URL,
|
||||
MOZILLA_EXTENSION_URL,
|
||||
VEGA_WALLET_URL,
|
||||
} = useEnvironment();
|
||||
|
||||
const vegaChainId = useChainId(VEGA_URL);
|
||||
const { ETHEREUM_PROVIDER_URL, ETH_LOCAL_PROVIDER_URL, ETH_WALLET_MNEMONIC } =
|
||||
useEnvironment();
|
||||
|
||||
useEffect(() => {
|
||||
if (chainId) {
|
||||
@@ -150,50 +124,31 @@ const Web3Container = ({
|
||||
ETH_LOCAL_PROVIDER_URL,
|
||||
ETH_WALLET_MNEMONIC,
|
||||
]);
|
||||
const sideBar = React.useMemo(() => {
|
||||
return [<EthWallet />, <VegaWallet />];
|
||||
}, []);
|
||||
|
||||
if (connectors.length === 0) {
|
||||
const vegaWalletConfig = useVegaWalletConfig();
|
||||
|
||||
if (!vegaWalletConfig || connectors.length === 0) {
|
||||
// Prevent loading when the connectors are not initialized
|
||||
return <SplashLoader />;
|
||||
}
|
||||
|
||||
if (
|
||||
!VEGA_URL ||
|
||||
!VEGA_WALLET_URL ||
|
||||
!VEGA_EXPLORER_URL ||
|
||||
!DocsLinks ||
|
||||
!CHROME_EXTENSION_URL ||
|
||||
!MOZILLA_EXTENSION_URL ||
|
||||
!vegaChainId
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Web3Provider connectors={connectors}>
|
||||
<Web3Connector connectors={connectors} chainId={Number(chainId)}>
|
||||
<VegaWalletProvider
|
||||
config={{
|
||||
network: VEGA_ENV,
|
||||
vegaUrl: VEGA_URL,
|
||||
chainId: vegaChainId,
|
||||
vegaWalletServiceUrl: VEGA_WALLET_URL,
|
||||
links: {
|
||||
explorer: VEGA_EXPLORER_URL,
|
||||
concepts: DocsLinks?.VEGA_WALLET_CONCEPTS_URL,
|
||||
chromeExtensionUrl: CHROME_EXTENSION_URL,
|
||||
mozillaExtensionUrl: MOZILLA_EXTENSION_URL,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<WalletProvider config={vegaWalletConfig}>
|
||||
<ContractsProvider>
|
||||
<AppLoader>
|
||||
<BalanceManager>
|
||||
<>
|
||||
<AppLayout>
|
||||
<TemplateSidebar sidebar={sideBar}>
|
||||
<TemplateSidebar
|
||||
sidebar={
|
||||
<>
|
||||
<EthWallet />
|
||||
<VegaWallet />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<AppRouter />
|
||||
</TemplateSidebar>
|
||||
<footer className="p-4 break-all border-t border-neutral-700">
|
||||
@@ -211,7 +166,7 @@ const Web3Container = ({
|
||||
</BalanceManager>
|
||||
</AppLoader>
|
||||
</ContractsProvider>
|
||||
</VegaWalletProvider>
|
||||
</WalletProvider>
|
||||
</Web3Connector>
|
||||
</Web3Provider>
|
||||
);
|
||||
@@ -231,20 +186,9 @@ const ScrollToTop = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const removeQueryParams = (url: string) => {
|
||||
return url.split('?')[0];
|
||||
};
|
||||
|
||||
const AppContainer = () => {
|
||||
const { config, loading, error } = useEthereumConfig();
|
||||
const {
|
||||
VEGA_ENV,
|
||||
VEGA_URL,
|
||||
GIT_COMMIT_HASH,
|
||||
GIT_BRANCH,
|
||||
ETHEREUM_PROVIDER_URL,
|
||||
} = useEnvironment();
|
||||
const [telemetryOn] = useLocalStorage(TELEMETRY_ON);
|
||||
const { VEGA_URL, ETHEREUM_PROVIDER_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
const [nodeSwitcherOpen, setNodeSwitcher] = useNodeSwitcherStore((store) => [
|
||||
store.dialogOpen,
|
||||
@@ -254,70 +198,7 @@ const AppContainer = () => {
|
||||
// Hacky skip all the loading & web3 init for geo restricted users
|
||||
const isRestricted = document?.location?.pathname?.includes('/restricted');
|
||||
|
||||
useEffect(() => {
|
||||
if (ENV.dsn && telemetryOn === 'true') {
|
||||
Sentry.init({
|
||||
dsn: ENV.dsn,
|
||||
tracesSampleRate: 0.1,
|
||||
enabled: true,
|
||||
environment: VEGA_ENV,
|
||||
release: GIT_COMMIT_HASH,
|
||||
beforeSend(event, hint) {
|
||||
const error = hint?.originalException;
|
||||
const errorIsString = typeof error === 'string';
|
||||
const errorIsObject = error instanceof Error;
|
||||
const requestUrl = event.request?.url;
|
||||
const transaction = event.transaction;
|
||||
|
||||
if (
|
||||
(errorIsString && isPartyNotFoundError({ message: error })) ||
|
||||
(errorIsObject && isPartyNotFoundError(error))
|
||||
) {
|
||||
// This error is caused by a pubkey making an API request before
|
||||
// it has interacted with the chain. This isn't needed in Sentry.
|
||||
return null;
|
||||
}
|
||||
|
||||
const updatedRequest =
|
||||
requestUrl && requestUrl.includes('/claim?')
|
||||
? { ...event.request, url: removeQueryParams(requestUrl) }
|
||||
: event.request;
|
||||
|
||||
const updatedTransaction =
|
||||
transaction && transaction.includes('/claim?')
|
||||
? removeQueryParams(transaction)
|
||||
: transaction;
|
||||
|
||||
const updatedBreadcrumbs = event.breadcrumbs?.map((breadcrumb) => {
|
||||
if (
|
||||
breadcrumb.type === 'navigation' &&
|
||||
breadcrumb.data?.to?.includes('/claim?')
|
||||
) {
|
||||
return {
|
||||
...breadcrumb,
|
||||
data: {
|
||||
...breadcrumb.data,
|
||||
to: removeQueryParams(breadcrumb.data.to),
|
||||
},
|
||||
};
|
||||
}
|
||||
return breadcrumb;
|
||||
});
|
||||
|
||||
return {
|
||||
...event,
|
||||
request: updatedRequest,
|
||||
transaction: updatedTransaction,
|
||||
breadcrumbs: updatedBreadcrumbs ?? event.breadcrumbs,
|
||||
};
|
||||
},
|
||||
});
|
||||
Sentry.setTag('branch', GIT_BRANCH);
|
||||
Sentry.setTag('commit', GIT_COMMIT_HASH);
|
||||
} else {
|
||||
Sentry.close();
|
||||
}
|
||||
}, [GIT_COMMIT_HASH, GIT_BRANCH, VEGA_ENV, telemetryOn]);
|
||||
useSentryInit();
|
||||
|
||||
if (isRestricted) {
|
||||
return (
|
||||
@@ -359,6 +240,15 @@ const AppContainer = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const InitializeHandlers = () => {
|
||||
useVegaTransactionManager();
|
||||
useVegaTransactionUpdater();
|
||||
useEthTransactionManager();
|
||||
useEthTransactionUpdater();
|
||||
useEthWithdrawApprovalsManager();
|
||||
return null;
|
||||
};
|
||||
|
||||
function App() {
|
||||
useInitializeEnv();
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useGetAssociationBreakdown } from '../../hooks/use-get-association-brea
|
||||
import { useGetUserBalances } from '../../hooks/use-get-user-balances';
|
||||
import { useBalances } from '../../lib/balances/balances-store';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet-react';
|
||||
import { useListenForStakingEvents as useListenForAssociationEvents } from '../../hooks/use-listen-for-staking-events';
|
||||
import { useTranches } from '../../lib/tranches/tranches-store';
|
||||
import { useUserTrancheBalances } from '../../routes/redemption/hooks';
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import { useDialogStore } from '@vegaprotocol/wallet-react';
|
||||
|
||||
export const ConnectToVega = () => {
|
||||
const { t } = useTranslation();
|
||||
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
|
||||
openVegaWalletDialog: store.openVegaWalletDialog,
|
||||
}));
|
||||
const openVegaWalletDialog = useDialogStore((store) => store.open);
|
||||
return (
|
||||
<Button
|
||||
onClick={() => {
|
||||
openVegaWalletDialog();
|
||||
}}
|
||||
onClick={openVegaWalletDialog}
|
||||
data-testid="connect-to-vega-wallet-btn"
|
||||
variant="primary"
|
||||
>
|
||||
|
||||
@@ -47,7 +47,7 @@ export const SubHeading = ({
|
||||
|
||||
return (
|
||||
<h2
|
||||
className={classNames('text-2xl font-alpha calt uppercase break-words', {
|
||||
className={classNames('text-2xl font-alpha calt break-words', {
|
||||
'mx-auto': centerContent,
|
||||
'mb-0': !marginBottom,
|
||||
'mb-4': marginBottom,
|
||||
|
||||
+3
-3
@@ -6,8 +6,8 @@ import {
|
||||
Intent,
|
||||
Icon,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '../use-vega-wallet';
|
||||
import { useT } from '../use-t';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface VegaManageDialogProps {
|
||||
dialogOpen: boolean;
|
||||
@@ -18,7 +18,7 @@ export const VegaManageDialog = ({
|
||||
dialogOpen,
|
||||
setDialogOpen,
|
||||
}: VegaManageDialogProps) => {
|
||||
const t = useT();
|
||||
const { t } = useTranslation();
|
||||
const { pubKey, pubKeys, selectPubKey, disconnect } = useVegaWallet();
|
||||
return (
|
||||
<Dialog
|
||||
@@ -1,5 +1,5 @@
|
||||
import classNames from 'classnames';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet-react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { AnnouncementBanner } from '@vegaprotocol/announcements';
|
||||
import { Nav } from '../nav';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Children, type ReactNode } from 'react';
|
||||
|
||||
export interface TemplateSidebarProps {
|
||||
children: React.ReactNode;
|
||||
sidebar: React.ReactNode[];
|
||||
children: ReactNode;
|
||||
sidebar: ReactNode;
|
||||
}
|
||||
|
||||
export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
|
||||
@@ -12,9 +12,9 @@ export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
|
||||
{children}
|
||||
</main>
|
||||
<aside className="col-start-2 row-start-1 row-span-2 hidden lg:block p-4 bg-banner bg-contain border-l border-neutral-700">
|
||||
{sidebar.map((Component, i) => (
|
||||
{Children.map(sidebar, (child, i) => (
|
||||
<section className="mb-4 last:mb-0" key={i}>
|
||||
{Component}
|
||||
{child}
|
||||
</section>
|
||||
))}
|
||||
</aside>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet, useDialogStore } from '@vegaprotocol/wallet-react';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -10,9 +10,7 @@ interface VegaWalletContainerProps {
|
||||
export const VegaWalletContainer = ({ children }: VegaWalletContainerProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
|
||||
openVegaWalletDialog: store.openVegaWalletDialog,
|
||||
}));
|
||||
const openVegaWalletDialog = useDialogStore((store) => store.open);
|
||||
|
||||
if (!pubKey) {
|
||||
return (
|
||||
|
||||
@@ -9,7 +9,7 @@ import Routes from '../../routes/routes';
|
||||
export const RiskMessage = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="bg-vega-light-100 dark:bg-vega-dark-100 p-6 mb-6">
|
||||
<div className="bg-vega-light-100 dark:bg-vega-dark-100 p-6">
|
||||
<ul className="list-[square] ml-4">
|
||||
<li>
|
||||
{t(
|
||||
@@ -23,7 +23,7 @@ export const RiskMessage = () => {
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p className="mb-8">
|
||||
<p>
|
||||
{t(
|
||||
'By using the Vega Governance App, you acknowledge that you have read and understood the'
|
||||
)}{' '}
|
||||
|
||||
@@ -1,25 +1,39 @@
|
||||
import {
|
||||
VegaConnectDialog,
|
||||
VegaManageDialog,
|
||||
ViewAsDialog,
|
||||
} from '@vegaprotocol/wallet';
|
||||
ConnectDialogWithRiskAck,
|
||||
useDialogStore,
|
||||
} from '@vegaprotocol/wallet-react';
|
||||
import {
|
||||
AppStateActionType,
|
||||
useAppState,
|
||||
} from '../../contexts/app-state/app-state-context';
|
||||
import { useConnectors } from '../../lib/vega-connectors';
|
||||
import { RiskMessage } from './risk-message';
|
||||
import { VegaManageDialog } from '../manage-dialog';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
|
||||
export const VegaWalletDialogs = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const { appState, appDispatch } = useAppState();
|
||||
const connectors = useConnectors();
|
||||
const [riskAccepted, setRiskAccepted] = useLocalStorage(
|
||||
'vega_wallet_risk_accepted'
|
||||
);
|
||||
const vegaWalletDialogOpen = useDialogStore((store) => store.isOpen);
|
||||
const setVegaWalletDialog = useDialogStore((store) => store.set);
|
||||
return (
|
||||
<>
|
||||
<VegaConnectDialog
|
||||
connectors={connectors}
|
||||
riskMessage={<RiskMessage />}
|
||||
<ConnectDialogWithRiskAck
|
||||
open={vegaWalletDialogOpen}
|
||||
onChange={setVegaWalletDialog}
|
||||
riskAccepted={
|
||||
VEGA_ENV === Networks.TESTNET ? riskAccepted === 'true' : true
|
||||
}
|
||||
riskAckContent={<RiskMessage />}
|
||||
onRiskAccepted={() => setRiskAccepted('true')}
|
||||
onRiskRejected={() => {
|
||||
setRiskAccepted('false');
|
||||
setVegaWalletDialog(false);
|
||||
}}
|
||||
/>
|
||||
|
||||
<VegaManageDialog
|
||||
dialogOpen={appState.vegaWalletManageOverlay}
|
||||
setDialogOpen={(open) =>
|
||||
@@ -29,8 +43,6 @@ export const VegaWalletDialogs = () => {
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<ViewAsDialog connector={connectors.view} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ import vegaBlack from '../../images/vega_black.png';
|
||||
import vegaVesting from '../../images/vega_vesting.png';
|
||||
import { BigNumber } from '../../lib/bignumber';
|
||||
import { type WalletCardAssetProps } from '../wallet-card';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet-react';
|
||||
import { useContracts } from '../../contexts/contracts/contracts-context';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { ButtonLink, Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ExternalLinks } from '@vegaprotocol/environment';
|
||||
import { useViewAsDialog } from '@vegaprotocol/wallet';
|
||||
import { useConnect } from '@vegaprotocol/wallet-react';
|
||||
|
||||
export const VegaWalletPrompt = () => {
|
||||
const { t } = useTranslation();
|
||||
const setViewAsDialog = useViewAsDialog((state) => state.setOpen);
|
||||
const { connect } = useConnect();
|
||||
return (
|
||||
<>
|
||||
<h3 className="mt-4 mb-2">{t('getWallet')}</h3>
|
||||
@@ -16,7 +16,7 @@ export const VegaWalletPrompt = () => {
|
||||
<ButtonLink
|
||||
className="text-neutral-500"
|
||||
data-testid="view-as-user"
|
||||
onClick={() => setViewAsDialog(true)}
|
||||
onClick={() => connect('viewParty')}
|
||||
>
|
||||
{t('viewAsParty')}
|
||||
</ButtonLink>
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
} from '../wallet-card';
|
||||
import { VegaWalletPrompt } from './vega-wallet-prompt';
|
||||
import { usePollForDelegations } from './hooks';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet, useDialogStore } from '@vegaprotocol/wallet-react';
|
||||
import { Button, ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { toBigNum } from '@vegaprotocol/utils';
|
||||
import { usePendingBalancesStore } from '../../hooks/use-pending-balances-manager';
|
||||
@@ -34,16 +34,17 @@ import omit from 'lodash/omit';
|
||||
|
||||
export const VegaWallet = () => {
|
||||
const { t } = useTranslation();
|
||||
const { pubKey, pubKeys } = useVegaWallet();
|
||||
const { status, pubKey, pubKeys } = useVegaWallet();
|
||||
const pubKeyObj = useMemo(() => {
|
||||
return pubKeys?.find((pk) => pk.publicKey === pubKey);
|
||||
}, [pubKey, pubKeys]);
|
||||
|
||||
const child = !pubKeys ? (
|
||||
<VegaWalletNotConnected />
|
||||
) : (
|
||||
<VegaWalletConnected vegaKeys={pubKeys.map((pk) => pk.publicKey)} />
|
||||
);
|
||||
const child =
|
||||
status === 'connected' ? (
|
||||
<VegaWalletConnected vegaKeys={pubKeys.map((pk) => pk.publicKey)} />
|
||||
) : (
|
||||
<VegaWalletNotConnected />
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="vega-wallet" data-testid="vega-wallet">
|
||||
@@ -75,9 +76,7 @@ export const VegaWallet = () => {
|
||||
|
||||
const VegaWalletNotConnected = () => {
|
||||
const { t } = useTranslation();
|
||||
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
|
||||
openVegaWalletDialog: store.openVegaWalletDialog,
|
||||
}));
|
||||
const openVegaWalletDialog = useDialogStore((store) => store.open);
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
|
||||
@@ -41,7 +41,7 @@ export const ContractAddresses: {
|
||||
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
|
||||
lockedAddress: '0x0', // TODO not deployed to this env
|
||||
},
|
||||
VALIDATOR_TESTNET: {
|
||||
VALIDATORS_TESTNET: {
|
||||
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
|
||||
lockedAddress: '0x0', // TODO not deployed to this env
|
||||
// This is a fallback contract address for the validator testnet network which does not
|
||||
|
||||
@@ -111,3 +111,4 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
|
||||
</ContractsContext.Provider>
|
||||
);
|
||||
};
|
||||
ContractsProvider.displayName = 'ContractsProvider';
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useWeb3React } from '@web3-react/core';
|
||||
|
||||
export const useListenForStakingEvents = (
|
||||
contract: Contract | undefined,
|
||||
vegaPublicKey: string | null,
|
||||
vegaPublicKey: string | undefined,
|
||||
numberOfConfirmations: number
|
||||
) => {
|
||||
const { account } = useWeb3React();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { toBigNum } from '@vegaprotocol/utils';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet-react';
|
||||
import { useEthereumConfig } from '@vegaprotocol/web3';
|
||||
import React from 'react';
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { useEffect } from 'react';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { TELEMETRY_ON } from '../components/telemetry-dialog/telemetry-dialog';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { ENV } from '../config';
|
||||
import { isPartyNotFoundError } from '../lib/party';
|
||||
|
||||
export const useSentryInit = () => {
|
||||
const { VEGA_ENV, GIT_COMMIT_HASH, GIT_BRANCH } = useEnvironment();
|
||||
const [telemetryOn] = useLocalStorage(TELEMETRY_ON);
|
||||
|
||||
useEffect(() => {
|
||||
if (ENV.dsn && telemetryOn === 'true') {
|
||||
Sentry.init({
|
||||
dsn: ENV.dsn,
|
||||
tracesSampleRate: 0.1,
|
||||
enabled: true,
|
||||
environment: VEGA_ENV,
|
||||
release: GIT_COMMIT_HASH,
|
||||
beforeSend(event, hint) {
|
||||
const error = hint?.originalException;
|
||||
const errorIsString = typeof error === 'string';
|
||||
const errorIsObject = error instanceof Error;
|
||||
const requestUrl = event.request?.url;
|
||||
const transaction = event.transaction;
|
||||
|
||||
if (
|
||||
(errorIsString && isPartyNotFoundError({ message: error })) ||
|
||||
(errorIsObject && isPartyNotFoundError(error))
|
||||
) {
|
||||
// This error is caused by a pubkey making an API request before
|
||||
// it has interacted with the chain. This isn't needed in Sentry.
|
||||
return null;
|
||||
}
|
||||
|
||||
const updatedRequest =
|
||||
requestUrl && requestUrl.includes('/claim?')
|
||||
? { ...event.request, url: removeQueryParams(requestUrl) }
|
||||
: event.request;
|
||||
|
||||
const updatedTransaction =
|
||||
transaction && transaction.includes('/claim?')
|
||||
? removeQueryParams(transaction)
|
||||
: transaction;
|
||||
|
||||
const updatedBreadcrumbs = event.breadcrumbs?.map((breadcrumb) => {
|
||||
if (
|
||||
breadcrumb.type === 'navigation' &&
|
||||
breadcrumb.data?.to?.includes('/claim?')
|
||||
) {
|
||||
return {
|
||||
...breadcrumb,
|
||||
data: {
|
||||
...breadcrumb.data,
|
||||
to: removeQueryParams(breadcrumb.data.to),
|
||||
},
|
||||
};
|
||||
}
|
||||
return breadcrumb;
|
||||
});
|
||||
|
||||
return {
|
||||
...event,
|
||||
request: updatedRequest,
|
||||
transaction: updatedTransaction,
|
||||
breadcrumbs: updatedBreadcrumbs ?? event.breadcrumbs,
|
||||
};
|
||||
},
|
||||
});
|
||||
Sentry.setTag('branch', GIT_BRANCH);
|
||||
Sentry.setTag('commit', GIT_COMMIT_HASH);
|
||||
} else {
|
||||
Sentry.close();
|
||||
}
|
||||
}, [GIT_COMMIT_HASH, GIT_BRANCH, VEGA_ENV, telemetryOn]);
|
||||
};
|
||||
|
||||
const removeQueryParams = (url: string) => {
|
||||
return url.split('?')[0];
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
InjectedConnector,
|
||||
JsonRpcConnector,
|
||||
SnapConnector,
|
||||
ViewPartyConnector,
|
||||
createConfig,
|
||||
fairground,
|
||||
validatorsTestnet,
|
||||
stagnet,
|
||||
mainnet,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { CHAIN_IDS, useEnvironment } from '@vegaprotocol/environment';
|
||||
|
||||
export const useVegaWalletConfig = () => {
|
||||
const { VEGA_ENV, VEGA_URL, VEGA_WALLET_URL } = useEnvironment();
|
||||
return useMemo(() => {
|
||||
if (!VEGA_ENV || !VEGA_URL || !VEGA_WALLET_URL) return;
|
||||
|
||||
const injected = new InjectedConnector();
|
||||
|
||||
const jsonRpc = new JsonRpcConnector({
|
||||
url: VEGA_WALLET_URL,
|
||||
});
|
||||
|
||||
const snap = new SnapConnector({
|
||||
node: new URL(VEGA_URL).origin,
|
||||
snapId: 'npm:@vegaprotocol/snap',
|
||||
version: '1.0.1',
|
||||
});
|
||||
|
||||
const viewParty = new ViewPartyConnector();
|
||||
|
||||
const config = createConfig({
|
||||
chains: [mainnet, fairground, validatorsTestnet, stagnet],
|
||||
defaultChainId: CHAIN_IDS[VEGA_ENV],
|
||||
connectors: [injected, snap, jsonRpc, viewParty],
|
||||
});
|
||||
|
||||
return config;
|
||||
}, [VEGA_ENV, VEGA_URL, VEGA_WALLET_URL]);
|
||||
};
|
||||
@@ -31,7 +31,7 @@ i18n
|
||||
load: 'languageOnly',
|
||||
debug: isInDev,
|
||||
// have a common namespace used around the full app
|
||||
ns: ['governance'],
|
||||
ns: ['governance', 'wallet', 'wallet-react', 'assets', 'utils'],
|
||||
defaultNS: 'governance',
|
||||
keySeparator: false, // we use content as keys
|
||||
nsSeparator: false,
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
JsonRpcConnector,
|
||||
ViewConnector,
|
||||
InjectedConnector,
|
||||
SnapConnector,
|
||||
DEFAULT_SNAP_ID,
|
||||
} from '@vegaprotocol/wallet';
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
export const jsonRpc = new JsonRpcConnector();
|
||||
export const injected = new InjectedConnector();
|
||||
export const view = new ViewConnector(urlParams.get('address'));
|
||||
|
||||
export const snap = new SnapConnector(DEFAULT_SNAP_ID);
|
||||
|
||||
export const useConnectors = () => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
return useMemo(
|
||||
() => ({
|
||||
injected,
|
||||
jsonRpc,
|
||||
view,
|
||||
snap: featureFlags.METAMASK_SNAPS ? snap : undefined,
|
||||
}),
|
||||
[featureFlags.METAMASK_SNAPS]
|
||||
);
|
||||
};
|
||||
+7
-12
@@ -5,7 +5,6 @@ import {
|
||||
ProposalState,
|
||||
VoteValue,
|
||||
} from '@vegaprotocol/types';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||
import {
|
||||
generateNoVotes,
|
||||
@@ -16,9 +15,7 @@ import { ProposalHeader, NewTransferSummary } from './proposal-header';
|
||||
import {
|
||||
lastWeek,
|
||||
nextWeek,
|
||||
mockWalletContext,
|
||||
createUserVoteQueryMock,
|
||||
networkParamsQueryMock,
|
||||
} from '../../test-helpers/mocks';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { VoteState } from '../vote-details/use-user-vote';
|
||||
@@ -45,14 +42,12 @@ const renderComponent = (
|
||||
render(
|
||||
<AppStateProvider>
|
||||
<BrowserRouter>
|
||||
<MockedProvider mocks={[networkParamsQueryMock, ...mocks]}>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<ProposalHeader
|
||||
proposal={proposal}
|
||||
isListItem={isListItem}
|
||||
voteState={voteState}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<ProposalHeader
|
||||
proposal={proposal}
|
||||
isListItem={isListItem}
|
||||
voteState={voteState}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</BrowserRouter>
|
||||
</AppStateProvider>
|
||||
@@ -160,7 +155,7 @@ describe('Proposal header', () => {
|
||||
screen.queryByTestId('proposal-description')
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
|
||||
'Update to market: MarketId'
|
||||
/Update to market: MarketId/
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
+4
-1
@@ -38,6 +38,7 @@ import { differenceInHours, format, formatDistanceToNowStrict } from 'date-fns';
|
||||
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
|
||||
import { MarketName } from '../proposal/market-name';
|
||||
import { Indicator } from '../proposal/indicator';
|
||||
import { type ProposalNode } from '../proposal/proposal-utils';
|
||||
|
||||
const ProposalTypeTags = ({
|
||||
proposal,
|
||||
@@ -540,10 +541,12 @@ const BatchProposalStateText = ({
|
||||
|
||||
export const ProposalHeader = ({
|
||||
proposal,
|
||||
restData,
|
||||
isListItem = true,
|
||||
voteState,
|
||||
}: {
|
||||
proposal: Proposal | BatchProposal;
|
||||
restData?: ProposalNode | null;
|
||||
isListItem?: boolean;
|
||||
voteState?: VoteState | null;
|
||||
}) => {
|
||||
@@ -595,7 +598,7 @@ export const ProposalHeader = ({
|
||||
)}
|
||||
</div>
|
||||
<ProposalDetails proposal={proposal} />
|
||||
<VoteBreakdown proposal={proposal} />
|
||||
<VoteBreakdown proposal={proposal} restData={restData} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+33
-46
@@ -19,6 +19,7 @@ import {
|
||||
getSigners,
|
||||
MarginScalingFactorsPanel,
|
||||
marketInfoProvider,
|
||||
PriceMonitoringSettingsInfoPanel,
|
||||
} from '@vegaprotocol/markets';
|
||||
import {
|
||||
Button,
|
||||
@@ -49,7 +50,7 @@ export const useMarketDataDialogStore = create<MarketDataDialogState>(
|
||||
const marketDataHeaderStyles =
|
||||
'font-alpha calt text-base border-b border-vega-dark-200 mt-2 py-2';
|
||||
|
||||
export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
|
||||
export const ProposalMarketData = ({ marketId }: { marketId: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, open, close } = useMarketDataDialogStore();
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
@@ -58,7 +59,7 @@ export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
|
||||
dataProvider: marketInfoProvider,
|
||||
skipUpdates: true,
|
||||
variables: {
|
||||
marketId: proposalId,
|
||||
marketId: marketId,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -71,7 +72,7 @@ export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
|
||||
},
|
||||
});
|
||||
|
||||
if (!marketData || !parentMarketData) {
|
||||
if (!marketData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -133,13 +134,13 @@ export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
|
||||
<h2 className={marketDataHeaderStyles}>{t('Key details')}</h2>
|
||||
<KeyDetailsInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
parentMarket={parentMarketData ? parentMarketData : undefined}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Instrument')}</h2>
|
||||
<InstrumentInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
parentMarket={parentMarketData ? parentMarketData : undefined}
|
||||
/>
|
||||
|
||||
{settlementData &&
|
||||
@@ -155,7 +156,7 @@ export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
|
||||
isParentSettlementDataEqual ||
|
||||
isParentSettlementScheduleDataEqual
|
||||
? undefined
|
||||
: parentMarketData
|
||||
: parentMarketData || undefined
|
||||
}
|
||||
/>
|
||||
</>
|
||||
@@ -168,7 +169,9 @@ export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
|
||||
market={marketData}
|
||||
type="settlementData"
|
||||
parentMarket={
|
||||
isParentSettlementDataEqual ? undefined : parentMarketData
|
||||
isParentSettlementDataEqual
|
||||
? undefined
|
||||
: parentMarketData || undefined
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -184,7 +187,7 @@ export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
|
||||
parentMarket={
|
||||
isParentTerminationDataEqual
|
||||
? undefined
|
||||
: parentMarketData
|
||||
: parentMarketData || undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
@@ -202,7 +205,7 @@ export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
|
||||
parentMarket={
|
||||
isParentSettlementScheduleDataEqual
|
||||
? undefined
|
||||
: parentMarketData
|
||||
: parentMarketData || undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
@@ -216,19 +219,19 @@ export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
|
||||
<h2 className={marketDataHeaderStyles}>{t('Settlement assets')}</h2>
|
||||
<SettlementAssetInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
parentMarket={parentMarketData || undefined}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Metadata')}</h2>
|
||||
<MetadataInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
parentMarket={parentMarketData || undefined}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Risk model')}</h2>
|
||||
<RiskModelInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
parentMarket={parentMarketData || undefined}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
@@ -236,61 +239,45 @@ export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
|
||||
</h2>
|
||||
<MarginScalingFactorsPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
parentMarket={parentMarketData || undefined}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Risk factors')}</h2>
|
||||
<RiskFactorsInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
parentMarket={parentMarketData || undefined}
|
||||
/>
|
||||
|
||||
{showParentPriceMonitoringBounds &&
|
||||
(
|
||||
parentMarketData?.priceMonitoringSettings?.parameters
|
||||
?.triggers || []
|
||||
).map((_, triggerIndex) => (
|
||||
<>
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t(`Parent price monitoring bounds ${triggerIndex + 1}`)}
|
||||
</h2>
|
||||
|
||||
<div className="text-vega-dark-300 line-through">
|
||||
<PriceMonitoringBoundsInfoPanel
|
||||
market={parentMarketData}
|
||||
triggerIndex={triggerIndex}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
|
||||
{(
|
||||
marketData.priceMonitoringSettings?.parameters?.triggers || []
|
||||
).map((_, triggerIndex) => (
|
||||
{showParentPriceMonitoringBounds && (
|
||||
// shows bounds for parent market
|
||||
<>
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t(`Price monitoring bounds ${triggerIndex + 1}`)}
|
||||
{t('Parent price monitoring bounds')}
|
||||
</h2>
|
||||
|
||||
<PriceMonitoringBoundsInfoPanel
|
||||
market={marketData}
|
||||
triggerIndex={triggerIndex}
|
||||
/>
|
||||
<div className="text-vega-dark-300 line-through">
|
||||
<PriceMonitoringBoundsInfoPanel market={parentMarketData} />
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
)}
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t('Price monitoring settings')}
|
||||
</h2>
|
||||
<PriceMonitoringSettingsInfoPanel market={marketData} />
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t('Liquidity monitoring parameters')}
|
||||
</h2>
|
||||
<LiquidityMonitoringParametersInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
parentMarket={parentMarketData || undefined}
|
||||
/>
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t('Liquidity price range')}
|
||||
</h2>
|
||||
<LiquidityPriceRangeInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
parentMarket={parentMarketData || undefined}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
@@ -298,7 +285,7 @@ export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
|
||||
</h2>
|
||||
<LiquiditySLAParametersInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
parentMarket={parentMarketData || undefined}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
+16
-2
@@ -17,17 +17,20 @@ import { type ProposalNode } from './proposal-utils';
|
||||
import { Lozenge } from '@vegaprotocol/ui-toolkit';
|
||||
import { Indicator } from './indicator';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { determineId } from '@vegaprotocol/wallet';
|
||||
|
||||
export const ProposalChangeDetails = ({
|
||||
proposal,
|
||||
terms,
|
||||
restData,
|
||||
indicator,
|
||||
termsCount = 0,
|
||||
}: {
|
||||
proposal: Proposal | BatchProposal;
|
||||
terms: ProposalTermsFieldsFragment;
|
||||
restData: ProposalNode | null;
|
||||
indicator?: number;
|
||||
termsCount?: number;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
let details = null;
|
||||
@@ -61,7 +64,18 @@ export const ProposalChangeDetails = ({
|
||||
}
|
||||
case 'NewMarket': {
|
||||
if (proposal.id) {
|
||||
details = <ProposalMarketData proposalId={proposal.id} />;
|
||||
let marketId = proposal.id;
|
||||
|
||||
// TODO: when https://github.com/vegaprotocol/vega/issues/11005 gets merged
|
||||
// this will need to be updated to loop forward from 0. Right now subProposals
|
||||
// are returned (when using GQL) in the reverse order
|
||||
if (proposal.__typename === 'BatchProposal') {
|
||||
for (let i = termsCount - 1; i >= 0; i--) {
|
||||
marketId = determineId(marketId);
|
||||
}
|
||||
}
|
||||
|
||||
details = <ProposalMarketData marketId={marketId} />;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -69,7 +83,7 @@ export const ProposalChangeDetails = ({
|
||||
if (proposal.id) {
|
||||
details = (
|
||||
<div className="flex flex-col gap-4">
|
||||
<ProposalMarketData proposalId={proposal.id} />
|
||||
<ProposalMarketData marketId={proposal.id} />
|
||||
<ProposalMarketChanges
|
||||
indicator={indicator}
|
||||
marketId={terms.change.marketId}
|
||||
|
||||
@@ -91,6 +91,28 @@ export type ProposalNode = {
|
||||
proposal: ProposalData;
|
||||
proposalType: ProposalNodeType;
|
||||
proposals: SubProposalData[];
|
||||
yes?: [
|
||||
{
|
||||
partyId: string;
|
||||
elsPerMarket?: [
|
||||
{
|
||||
marketId: string;
|
||||
els: string;
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
no?: [
|
||||
{
|
||||
partyId: string;
|
||||
elsPerMarket?: [
|
||||
{
|
||||
marketId: string;
|
||||
els: string;
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
type SingleProposalNode = ProposalNode & {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
||||
import { type VegaWalletConfig } from '@vegaprotocol/wallet';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { generateProposal } from '../../test-helpers/generate-proposals';
|
||||
import { Proposal } from './proposal';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { type Proposal as IProposal } from '../../types';
|
||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
|
||||
|
||||
jest.mock('@vegaprotocol/network-parameters', () => ({
|
||||
...jest.requireActual('@vegaprotocol/network-parameters'),
|
||||
@@ -24,45 +24,44 @@ jest.mock('@vegaprotocol/network-parameters', () => ({
|
||||
error: null,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('../proposal-detail-header/proposal-header', () => ({
|
||||
ProposalHeader: () => <div data-testid="proposal-header"></div>,
|
||||
}));
|
||||
|
||||
jest.mock('../proposal-change-table', () => ({
|
||||
ProposalChangeTable: () => <div data-testid="proposal-change-table"></div>,
|
||||
}));
|
||||
|
||||
jest.mock('../proposal-json', () => ({
|
||||
ProposalJson: () => <div data-testid="proposal-json"></div>,
|
||||
}));
|
||||
|
||||
jest.mock('../list-asset', () => ({
|
||||
ListAsset: () => <div data-testid="proposal-list-asset"></div>,
|
||||
}));
|
||||
|
||||
jest.mock('./proposal-change-details', () => ({
|
||||
ProposalChangeDetails: () => (
|
||||
<div data-testid="proposal-change-details"></div>
|
||||
),
|
||||
jest.mock('../list-asset', () => ({
|
||||
ListAsset: () => <div data-testid="proposal-list-asset"></div>,
|
||||
}));
|
||||
|
||||
const vegaWalletConfig: VegaWalletConfig = {
|
||||
network: 'TESTNET',
|
||||
vegaUrl: 'https://vega.xyz',
|
||||
vegaWalletServiceUrl: 'https://wallet.vega.xyz',
|
||||
links: {
|
||||
explorer: 'explorer',
|
||||
concepts: 'concepts',
|
||||
chromeExtensionUrl: 'chrome',
|
||||
mozillaExtensionUrl: 'mozilla',
|
||||
},
|
||||
chainId: 'VEGA_CHAIN_ID',
|
||||
};
|
||||
jest.mock('../vote-details', () => ({
|
||||
UserVote: () => <div data-testid="user-vote"></div>,
|
||||
}));
|
||||
|
||||
jest.mock('./proposal-change-details', () => ({
|
||||
ProposalChangeDetails: () => <div data-testid="proposal-change-details" />,
|
||||
}));
|
||||
|
||||
const renderComponent = (proposal: IProposal) => {
|
||||
render(
|
||||
return render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<VegaWalletProvider config={vegaWalletConfig}>
|
||||
<Proposal restData={null} proposal={proposal} />
|
||||
</VegaWalletProvider>
|
||||
<MockedWalletProvider>
|
||||
<AppStateProvider>
|
||||
<Proposal restData={null} proposal={proposal} />
|
||||
</AppStateProvider>
|
||||
</MockedWalletProvider>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
@@ -23,7 +23,7 @@ export interface ProposalProps {
|
||||
|
||||
export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { submit, Dialog, finalizedVote, transaction } = useVoteSubmit();
|
||||
const { submit, finalizedVote, transaction } = useVoteSubmit();
|
||||
const { voteState, voteDatetime } = useUserVote(proposal?.id, finalizedVote);
|
||||
|
||||
return (
|
||||
@@ -48,6 +48,7 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
||||
|
||||
<ProposalHeader
|
||||
proposal={proposal}
|
||||
restData={restData}
|
||||
isListItem={false}
|
||||
voteState={voteState}
|
||||
/>
|
||||
@@ -77,6 +78,7 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
||||
proposal={proposal}
|
||||
terms={p.terms}
|
||||
restData={restData}
|
||||
termsCount={proposal.subProposals?.length}
|
||||
/>
|
||||
);
|
||||
})
|
||||
@@ -88,7 +90,6 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
||||
<UserVote
|
||||
proposal={proposal}
|
||||
submit={submit}
|
||||
dialog={Dialog}
|
||||
transaction={transaction}
|
||||
voteState={voteState}
|
||||
voteDatetime={voteDatetime}
|
||||
|
||||
+3
-4
@@ -1,15 +1,14 @@
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||
import { ProposalsListItemDetails } from './proposals-list-item-details';
|
||||
import { mockWalletContext } from '../../test-helpers/mocks';
|
||||
|
||||
const renderComponent = (id: string) =>
|
||||
render(
|
||||
<Router>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<AppStateProvider>
|
||||
<ProposalsListItemDetails id={id} />
|
||||
</VegaWalletContext.Provider>
|
||||
</AppStateProvider>
|
||||
</Router>
|
||||
);
|
||||
|
||||
|
||||
+29
-28
@@ -1,25 +1,32 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import {
|
||||
MockedWalletProvider,
|
||||
mockConfig,
|
||||
} from '@vegaprotocol/wallet-react/testing';
|
||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||
import { ProposalFormSubmit } from './proposal-form-submit';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
|
||||
const renderComponent = (
|
||||
context: VegaWalletContextShape,
|
||||
isSubmitting: boolean
|
||||
) => {
|
||||
const renderComponent = (isSubmitting: boolean) => {
|
||||
render(
|
||||
<AppStateProvider>
|
||||
<VegaWalletContext.Provider value={context}>
|
||||
<MockedWalletProvider>
|
||||
<AppStateProvider>
|
||||
<ProposalFormSubmit isSubmitting={isSubmitting} />
|
||||
</VegaWalletContext.Provider>
|
||||
</AppStateProvider>
|
||||
</AppStateProvider>
|
||||
</MockedWalletProvider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('Proposal Form Submit', () => {
|
||||
const pubKey = { publicKey: '123456__123456', name: 'test' };
|
||||
|
||||
afterEach(() => {
|
||||
act(() => {
|
||||
mockConfig.reset();
|
||||
});
|
||||
});
|
||||
|
||||
it('should display connection message and button if wallet not connected', () => {
|
||||
renderComponent({ pubKey: null } as VegaWalletContextShape, false);
|
||||
renderComponent(false);
|
||||
|
||||
expect(
|
||||
screen.getByText('Connect your wallet to submit a proposal')
|
||||
@@ -30,28 +37,22 @@ describe('Proposal Form Submit', () => {
|
||||
});
|
||||
|
||||
it('should display submit button if wallet is connected', () => {
|
||||
const pubKey = { publicKey: '123456__123456', name: 'test' };
|
||||
renderComponent(
|
||||
{
|
||||
pubKey: pubKey.publicKey,
|
||||
pubKeys: [pubKey],
|
||||
} as VegaWalletContextShape,
|
||||
false
|
||||
);
|
||||
mockConfig.store.setState({
|
||||
pubKey: pubKey.publicKey,
|
||||
keys: [pubKey],
|
||||
});
|
||||
renderComponent(false);
|
||||
expect(screen.getByTestId('proposal-submit')).toHaveTextContent(
|
||||
'Submit proposal'
|
||||
);
|
||||
});
|
||||
|
||||
it('should display submitting button text if wallet is connected and submitting', () => {
|
||||
const pubKey = { publicKey: '123456__123456', name: 'test' };
|
||||
renderComponent(
|
||||
{
|
||||
pubKey: pubKey.publicKey,
|
||||
pubKeys: [pubKey],
|
||||
} as VegaWalletContextShape,
|
||||
true
|
||||
);
|
||||
mockConfig.store.setState({
|
||||
pubKey: pubKey.publicKey,
|
||||
keys: [pubKey],
|
||||
});
|
||||
renderComponent(true);
|
||||
expect(screen.getByTestId('proposal-submit')).toHaveTextContent(
|
||||
'Submitting proposal'
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet-react';
|
||||
import { VegaWalletContainer } from '../../../../components/vega-wallet-container';
|
||||
|
||||
interface ProposalFormSubmitProps {
|
||||
|
||||
+13
-5
@@ -1,19 +1,24 @@
|
||||
import {
|
||||
VegaTransactionDialog,
|
||||
getProposalDialogIcon,
|
||||
getProposalDialogIntent,
|
||||
useGetProposalDialogTitle,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import type { ProposalEventFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import type { DialogProps } from '@vegaprotocol/proposals';
|
||||
import type {
|
||||
ProposalEventFieldsFragment,
|
||||
VegaTxState,
|
||||
} from '@vegaprotocol/proposals';
|
||||
|
||||
interface ProposalFormTransactionDialogProps {
|
||||
finalizedProposal: ProposalEventFieldsFragment | null;
|
||||
TransactionDialog: (props: DialogProps) => JSX.Element;
|
||||
transaction: VegaTxState;
|
||||
onChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const ProposalFormTransactionDialog = ({
|
||||
finalizedProposal,
|
||||
TransactionDialog,
|
||||
transaction,
|
||||
onChange,
|
||||
}: ProposalFormTransactionDialogProps) => {
|
||||
const title = useGetProposalDialogTitle(finalizedProposal?.state);
|
||||
// Render a custom complete UI if the proposal was rejected otherwise
|
||||
@@ -24,13 +29,16 @@ export const ProposalFormTransactionDialog = ({
|
||||
|
||||
return (
|
||||
<div data-testid="proposal-transaction-dialog">
|
||||
<TransactionDialog
|
||||
<VegaTransactionDialog
|
||||
title={title}
|
||||
intent={getProposalDialogIntent(finalizedProposal?.state)}
|
||||
icon={getProposalDialogIcon(finalizedProposal?.state)}
|
||||
content={{
|
||||
Complete: completeContent,
|
||||
}}
|
||||
transaction={transaction}
|
||||
isOpen={transaction.dialogOpen}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
+2
-5
@@ -1,10 +1,8 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
lastWeek,
|
||||
mockWalletContext,
|
||||
networkParamsQueryMock,
|
||||
nextWeek,
|
||||
} from '../../test-helpers/mocks';
|
||||
@@ -48,9 +46,7 @@ const renderComponent = (
|
||||
render(
|
||||
<Router>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<VoteBreakdown proposal={proposal} />
|
||||
</VegaWalletContext.Provider>
|
||||
<VoteBreakdown proposal={proposal} />
|
||||
</MockedProvider>
|
||||
</Router>
|
||||
);
|
||||
@@ -60,6 +56,7 @@ describe('VoteBreakdown', () => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(0);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
import { useBatchVoteInformation } from '../../hooks/use-vote-information';
|
||||
import { MarketName } from '../proposal/market-name';
|
||||
import { Indicator } from '../proposal/indicator';
|
||||
import { type ProposalNode } from '../proposal/proposal-utils';
|
||||
|
||||
export const CompactVotes = ({ number }: { number: BigNumber }) => (
|
||||
<CompactNumber
|
||||
@@ -110,24 +111,64 @@ const Status = ({ reached, threshold, text, testId }: StatusProps) => {
|
||||
|
||||
export const VoteBreakdown = ({
|
||||
proposal,
|
||||
restData,
|
||||
}: {
|
||||
proposal: Proposal | BatchProposal;
|
||||
restData?: ProposalNode | null;
|
||||
}) => {
|
||||
if (proposal.__typename === 'Proposal') {
|
||||
return <VoteBreakdownNormal proposal={proposal} />;
|
||||
}
|
||||
|
||||
if (proposal.__typename === 'BatchProposal') {
|
||||
return <VoteBreakdownBatch proposal={proposal} />;
|
||||
return <VoteBreakdownBatch proposal={proposal} restData={restData} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const VoteBreakdownBatch = ({ proposal }: { proposal: BatchProposal }) => {
|
||||
const VoteBreakdownBatch = ({
|
||||
proposal,
|
||||
restData,
|
||||
}: {
|
||||
proposal: BatchProposal;
|
||||
restData?: ProposalNode | null;
|
||||
}) => {
|
||||
const [fullBreakdown, setFullBreakdown] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const yesELS =
|
||||
restData?.yes?.reduce((all, y) => {
|
||||
if (y.elsPerMarket) {
|
||||
y.elsPerMarket.forEach((item) => {
|
||||
const share = Number(item.els);
|
||||
if (all[item.marketId]) {
|
||||
all[item.marketId].push(share);
|
||||
} else {
|
||||
all[item.marketId] = [share];
|
||||
}
|
||||
return all;
|
||||
});
|
||||
}
|
||||
return all;
|
||||
}, {} as Record<string, number[]>) || {};
|
||||
|
||||
const noELS =
|
||||
restData?.no?.reduce((all, y) => {
|
||||
if (y.elsPerMarket) {
|
||||
y.elsPerMarket.forEach((item) => {
|
||||
const share = Number(item.els);
|
||||
if (all[item.marketId]) {
|
||||
all[item.marketId].push(share);
|
||||
} else {
|
||||
all[item.marketId] = [share];
|
||||
}
|
||||
return all;
|
||||
});
|
||||
}
|
||||
return all;
|
||||
}, {} as Record<string, number[]>) || {};
|
||||
|
||||
const voteInfo = useBatchVoteInformation({
|
||||
terms: compact(
|
||||
proposal.subProposals ? proposal.subProposals.map((p) => p?.terms) : []
|
||||
@@ -194,6 +235,8 @@ const VoteBreakdownBatch = ({ proposal }: { proposal: BatchProposal }) => {
|
||||
proposal={proposal}
|
||||
votes={proposal.votes}
|
||||
terms={p.terms}
|
||||
yesELS={yesELS}
|
||||
noELS={noELS}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -254,6 +297,8 @@ const VoteBreakdownBatch = ({ proposal }: { proposal: BatchProposal }) => {
|
||||
proposal={proposal}
|
||||
votes={proposal.votes}
|
||||
terms={p.terms}
|
||||
yesELS={yesELS}
|
||||
noELS={noELS}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -271,17 +316,17 @@ const VoteBreakdownBatchSubProposal = ({
|
||||
votes,
|
||||
terms,
|
||||
indicator,
|
||||
yesELS,
|
||||
noELS,
|
||||
}: {
|
||||
proposal: BatchProposal;
|
||||
votes: VoteFieldsFragment;
|
||||
terms: ProposalTermsFieldsFragment;
|
||||
indicator?: number;
|
||||
yesELS: Record<string, number[]>;
|
||||
noELS: Record<string, number[]>;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const voteInfo = useVoteInformation({
|
||||
votes,
|
||||
terms,
|
||||
});
|
||||
|
||||
const isProposalOpen = proposal?.state === ProposalState.STATE_OPEN;
|
||||
const isUpdateMarket = terms?.change?.__typename === 'UpdateMarket';
|
||||
@@ -294,6 +339,15 @@ const VoteBreakdownBatchSubProposal = ({
|
||||
marketId = terms.change.market.id;
|
||||
}
|
||||
|
||||
const voteInfo = useVoteInformation({
|
||||
votes,
|
||||
terms,
|
||||
// yes votes ELS for this specific proposal (market)
|
||||
yesELS: marketId ? yesELS[marketId] : undefined,
|
||||
// no votes ELS for this specific proposal (market)
|
||||
noELS: marketId ? noELS[marketId] : undefined,
|
||||
});
|
||||
|
||||
const marketName = marketId ? (
|
||||
<>
|
||||
: <MarketName marketId={marketId} />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { captureMessage } from '@sentry/minimal';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet-react';
|
||||
import { VoteValue } from '@vegaprotocol/types';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useUserVoteQuery } from './__generated__/Vote';
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Icon, ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet-react';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { ConnectToVega } from '../../../../components/connect-to-vega';
|
||||
import { VoteButtonsContainer } from './vote-buttons';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { type VoteValue } from '@vegaprotocol/types';
|
||||
import { type DialogProps, type VegaTxState } from '@vegaprotocol/proposals';
|
||||
import { type VegaTxState } from '@vegaprotocol/proposals';
|
||||
import { type VoteState } from './use-user-vote';
|
||||
import { type Proposal, type BatchProposal } from '../../types';
|
||||
|
||||
interface UserVoteProps {
|
||||
proposal: Proposal | BatchProposal;
|
||||
transaction: VegaTxState | null;
|
||||
transaction: VegaTxState;
|
||||
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
|
||||
dialog: (props: DialogProps) => JSX.Element;
|
||||
voteState: VoteState | null;
|
||||
voteDatetime: Date | null;
|
||||
}
|
||||
@@ -23,7 +22,6 @@ export const UserVote = ({
|
||||
proposal,
|
||||
submit,
|
||||
transaction,
|
||||
dialog,
|
||||
voteState,
|
||||
voteDatetime,
|
||||
}: UserVoteProps) => {
|
||||
@@ -56,7 +54,6 @@ export const UserVote = ({
|
||||
className="flex"
|
||||
submit={submit}
|
||||
transaction={transaction}
|
||||
dialog={dialog}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
|
||||
+36
-34
@@ -2,33 +2,20 @@ import { render, screen } from '@testing-library/react';
|
||||
import { VoteTransactionDialog } from './vote-transaction-dialog';
|
||||
import { VoteState } from './use-user-vote';
|
||||
import { VegaTxStatus } from '@vegaprotocol/proposals';
|
||||
import { ConnectorErrors, unknownError } from '@vegaprotocol/wallet';
|
||||
|
||||
describe('VoteTransactionDialog', () => {
|
||||
const mockTransactionDialog = jest.fn(({ title, content }) => (
|
||||
<div>
|
||||
<div>{title}</div>
|
||||
<div>{content?.Complete}</div>
|
||||
</div>
|
||||
));
|
||||
|
||||
it('renders without crashing', () => {
|
||||
render(
|
||||
<VoteTransactionDialog
|
||||
voteState={VoteState.Yes}
|
||||
transaction={null}
|
||||
TransactionDialog={mockTransactionDialog}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('vote-transaction-dialog')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders with txRequested title when voteState is Requested', () => {
|
||||
render(
|
||||
<VoteTransactionDialog
|
||||
voteState={VoteState.Requested}
|
||||
transaction={null}
|
||||
TransactionDialog={mockTransactionDialog}
|
||||
transaction={{
|
||||
error: null,
|
||||
txHash: null,
|
||||
signature: null,
|
||||
status: VegaTxStatus.Requested,
|
||||
dialogOpen: true,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -39,8 +26,13 @@ describe('VoteTransactionDialog', () => {
|
||||
render(
|
||||
<VoteTransactionDialog
|
||||
voteState={VoteState.Pending}
|
||||
transaction={null}
|
||||
TransactionDialog={mockTransactionDialog}
|
||||
transaction={{
|
||||
error: null,
|
||||
txHash: null,
|
||||
signature: null,
|
||||
status: VegaTxStatus.Pending,
|
||||
dialogOpen: true,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -51,8 +43,13 @@ describe('VoteTransactionDialog', () => {
|
||||
render(
|
||||
<VoteTransactionDialog
|
||||
voteState={VoteState.Yes} // or any other state other than Requested or Pending
|
||||
transaction={null}
|
||||
TransactionDialog={mockTransactionDialog}
|
||||
transaction={{
|
||||
error: null,
|
||||
txHash: null,
|
||||
signature: null,
|
||||
status: VegaTxStatus.Complete,
|
||||
dialogOpen: true,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -65,17 +62,18 @@ describe('VoteTransactionDialog', () => {
|
||||
<VoteTransactionDialog
|
||||
voteState={VoteState.Failed}
|
||||
transaction={{
|
||||
error: { message: 'Custom error test message', name: 'blah' },
|
||||
error: unknownError(),
|
||||
txHash: null,
|
||||
signature: null,
|
||||
status: VegaTxStatus.Error,
|
||||
dialogOpen: false,
|
||||
dialogOpen: true,
|
||||
}}
|
||||
TransactionDialog={mockTransactionDialog}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Custom error test message')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(ConnectorErrors.unknown.message)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders default error message when voteState is failed and no error message exists on the tx', () => {
|
||||
@@ -86,10 +84,9 @@ describe('VoteTransactionDialog', () => {
|
||||
error: null,
|
||||
txHash: null,
|
||||
signature: null,
|
||||
status: VegaTxStatus.Error,
|
||||
dialogOpen: false,
|
||||
status: VegaTxStatus.Complete,
|
||||
dialogOpen: true,
|
||||
}}
|
||||
TransactionDialog={mockTransactionDialog}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -100,8 +97,13 @@ describe('VoteTransactionDialog', () => {
|
||||
render(
|
||||
<VoteTransactionDialog
|
||||
voteState={VoteState.Yes}
|
||||
transaction={null}
|
||||
TransactionDialog={mockTransactionDialog}
|
||||
transaction={{
|
||||
error: null,
|
||||
txHash: null,
|
||||
signature: null,
|
||||
status: VegaTxStatus.Default,
|
||||
dialogOpen: true,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
+66
-153
@@ -1,120 +1,77 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { VoteButtons } from './vote-buttons';
|
||||
import { VoteButtons, type VoteButtonsProps } from './vote-buttons';
|
||||
import { VoteState } from './use-user-vote';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { mockWalletContext } from '../../test-helpers/mocks';
|
||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||
import { MockedProvider } from '@apollo/react-testing';
|
||||
import { VegaTxStatus } from '@vegaprotocol/proposals';
|
||||
import {
|
||||
MockedWalletProvider,
|
||||
mockConfig,
|
||||
} from '@vegaprotocol/wallet-react/testing';
|
||||
|
||||
describe('Vote buttons', () => {
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(
|
||||
const key = { publicKey: '0x123', name: 'key 1' };
|
||||
const transaction = {
|
||||
status: VegaTxStatus.Default,
|
||||
error: null,
|
||||
txHash: null,
|
||||
signature: null,
|
||||
dialogOpen: false,
|
||||
};
|
||||
const props = {
|
||||
voteState: VoteState.NotCast,
|
||||
voteDatetime: null,
|
||||
proposalState: ProposalState.STATE_OPEN,
|
||||
proposalId: null,
|
||||
minVoterBalance: null,
|
||||
spamProtectionMinTokens: null,
|
||||
currentStakeAvailable: new BigNumber(1),
|
||||
submit: () => Promise.resolve(),
|
||||
transaction,
|
||||
};
|
||||
|
||||
const renderComponent = (testProps?: Partial<VoteButtonsProps>) => {
|
||||
return render(
|
||||
<AppStateProvider>
|
||||
<MockedProvider>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<VoteButtons
|
||||
voteState={VoteState.NotCast}
|
||||
voteDatetime={null}
|
||||
proposalState={ProposalState.STATE_OPEN}
|
||||
proposalId={null}
|
||||
minVoterBalance={null}
|
||||
spamProtectionMinTokens={null}
|
||||
currentStakeAvailable={new BigNumber(1)}
|
||||
dialog={() => <div>Blah</div>}
|
||||
submit={() => Promise.resolve()}
|
||||
transaction={null}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
<MockedWalletProvider>
|
||||
<VoteButtons {...props} {...testProps} />
|
||||
</MockedWalletProvider>
|
||||
</MockedProvider>
|
||||
</AppStateProvider>
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockConfig.store.setState({ pubKey: key.publicKey, keys: [key] });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
act(() => {
|
||||
mockConfig.reset();
|
||||
});
|
||||
});
|
||||
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = renderComponent();
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should explain that voting is closed if the proposal is not open', () => {
|
||||
render(
|
||||
<AppStateProvider>
|
||||
<MockedProvider>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<VoteButtons
|
||||
voteState={VoteState.NotCast}
|
||||
voteDatetime={null}
|
||||
proposalState={ProposalState.STATE_PASSED}
|
||||
proposalId={null}
|
||||
minVoterBalance={null}
|
||||
spamProtectionMinTokens={null}
|
||||
currentStakeAvailable={new BigNumber(1)}
|
||||
dialog={() => <div>Blah</div>}
|
||||
submit={() => Promise.resolve()}
|
||||
transaction={null}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</AppStateProvider>
|
||||
);
|
||||
renderComponent({ proposalState: ProposalState.STATE_PASSED });
|
||||
expect(screen.getByText('Voting has ended.')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should provide a connect wallet prompt if no pubkey', () => {
|
||||
const mockWalletNoPubKeyContext = {
|
||||
pubKey: null,
|
||||
pubKeys: [],
|
||||
isReadOnly: false,
|
||||
sendTx: jest.fn().mockReturnValue(Promise.resolve(null)),
|
||||
connect: jest.fn(),
|
||||
disconnect: jest.fn(),
|
||||
selectPubKey: jest.fn(),
|
||||
connector: null,
|
||||
} as unknown as VegaWalletContextShape;
|
||||
|
||||
render(
|
||||
<AppStateProvider>
|
||||
<MockedProvider>
|
||||
<VegaWalletContext.Provider value={mockWalletNoPubKeyContext}>
|
||||
<VoteButtons
|
||||
voteState={VoteState.NotCast}
|
||||
voteDatetime={null}
|
||||
proposalState={ProposalState.STATE_OPEN}
|
||||
proposalId={null}
|
||||
minVoterBalance={null}
|
||||
spamProtectionMinTokens={null}
|
||||
currentStakeAvailable={new BigNumber(1)}
|
||||
dialog={() => <div>Blah</div>}
|
||||
submit={() => Promise.resolve()}
|
||||
transaction={null}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</AppStateProvider>
|
||||
);
|
||||
|
||||
mockConfig.reset();
|
||||
renderComponent();
|
||||
expect(screen.getByTestId('connect-wallet')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should tell the user they need tokens if their current stake is 0', () => {
|
||||
render(
|
||||
<AppStateProvider>
|
||||
<MockedProvider>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<VoteButtons
|
||||
voteState={VoteState.NotCast}
|
||||
voteDatetime={null}
|
||||
proposalState={ProposalState.STATE_OPEN}
|
||||
proposalId={null}
|
||||
minVoterBalance={null}
|
||||
spamProtectionMinTokens={null}
|
||||
currentStakeAvailable={new BigNumber(0)}
|
||||
dialog={() => <div>Blah</div>}
|
||||
submit={() => Promise.resolve()}
|
||||
transaction={null}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</AppStateProvider>
|
||||
);
|
||||
renderComponent({ currentStakeAvailable: new BigNumber(0) });
|
||||
expect(
|
||||
screen.getByText(
|
||||
'You need some VEGA tokens to participate in governance.'
|
||||
@@ -123,26 +80,10 @@ describe('Vote buttons', () => {
|
||||
});
|
||||
|
||||
it('should tell the user of the minimum requirements if they have some, but not enough tokens', () => {
|
||||
render(
|
||||
<AppStateProvider>
|
||||
<MockedProvider>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<VoteButtons
|
||||
voteState={VoteState.NotCast}
|
||||
voteDatetime={null}
|
||||
proposalState={ProposalState.STATE_OPEN}
|
||||
proposalId={null}
|
||||
minVoterBalance="2000000000000000000"
|
||||
spamProtectionMinTokens="1000000000000000000"
|
||||
currentStakeAvailable={new BigNumber(1)}
|
||||
dialog={() => <div>Blah</div>}
|
||||
submit={() => Promise.resolve()}
|
||||
transaction={null}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</AppStateProvider>
|
||||
);
|
||||
renderComponent({
|
||||
minVoterBalance: '2000000000000000000',
|
||||
spamProtectionMinTokens: '1000000000000000000',
|
||||
});
|
||||
expect(
|
||||
screen.getByText(
|
||||
'You must have at least 2 VEGA associated to vote on this proposal'
|
||||
@@ -151,51 +92,23 @@ describe('Vote buttons', () => {
|
||||
});
|
||||
|
||||
it('should show you voted if vote state is correct, and if the proposal is still open, it will display a change vote button', () => {
|
||||
render(
|
||||
<AppStateProvider>
|
||||
<MockedProvider>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<VoteButtons
|
||||
voteState={VoteState.Yes}
|
||||
voteDatetime={null}
|
||||
proposalState={ProposalState.STATE_OPEN}
|
||||
proposalId={null}
|
||||
minVoterBalance="2000000000000000000"
|
||||
spamProtectionMinTokens="1000000000000000000"
|
||||
currentStakeAvailable={new BigNumber(10)}
|
||||
dialog={() => <div>Blah</div>}
|
||||
submit={() => Promise.resolve()}
|
||||
transaction={null}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</AppStateProvider>
|
||||
);
|
||||
renderComponent({
|
||||
voteState: VoteState.Yes,
|
||||
minVoterBalance: '2000000000000000000',
|
||||
spamProtectionMinTokens: '1000000000000000000',
|
||||
currentStakeAvailable: new BigNumber(10),
|
||||
});
|
||||
expect(screen.getByTestId('you-voted')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('change-vote-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should allow you to change your vote', () => {
|
||||
render(
|
||||
<AppStateProvider>
|
||||
<MockedProvider>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<VoteButtons
|
||||
voteState={VoteState.No}
|
||||
voteDatetime={null}
|
||||
proposalState={ProposalState.STATE_OPEN}
|
||||
proposalId={null}
|
||||
minVoterBalance="2000000000000000000"
|
||||
spamProtectionMinTokens="1000000000000000000"
|
||||
currentStakeAvailable={new BigNumber(10)}
|
||||
dialog={() => <div>Blah</div>}
|
||||
submit={() => Promise.resolve()}
|
||||
transaction={null}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</AppStateProvider>
|
||||
);
|
||||
renderComponent({
|
||||
voteState: VoteState.No,
|
||||
minVoterBalance: '2000000000000000000',
|
||||
spamProtectionMinTokens: '1000000000000000000',
|
||||
currentStakeAvailable: new BigNumber(10),
|
||||
});
|
||||
fireEvent.click(screen.getByTestId('change-vote-button'));
|
||||
expect(screen.getByTestId('vote-buttons')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { format } from 'date-fns';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet, useDialogStore } from '@vegaprotocol/wallet-react';
|
||||
import {
|
||||
AsyncRenderer,
|
||||
Button,
|
||||
@@ -17,7 +17,7 @@ import { VoteState } from './use-user-vote';
|
||||
import { ProposalMinRequirements, ProposalUserAction } from '../shared';
|
||||
import { VoteTransactionDialog } from './vote-transaction-dialog';
|
||||
import { useVoteButtonsQuery } from './__generated__/Stake';
|
||||
import type { DialogProps, VegaTxState } from '@vegaprotocol/proposals';
|
||||
import type { VegaTxState } from '@vegaprotocol/proposals';
|
||||
import { filterAcceptableGraphqlErrors } from '../../../../lib/party';
|
||||
import {
|
||||
NetworkParams,
|
||||
@@ -32,8 +32,7 @@ interface VoteButtonsContainerProps {
|
||||
proposalId: string | null;
|
||||
proposalState: ProposalState;
|
||||
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
|
||||
transaction: VegaTxState | null;
|
||||
dialog: (props: DialogProps) => JSX.Element;
|
||||
transaction: VegaTxState;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -136,17 +135,16 @@ export const VoteButtonsContainer = (props: VoteButtonsContainerProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
interface VoteButtonsProps {
|
||||
export interface VoteButtonsProps {
|
||||
voteState: VoteState | null;
|
||||
voteDatetime: Date | null;
|
||||
proposalId: string | null;
|
||||
proposalState: ProposalState;
|
||||
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
|
||||
transaction: VegaTxState | null;
|
||||
dialog: (props: DialogProps) => JSX.Element;
|
||||
proposalId: string | null;
|
||||
currentStakeAvailable: BigNumber;
|
||||
minVoterBalance: string | null;
|
||||
spamProtectionMinTokens: string | null;
|
||||
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
|
||||
transaction: VegaTxState;
|
||||
}
|
||||
|
||||
export const VoteButtons = ({
|
||||
@@ -159,13 +157,10 @@ export const VoteButtons = ({
|
||||
spamProtectionMinTokens,
|
||||
submit,
|
||||
transaction,
|
||||
dialog: Dialog,
|
||||
}: VoteButtonsProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
|
||||
openVegaWalletDialog: store.openVegaWalletDialog,
|
||||
}));
|
||||
const openVegaWalletDialog = useDialogStore((store) => store.open);
|
||||
const [changeVote, setChangeVote] = React.useState(false);
|
||||
const proposalVotable = useMemo(
|
||||
() =>
|
||||
@@ -184,11 +179,7 @@ export const VoteButtons = ({
|
||||
if (!pubKey) {
|
||||
return (
|
||||
<div data-testid="connect-wallet">
|
||||
<ButtonLink
|
||||
onClick={() => {
|
||||
openVegaWalletDialog();
|
||||
}}
|
||||
>
|
||||
<ButtonLink onClick={openVegaWalletDialog}>
|
||||
{t('connectVegaWallet')}
|
||||
</ButtonLink>{' '}
|
||||
{t('toVote')}
|
||||
@@ -301,11 +292,7 @@ export const VoteButtons = ({
|
||||
</p>
|
||||
)
|
||||
)}
|
||||
<VoteTransactionDialog
|
||||
voteState={voteState}
|
||||
transaction={transaction}
|
||||
TransactionDialog={Dialog}
|
||||
/>
|
||||
<VoteTransactionDialog voteState={voteState} transaction={transaction} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+9
-6
@@ -1,11 +1,13 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { VoteState } from './use-user-vote';
|
||||
import type { DialogProps, VegaTxState } from '@vegaprotocol/proposals';
|
||||
import {
|
||||
VegaTransactionDialog,
|
||||
type VegaTxState,
|
||||
} from '@vegaprotocol/proposals';
|
||||
|
||||
interface VoteTransactionDialogProps {
|
||||
voteState: VoteState;
|
||||
transaction: VegaTxState | null;
|
||||
TransactionDialog: (props: DialogProps) => JSX.Element;
|
||||
transaction: VegaTxState;
|
||||
}
|
||||
|
||||
const dialogTitle = (voteState: VoteState): string | undefined => {
|
||||
@@ -22,22 +24,23 @@ const dialogTitle = (voteState: VoteState): string | undefined => {
|
||||
export const VoteTransactionDialog = ({
|
||||
voteState,
|
||||
transaction,
|
||||
TransactionDialog,
|
||||
}: VoteTransactionDialogProps) => {
|
||||
// Render a custom message if the voting fails otherwise
|
||||
// pass undefined so that the default vega transaction dialog UI gets used
|
||||
const customMessage =
|
||||
voteState === VoteState.Failed ? (
|
||||
<p>{transaction?.error?.message || t('voteError')}</p>
|
||||
<p>{transaction.error?.message || t('voteError')}</p>
|
||||
) : undefined;
|
||||
|
||||
return (
|
||||
<div data-testid="vote-transaction-dialog">
|
||||
<TransactionDialog
|
||||
<VegaTransactionDialog
|
||||
title={dialogTitle(voteState)}
|
||||
transaction={transaction}
|
||||
content={{
|
||||
Complete: customMessage,
|
||||
}}
|
||||
isOpen={transaction.dialogOpen}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -8,13 +8,18 @@ import {
|
||||
type VoteFieldsFragment,
|
||||
} from '../__generated__/Proposals';
|
||||
import { type ProposalChangeType } from '../types';
|
||||
import sum from 'lodash/sum';
|
||||
|
||||
export const useVoteInformation = ({
|
||||
votes,
|
||||
terms,
|
||||
yesELS,
|
||||
noELS,
|
||||
}: {
|
||||
votes: VoteFieldsFragment;
|
||||
terms: ProposalTermsFieldsFragment;
|
||||
yesELS?: number[];
|
||||
noELS?: number[];
|
||||
}) => {
|
||||
const {
|
||||
appState: { totalSupply, decimals },
|
||||
@@ -31,7 +36,9 @@ export const useVoteInformation = ({
|
||||
paramsForChange,
|
||||
votes,
|
||||
totalSupply,
|
||||
decimals
|
||||
decimals,
|
||||
yesELS,
|
||||
noELS
|
||||
);
|
||||
};
|
||||
|
||||
@@ -72,7 +79,11 @@ const getVoteData = (
|
||||
},
|
||||
votes: ProposalFieldsFragment['votes'],
|
||||
totalSupply: BigNumber,
|
||||
decimals: number
|
||||
decimals: number,
|
||||
/** A list of ELS yes votes */
|
||||
yesELS?: number[],
|
||||
/** A list if ELS no votes */
|
||||
noELS?: number[]
|
||||
) => {
|
||||
const requiredMajorityPercentage = params.requiredMajority
|
||||
? new BigNumber(params.requiredMajority).times(100)
|
||||
@@ -86,17 +97,31 @@ const getVoteData = (
|
||||
addDecimal(votes.no.totalTokens ?? 0, decimals)
|
||||
);
|
||||
|
||||
const noEquityLikeShareWeight = !votes.no.totalEquityLikeShareWeight
|
||||
let noEquityLikeShareWeight = !votes.no.totalEquityLikeShareWeight
|
||||
? new BigNumber(0)
|
||||
: new BigNumber(votes.no.totalEquityLikeShareWeight).times(100);
|
||||
// there's no meaningful `totalEquityLikeShareWeight` in batch proposals,
|
||||
// it has to be deduced from `elsPerMarket` of `no` votes of given proposal
|
||||
// data. (by REST DATA)
|
||||
if (noELS != null) {
|
||||
const noTotalELS = sum(noELS);
|
||||
noEquityLikeShareWeight = new BigNumber(noTotalELS).times(100);
|
||||
}
|
||||
|
||||
const yesTokens = new BigNumber(
|
||||
addDecimal(votes.yes.totalTokens ?? 0, decimals)
|
||||
);
|
||||
|
||||
const yesEquityLikeShareWeight = !votes.yes.totalEquityLikeShareWeight
|
||||
let yesEquityLikeShareWeight = !votes.yes.totalEquityLikeShareWeight
|
||||
? new BigNumber(0)
|
||||
: new BigNumber(votes.yes.totalEquityLikeShareWeight).times(100);
|
||||
// there's no meaningful `totalEquityLikeShareWeight` in batch proposals,
|
||||
// it has to be deduced from `elsPerMarket` of `yes` votes of given proposal
|
||||
// data. (by REST DATA)
|
||||
if (noELS != null) {
|
||||
const yesTotalELS = sum(yesELS);
|
||||
yesEquityLikeShareWeight = new BigNumber(yesTotalELS).times(100);
|
||||
}
|
||||
|
||||
const totalTokensVoted = yesTokens.plus(noTokens);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user