Compare commits
1 Commits
develop
...
fix/add-mi
Author | SHA1 | Date | |
---|---|---|---|
|
fc101a74a9 |
@ -4,5 +4,6 @@ tmp/*
|
||||
.dockerignore
|
||||
dockerfiles
|
||||
node_modules
|
||||
.git
|
||||
.github
|
||||
.vscode
|
||||
|
3
.github/workflows/ci-cd-trigger.yml
vendored
3
.github/workflows/ci-cd-trigger.yml
vendored
@ -196,9 +196,9 @@ jobs:
|
||||
cypress:
|
||||
needs: [build-sources, check-e2e-needed]
|
||||
name: '(CI) cypress'
|
||||
if: ${{ needs.check-e2e-needed.outputs.run-tests == 'true' }}
|
||||
uses: ./.github/workflows/cypress-run.yml
|
||||
secrets: inherit
|
||||
if: needs.check-e2e-needed.outputs.run-tests == 'true' && (contains(needs.build-sources.outputs.projects, 'governance') || contains(needs.build-sources.outputs.projects, 'explorer'))
|
||||
with:
|
||||
projects: ${{ needs.build-sources.outputs.projects-e2e }}
|
||||
tags: '@smoke'
|
||||
@ -287,7 +287,6 @@ jobs:
|
||||
steps:
|
||||
- run: |
|
||||
result="${{ needs.cypress.result }}"
|
||||
echo "Result: $result"
|
||||
if [[ $result == "success" || $result == "skipped" ]]; then
|
||||
exit 0
|
||||
else
|
||||
|
36
.github/workflows/cypress-live-test.yml
vendored
Normal file
36
.github/workflows/cypress-live-test.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
name: Cypress Console tests -- live environment
|
||||
|
||||
# This workflow runs using provided url
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
url:
|
||||
description: 'Url'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
cypress-run:
|
||||
name: Run Cypress Trading tests -- live environment
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js 20
|
||||
id: Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Run Cypress tests
|
||||
uses: cypress-io/github-action@v4
|
||||
with:
|
||||
browser: chrome
|
||||
record: true
|
||||
project: ./apps/trading-e2e
|
||||
config: baseUrl=${{ github.event.inputs.url }}
|
||||
env: grepTags=@live
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
1
.github/workflows/cypress-manual-trigger.yml
vendored
1
.github/workflows/cypress-manual-trigger.yml
vendored
@ -12,6 +12,7 @@ on:
|
||||
options:
|
||||
- explorer-e2e
|
||||
- governance-e2e
|
||||
- trading-e2e
|
||||
tags:
|
||||
description: 'Test tags to run'
|
||||
required: true
|
||||
|
2
.github/workflows/cypress-nightly.yml
vendored
2
.github/workflows/cypress-nightly.yml
vendored
@ -10,5 +10,5 @@ jobs:
|
||||
uses: ./.github/workflows/cypress-run.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
projects: '["explorer-e2e","governance-e2e"]'
|
||||
projects: '["explorer-e2e","governance-e2e","trading-e2e"]'
|
||||
tags: '@smoke @regression @slow'
|
||||
|
4
.github/workflows/lint-pr.yml
vendored
4
.github/workflows/lint-pr.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
rm package.json
|
||||
npm install --no-save @commitlint/cli@16.3.0 @commitlint/config-conventional@18.6.1 @commitlint/config-nx-scopes@18.6.1 nx@17.1.2
|
||||
npm install --no-save @commitlint/cli @commitlint/config-conventional @commitlint/config-nx-scopes nx
|
||||
|
||||
- name: Check PR title
|
||||
run: echo "${{ github.event.pull_request.title }}" | npx @commitlint/cli@16.3.0 --config ./commitlint.config-ci.js
|
||||
run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js
|
||||
|
@ -1,28 +0,0 @@
|
||||
# path to a directory with all packages
|
||||
storage: ../tmp/local-registry/storage
|
||||
|
||||
# a list of other known repositories we can talk to
|
||||
uplinks:
|
||||
npmjs:
|
||||
url: https://registry.yarnpkg.com
|
||||
maxage: 60m
|
||||
|
||||
packages:
|
||||
'**':
|
||||
# give all users (including non-authenticated users) full access
|
||||
# because it is a local registry
|
||||
access: $all
|
||||
publish: $all
|
||||
unpublish: $all
|
||||
|
||||
# if package is not available locally, proxy requests to npm registry
|
||||
proxy: npmjs
|
||||
|
||||
# log settings
|
||||
logs:
|
||||
type: stdout
|
||||
format: pretty
|
||||
level: warn
|
||||
|
||||
publish:
|
||||
allow_offline: true # set offline to true to allow publish offline
|
@ -1,7 +1,7 @@
|
||||
import { getNewAssetTxBody } from '../support/governance.functions';
|
||||
|
||||
context('Proposal page', { tags: '@smoke' }, function () {
|
||||
describe.skip('Verify elements on page', function () {
|
||||
describe('Verify elements on page', function () {
|
||||
const proposalHeading = 'proposals-heading';
|
||||
const dateTimeRegex =
|
||||
/(\d{1,2})\/(\d{1,2})\/(\d{4}), (\d{1,2}):(\d{1,2}):(\d{1,2})/gm;
|
||||
|
@ -7,7 +7,6 @@ export type AssetBalanceProps = {
|
||||
price: string;
|
||||
showAssetLink?: boolean;
|
||||
showAssetSymbol?: boolean;
|
||||
rounded?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -19,17 +18,12 @@ const AssetBalance = ({
|
||||
price,
|
||||
showAssetLink = true,
|
||||
showAssetSymbol = false,
|
||||
rounded = false,
|
||||
}: AssetBalanceProps) => {
|
||||
const { data: asset, loading } = useAssetDataProvider(assetId);
|
||||
|
||||
const label =
|
||||
!loading && asset && asset.decimals
|
||||
? addDecimalsFixedFormatNumber(
|
||||
price,
|
||||
asset.decimals,
|
||||
rounded ? 0 : undefined
|
||||
)
|
||||
? addDecimalsFixedFormatNumber(price, asset.decimals)
|
||||
: price;
|
||||
|
||||
return (
|
||||
|
@ -41,7 +41,6 @@ export const Header = () => {
|
||||
Routes.ASSETS,
|
||||
Routes.MARKETS,
|
||||
Routes.GOVERNANCE,
|
||||
Routes.TREASURY,
|
||||
Routes.NETWORK_PARAMETERS,
|
||||
Routes.GENESIS,
|
||||
].map((n) => pages.find((r) => r.path === n))
|
||||
|
@ -32,17 +32,9 @@ export function getNameForParty(id: string, data?: ExplorerNodeNamesQuery) {
|
||||
export type PartyLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
id: string;
|
||||
truncate?: boolean;
|
||||
networkLabel?: string;
|
||||
truncateLength?: number;
|
||||
};
|
||||
|
||||
const PartyLink = ({
|
||||
id,
|
||||
truncate = false,
|
||||
truncateLength = 4,
|
||||
networkLabel = t('Network'),
|
||||
...props
|
||||
}: PartyLinkProps) => {
|
||||
const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
|
||||
const { data } = useExplorerNodeNamesQuery();
|
||||
const name = useMemo(() => getNameForParty(id, data), [data, id]);
|
||||
const useName = name !== id;
|
||||
@ -52,7 +44,7 @@ const PartyLink = ({
|
||||
if (id === SPECIAL_CASE_NETWORK || id === SPECIAL_CASE_NETWORK_ID) {
|
||||
return (
|
||||
<span className="font-mono" data-testid="network">
|
||||
{networkLabel}
|
||||
{t('Network')}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@ -78,11 +70,7 @@ const PartyLink = ({
|
||||
{useName ? (
|
||||
name
|
||||
) : (
|
||||
<Hash
|
||||
text={
|
||||
truncate ? truncateMiddle(id, truncateLength, truncateLength) : id
|
||||
}
|
||||
/>
|
||||
<Hash text={truncate ? truncateMiddle(id, 4, 4) : id} />
|
||||
)}
|
||||
</Link>
|
||||
</span>
|
||||
|
@ -175,7 +175,6 @@ describe('Amend order details', () => {
|
||||
|
||||
const res = renderExistingAmend('123', 1, amend);
|
||||
expect(await res.findByText('New size')).toBeInTheDocument();
|
||||
expect(await res.findByText('Size ±')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders Reference if provided', async () => {
|
||||
|
@ -82,7 +82,7 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
|
||||
{amend.sizeDelta && amend.sizeDelta !== '0' ? (
|
||||
<div className="mb-12 md:mb-0">
|
||||
<h2 className="text-dark mb-4 text-2xl font-bold">
|
||||
{t('Size ±')}
|
||||
{t('New size')}
|
||||
</h2>
|
||||
<h5
|
||||
className={`mb-0 text-lg font-medium capitalize text-gray-500 ${getSideDeltaColour(
|
||||
@ -93,16 +93,6 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
|
||||
</h5>
|
||||
</div>
|
||||
) : null}
|
||||
{o && (
|
||||
<div className="">
|
||||
<h2 className="text-dark mb-4 text-2xl font-bold">
|
||||
{t('New size')}
|
||||
</h2>
|
||||
<h5 className="mb-0 text-lg font-medium text-gray-500">
|
||||
{o ? o.size : null}
|
||||
</h5>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{amend.price && amend.price !== '0' ? (
|
||||
<div className="">
|
||||
|
@ -34,7 +34,10 @@ export function TransferStatusView({ status, loading }: TransferStatusProps) {
|
||||
) : (
|
||||
<>
|
||||
<p className="leading-10 my-2">
|
||||
<TransferStatusIcon status={status} />
|
||||
<Icon
|
||||
name={getIconForStatus(status)}
|
||||
className={getColourForStatus(status)}
|
||||
/>
|
||||
</p>
|
||||
<p className="leading-10 my-2">{TransferStatusMapping[status]}</p>
|
||||
</>
|
||||
@ -44,21 +47,6 @@ export function TransferStatusView({ status, loading }: TransferStatusProps) {
|
||||
);
|
||||
}
|
||||
|
||||
interface TransferStatusIconProps {
|
||||
status: TransferStatus;
|
||||
}
|
||||
|
||||
export function TransferStatusIcon({ status }: TransferStatusIconProps) {
|
||||
return (
|
||||
<span title={TransferStatusMapping[status]}>
|
||||
<Icon
|
||||
name={getIconForStatus(status)}
|
||||
className={getColourForStatus(status)}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple mapping from status to icon name
|
||||
* @param status TransferStatus
|
||||
@ -72,8 +60,6 @@ export function getIconForStatus(status: TransferStatus): IconName {
|
||||
return IconNames.TICK;
|
||||
case TransferStatus.STATUS_REJECTED:
|
||||
return IconNames.CROSS;
|
||||
case TransferStatus.STATUS_CANCELLED:
|
||||
return IconNames.CROSS;
|
||||
default:
|
||||
return IconNames.TIME;
|
||||
}
|
||||
@ -92,8 +78,6 @@ export function getColourForStatus(status: TransferStatus): string {
|
||||
return 'text-green-500';
|
||||
case TransferStatus.STATUS_REJECTED:
|
||||
return 'text-red-500';
|
||||
case TransferStatus.STATUS_CANCELLED:
|
||||
return 'text-red-600';
|
||||
default:
|
||||
return 'text-yellow-500';
|
||||
}
|
||||
|
@ -12,5 +12,4 @@ export const Routes = {
|
||||
ORACLES: 'oracles',
|
||||
NETWORK_PARAMETERS: 'network-parameters',
|
||||
DISCLAIMER: 'disclaimer',
|
||||
TREASURY: 'treasury',
|
||||
};
|
||||
|
@ -30,7 +30,6 @@ import { PartyAccountsByAsset } from './parties/id/accounts';
|
||||
import { Disclaimer } from './pages/disclaimer';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import RestrictedPage from './restricted';
|
||||
import { NetworkTreasury } from './treasury';
|
||||
|
||||
export type Navigable = {
|
||||
path: string;
|
||||
@ -230,17 +229,6 @@ export const useRouterConfig = () => {
|
||||
]
|
||||
: [];
|
||||
|
||||
const treasuryRoutes: Route[] = [
|
||||
{
|
||||
path: Routes.TREASURY,
|
||||
handle: {
|
||||
name: t('Treasury'),
|
||||
text: t('Treasury'),
|
||||
breadcrumb: () => <Link to={Routes.TREASURY}>{t('Treasury')}</Link>,
|
||||
},
|
||||
element: <NetworkTreasury />,
|
||||
},
|
||||
];
|
||||
const validators: Route[] = featureFlags.EXPLORER_VALIDATORS
|
||||
? [
|
||||
{
|
||||
@ -370,7 +358,6 @@ export const useRouterConfig = () => {
|
||||
...marketsRoutes,
|
||||
...networkParametersRoutes,
|
||||
...validators,
|
||||
...treasuryRoutes,
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -1,12 +0,0 @@
|
||||
query ExplorerTreasury {
|
||||
assetsConnection(pagination: { last: 1000 }) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
networkTreasuryAccount {
|
||||
balance
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
query ExplorerTreasuryTransfers {
|
||||
transfersConnection(
|
||||
partyId: "network"
|
||||
direction: ToOrFrom
|
||||
pagination: { last: 200 }
|
||||
) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
transfer {
|
||||
timestamp
|
||||
from
|
||||
amount
|
||||
to
|
||||
status
|
||||
reason
|
||||
toAccountType
|
||||
fromAccountType
|
||||
asset {
|
||||
id
|
||||
}
|
||||
id
|
||||
status
|
||||
kind {
|
||||
... on OneOffTransfer {
|
||||
deliverOn
|
||||
}
|
||||
... on RecurringTransfer {
|
||||
startEpoch
|
||||
}
|
||||
... on OneOffGovernanceTransfer {
|
||||
deliverOn
|
||||
}
|
||||
... on RecurringGovernanceTransfer {
|
||||
endEpoch
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerTreasuryQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ExplorerTreasuryQuery = { __typename?: 'Query', assetsConnection?: { __typename?: 'AssetsConnection', edges?: Array<{ __typename?: 'AssetEdge', node: { __typename?: 'Asset', id: string, networkTreasuryAccount?: { __typename?: 'AccountBalance', balance: string } | null } } | null> | null } | null };
|
||||
|
||||
|
||||
export const ExplorerTreasuryDocument = gql`
|
||||
query ExplorerTreasury {
|
||||
assetsConnection(pagination: {last: 1000}) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
networkTreasuryAccount {
|
||||
balance
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerTreasuryQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerTreasuryQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerTreasuryQuery` 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 } = useExplorerTreasuryQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerTreasuryQuery(baseOptions?: Apollo.QueryHookOptions<ExplorerTreasuryQuery, ExplorerTreasuryQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerTreasuryQuery, ExplorerTreasuryQueryVariables>(ExplorerTreasuryDocument, options);
|
||||
}
|
||||
export function useExplorerTreasuryLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerTreasuryQuery, ExplorerTreasuryQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerTreasuryQuery, ExplorerTreasuryQueryVariables>(ExplorerTreasuryDocument, options);
|
||||
}
|
||||
export type ExplorerTreasuryQueryHookResult = ReturnType<typeof useExplorerTreasuryQuery>;
|
||||
export type ExplorerTreasuryLazyQueryHookResult = ReturnType<typeof useExplorerTreasuryLazyQuery>;
|
||||
export type ExplorerTreasuryQueryResult = Apollo.QueryResult<ExplorerTreasuryQuery, ExplorerTreasuryQueryVariables>;
|
@ -1,84 +0,0 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerTreasuryTransfersQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ExplorerTreasuryTransfersQuery = { __typename?: 'Query', transfersConnection?: { __typename?: 'TransferConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'TransferEdge', node: { __typename?: 'TransferNode', transfer: { __typename?: 'Transfer', timestamp: any, from: string, amount: string, to: string, status: Types.TransferStatus, reason?: string | null, toAccountType: Types.AccountType, fromAccountType: Types.AccountType, id: string, asset?: { __typename?: 'Asset', id: string } | null, kind: { __typename?: 'OneOffGovernanceTransfer', deliverOn?: any | null } | { __typename?: 'OneOffTransfer', deliverOn?: any | null } | { __typename?: 'RecurringGovernanceTransfer', endEpoch?: number | null } | { __typename?: 'RecurringTransfer', startEpoch: number } } } } | null> | null } | null };
|
||||
|
||||
|
||||
export const ExplorerTreasuryTransfersDocument = gql`
|
||||
query ExplorerTreasuryTransfers {
|
||||
transfersConnection(
|
||||
partyId: "network"
|
||||
direction: ToOrFrom
|
||||
pagination: {last: 200}
|
||||
) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
transfer {
|
||||
timestamp
|
||||
from
|
||||
amount
|
||||
to
|
||||
status
|
||||
reason
|
||||
toAccountType
|
||||
fromAccountType
|
||||
asset {
|
||||
id
|
||||
}
|
||||
id
|
||||
status
|
||||
kind {
|
||||
... on OneOffTransfer {
|
||||
deliverOn
|
||||
}
|
||||
... on RecurringTransfer {
|
||||
startEpoch
|
||||
}
|
||||
... on OneOffGovernanceTransfer {
|
||||
deliverOn
|
||||
}
|
||||
... on RecurringGovernanceTransfer {
|
||||
endEpoch
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerTreasuryTransfersQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerTreasuryTransfersQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerTreasuryTransfersQuery` 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 } = useExplorerTreasuryTransfersQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerTreasuryTransfersQuery(baseOptions?: Apollo.QueryHookOptions<ExplorerTreasuryTransfersQuery, ExplorerTreasuryTransfersQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerTreasuryTransfersQuery, ExplorerTreasuryTransfersQueryVariables>(ExplorerTreasuryTransfersDocument, options);
|
||||
}
|
||||
export function useExplorerTreasuryTransfersLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerTreasuryTransfersQuery, ExplorerTreasuryTransfersQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerTreasuryTransfersQuery, ExplorerTreasuryTransfersQueryVariables>(ExplorerTreasuryTransfersDocument, options);
|
||||
}
|
||||
export type ExplorerTreasuryTransfersQueryHookResult = ReturnType<typeof useExplorerTreasuryTransfersQuery>;
|
||||
export type ExplorerTreasuryTransfersLazyQueryHookResult = ReturnType<typeof useExplorerTreasuryTransfersLazyQuery>;
|
||||
export type ExplorerTreasuryTransfersQueryResult = Apollo.QueryResult<ExplorerTreasuryTransfersQuery, ExplorerTreasuryTransfersQueryVariables>;
|
@ -1,37 +0,0 @@
|
||||
// NOTE: These are a temporary measure, pulled from an old branch on console.
|
||||
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import { USDc } from './usdc';
|
||||
import { Vega } from './vega';
|
||||
import { USDt } from './usdt';
|
||||
|
||||
export interface AssetIconProps {
|
||||
symbol: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A poorly implemented, limited support for asset icons.
|
||||
*
|
||||
* These are committed as 'deprecated' to discourage use outside the Treasury page. Rather
|
||||
* than use this, a better approach would be to use source contract addresses to match assets.
|
||||
* This will be done separately.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
export function AssetIcon({ symbol }: AssetIconProps) {
|
||||
const s = symbol.toLowerCase();
|
||||
switch (s) {
|
||||
case 'a4a16e250a09a86061ec83c2f9466fc9dc33d332f86876ee74b6f128a5cd6710': // mainnet
|
||||
case 'c9fe6fc24fce121b2cc72680543a886055abb560043fda394ba5376203b7527d': // mainnet
|
||||
return <USDc size={32} />;
|
||||
case 'd1984e3d365faa05bcafbe41f50f90e3663ee7c0da22bb1e24b164e9532691b2': // mainnet
|
||||
case 'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55': // testnet
|
||||
return <Vega size={32} />;
|
||||
case 'bf1e88d19db4b3ca0d1d5bdb73718a01686b18cf731ca26adedf3c8b83802bba': // mainnet
|
||||
case 'ede4076aef07fd79502d14326c54ab3911558371baaf697a19d077f4f89de399': // testnet
|
||||
return <USDt size={32} />;
|
||||
default:
|
||||
return <Icon name={IconNames.BANK_ACCOUNT} size={8} />;
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
/**
|
||||
* See note in index.tsx. This component is intended as a placeholder for a
|
||||
* better, more generic solution.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
export const USDc = ({ size = 16 }: { size?: number }) => {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 2000 2000">
|
||||
<path
|
||||
d="M1000 2000c554.17 0 1000-445.83 1000-1000S1554.17 0 1000 0 0 445.83 0 1000s445.83 1000 1000 1000z"
|
||||
fill="#2775ca"
|
||||
/>
|
||||
<path
|
||||
d="M1275 1158.33c0-145.83-87.5-195.83-262.5-216.66-125-16.67-150-50-150-108.34s41.67-95.83 125-95.83c75 0 116.67 25 137.5 87.5 4.17 12.5 16.67 20.83 29.17 20.83h66.66c16.67 0 29.17-12.5 29.17-29.16v-4.17c-16.67-91.67-91.67-162.5-187.5-170.83v-100c0-16.67-12.5-29.17-33.33-33.34h-62.5c-16.67 0-29.17 12.5-33.34 33.34v95.83c-125 16.67-204.16 100-204.16 204.17 0 137.5 83.33 191.66 258.33 212.5 116.67 20.83 154.17 45.83 154.17 112.5s-58.34 112.5-137.5 112.5c-108.34 0-145.84-45.84-158.34-108.34-4.16-16.66-16.66-25-29.16-25h-70.84c-16.66 0-29.16 12.5-29.16 29.17v4.17c16.66 104.16 83.33 179.16 220.83 200v100c0 16.66 12.5 29.16 33.33 33.33h62.5c16.67 0 29.17-12.5 33.34-33.33v-100c125-20.84 208.33-108.34 208.33-220.84z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M787.5 1595.83c-325-116.66-491.67-479.16-370.83-800 62.5-175 200-308.33 370.83-370.83 16.67-8.33 25-20.83 25-41.67V325c0-16.67-8.33-29.17-25-33.33-4.17 0-12.5 0-16.67 4.16-395.83 125-612.5 545.84-487.5 941.67 75 233.33 254.17 412.5 487.5 487.5 16.67 8.33 33.34 0 37.5-16.67 4.17-4.16 4.17-8.33 4.17-16.66v-58.34c0-12.5-12.5-29.16-25-37.5zM1229.17 295.83c-16.67-8.33-33.34 0-37.5 16.67-4.17 4.17-4.17 8.33-4.17 16.67v58.33c0 16.67 12.5 33.33 25 41.67 325 116.66 491.67 479.16 370.83 800-62.5 175-200 308.33-370.83 370.83-16.67 8.33-25 20.83-25 41.67V1700c0 16.67 8.33 29.17 25 33.33 4.17 0 12.5 0 16.67-4.16 395.83-125 612.5-545.84 487.5-941.67-75-237.5-258.34-416.67-487.5-491.67z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
@ -1,20 +0,0 @@
|
||||
/**
|
||||
* See note in index.tsx. This component is intended as a placeholder for a
|
||||
* better, more generic solution.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
export const USDt = ({ size = 16 }: { size?: number }) => {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 339.43 295.27">
|
||||
<path
|
||||
fill="#50af95"
|
||||
d="M62.15,1.45l-61.89,130a2.52,2.52,0,0,0,.54,2.94L167.95,294.56a2.55,2.55,0,0,0,3.53,0L338.63,134.4a2.52,2.52,0,0,0,.54-2.94l-61.89-130A2.5,2.5,0,0,0,275,0H64.45a2.5,2.5,0,0,0-2.3,1.45h0Z"
|
||||
/>
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M191.19,144.8v0c-1.2.09-7.4,0.46-21.23,0.46-11,0-18.81-.33-21.55-0.46v0c-42.51-1.87-74.24-9.27-74.24-18.13s31.73-16.25,74.24-18.15v28.91c2.78,0.2,10.74.67,21.74,0.67,13.2,0,19.81-.55,21-0.66v-28.9c42.42,1.89,74.08,9.29,74.08,18.13s-31.65,16.24-74.08,18.12h0Zm0-39.25V79.68h59.2V40.23H89.21V79.68H148.4v25.86c-48.11,2.21-84.29,11.74-84.29,23.16s36.18,20.94,84.29,23.16v82.9h42.78V151.83c48-2.21,84.12-11.73,84.12-23.14s-36.09-20.93-84.12-23.15h0Zm0,0h0Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
@ -1,28 +0,0 @@
|
||||
/**
|
||||
* See note in index.tsx. This component is intended as a placeholder for a
|
||||
* better, more generic solution.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
export const Vega = ({ size = 16 }: { size?: number }) => {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 42 42">
|
||||
<rect width="42" height="42" rx="21" fill="black" />
|
||||
<path d="M13 27.2726H16.4545V10H13V27.2726Z" fill="white" />
|
||||
<path d="M25.667 23.8181H29.1215V10H25.667V23.8181Z" fill="white" />
|
||||
<path d="M19.333 33.6059H22.7875V30.1514H19.333V33.6059Z" fill="white" />
|
||||
<path
|
||||
d="M22.7871 30.7271H26.2416V27.2726H22.7871V30.7271Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M29.1211 27.2726H31.9999V23.8181H29.1211V27.2726Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M16.4551 30.7271H19.3339V27.2726H16.4551V30.7271Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
@ -1,171 +0,0 @@
|
||||
import type { DeepPartial } from '@apollo/client/utilities';
|
||||
import { parseResultsToAccounts } from './network-accounts-table';
|
||||
import {
|
||||
ExplorerTreasuryDocument,
|
||||
type ExplorerTreasuryQuery,
|
||||
} from '../__generated__/Treasury';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { NetworkAccountsTable } from './network-accounts-table';
|
||||
|
||||
describe('parseResultsToAccounts', () => {
|
||||
it('should return an array of non-zero treasury accounts', () => {
|
||||
const data: DeepPartial<ExplorerTreasuryQuery> = {
|
||||
assetsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: 'asset1',
|
||||
networkTreasuryAccount: {
|
||||
balance: '100',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: 'has0assets',
|
||||
networkTreasuryAccount: {
|
||||
balance: '0',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: 'asset3',
|
||||
networkTreasuryAccount: {
|
||||
balance: '50',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: 'hasnonetworktreasuryaccount',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = parseResultsToAccounts(data as ExplorerTreasuryQuery);
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toEqual([
|
||||
{
|
||||
assetId: 'asset1',
|
||||
balance: '100',
|
||||
type: 'ACCOUNT_TYPE_NETWORK_TREASURY',
|
||||
},
|
||||
{
|
||||
assetId: 'asset3',
|
||||
balance: '50',
|
||||
type: 'ACCOUNT_TYPE_NETWORK_TREASURY',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return an empty array if no non-zero accounts are found', () => {
|
||||
const data: DeepPartial<ExplorerTreasuryQuery> = {
|
||||
assetsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: 'asset1',
|
||||
networkTreasuryAccount: {
|
||||
balance: '0',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: 'asset2',
|
||||
networkTreasuryAccount: {
|
||||
balance: '0',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = parseResultsToAccounts(data as ExplorerTreasuryQuery);
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle missing data', () => {
|
||||
const result = parseResultsToAccounts(
|
||||
undefined as unknown as ExplorerTreasuryQuery
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('NetworkAccountsTable', () => {
|
||||
const mockData: ExplorerTreasuryQuery = {
|
||||
assetsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: 'asset1',
|
||||
networkTreasuryAccount: {
|
||||
balance: '100',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: 'asset2',
|
||||
networkTreasuryAccount: {
|
||||
balance: '50',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
query: ExplorerTreasuryDocument,
|
||||
},
|
||||
result: {
|
||||
data: mockData,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
it('should render network accounts (as many as match - often just 1)', async () => {
|
||||
render(
|
||||
<MockedProvider mocks={mocks} addTypename={false}>
|
||||
<MemoryRouter>
|
||||
<NetworkAccountsTable />
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
// Wait for the data to load
|
||||
await screen.findByText('Loading...');
|
||||
|
||||
// Assert that the network accounts are rendered
|
||||
expect(screen.getByText('asset1')).toBeInTheDocument();
|
||||
expect(screen.getByText('asset2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle loading state', async () => {
|
||||
render(
|
||||
<MockedProvider mocks={mocks} addTypename={false}>
|
||||
<MemoryRouter>
|
||||
<NetworkAccountsTable />
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
// Assert that the loading state is rendered
|
||||
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -1,87 +0,0 @@
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
type ExplorerTreasuryQuery,
|
||||
useExplorerTreasuryQuery,
|
||||
} from '../__generated__/Treasury';
|
||||
import AssetBalance from '../../../components/asset-balance/asset-balance';
|
||||
import { AssetLink } from '../../../components/links';
|
||||
import { useMemo } from 'react';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
import { AssetIcon } from './asset-icon';
|
||||
import { type NonZeroAccount } from '../network-treasury';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
|
||||
export const NetworkAccountsTable = () => {
|
||||
const { data, loading, error } = useExplorerTreasuryQuery({
|
||||
// This needs to ignore error as old assets may no longer properly resolve
|
||||
errorPolicy: 'ignore',
|
||||
});
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const shouldRound = useMemo(
|
||||
() => ['xs', 'sm', 'md', 'lg'].includes(screenSize),
|
||||
[screenSize]
|
||||
);
|
||||
|
||||
return (
|
||||
<AsyncRenderer
|
||||
data={data}
|
||||
loading={loading}
|
||||
error={error}
|
||||
render={(data) => {
|
||||
const c = parseResultsToAccounts(data);
|
||||
return (
|
||||
<section className="md:flex md:flex-row flex-wrap">
|
||||
{c.map((a) => (
|
||||
<div className="basis-1/2 md:basis-1/4">
|
||||
<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">
|
||||
<AssetIcon symbol={a.assetId} />
|
||||
</p>
|
||||
<p className="mt-3" data-testid="name">
|
||||
<AssetLink assetId={a.assetId} />
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center py-5" data-testid="balance">
|
||||
<AssetBalance
|
||||
assetId={a.assetId}
|
||||
price={a.balance}
|
||||
showAssetSymbol={true}
|
||||
rounded={shouldRound}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export function parseResultsToAccounts(
|
||||
data: ExplorerTreasuryQuery
|
||||
): NonZeroAccount[] {
|
||||
const nonZeroAccounts: NonZeroAccount[] = [];
|
||||
if (data?.assetsConnection?.edges) {
|
||||
const edges = removePaginationWrapper(data?.assetsConnection?.edges);
|
||||
if (edges) {
|
||||
edges.forEach((edge) => {
|
||||
if (
|
||||
edge.networkTreasuryAccount &&
|
||||
edge.networkTreasuryAccount?.balance !== '0'
|
||||
) {
|
||||
nonZeroAccounts.push({
|
||||
assetId: edge.id,
|
||||
balance: edge.networkTreasuryAccount?.balance,
|
||||
type: AccountType.ACCOUNT_TYPE_NETWORK_TREASURY,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return nonZeroAccounts;
|
||||
}
|
@ -1,262 +0,0 @@
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import {
|
||||
typeLabel,
|
||||
getToAccountTypeLabel,
|
||||
filterAccountTransfers,
|
||||
} from './network-transfers-table';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { NetworkTransfersTable } from './network-transfers-table';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import {
|
||||
ExplorerTreasuryTransfersDocument,
|
||||
type ExplorerTreasuryTransfersQuery,
|
||||
} from '../__generated__/TreasuryTransfers';
|
||||
import type { DeepPartial } from '@apollo/client/utilities';
|
||||
|
||||
describe('typeLabel', () => {
|
||||
it('should return "Transfer" for "OneOffTransfer" kind', () => {
|
||||
expect(typeLabel('OneOffTransfer')).toBe('Transfer');
|
||||
});
|
||||
|
||||
it('should return "Transfer" for "RecurringTransfer" kind', () => {
|
||||
expect(typeLabel('RecurringTransfer')).toBe('Transfer');
|
||||
});
|
||||
|
||||
it('should return "Governance" for "OneOffGovernanceTransfer" kind', () => {
|
||||
expect(typeLabel('OneOffGovernanceTransfer')).toBe('Governance');
|
||||
});
|
||||
|
||||
it('should return "Governance" for "RecurringGovernanceTransfer" kind', () => {
|
||||
expect(typeLabel('RecurringGovernanceTransfer')).toBe('Governance');
|
||||
});
|
||||
|
||||
it('should return "Unknown" for unknown kind', () => {
|
||||
expect(typeLabel()).toBe('Unknown');
|
||||
expect(typeLabel('')).toBe('Unknown');
|
||||
expect(typeLabel('InvalidKind')).toBe('Unknown');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getToAccountTypeLabel', () => {
|
||||
it('should return "Treasury" when type is ACCOUNT_TYPE_NETWORK_TREASURY', () => {
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_NETWORK_TREASURY)
|
||||
).toBe('Treasury');
|
||||
});
|
||||
|
||||
it('should return "Fees" when type is any of the fee account types', () => {
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE)
|
||||
).toBe('Fees');
|
||||
expect(getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_FEES_MAKER)).toBe(
|
||||
'Fees'
|
||||
);
|
||||
expect(getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY)).toBe(
|
||||
'Fees'
|
||||
);
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_LP_LIQUIDITY_FEES)
|
||||
).toBe('Fees');
|
||||
expect(
|
||||
getToAccountTypeLabel(
|
||||
AccountType.ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD
|
||||
)
|
||||
).toBe('Fees');
|
||||
});
|
||||
|
||||
it('should return "Insurance" when type is ACCOUNT_TYPE_GLOBAL_INSURANCE', () => {
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_GLOBAL_INSURANCE)
|
||||
).toBe('Insurance');
|
||||
});
|
||||
|
||||
it('should return "Rewards" when type is any of the reward account types', () => {
|
||||
expect(getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_GLOBAL_REWARD)).toBe(
|
||||
'Rewards'
|
||||
);
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION)
|
||||
).toBe('Rewards');
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES)
|
||||
).toBe('Rewards');
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES)
|
||||
).toBe('Rewards');
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES)
|
||||
).toBe('Rewards');
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS)
|
||||
).toBe('Rewards');
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_RELATIVE_RETURN)
|
||||
).toBe('Rewards');
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY)
|
||||
).toBe('Rewards');
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING)
|
||||
).toBe('Rewards');
|
||||
expect(getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_VESTED_REWARDS)).toBe(
|
||||
'Rewards'
|
||||
);
|
||||
expect(
|
||||
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_VESTING_REWARDS)
|
||||
).toBe('Rewards');
|
||||
});
|
||||
|
||||
it('should return "Other" for any other type', () => {
|
||||
expect(getToAccountTypeLabel(undefined)).toBe('Other');
|
||||
expect(getToAccountTypeLabel('unknown' as AccountType)).toBe('Other');
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterAccountTransfers', () => {
|
||||
it('filters out transactions that are not to or from a treasury account', () => {
|
||||
const data: DeepPartial<ExplorerTreasuryTransfersQuery> = {
|
||||
transfersConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
transfer: {
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_NETWORK_TREASURY,
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
transfer: {
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_NETWORK_TREASURY,
|
||||
fromAccountType:
|
||||
AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
transfer: {
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_NETWORK_TREASURY,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
transfer: {
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION,
|
||||
fromAccountType:
|
||||
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = filterAccountTransfers(
|
||||
data as ExplorerTreasuryTransfersQuery
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('should return an empty array if no transfers match the filter', () => {
|
||||
const data: DeepPartial<ExplorerTreasuryTransfersQuery> = {
|
||||
transfersConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
transfer: {
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
transfer: {
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION,
|
||||
fromAccountType:
|
||||
AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = filterAccountTransfers(
|
||||
data as ExplorerTreasuryTransfersQuery
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('NetworkTransfersTable', () => {
|
||||
it('renders table headers correctly', async () => {
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
query: ExplorerTreasuryTransfersDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
transfersConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
transfer: {
|
||||
id: '123',
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_NETWORK_TREASURY,
|
||||
fromAccountType:
|
||||
AccountType.ACCOUNT_TYPE_NETWORK_TREASURY,
|
||||
amount: '100',
|
||||
asset: {
|
||||
id: '1',
|
||||
},
|
||||
timestamp: '2022-01-01T00:00:00Z',
|
||||
from: 'network',
|
||||
to: '7100a8a82ef45adb9efa070cc821c6c5c48172d6dc5f842431549490fe5897a0',
|
||||
reason: '',
|
||||
status: 'COMPLETED',
|
||||
kind: {
|
||||
__typename: 'OneOffGovernanceTransfer',
|
||||
deliverOn: '123',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
render(
|
||||
<MockedProvider mocks={mocks} addTypename={true}>
|
||||
<MemoryRouter>
|
||||
<NetworkTransfersTable />
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(await screen.findByText('Amount')).toBeInTheDocument();
|
||||
expect(screen.getByText('Asset')).toBeInTheDocument();
|
||||
expect(screen.getByText('Age')).toBeInTheDocument();
|
||||
expect(screen.getByText('From')).toBeInTheDocument();
|
||||
expect(screen.getByText('To')).toBeInTheDocument();
|
||||
expect(screen.getByText('Status')).toBeInTheDocument();
|
||||
expect(screen.getByText('Type')).toBeInTheDocument();
|
||||
|
||||
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'
|
||||
);
|
||||
});
|
||||
});
|
@ -1,253 +0,0 @@
|
||||
import { AsyncRenderer, Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import AssetBalance from '../../../components/asset-balance/asset-balance';
|
||||
import { AccountType, AccountTypeMapping } from '@vegaprotocol/types';
|
||||
import { AssetLink, PartyLink } from '../../../components/links';
|
||||
import {
|
||||
type ExplorerTreasuryTransfersQuery,
|
||||
useExplorerTreasuryTransfersQuery,
|
||||
} from '../__generated__/TreasuryTransfers';
|
||||
import { TimeAgo } from '../../../components/time-ago';
|
||||
import { TransferStatusIcon } from '../../../components/txs/details/transfer/blocks/transfer-status';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import { useMemo } from 'react';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export const colours = {
|
||||
INCOMING: '!fill-vega-green-600 text-vega-green-600 mr-2',
|
||||
OUTGOING: '!fill-vega-pink-600 text-vega-pink-600 mr-2',
|
||||
};
|
||||
|
||||
export const theadClasses =
|
||||
'py-2 border text-center bg-vega-light-150 dark:bg-vega-dark-150';
|
||||
|
||||
export function getToAccountTypeLabel(type?: AccountType): string {
|
||||
switch (type) {
|
||||
case AccountType.ACCOUNT_TYPE_NETWORK_TREASURY:
|
||||
return t('Treasury');
|
||||
case AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE:
|
||||
case AccountType.ACCOUNT_TYPE_FEES_MAKER:
|
||||
case AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY:
|
||||
case AccountType.ACCOUNT_TYPE_LP_LIQUIDITY_FEES:
|
||||
case AccountType.ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD:
|
||||
return t('Fees');
|
||||
case AccountType.ACCOUNT_TYPE_GLOBAL_INSURANCE:
|
||||
return t('Insurance');
|
||||
case AccountType.ACCOUNT_TYPE_GLOBAL_REWARD:
|
||||
case AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION:
|
||||
case AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES:
|
||||
case AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES:
|
||||
case AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES:
|
||||
case AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS:
|
||||
case AccountType.ACCOUNT_TYPE_REWARD_RELATIVE_RETURN:
|
||||
case AccountType.ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY:
|
||||
case AccountType.ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING:
|
||||
case AccountType.ACCOUNT_TYPE_VESTED_REWARDS:
|
||||
case AccountType.ACCOUNT_TYPE_VESTING_REWARDS:
|
||||
return t('Rewards');
|
||||
default:
|
||||
return t('Other');
|
||||
}
|
||||
}
|
||||
|
||||
export function typeLabel(kind?: string): string {
|
||||
switch (kind) {
|
||||
case 'OneOffTransfer':
|
||||
case 'RecurringTransfer':
|
||||
return t('Transfer');
|
||||
case 'OneOffGovernanceTransfer':
|
||||
case 'RecurringGovernanceTransfer':
|
||||
return t('Governance');
|
||||
default:
|
||||
return t('Unknown');
|
||||
}
|
||||
}
|
||||
|
||||
export function filterAccountTransfers(data: ExplorerTreasuryTransfersQuery) {
|
||||
return data.transfersConnection?.edges
|
||||
?.filter((edge) => {
|
||||
if (
|
||||
edge?.node.transfer.toAccountType ===
|
||||
AccountType.ACCOUNT_TYPE_NETWORK_TREASURY
|
||||
) {
|
||||
return true;
|
||||
} else if (
|
||||
edge?.node.transfer.fromAccountType ===
|
||||
AccountType.ACCOUNT_TYPE_NETWORK_TREASURY
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
.map((edge) => {
|
||||
return edge?.node.transfer;
|
||||
});
|
||||
}
|
||||
|
||||
export const NetworkTransfersTable = () => {
|
||||
const { data, loading, error } = useExplorerTreasuryTransfersQuery({
|
||||
// This needs to ignore error as old assets may no longer properly resolve
|
||||
errorPolicy: 'ignore',
|
||||
});
|
||||
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const shouldRound = useMemo(
|
||||
() => ['xs', 'sm', 'md', 'lg'].includes(screenSize),
|
||||
[screenSize]
|
||||
);
|
||||
const shouldTruncate = useMemo(
|
||||
() => ['xs', 'sm', 'md', 'lg', 'xl'].includes(screenSize),
|
||||
[screenSize]
|
||||
);
|
||||
const shouldHideColumns = useMemo(
|
||||
() => ['xs', 'sm'].includes(screenSize),
|
||||
[screenSize]
|
||||
);
|
||||
|
||||
return (
|
||||
<section>
|
||||
<AsyncRenderer
|
||||
data={data}
|
||||
loading={loading}
|
||||
error={error}
|
||||
render={(data) => {
|
||||
const c = filterAccountTransfers(data);
|
||||
if (!c) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<table className="table-fixed border-spacing-3">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className={theadClasses}>{t('Amount')}</th>
|
||||
<th className={theadClasses}>{t('Asset')}</th>
|
||||
<th className={theadClasses}>{t('Age')}</th>
|
||||
<th className={theadClasses}>{t('From')}</th>
|
||||
<th className={theadClasses}>{t('To')}</th>
|
||||
<th
|
||||
className={`${theadClasses} ${
|
||||
shouldHideColumns ? 'hidden' : ''
|
||||
}`}
|
||||
>
|
||||
{t('Status')}
|
||||
</th>
|
||||
<th
|
||||
className={`${theadClasses} ${
|
||||
shouldHideColumns ? 'hidden' : ''
|
||||
}`}
|
||||
>
|
||||
{t('Type')}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{c.map((a) => {
|
||||
const isIncoming =
|
||||
a?.toAccountType ===
|
||||
AccountType.ACCOUNT_TYPE_NETWORK_TREASURY;
|
||||
return (
|
||||
<tr>
|
||||
{a && a.amount && a.asset && (
|
||||
<td
|
||||
className={`px-2 py-1 border whitespace-nowrap text-right ${
|
||||
isIncoming ? colours.INCOMING : colours.OUTGOING
|
||||
}`}
|
||||
title={a.amount}
|
||||
>
|
||||
{a &&
|
||||
a.toAccountType ===
|
||||
AccountType.ACCOUNT_TYPE_NETWORK_TREASURY ? (
|
||||
<Icon
|
||||
name={IconNames.PLUS}
|
||||
className={colours.INCOMING}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
name={IconNames.MINUS}
|
||||
className={colours.OUTGOING}
|
||||
/>
|
||||
)}
|
||||
<AssetBalance
|
||||
assetId={a.asset.id}
|
||||
price={a.amount}
|
||||
showAssetLink={false}
|
||||
rounded={shouldRound}
|
||||
/>
|
||||
</td>
|
||||
)}
|
||||
<td className="px-2 py-1 border whitespace-nowrap">
|
||||
{a && a.amount && a.asset && (
|
||||
<AssetLink
|
||||
assetId={a.asset.id}
|
||||
showAssetSymbol={true}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-2 py-1 border">
|
||||
{a && a.timestamp && <TimeAgo date={a.timestamp} />}
|
||||
</td>
|
||||
<td
|
||||
className="px-2 py-1 border"
|
||||
data-testid="from-account"
|
||||
>
|
||||
{a && a.from && (
|
||||
<PartyLink
|
||||
id={a.from}
|
||||
truncate={true}
|
||||
truncateLength={shouldTruncate ? 4 : 15}
|
||||
networkLabel={t('Treasury')}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-2 py-1 border" data-testid="to-account">
|
||||
{a && a.to && (
|
||||
<PartyLink
|
||||
id={a.to}
|
||||
networkLabel={t('Treasury')}
|
||||
truncate={true}
|
||||
truncateLength={shouldTruncate ? 4 : 15}
|
||||
/>
|
||||
)}
|
||||
{a && !a.to && (
|
||||
<span
|
||||
className="underline decoration-dotted"
|
||||
title={AccountTypeMapping[a.toAccountType]}
|
||||
>
|
||||
{getToAccountTypeLabel(a.toAccountType)}
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td
|
||||
className={`px-2 py-1 border text-center ${
|
||||
shouldHideColumns ? 'hidden' : ''
|
||||
}`}
|
||||
>
|
||||
{a && a.status && (
|
||||
<TransferStatusIcon status={a.status} />
|
||||
)}
|
||||
</td>
|
||||
<td
|
||||
className={`px-2 py-1 border ${
|
||||
shouldHideColumns ? 'hidden' : ''
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className="underline decoration-dotted"
|
||||
title={a?.kind.__typename}
|
||||
data-testid="transfer-kind"
|
||||
>
|
||||
{a && typeLabel(a.kind.__typename)}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
@ -1 +0,0 @@
|
||||
export * from './network-treasury';
|
@ -1,28 +0,0 @@
|
||||
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||
import type { AccountType } from '@vegaprotocol/types';
|
||||
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';
|
||||
|
||||
export type NonZeroAccount = {
|
||||
assetId: string;
|
||||
balance: string;
|
||||
type: AccountType;
|
||||
};
|
||||
|
||||
export const NetworkTreasury = () => {
|
||||
useDocumentTitle(['Network Treasury']);
|
||||
return (
|
||||
<section>
|
||||
<RouteTitle data-testid="block-header">{t(`Treasury`)}</RouteTitle>
|
||||
<div>
|
||||
<NetworkAccountsTable />
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<h2 className="text-3xl mb-2">{t('Transfers')}</h2>
|
||||
<NetworkTransfersTable />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
@ -1,9 +1,7 @@
|
||||
const { join } = require('path');
|
||||
const { createGlobPatternsForDependencies } = require('@nx/react/tailwind');
|
||||
const { theme } = require('../../libs/tailwindcss-config/src/theme');
|
||||
const {
|
||||
vegaCustomClasses,
|
||||
} = require('../../libs/tailwindcss-config/src/vega-custom-classes');
|
||||
const theme = require('../../libs/tailwindcss-config/src/theme');
|
||||
const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom-classes');
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
|
@ -7,7 +7,6 @@ import {
|
||||
navigateTo,
|
||||
navigation,
|
||||
turnTelemetryOff,
|
||||
setRiskAccepted,
|
||||
} from '../../support/common.functions';
|
||||
import {
|
||||
clickOnValidatorFromList,
|
||||
@ -58,7 +57,6 @@ context(
|
||||
// 1002-STKE-002, 1002-STKE-032
|
||||
before('visit staking tab and connect vega wallet', function () {
|
||||
cy.visit('/');
|
||||
setRiskAccepted();
|
||||
ethereumWalletConnect();
|
||||
cy.connectVegaWallet();
|
||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||
|
@ -5,7 +5,6 @@ import {
|
||||
navigateTo,
|
||||
navigation,
|
||||
turnTelemetryOff,
|
||||
setRiskAccepted,
|
||||
} from '../../support/common.functions';
|
||||
import {
|
||||
stakingPageAssociateTokens,
|
||||
@ -58,7 +57,6 @@ context(
|
||||
function () {
|
||||
cy.clearLocalStorage();
|
||||
turnTelemetryOff();
|
||||
setRiskAccepted();
|
||||
cy.mockChainId();
|
||||
cy.reload();
|
||||
waitForSpinner();
|
||||
|
@ -7,7 +7,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
|
||||
describe('Links and buttons', function () {
|
||||
it('should have link for proposal page', function () {
|
||||
it.skip('should have link for proposal page', function () {
|
||||
cy.getByTestId('home-proposals').within(() => {
|
||||
cy.get('[href="/proposals"]')
|
||||
.should('exist')
|
||||
@ -51,7 +51,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have external link for governance', function () {
|
||||
it.skip('should have external link for governance', function () {
|
||||
cy.getByTestId('home-proposals').within(() => {
|
||||
cy.getByTestId('external-link')
|
||||
.should('have.attr', 'href')
|
||||
@ -59,7 +59,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have link for validator page', function () {
|
||||
it.skip('should have link for validator page', function () {
|
||||
cy.getByTestId('home-validators').within(() => {
|
||||
cy.get('[href="/validators"]')
|
||||
.first()
|
||||
@ -68,7 +68,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have external link for validators', function () {
|
||||
it.skip('should have external link for validators', function () {
|
||||
cy.getByTestId('home-validators').within(() => {
|
||||
cy.getByTestId('external-link')
|
||||
.should('have.attr', 'href')
|
||||
@ -101,7 +101,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have link for rewards page', function () {
|
||||
it.skip('should have link for rewards page', function () {
|
||||
cy.getByTestId('home-rewards').within(() => {
|
||||
cy.get('[href="/rewards"]')
|
||||
.first()
|
||||
@ -110,7 +110,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have link for withdrawal page', function () {
|
||||
it.skip('should have link for withdrawal page', function () {
|
||||
cy.getByTestId('home-vega-token').within(() => {
|
||||
cy.get('[href="/token/withdraw"]')
|
||||
.first()
|
||||
@ -132,7 +132,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
|
||||
// 0006-NETW-003 0006-NETW-008 0006-NETW-009 0006-NETW-010 0006-NETW-012 0006-NETW-013 0006-NETW-017 0006-NETW-018 0006-NETW-019 0006-NETW-020
|
||||
it('should have option to switch to different network node', function () {
|
||||
it.skip('should have option to switch to different network node', function () {
|
||||
cy.getByTestId('git-network-data').within(() => {
|
||||
cy.getByTestId('link').click();
|
||||
});
|
||||
@ -153,13 +153,13 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
.invoke('text')
|
||||
.should('not.eq', currentBlockHeight);
|
||||
});
|
||||
cy.getByTestId('subscription-cell').should('be.be.visible');
|
||||
cy.getByTestId('subscription-cell').should('have.text', 'Yes');
|
||||
});
|
||||
cy.getByTestId('connect').should('be.disabled');
|
||||
cy.getByTestId('node-url-custom').click({ force: true });
|
||||
cy.get('input').should('exist');
|
||||
cy.getByTestId('connect').should('be.disabled');
|
||||
cy.getByTestId('dialog-close').click();
|
||||
cy.getByTestId('icon-cross').click();
|
||||
});
|
||||
|
||||
it('should display eth data', function () {
|
||||
|
@ -33,12 +33,12 @@ context(
|
||||
verifyTabHighlighted(navigation.proposals);
|
||||
});
|
||||
|
||||
it('should have GOVERNANCE header visible', function () {
|
||||
it.skip('should have GOVERNANCE header visible', function () {
|
||||
verifyPageHeader('Proposals');
|
||||
});
|
||||
|
||||
// 3002-PROP-023 3004-PMAC-002 3005-PASN-002 3006-PASC-002 3007-PNEC-002 3008-PFRO-003
|
||||
it('new proposal page should have button for link to more information on proposals', function () {
|
||||
it.skip('new proposal page should have button for link to more information on proposals', function () {
|
||||
cy.getByTestId('new-proposal-link').click();
|
||||
cy.url().should('include', '/proposals/propose/raw');
|
||||
cy.contains('To see Explorer data on proposals visit').within(() => {
|
||||
@ -73,7 +73,7 @@ context(
|
||||
navigateTo(navigation.proposals);
|
||||
});
|
||||
|
||||
it('should be able to see a working link for - find out more about Vega governance', function () {
|
||||
it.skip('should be able to see a working link for - find out more about Vega governance', function () {
|
||||
// 3001-VOTE-001 // 3002-PROP-001
|
||||
cy.getByTestId(proposalDocumentationLink)
|
||||
.should('be.visible')
|
||||
|
@ -34,10 +34,16 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
|
||||
cy.connectPublicKey(vegaWalletPubKey);
|
||||
});
|
||||
|
||||
it.skip('Able to connect public key via wallet and view assets in wallet', function () {
|
||||
it('Able to connect public key using url', function () {
|
||||
cy.getByTestId('exit-view').click();
|
||||
cy.visit(`/?address=${vegaWalletPubKey}`);
|
||||
verifyConnectedToPubKey();
|
||||
});
|
||||
|
||||
it('Able to connect public key via wallet and view assets in wallet', function () {
|
||||
verifyConnectedToPubKey();
|
||||
cy.getByTestId('currency-title', { timeout: 10000 })
|
||||
.should('have.length.at.least', 2)
|
||||
.should('have.length.at.least', 4)
|
||||
.and('contain.text', 'USDC (fake)');
|
||||
});
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import {
|
||||
navigation,
|
||||
setRiskAccepted,
|
||||
verifyPageHeader,
|
||||
verifyTabHighlighted,
|
||||
} from '../../support/common.functions';
|
||||
@ -47,11 +46,11 @@ context('Validators Page - verify elements on page', function () {
|
||||
|
||||
// @ts-ignore clash between jest and cypress
|
||||
describe('with wallets disconnected', { tags: '@smoke' }, function () {
|
||||
it('Should have validators tab highlighted', function () {
|
||||
it.skip('Should have validators tab highlighted', function () {
|
||||
verifyTabHighlighted(navigation.validators);
|
||||
});
|
||||
|
||||
it('Should have validators ON VEGA header visible', function () {
|
||||
it.skip('Should have validators ON VEGA header visible', function () {
|
||||
verifyPageHeader('Validators');
|
||||
});
|
||||
|
||||
@ -188,13 +187,12 @@ 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);
|
||||
});
|
||||
|
||||
// 1002-STKE-006
|
||||
it('Should be able to see validator name', function () {
|
||||
it.skip('Should be able to see validator name', function () {
|
||||
cy.getByTestId(validatorTitle).should('not.be.empty');
|
||||
});
|
||||
|
||||
|
@ -1,8 +1,5 @@
|
||||
import { truncateByChars } from '@vegaprotocol/utils';
|
||||
import {
|
||||
setRiskAccepted,
|
||||
waitForSpinner,
|
||||
} from '../../support/common.functions';
|
||||
import { waitForSpinner } from '../../support/common.functions';
|
||||
import {
|
||||
vegaWalletFaucetAssetsWithoutCheck,
|
||||
vegaWalletTeardown,
|
||||
@ -14,6 +11,7 @@ const connectButton = 'connect-vega-wallet';
|
||||
const getVegaLink = 'link';
|
||||
const dialog = '[role="dialog"]:visible';
|
||||
const dialogHeader = 'dialog-title';
|
||||
const walletDialogHeader = 'wallet-dialog-title';
|
||||
const connectorsList = 'connectors-list';
|
||||
const dialogCloseBtn = 'dialog-close';
|
||||
const accountNo = 'vega-account-truncated';
|
||||
@ -36,7 +34,6 @@ context(
|
||||
() => {
|
||||
before('visit token home page', () => {
|
||||
cy.visit('/');
|
||||
setRiskAccepted();
|
||||
cy.get(walletContainer, { timeout: 60000 }).should('be.visible');
|
||||
});
|
||||
|
||||
@ -66,12 +63,17 @@ context(
|
||||
|
||||
it('should have Connect Vega header visible', () => {
|
||||
cy.get(dialog).within(() => {
|
||||
cy.getByTestId(connectorsList)
|
||||
cy.getByTestId(walletDialogHeader)
|
||||
.should('be.visible')
|
||||
.and(
|
||||
'have.text',
|
||||
'Get the Vega WalletGet MetaMask>_Command Line WalletView as public key'
|
||||
);
|
||||
.and('have.text', 'Get a Vega wallet');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have jsonRpc and hosted connection options visible on list', function () {
|
||||
cy.getByTestId(connectorsList).within(() => {
|
||||
cy.getByTestId('connector-jsonRpc')
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Use the Desktop App/CLI');
|
||||
});
|
||||
});
|
||||
|
||||
@ -86,6 +88,7 @@ context(
|
||||
before('connect vega wallet', function () {
|
||||
cy.mockChainId();
|
||||
cy.visit('/');
|
||||
cy.wait('@ChainId');
|
||||
cy.connectVegaWallet();
|
||||
vegaWalletTeardown();
|
||||
});
|
||||
|
@ -102,12 +102,6 @@ export function turnTelemetryOff() {
|
||||
);
|
||||
}
|
||||
|
||||
export function setRiskAccepted() {
|
||||
cy.window().then((win) =>
|
||||
win.localStorage.setItem('vega_wallet_risk_accepted', 'true')
|
||||
);
|
||||
}
|
||||
|
||||
export function dissociateFromSecondWalletKey() {
|
||||
const secondWalletKey = Cypress.env('vegaWalletPublicKey2Short');
|
||||
cy.getByTestId('vega-in-wallet')
|
||||
|
@ -14,6 +14,7 @@ NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/m
|
||||
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
|
||||
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
@ -23,7 +24,7 @@ NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/en-GB/firefox/addon/vega-wallet-beta/
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
@ -32,7 +33,7 @@ LC_ALL="en_US.UTF-8"
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_PRODUCT_PERPETUALS=true
|
||||
NX_UPDATE_MARKET_STATE=true
|
||||
NX_PRODUCT_PERPETUALS=false
|
||||
NX_UPDATE_MARKET_STATE=false
|
||||
NX_REFERRALS=true
|
||||
NX_GOVERNANCE_TRANSFERS=true
|
||||
NX_GOVERNANCE_TRANSFERS=false
|
||||
|
@ -15,11 +15,12 @@ NX_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit suppl
|
||||
NX_LOCAL_PROVIDER_URL=http://localhost:8545/
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
|
||||
NX_VEGA_REST_URL=http://localhost:3008/api/v2/
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/en-GB/firefox/addon/vega-wallet-beta/
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
|
||||
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
||||
|
||||
|
@ -8,13 +8,14 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_VEGA_EXPLORER_URL=#
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-devnet1-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_REST_URL=https://api.n00.devnet1.vega.xyz/api/v2/
|
||||
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/en-GB/firefox/addon/vega-wallet-beta/
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
|
||||
|
@ -10,6 +10,7 @@ NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
|
||||
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40:87edc2605e544f888305d7fc4a9141bd@o286262.ingest.sentry.io/5882996
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-mainnet-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
|
||||
NX_VEGA_REST_URL=https://api.vega.community/api/v2/
|
||||
|
@ -9,6 +9,7 @@ NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.mainnet-mirror.vega.rocks
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-mainnet-mirror-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
|
||||
NX_VEGA_REST_URL=https://api.mainnet-mirror.vega.rocks/api/v2/
|
||||
|
@ -5,6 +5,7 @@ NX_VEGA_NETWORKS='{"DEVNET":"https://dev.governance.vega.xyz","STAGNET1":"https:
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_REST_URL=https://api.n00.stagnet1.vega.xyz/api/v2/
|
||||
@ -12,7 +13,7 @@ NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/m
|
||||
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/en-GB/firefox/addon/vega-wallet-beta/
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
|
@ -9,6 +9,7 @@ NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-testnet-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_REST_URL=https://api.n07.testnet.vega.xyz/api/v2/
|
||||
@ -17,7 +18,7 @@ NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/m
|
||||
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/en-GB/firefox/addon/vega-wallet-beta/
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
|
@ -14,7 +14,7 @@ NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/
|
||||
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/en-GB/firefox/addon/vega-wallet-beta/
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { toBigNum } from '@vegaprotocol/utils';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet, useEagerConnect } from '@vegaprotocol/wallet-react';
|
||||
import { useVegaWallet, useEagerConnect } from '@vegaprotocol/wallet';
|
||||
import { useFeatureFlags, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import React, { Suspense } from 'react';
|
||||
@ -15,6 +15,20 @@ import {
|
||||
} from './contexts/app-state/app-state-context';
|
||||
import { useContracts } from './contexts/contracts/contracts-context';
|
||||
import { useRefreshAssociatedBalances } from './hooks/use-refresh-associated-balances';
|
||||
import { useConnectors } from './lib/vega-connectors';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
const useVegaWalletEagerConnect = () => {
|
||||
const connectors = useConnectors();
|
||||
const vegaConnecting = useEagerConnect(connectors);
|
||||
const { pubKey, connect } = useVegaWallet();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [query] = React.useState(searchParams.get('address'));
|
||||
if (query && !pubKey) {
|
||||
connect(connectors.view);
|
||||
}
|
||||
return vegaConnecting;
|
||||
};
|
||||
|
||||
export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
@ -26,9 +40,9 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||
const { token, staking, vesting } = useContracts();
|
||||
const setAssociatedBalances = useRefreshAssociatedBalances();
|
||||
const [balancesLoaded, setBalancesLoaded] = React.useState(false);
|
||||
const vegaWalletStatus = useEagerConnect();
|
||||
const vegaConnecting = useVegaWalletEagerConnect();
|
||||
|
||||
const loaded = balancesLoaded && vegaWalletStatus !== 'connecting';
|
||||
const loaded = balancesLoaded && !vegaConnecting;
|
||||
|
||||
React.useEffect(() => {
|
||||
const run = async () => {
|
||||
@ -169,5 +183,3 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||
}
|
||||
return <Suspense fallback={loading}>{children}</Suspense>;
|
||||
};
|
||||
|
||||
AppLoader.displayName = 'AppLoader';
|
||||
|
@ -1,6 +1,7 @@
|
||||
import './i18n';
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { BrowserRouter as Router, useLocation } from 'react-router-dom';
|
||||
import { AppLoader } from './app-loader';
|
||||
import { NetworkInfo } from '@vegaprotocol/network-info';
|
||||
@ -25,7 +26,7 @@ import {
|
||||
} from '@vegaprotocol/web3';
|
||||
import { Web3Provider } from '@vegaprotocol/web3';
|
||||
import { VegaWalletDialogs } from './components/vega-wallet-dialogs';
|
||||
import { WalletProvider } from '@vegaprotocol/wallet-react';
|
||||
import { VegaWalletProvider, useChainId } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
useVegaTransactionManager,
|
||||
useVegaTransactionUpdater,
|
||||
@ -35,30 +36,32 @@ import { useEthereumConfig } from '@vegaprotocol/web3';
|
||||
import {
|
||||
useEnvironment,
|
||||
NetworkLoader,
|
||||
useInitializeEnv,
|
||||
NodeGuard,
|
||||
NodeSwitcherDialog,
|
||||
useNodeSwitcherStore,
|
||||
DocsLinks,
|
||||
NodeFailure,
|
||||
AppLoader as Loader,
|
||||
useInitializeEnv,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { ENV } from './config';
|
||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||
import { CreateWithdrawalDialog } from '@vegaprotocol/withdraws';
|
||||
import { SplashLoader } from './components/splash-loader';
|
||||
import { ToastsManager } from './toasts-manager';
|
||||
import { TelemetryDialog } from './components/telemetry-dialog/telemetry-dialog';
|
||||
import {
|
||||
TelemetryDialog,
|
||||
TELEMETRY_ON,
|
||||
} from './components/telemetry-dialog/telemetry-dialog';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSentryInit } from './hooks/use-sentry-init';
|
||||
import { useVegaWalletConfig } from './hooks/use-vega-wallet-config';
|
||||
import { isPartyNotFoundError } from './lib/party';
|
||||
|
||||
const cache: InMemoryCacheConfig = {
|
||||
typePolicies: {
|
||||
Account: {
|
||||
keyFields: false,
|
||||
},
|
||||
Instrument: {
|
||||
keyFields: ['code'],
|
||||
},
|
||||
Delegation: {
|
||||
keyFields: false,
|
||||
// Only get full updates
|
||||
@ -98,12 +101,32 @@ const Web3Container = ({
|
||||
/** Ethereum provider url */
|
||||
providerUrl: string;
|
||||
}) => {
|
||||
const InitializeHandlers = () => {
|
||||
useVegaTransactionManager();
|
||||
useVegaTransactionUpdater();
|
||||
useEthTransactionManager();
|
||||
useEthTransactionUpdater();
|
||||
useEthWithdrawApprovalsManager();
|
||||
return null;
|
||||
};
|
||||
|
||||
const [connectors, initializeConnectors] = useWeb3ConnectStore((store) => [
|
||||
store.connectors,
|
||||
store.initialize,
|
||||
]);
|
||||
const { ETHEREUM_PROVIDER_URL, ETH_LOCAL_PROVIDER_URL, ETH_WALLET_MNEMONIC } =
|
||||
useEnvironment();
|
||||
const {
|
||||
ETHEREUM_PROVIDER_URL,
|
||||
ETH_LOCAL_PROVIDER_URL,
|
||||
ETH_WALLET_MNEMONIC,
|
||||
VEGA_ENV,
|
||||
VEGA_URL,
|
||||
VEGA_EXPLORER_URL,
|
||||
CHROME_EXTENSION_URL,
|
||||
MOZILLA_EXTENSION_URL,
|
||||
VEGA_WALLET_URL,
|
||||
} = useEnvironment();
|
||||
|
||||
const vegaChainId = useChainId(VEGA_URL);
|
||||
|
||||
useEffect(() => {
|
||||
if (chainId) {
|
||||
@ -124,31 +147,50 @@ const Web3Container = ({
|
||||
ETH_LOCAL_PROVIDER_URL,
|
||||
ETH_WALLET_MNEMONIC,
|
||||
]);
|
||||
const sideBar = React.useMemo(() => {
|
||||
return [<EthWallet />, <VegaWallet />];
|
||||
}, []);
|
||||
|
||||
const vegaWalletConfig = useVegaWalletConfig();
|
||||
|
||||
if (!vegaWalletConfig || connectors.length === 0) {
|
||||
if (connectors.length === 0) {
|
||||
// Prevent loading when the connectors are not initialized
|
||||
return <SplashLoader />;
|
||||
}
|
||||
|
||||
if (
|
||||
!VEGA_URL ||
|
||||
!VEGA_WALLET_URL ||
|
||||
!VEGA_EXPLORER_URL ||
|
||||
!DocsLinks ||
|
||||
!CHROME_EXTENSION_URL ||
|
||||
!MOZILLA_EXTENSION_URL ||
|
||||
!vegaChainId
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Web3Provider connectors={connectors}>
|
||||
<Web3Connector connectors={connectors} chainId={Number(chainId)}>
|
||||
<WalletProvider config={vegaWalletConfig}>
|
||||
<VegaWalletProvider
|
||||
config={{
|
||||
network: VEGA_ENV,
|
||||
vegaUrl: VEGA_URL,
|
||||
chainId: vegaChainId,
|
||||
vegaWalletServiceUrl: VEGA_WALLET_URL,
|
||||
links: {
|
||||
explorer: VEGA_EXPLORER_URL,
|
||||
concepts: DocsLinks?.VEGA_WALLET_CONCEPTS_URL,
|
||||
chromeExtensionUrl: CHROME_EXTENSION_URL,
|
||||
mozillaExtensionUrl: MOZILLA_EXTENSION_URL,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ContractsProvider>
|
||||
<AppLoader>
|
||||
<BalanceManager>
|
||||
<>
|
||||
<AppLayout>
|
||||
<TemplateSidebar
|
||||
sidebar={
|
||||
<>
|
||||
<EthWallet />
|
||||
<VegaWallet />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<TemplateSidebar sidebar={sideBar}>
|
||||
<AppRouter />
|
||||
</TemplateSidebar>
|
||||
<footer className="p-4 break-all border-t border-neutral-700">
|
||||
@ -166,7 +208,7 @@ const Web3Container = ({
|
||||
</BalanceManager>
|
||||
</AppLoader>
|
||||
</ContractsProvider>
|
||||
</WalletProvider>
|
||||
</VegaWalletProvider>
|
||||
</Web3Connector>
|
||||
</Web3Provider>
|
||||
);
|
||||
@ -186,9 +228,20 @@ const ScrollToTop = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const removeQueryParams = (url: string) => {
|
||||
return url.split('?')[0];
|
||||
};
|
||||
|
||||
const AppContainer = () => {
|
||||
const { config, loading, error } = useEthereumConfig();
|
||||
const { VEGA_URL, ETHEREUM_PROVIDER_URL } = useEnvironment();
|
||||
const {
|
||||
VEGA_ENV,
|
||||
VEGA_URL,
|
||||
GIT_COMMIT_HASH,
|
||||
GIT_BRANCH,
|
||||
ETHEREUM_PROVIDER_URL,
|
||||
} = useEnvironment();
|
||||
const [telemetryOn] = useLocalStorage(TELEMETRY_ON);
|
||||
const { t } = useTranslation();
|
||||
const [nodeSwitcherOpen, setNodeSwitcher] = useNodeSwitcherStore((store) => [
|
||||
store.dialogOpen,
|
||||
@ -198,7 +251,70 @@ const AppContainer = () => {
|
||||
// Hacky skip all the loading & web3 init for geo restricted users
|
||||
const isRestricted = document?.location?.pathname?.includes('/restricted');
|
||||
|
||||
useSentryInit();
|
||||
useEffect(() => {
|
||||
if (ENV.dsn && telemetryOn === 'true') {
|
||||
Sentry.init({
|
||||
dsn: ENV.dsn,
|
||||
tracesSampleRate: 0.1,
|
||||
enabled: true,
|
||||
environment: VEGA_ENV,
|
||||
release: GIT_COMMIT_HASH,
|
||||
beforeSend(event, hint) {
|
||||
const error = hint?.originalException;
|
||||
const errorIsString = typeof error === 'string';
|
||||
const errorIsObject = error instanceof Error;
|
||||
const requestUrl = event.request?.url;
|
||||
const transaction = event.transaction;
|
||||
|
||||
if (
|
||||
(errorIsString && isPartyNotFoundError({ message: error })) ||
|
||||
(errorIsObject && isPartyNotFoundError(error))
|
||||
) {
|
||||
// This error is caused by a pubkey making an API request before
|
||||
// it has interacted with the chain. This isn't needed in Sentry.
|
||||
return null;
|
||||
}
|
||||
|
||||
const updatedRequest =
|
||||
requestUrl && requestUrl.includes('/claim?')
|
||||
? { ...event.request, url: removeQueryParams(requestUrl) }
|
||||
: event.request;
|
||||
|
||||
const updatedTransaction =
|
||||
transaction && transaction.includes('/claim?')
|
||||
? removeQueryParams(transaction)
|
||||
: transaction;
|
||||
|
||||
const updatedBreadcrumbs = event.breadcrumbs?.map((breadcrumb) => {
|
||||
if (
|
||||
breadcrumb.type === 'navigation' &&
|
||||
breadcrumb.data?.to?.includes('/claim?')
|
||||
) {
|
||||
return {
|
||||
...breadcrumb,
|
||||
data: {
|
||||
...breadcrumb.data,
|
||||
to: removeQueryParams(breadcrumb.data.to),
|
||||
},
|
||||
};
|
||||
}
|
||||
return breadcrumb;
|
||||
});
|
||||
|
||||
return {
|
||||
...event,
|
||||
request: updatedRequest,
|
||||
transaction: updatedTransaction,
|
||||
breadcrumbs: updatedBreadcrumbs ?? event.breadcrumbs,
|
||||
};
|
||||
},
|
||||
});
|
||||
Sentry.setTag('branch', GIT_BRANCH);
|
||||
Sentry.setTag('commit', GIT_COMMIT_HASH);
|
||||
} else {
|
||||
Sentry.close();
|
||||
}
|
||||
}, [GIT_COMMIT_HASH, GIT_BRANCH, VEGA_ENV, telemetryOn]);
|
||||
|
||||
if (isRestricted) {
|
||||
return (
|
||||
@ -240,15 +356,6 @@ const AppContainer = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const InitializeHandlers = () => {
|
||||
useVegaTransactionManager();
|
||||
useVegaTransactionUpdater();
|
||||
useEthTransactionManager();
|
||||
useEthTransactionUpdater();
|
||||
useEthWithdrawApprovalsManager();
|
||||
return null;
|
||||
};
|
||||
|
||||
function App() {
|
||||
useInitializeEnv();
|
||||
|
||||
|
@ -7,7 +7,7 @@ import { useGetAssociationBreakdown } from '../../hooks/use-get-association-brea
|
||||
import { useGetUserBalances } from '../../hooks/use-get-user-balances';
|
||||
import { useBalances } from '../../lib/balances/balances-store';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet-react';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useListenForStakingEvents as useListenForAssociationEvents } from '../../hooks/use-listen-for-staking-events';
|
||||
import { useTranches } from '../../lib/tranches/tranches-store';
|
||||
import { useUserTrancheBalances } from '../../routes/redemption/hooks';
|
||||
|
@ -15,16 +15,19 @@ export const CollapsibleToggle = ({
|
||||
dataTestId,
|
||||
children,
|
||||
}: CollapsibleToggleProps) => {
|
||||
const classes = classnames('transition-transform ease-in-out duration-300', {
|
||||
'rotate-180': toggleState,
|
||||
});
|
||||
const classes = classnames(
|
||||
'mb-4 transition-transform ease-in-out duration-300',
|
||||
{
|
||||
'rotate-180': toggleState,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={() => setToggleState(!toggleState)}
|
||||
data-testid={dataTestId}
|
||||
>
|
||||
<div className="flex items-baseline gap-3">
|
||||
<div className="flex items-center gap-3">
|
||||
{children}
|
||||
<div className={classes} data-testid="toggle-icon-wrapper">
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={20} />
|
||||
|
@ -1,13 +1,18 @@
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDialogStore } from '@vegaprotocol/wallet-react';
|
||||
import { useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
|
||||
export const ConnectToVega = () => {
|
||||
const { t } = useTranslation();
|
||||
const openVegaWalletDialog = useDialogStore((store) => store.open);
|
||||
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
|
||||
openVegaWalletDialog: store.openVegaWalletDialog,
|
||||
}));
|
||||
return (
|
||||
<Button
|
||||
onClick={openVegaWalletDialog}
|
||||
onClick={() => {
|
||||
openVegaWalletDialog();
|
||||
}}
|
||||
data-testid="connect-to-vega-wallet-btn"
|
||||
variant="primary"
|
||||
>
|
||||
|
@ -1,8 +1,7 @@
|
||||
import classNames from 'classnames';
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
interface HeadingProps {
|
||||
title?: ReactNode;
|
||||
title?: string;
|
||||
centerContent?: boolean;
|
||||
marginTop?: boolean;
|
||||
marginBottom?: boolean;
|
||||
|
@ -1,44 +0,0 @@
|
||||
import {
|
||||
useLinks,
|
||||
DApp,
|
||||
CONSOLE_REWARDS_PAGE,
|
||||
} from '@vegaprotocol/environment';
|
||||
import {
|
||||
ExternalLink,
|
||||
Intent,
|
||||
NotificationBanner,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { Trans } from 'react-i18next';
|
||||
import { useMatch } from 'react-router-dom';
|
||||
import Routes from '../../routes/routes';
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
const ConsoleRewardsLink = ({ children }: { children: ReactNode }) => {
|
||||
const consoleLink = useLinks(DApp.Console);
|
||||
return (
|
||||
<ExternalLink
|
||||
href={consoleLink(CONSOLE_REWARDS_PAGE)}
|
||||
className="underline inline-flex gap-1 items-center"
|
||||
title="Rewards in Console"
|
||||
>
|
||||
<span>{children}</span>
|
||||
<VegaIcon size={12} name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
</ExternalLink>
|
||||
);
|
||||
};
|
||||
|
||||
export const RewardsMovedNotification = () => {
|
||||
const onRewardsPage = useMatch(Routes.REWARDS);
|
||||
if (!onRewardsPage) return null;
|
||||
|
||||
return (
|
||||
<NotificationBanner intent={Intent.Warning}>
|
||||
<Trans
|
||||
i18nKey="rewardsMovedNotification"
|
||||
components={[<ConsoleRewardsLink>Console</ConsoleRewardsLink>]}
|
||||
/>
|
||||
</NotificationBanner>
|
||||
);
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
import classNames from 'classnames';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet-react';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import type { ReactNode } from 'react';
|
||||
import { AnnouncementBanner } from '@vegaprotocol/announcements';
|
||||
import { Nav } from '../nav';
|
||||
@ -10,7 +10,6 @@ import {
|
||||
ProtocolUpgradeProposalNotification,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { ViewingAsBanner } from '@vegaprotocol/ui-toolkit';
|
||||
import { RewardsMovedNotification } from '../notifications/rewards-moved-notification';
|
||||
|
||||
interface AppLayoutProps {
|
||||
children: ReactNode;
|
||||
@ -46,10 +45,8 @@ export const AppLayout = ({ children }: AppLayoutProps) => {
|
||||
|
||||
const NotificationsContainer = () => {
|
||||
const { isReadOnly, pubKey, disconnect } = useVegaWallet();
|
||||
|
||||
return (
|
||||
<div data-testid="banners">
|
||||
<RewardsMovedNotification />
|
||||
<ProtocolUpgradeProposalNotification
|
||||
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
|
||||
/>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Children, type ReactNode } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
export interface TemplateSidebarProps {
|
||||
children: ReactNode;
|
||||
sidebar: ReactNode;
|
||||
children: React.ReactNode;
|
||||
sidebar: React.ReactNode[];
|
||||
}
|
||||
|
||||
export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
|
||||
@ -12,9 +12,9 @@ export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
|
||||
{children}
|
||||
</main>
|
||||
<aside className="col-start-2 row-start-1 row-span-2 hidden lg:block p-4 bg-banner bg-contain border-l border-neutral-700">
|
||||
{Children.map(sidebar, (child, i) => (
|
||||
{sidebar.map((Component, i) => (
|
||||
<section className="mb-4 last:mb-0" key={i}>
|
||||
{child}
|
||||
{Component}
|
||||
</section>
|
||||
))}
|
||||
</aside>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet, useDialogStore } from '@vegaprotocol/wallet-react';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@ -10,7 +10,9 @@ interface VegaWalletContainerProps {
|
||||
export const VegaWalletContainer = ({ children }: VegaWalletContainerProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { pubKey } = useVegaWallet();
|
||||
const openVegaWalletDialog = useDialogStore((store) => store.open);
|
||||
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
|
||||
openVegaWalletDialog: store.openVegaWalletDialog,
|
||||
}));
|
||||
|
||||
if (!pubKey) {
|
||||
return (
|
||||
|
@ -9,7 +9,7 @@ import Routes from '../../routes/routes';
|
||||
export const RiskMessage = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="bg-vega-light-100 dark:bg-vega-dark-100 p-6">
|
||||
<div className="bg-vega-light-100 dark:bg-vega-dark-100 p-6 mb-6">
|
||||
<ul className="list-[square] ml-4">
|
||||
<li>
|
||||
{t(
|
||||
@ -23,7 +23,7 @@ export const RiskMessage = () => {
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
<p className="mb-8">
|
||||
{t(
|
||||
'By using the Vega Governance App, you acknowledge that you have read and understood the'
|
||||
)}{' '}
|
||||
|
@ -1,39 +1,25 @@
|
||||
import {
|
||||
ConnectDialogWithRiskAck,
|
||||
useDialogStore,
|
||||
} from '@vegaprotocol/wallet-react';
|
||||
VegaConnectDialog,
|
||||
VegaManageDialog,
|
||||
ViewAsDialog,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import {
|
||||
AppStateActionType,
|
||||
useAppState,
|
||||
} from '../../contexts/app-state/app-state-context';
|
||||
import { useConnectors } from '../../lib/vega-connectors';
|
||||
import { RiskMessage } from './risk-message';
|
||||
import { VegaManageDialog } from '../manage-dialog';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
|
||||
export const VegaWalletDialogs = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const { appState, appDispatch } = useAppState();
|
||||
const [riskAccepted, setRiskAccepted] = useLocalStorage(
|
||||
'vega_wallet_risk_accepted'
|
||||
);
|
||||
const vegaWalletDialogOpen = useDialogStore((store) => store.isOpen);
|
||||
const setVegaWalletDialog = useDialogStore((store) => store.set);
|
||||
const connectors = useConnectors();
|
||||
return (
|
||||
<>
|
||||
<ConnectDialogWithRiskAck
|
||||
open={vegaWalletDialogOpen}
|
||||
onChange={setVegaWalletDialog}
|
||||
riskAccepted={
|
||||
VEGA_ENV === Networks.TESTNET ? riskAccepted === 'true' : true
|
||||
}
|
||||
riskAckContent={<RiskMessage />}
|
||||
onRiskAccepted={() => setRiskAccepted('true')}
|
||||
onRiskRejected={() => {
|
||||
setRiskAccepted('false');
|
||||
setVegaWalletDialog(false);
|
||||
}}
|
||||
<VegaConnectDialog
|
||||
connectors={connectors}
|
||||
riskMessage={<RiskMessage />}
|
||||
/>
|
||||
|
||||
<VegaManageDialog
|
||||
dialogOpen={appState.vegaWalletManageOverlay}
|
||||
setDialogOpen={(open) =>
|
||||
@ -43,6 +29,8 @@ export const VegaWalletDialogs = () => {
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<ViewAsDialog connector={connectors.view} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -4,13 +4,14 @@ import keyBy from 'lodash/keyBy';
|
||||
import uniq from 'lodash/uniq';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ENV } from '../../config';
|
||||
|
||||
import noIcon from '../../images/token-no-icon.png';
|
||||
import vegaBlack from '../../images/vega_black.png';
|
||||
import vegaVesting from '../../images/vega_vesting.png';
|
||||
import { BigNumber } from '../../lib/bignumber';
|
||||
import { type WalletCardAssetProps } from '../wallet-card';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet-react';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useContracts } from '../../contexts/contracts/contracts-context';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import {
|
||||
@ -36,6 +37,7 @@ export const usePollForDelegations = () => {
|
||||
const { t } = useTranslation();
|
||||
const { pubKey } = useVegaWallet();
|
||||
const client = useApolloClient();
|
||||
const { delegationsPagination } = ENV;
|
||||
const [delegations, setDelegations] = React.useState<
|
||||
WalletDelegationFieldsFragment[]
|
||||
>([]);
|
||||
@ -66,9 +68,11 @@ export const usePollForDelegations = () => {
|
||||
query: DelegationsDocument,
|
||||
variables: {
|
||||
partyId: pubKey,
|
||||
delegationsPagination: {
|
||||
first: 50,
|
||||
},
|
||||
delegationsPagination: delegationsPagination
|
||||
? {
|
||||
first: Number(delegationsPagination),
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
fetchPolicy: 'network-only',
|
||||
})
|
||||
@ -232,14 +236,14 @@ export const usePollForDelegations = () => {
|
||||
// will just continue to fail
|
||||
clearInterval(interval);
|
||||
});
|
||||
}, 20000);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
mounted = false;
|
||||
};
|
||||
}, [client, decimals, pubKey, t, vegaToken.address]);
|
||||
}, [delegationsPagination, client, decimals, pubKey, t, vegaToken.address]);
|
||||
|
||||
return { delegations, currentStakeAvailable, delegatedNodes, accounts };
|
||||
};
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { ButtonLink, Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ExternalLinks } from '@vegaprotocol/environment';
|
||||
import { useConnect } from '@vegaprotocol/wallet-react';
|
||||
import { useViewAsDialog } from '@vegaprotocol/wallet';
|
||||
|
||||
export const VegaWalletPrompt = () => {
|
||||
const { t } = useTranslation();
|
||||
const { connect } = useConnect();
|
||||
const setViewAsDialog = useViewAsDialog((state) => state.setOpen);
|
||||
return (
|
||||
<>
|
||||
<h3 className="mt-4 mb-2">{t('getWallet')}</h3>
|
||||
@ -16,7 +16,7 @@ export const VegaWalletPrompt = () => {
|
||||
<ButtonLink
|
||||
className="text-neutral-500"
|
||||
data-testid="view-as-user"
|
||||
onClick={() => connect('viewParty')}
|
||||
onClick={() => setViewAsDialog(true)}
|
||||
>
|
||||
{t('viewAsParty')}
|
||||
</ButtonLink>
|
||||
|
@ -25,7 +25,7 @@ import {
|
||||
} from '../wallet-card';
|
||||
import { VegaWalletPrompt } from './vega-wallet-prompt';
|
||||
import { usePollForDelegations } from './hooks';
|
||||
import { useVegaWallet, useDialogStore } from '@vegaprotocol/wallet-react';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import { Button, ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { toBigNum } from '@vegaprotocol/utils';
|
||||
import { usePendingBalancesStore } from '../../hooks/use-pending-balances-manager';
|
||||
@ -34,17 +34,16 @@ import omit from 'lodash/omit';
|
||||
|
||||
export const VegaWallet = () => {
|
||||
const { t } = useTranslation();
|
||||
const { status, pubKey, pubKeys } = useVegaWallet();
|
||||
const { pubKey, pubKeys } = useVegaWallet();
|
||||
const pubKeyObj = useMemo(() => {
|
||||
return pubKeys?.find((pk) => pk.publicKey === pubKey);
|
||||
}, [pubKey, pubKeys]);
|
||||
|
||||
const child =
|
||||
status === 'connected' ? (
|
||||
<VegaWalletConnected vegaKeys={pubKeys.map((pk) => pk.publicKey)} />
|
||||
) : (
|
||||
<VegaWalletNotConnected />
|
||||
);
|
||||
const child = !pubKeys ? (
|
||||
<VegaWalletNotConnected />
|
||||
) : (
|
||||
<VegaWalletConnected vegaKeys={pubKeys.map((pk) => pk.publicKey)} />
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="vega-wallet" data-testid="vega-wallet">
|
||||
@ -76,7 +75,9 @@ export const VegaWallet = () => {
|
||||
|
||||
const VegaWalletNotConnected = () => {
|
||||
const { t } = useTranslation();
|
||||
const openVegaWalletDialog = useDialogStore((store) => store.open);
|
||||
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
|
||||
openVegaWalletDialog: store.openVegaWalletDialog,
|
||||
}));
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
|
@ -65,6 +65,7 @@ export const ENV = {
|
||||
docsUrl: windowOrDefault('NX_VEGA_DOCS_URL'),
|
||||
ethWalletMnemonic: windowOrDefault('NX_ETH_WALLET_MNEMONIC'),
|
||||
localProviderUrl: windowOrDefault('NX_LOCAL_PROVIDER_URL'),
|
||||
delegationsPagination: windowOrDefault('NX_DELEGATIONS_PAGINATION'),
|
||||
rest: windowOrDefault('NX_VEGA_REST_URL'),
|
||||
addresses:
|
||||
ContractAddresses[(envName === 'local' ? 'CUSTOM' : envName) as Networks],
|
||||
|
@ -111,4 +111,3 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
|
||||
</ContractsContext.Provider>
|
||||
);
|
||||
};
|
||||
ContractsProvider.displayName = 'ContractsProvider';
|
||||
|
@ -7,7 +7,7 @@ import { useWeb3React } from '@web3-react/core';
|
||||
|
||||
export const useListenForStakingEvents = (
|
||||
contract: Contract | undefined,
|
||||
vegaPublicKey: string | undefined,
|
||||
vegaPublicKey: string | null,
|
||||
numberOfConfirmations: number
|
||||
) => {
|
||||
const { account } = useWeb3React();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { toBigNum } from '@vegaprotocol/utils';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet-react';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useEthereumConfig } from '@vegaprotocol/web3';
|
||||
import React from 'react';
|
||||
|
||||
|
@ -1,81 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { TELEMETRY_ON } from '../components/telemetry-dialog/telemetry-dialog';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { ENV } from '../config';
|
||||
import { isPartyNotFoundError } from '../lib/party';
|
||||
|
||||
export const useSentryInit = () => {
|
||||
const { VEGA_ENV, GIT_COMMIT_HASH, GIT_BRANCH } = useEnvironment();
|
||||
const [telemetryOn] = useLocalStorage(TELEMETRY_ON);
|
||||
|
||||
useEffect(() => {
|
||||
if (ENV.dsn && telemetryOn === 'true') {
|
||||
Sentry.init({
|
||||
dsn: ENV.dsn,
|
||||
tracesSampleRate: 0.1,
|
||||
enabled: true,
|
||||
environment: VEGA_ENV,
|
||||
release: GIT_COMMIT_HASH,
|
||||
beforeSend(event, hint) {
|
||||
const error = hint?.originalException;
|
||||
const errorIsString = typeof error === 'string';
|
||||
const errorIsObject = error instanceof Error;
|
||||
const requestUrl = event.request?.url;
|
||||
const transaction = event.transaction;
|
||||
|
||||
if (
|
||||
(errorIsString && isPartyNotFoundError({ message: error })) ||
|
||||
(errorIsObject && isPartyNotFoundError(error))
|
||||
) {
|
||||
// This error is caused by a pubkey making an API request before
|
||||
// it has interacted with the chain. This isn't needed in Sentry.
|
||||
return null;
|
||||
}
|
||||
|
||||
const updatedRequest =
|
||||
requestUrl && requestUrl.includes('/claim?')
|
||||
? { ...event.request, url: removeQueryParams(requestUrl) }
|
||||
: event.request;
|
||||
|
||||
const updatedTransaction =
|
||||
transaction && transaction.includes('/claim?')
|
||||
? removeQueryParams(transaction)
|
||||
: transaction;
|
||||
|
||||
const updatedBreadcrumbs = event.breadcrumbs?.map((breadcrumb) => {
|
||||
if (
|
||||
breadcrumb.type === 'navigation' &&
|
||||
breadcrumb.data?.to?.includes('/claim?')
|
||||
) {
|
||||
return {
|
||||
...breadcrumb,
|
||||
data: {
|
||||
...breadcrumb.data,
|
||||
to: removeQueryParams(breadcrumb.data.to),
|
||||
},
|
||||
};
|
||||
}
|
||||
return breadcrumb;
|
||||
});
|
||||
|
||||
return {
|
||||
...event,
|
||||
request: updatedRequest,
|
||||
transaction: updatedTransaction,
|
||||
breadcrumbs: updatedBreadcrumbs ?? event.breadcrumbs,
|
||||
};
|
||||
},
|
||||
});
|
||||
Sentry.setTag('branch', GIT_BRANCH);
|
||||
Sentry.setTag('commit', GIT_COMMIT_HASH);
|
||||
} else {
|
||||
Sentry.close();
|
||||
}
|
||||
}, [GIT_COMMIT_HASH, GIT_BRANCH, VEGA_ENV, telemetryOn]);
|
||||
};
|
||||
|
||||
const removeQueryParams = (url: string) => {
|
||||
return url.split('?')[0];
|
||||
};
|
@ -1,41 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
InjectedConnector,
|
||||
JsonRpcConnector,
|
||||
SnapConnector,
|
||||
ViewPartyConnector,
|
||||
createConfig,
|
||||
fairground,
|
||||
stagnet,
|
||||
mainnet,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
|
||||
export const useVegaWalletConfig = () => {
|
||||
const { VEGA_ENV, VEGA_URL, VEGA_WALLET_URL } = useEnvironment();
|
||||
return useMemo(() => {
|
||||
if (!VEGA_ENV || !VEGA_URL || !VEGA_WALLET_URL) return;
|
||||
|
||||
const injected = new InjectedConnector();
|
||||
|
||||
const jsonRpc = new JsonRpcConnector({
|
||||
url: VEGA_WALLET_URL,
|
||||
});
|
||||
|
||||
const snap = new SnapConnector({
|
||||
node: new URL(VEGA_URL).origin,
|
||||
snapId: 'npm:@vegaprotocol/snap',
|
||||
version: '1.0.1',
|
||||
});
|
||||
|
||||
const viewParty = new ViewPartyConnector();
|
||||
|
||||
const config = createConfig({
|
||||
chains: [mainnet, fairground, stagnet],
|
||||
defaultChainId: fairground.id,
|
||||
connectors: [injected, snap, jsonRpc, viewParty],
|
||||
});
|
||||
|
||||
return config;
|
||||
}, [VEGA_ENV, VEGA_URL, VEGA_WALLET_URL]);
|
||||
};
|
@ -31,7 +31,7 @@ i18n
|
||||
load: 'languageOnly',
|
||||
debug: isInDev,
|
||||
// have a common namespace used around the full app
|
||||
ns: ['governance', 'wallet', 'wallet-react'],
|
||||
ns: ['governance'],
|
||||
defaultNS: 'governance',
|
||||
keySeparator: false, // we use content as keys
|
||||
nsSeparator: false,
|
||||
|
@ -20,7 +20,6 @@ describe('getMultisigStatus', () => {
|
||||
expect(result).toEqual({
|
||||
multisigStatus: MultisigStatus.noNodes,
|
||||
showMultisigStatusError: true,
|
||||
zeroScoreNodes: [],
|
||||
});
|
||||
});
|
||||
|
||||
@ -36,7 +35,6 @@ describe('getMultisigStatus', () => {
|
||||
expect(result).toEqual({
|
||||
multisigStatus: MultisigStatus.correct,
|
||||
showMultisigStatusError: false,
|
||||
zeroScoreNodes: [],
|
||||
});
|
||||
});
|
||||
|
||||
@ -52,22 +50,6 @@ describe('getMultisigStatus', () => {
|
||||
expect(result).toEqual({
|
||||
multisigStatus: MultisigStatus.nodeNeedsRemoving,
|
||||
showMultisigStatusError: true,
|
||||
zeroScoreNodes: [
|
||||
{
|
||||
id: '1',
|
||||
rewardScore: {
|
||||
multisigScore: '0',
|
||||
},
|
||||
stakedTotal: '1000',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
rewardScore: {
|
||||
multisigScore: '0',
|
||||
},
|
||||
stakedTotal: '1000',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
@ -83,15 +65,6 @@ describe('getMultisigStatus', () => {
|
||||
expect(result).toEqual({
|
||||
multisigStatus: MultisigStatus.nodeNeedsAdding,
|
||||
showMultisigStatusError: true,
|
||||
zeroScoreNodes: [
|
||||
{
|
||||
id: '1',
|
||||
rewardScore: {
|
||||
multisigScore: '0',
|
||||
},
|
||||
stakedTotal: '1000',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,8 +1,5 @@
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import type {
|
||||
PreviousEpochQuery,
|
||||
ValidatorNodeFragment,
|
||||
} from '../routes/staking/__generated__/PreviousEpoch';
|
||||
import type { PreviousEpochQuery } from '../routes/staking/__generated__/PreviousEpoch';
|
||||
|
||||
export enum MultisigStatus {
|
||||
'correct' = 'correct',
|
||||
@ -20,15 +17,12 @@ export const getMultisigStatusInfo = (
|
||||
previousEpochData?.epoch.validatorsConnection?.edges
|
||||
);
|
||||
|
||||
const zeroScore = (node: ValidatorNodeFragment) =>
|
||||
Number(node.rewardScore?.multisigScore) === 0;
|
||||
const oneScore = (node: ValidatorNodeFragment) =>
|
||||
Number(node.rewardScore?.multisigScore) === 1;
|
||||
|
||||
const hasZero = allNodesInPreviousEpoch.some(zeroScore);
|
||||
const hasOne = allNodesInPreviousEpoch.some(oneScore);
|
||||
|
||||
const zeroScoreNodes = allNodesInPreviousEpoch.filter(zeroScore);
|
||||
const hasZero = allNodesInPreviousEpoch.some(
|
||||
(node) => Number(node?.rewardScore?.multisigScore) === 0
|
||||
);
|
||||
const hasOne = allNodesInPreviousEpoch.some(
|
||||
(node) => Number(node?.rewardScore?.multisigScore) === 1
|
||||
);
|
||||
|
||||
if (hasZero && hasOne) {
|
||||
// If any individual node has 0 it means that node is missing from the multisig and needs to be added
|
||||
@ -44,6 +38,5 @@ export const getMultisigStatusInfo = (
|
||||
return {
|
||||
showMultisigStatusError: status !== MultisigStatus.correct,
|
||||
multisigStatus: status,
|
||||
zeroScoreNodes,
|
||||
};
|
||||
};
|
||||
|
30
apps/governance/src/lib/vega-connectors.ts
Normal file
30
apps/governance/src/lib/vega-connectors.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
JsonRpcConnector,
|
||||
ViewConnector,
|
||||
InjectedConnector,
|
||||
SnapConnector,
|
||||
DEFAULT_SNAP_ID,
|
||||
} from '@vegaprotocol/wallet';
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
export const jsonRpc = new JsonRpcConnector();
|
||||
export const injected = new InjectedConnector();
|
||||
export const view = new ViewConnector(urlParams.get('address'));
|
||||
|
||||
export const snap = new SnapConnector(DEFAULT_SNAP_ID);
|
||||
|
||||
export const useConnectors = () => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
return useMemo(
|
||||
() => ({
|
||||
injected,
|
||||
jsonRpc,
|
||||
view,
|
||||
snap: featureFlags.METAMASK_SNAPS ? snap : undefined,
|
||||
}),
|
||||
[featureFlags.METAMASK_SNAPS]
|
||||
);
|
||||
};
|
@ -5,6 +5,7 @@ import {
|
||||
ProposalState,
|
||||
VoteValue,
|
||||
} from '@vegaprotocol/types';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||
import {
|
||||
generateNoVotes,
|
||||
@ -15,7 +16,9 @@ import { ProposalHeader, NewTransferSummary } from './proposal-header';
|
||||
import {
|
||||
lastWeek,
|
||||
nextWeek,
|
||||
mockWalletContext,
|
||||
createUserVoteQueryMock,
|
||||
networkParamsQueryMock,
|
||||
} from '../../test-helpers/mocks';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { VoteState } from '../vote-details/use-user-vote';
|
||||
@ -42,12 +45,14 @@ const renderComponent = (
|
||||
render(
|
||||
<AppStateProvider>
|
||||
<BrowserRouter>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<ProposalHeader
|
||||
proposal={proposal}
|
||||
isListItem={isListItem}
|
||||
voteState={voteState}
|
||||
/>
|
||||
<MockedProvider mocks={[networkParamsQueryMock, ...mocks]}>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<ProposalHeader
|
||||
proposal={proposal}
|
||||
isListItem={isListItem}
|
||||
voteState={voteState}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</BrowserRouter>
|
||||
</AppStateProvider>
|
||||
@ -155,7 +160,7 @@ describe('Proposal header', () => {
|
||||
screen.queryByTestId('proposal-description')
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
|
||||
/Update to market: MarketId/
|
||||
'Update to market ID: MarketId'
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -36,9 +36,6 @@ import { type Proposal, type BatchProposal } from '../../types';
|
||||
import { type ProposalTermsFieldsFragment } from '../../__generated__/Proposals';
|
||||
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,
|
||||
@ -55,8 +52,11 @@ const ProposalTypeTags = ({
|
||||
|
||||
if (proposal.__typename === 'BatchProposal') {
|
||||
return (
|
||||
<div data-testid="proposal-type">
|
||||
<ProposalInfoLabel variant="secondary">BatchProposal</ProposalInfoLabel>
|
||||
<div data-testid="proposal-type" className="flex gap-1">
|
||||
{proposal.subProposals?.map((subProposal, i) => {
|
||||
if (!subProposal?.terms) return null;
|
||||
return <ProposalTypeTag key={i} terms={subProposal.terms} />;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -138,77 +138,50 @@ const ProposalDetails = ({
|
||||
);
|
||||
}
|
||||
case 'UpdateMarketState': {
|
||||
const marketPageLink = consoleLink(
|
||||
CONSOLE_MARKET_PAGE.replace(':marketId', terms.change.market.id)
|
||||
);
|
||||
return (
|
||||
<span>
|
||||
{featureFlags.UPDATE_MARKET_STATE &&
|
||||
terms.change?.market?.id &&
|
||||
terms.change.updateType ? (
|
||||
<>
|
||||
<span>{t(terms.change.updateType)}: </span>
|
||||
<span className="inline-flex gap-2">
|
||||
<span className="break-all">
|
||||
<MarketName marketId={terms.change.market.id} />
|
||||
</span>
|
||||
<span className="inline-flex items-end gap-0">
|
||||
<CopyWithTooltip
|
||||
text={terms.change.market.id}
|
||||
description={t('copyId')}
|
||||
>
|
||||
<button className="inline-block px-1">
|
||||
<VegaIcon size={20} name={VegaIconNames.COPY} />
|
||||
</button>
|
||||
</CopyWithTooltip>
|
||||
<Tooltip description={t('OpenInConsole')} align="center">
|
||||
<Link
|
||||
className="inline-block px-1"
|
||||
to={marketPageLink}
|
||||
target="_blank"
|
||||
>
|
||||
<VegaIcon
|
||||
size={20}
|
||||
name={VegaIconNames.OPEN_EXTERNAL}
|
||||
/>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</span>
|
||||
{t(terms.change.updateType)}:{' '}
|
||||
{truncateMiddle(terms.change.market.id)}
|
||||
</>
|
||||
) : null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
case 'UpdateMarket': {
|
||||
const marketPageLink = consoleLink(
|
||||
CONSOLE_MARKET_PAGE.replace(':marketId', terms.change.marketId)
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<span>{t('UpdateToMarket')}: </span>
|
||||
<span>{t('UpdateToMarket')}:</span>{' '}
|
||||
<span className="inline-flex items-start gap-2">
|
||||
<span className="break-all">
|
||||
<MarketName marketId={terms.change.marketId} />
|
||||
</span>
|
||||
<span className="break-all">{terms.change.marketId} </span>
|
||||
<span className="inline-flex items-end gap-0">
|
||||
<CopyWithTooltip
|
||||
text={terms.change.marketId}
|
||||
description={t('copyId')}
|
||||
description={t('copyToClipboard')}
|
||||
>
|
||||
<button className="inline-block px-1">
|
||||
<VegaIcon size={20} name={VegaIconNames.COPY} />
|
||||
</button>
|
||||
</CopyWithTooltip>
|
||||
<Tooltip description={t('OpenInConsole')} align="center">
|
||||
<Link
|
||||
<button
|
||||
className="inline-block px-1"
|
||||
target="_blank"
|
||||
to={marketPageLink}
|
||||
onClick={() => {
|
||||
const marketPageLink = consoleLink(
|
||||
CONSOLE_MARKET_PAGE.replace(
|
||||
':marketId',
|
||||
// @ts-ignore ts doesn't like this field even though its already a string above???
|
||||
terms.change.marketId
|
||||
)
|
||||
);
|
||||
window.open(marketPageLink, '_blank');
|
||||
}}
|
||||
>
|
||||
<VegaIcon size={20} name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
</Link>
|
||||
</button>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</span>
|
||||
@ -251,7 +224,7 @@ const ProposalDetails = ({
|
||||
}}
|
||||
components={{
|
||||
// @ts-ignore children passed by i18next
|
||||
lozenge: <Lozenge className="text-xs" />,
|
||||
lozenge: <Lozenge />,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@ -313,18 +286,12 @@ const ProposalDetails = ({
|
||||
{proposal.subProposals.map((p, i) => {
|
||||
if (!p?.terms) return null;
|
||||
return (
|
||||
<li
|
||||
key={i}
|
||||
className="grid grid-cols-[40px_minmax(0,1fr)] grid-rows-1 gap-3 items-center"
|
||||
>
|
||||
<Indicator indicator={i + 1} />
|
||||
<span>
|
||||
<div>{renderDetails(p.terms)}</div>
|
||||
<SubProposalStateText
|
||||
state={proposal.state}
|
||||
enactmentDatetime={p.terms.enactmentDatetime}
|
||||
/>
|
||||
</span>
|
||||
<li key={i}>
|
||||
<div>{renderDetails(p.terms)}</div>
|
||||
<SubProposalStateText
|
||||
state={proposal.state}
|
||||
enactmentDatetime={p.terms.enactmentDatetime}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
@ -541,12 +508,10 @@ const BatchProposalStateText = ({
|
||||
|
||||
export const ProposalHeader = ({
|
||||
proposal,
|
||||
restData,
|
||||
isListItem = true,
|
||||
voteState,
|
||||
}: {
|
||||
proposal: Proposal | BatchProposal;
|
||||
restData?: ProposalNode | null;
|
||||
isListItem?: boolean;
|
||||
voteState?: VoteState | null;
|
||||
}) => {
|
||||
@ -598,7 +563,7 @@ export const ProposalHeader = ({
|
||||
)}
|
||||
</div>
|
||||
<ProposalDetails proposal={proposal} />
|
||||
<VoteBreakdown proposal={proposal} restData={restData} />
|
||||
<VoteBreakdown proposal={proposal} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -3,8 +3,16 @@ import { useTranslation } from 'react-i18next';
|
||||
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
||||
import {
|
||||
type BatchProposalFieldsFragment,
|
||||
type ProposalFieldsFragment,
|
||||
} from '../../__generated__/Proposals';
|
||||
|
||||
export const ProposalJson = ({ proposal }: { proposal?: unknown }) => {
|
||||
export const ProposalJson = ({
|
||||
proposal,
|
||||
}: {
|
||||
proposal: ProposalFieldsFragment | BatchProposalFieldsFragment;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
|
||||
@ -18,7 +26,7 @@ export const ProposalJson = ({ proposal }: { proposal?: unknown }) => {
|
||||
<SubHeading title={t('proposalJson')} />
|
||||
</CollapsibleToggle>
|
||||
|
||||
{showDetails && <SyntaxHighlighter size="smaller" data={proposal} />}
|
||||
{showDetails && <SyntaxHighlighter data={proposal} />}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
@ -5,11 +5,6 @@ import {
|
||||
} from './proposal-market-changes';
|
||||
import type { JsonValue } from '../../../../components/json-diff';
|
||||
|
||||
jest.mock('../proposal/market-name.tsx', () => ({
|
||||
...jest.requireActual('../proposal/market-name.tsx'),
|
||||
MarketName: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('applyImmutableKeysFromEarlierVersion', () => {
|
||||
it('returns an empty object if any argument is not an object or null', () => {
|
||||
const earlierVersion: JsonValue = null;
|
||||
@ -62,21 +57,21 @@ describe('applyImmutableKeysFromEarlierVersion', () => {
|
||||
describe('ProposalMarketChanges', () => {
|
||||
it('renders correctly', () => {
|
||||
const { getByTestId } = render(
|
||||
<ProposalMarketChanges marketId="market-id" updateProposalNode={null} />
|
||||
<ProposalMarketChanges marketId="market-id" updatedProposal={{}} />
|
||||
);
|
||||
expect(getByTestId('proposal-market-changes')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('JsonDiff is not visible when showChanges is false', () => {
|
||||
const { queryByTestId } = render(
|
||||
<ProposalMarketChanges marketId="market-id" updateProposalNode={null} />
|
||||
<ProposalMarketChanges marketId="market-id" updatedProposal={{}} />
|
||||
);
|
||||
expect(queryByTestId('json-diff')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('JsonDiff is visible when showChanges is true', async () => {
|
||||
const { getByTestId } = render(
|
||||
<ProposalMarketChanges marketId="market-id" updateProposalNode={null} />
|
||||
<ProposalMarketChanges marketId="market-id" updatedProposal={{}} />
|
||||
);
|
||||
fireEvent.click(getByTestId('proposal-market-changes-toggle'));
|
||||
expect(getByTestId('json-diff')).toBeInTheDocument();
|
||||
|
@ -1,24 +1,14 @@
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import set from 'lodash/set';
|
||||
import get from 'lodash/get';
|
||||
import compact from 'lodash/compact';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { JsonDiff, type JsonValue } from '../../../../components/json-diff';
|
||||
import { JsonDiff } from '../../../../components/json-diff';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useState } from 'react';
|
||||
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import {
|
||||
useFetchProposal,
|
||||
useFetchProposals,
|
||||
flatten,
|
||||
isBatchProposalNode,
|
||||
isSingleProposalNode,
|
||||
type ProposalNode,
|
||||
type SingleProposalData,
|
||||
type SubProposalData,
|
||||
} from '../proposal/proposal-utils';
|
||||
import { MarketName } from '../proposal/market-name';
|
||||
import type { JsonValue } from '../../../../components/json-diff';
|
||||
import { useFetch } from '@vegaprotocol/react-helpers';
|
||||
import { ENV } from '../../../../config';
|
||||
|
||||
const immutableKeys = [
|
||||
'decimalPlaces',
|
||||
@ -28,8 +18,8 @@ const immutableKeys = [
|
||||
];
|
||||
|
||||
export const applyImmutableKeysFromEarlierVersion = (
|
||||
earlierVersion: unknown,
|
||||
updatedVersion: unknown
|
||||
earlierVersion: JsonValue,
|
||||
updatedVersion: JsonValue
|
||||
) => {
|
||||
if (
|
||||
typeof earlierVersion !== 'object' ||
|
||||
@ -45,8 +35,7 @@ export const applyImmutableKeysFromEarlierVersion = (
|
||||
|
||||
// Overwrite the immutable keys in the updatedVersionCopy with the earlier values
|
||||
immutableKeys.forEach((key) => {
|
||||
const earlier = get(earlierVersion, key);
|
||||
if (earlier) set(updatedVersionCopy, key, earlier);
|
||||
set(updatedVersionCopy, key, get(earlierVersion, key));
|
||||
});
|
||||
|
||||
return updatedVersionCopy;
|
||||
@ -54,84 +43,49 @@ export const applyImmutableKeysFromEarlierVersion = (
|
||||
|
||||
interface ProposalMarketChangesProps {
|
||||
marketId: string;
|
||||
/** This are the changes from proposal */
|
||||
updateProposalNode: ProposalNode | null;
|
||||
indicator?: number;
|
||||
updatedProposal: JsonValue;
|
||||
}
|
||||
|
||||
export const ProposalMarketChanges = ({
|
||||
marketId,
|
||||
updateProposalNode,
|
||||
indicator,
|
||||
updatedProposal,
|
||||
}: ProposalMarketChangesProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [showChanges, setShowChanges] = useState(false);
|
||||
|
||||
const { data: originalProposalData } = useFetchProposal({
|
||||
proposalId: marketId,
|
||||
});
|
||||
const {
|
||||
state: { data },
|
||||
} = useFetch(`${ENV.rest}governance?proposalId=${marketId}`, undefined, true);
|
||||
|
||||
const { data: enactedProposalsData } = useFetchProposals({
|
||||
proposalState: 'STATE_ENACTED',
|
||||
proposalType: 'TYPE_UPDATE_MARKET',
|
||||
});
|
||||
|
||||
let updateProposal: SingleProposalData | SubProposalData | undefined;
|
||||
if (isBatchProposalNode(updateProposalNode)) {
|
||||
updateProposal = updateProposalNode.proposals.find(
|
||||
(p, i) =>
|
||||
p.terms.updateMarket?.marketId === marketId &&
|
||||
(indicator != null ? i === indicator - 1 : true)
|
||||
);
|
||||
}
|
||||
if (isSingleProposalNode(updateProposalNode)) {
|
||||
updateProposal = updateProposalNode.proposal;
|
||||
}
|
||||
|
||||
// this should get the proposal before the current one
|
||||
const enactedUpdateMarketProposals = orderBy(
|
||||
compact(
|
||||
flatten(enactedProposalsData).filter((enacted) => {
|
||||
const related = enacted.terms.updateMarket?.marketId === marketId;
|
||||
const notCurrent =
|
||||
enacted.id !== updateProposal?.id ||
|
||||
('batchId' in enacted && enacted.batchId !== updateProposal.id);
|
||||
const beforeCurrent =
|
||||
Number(enacted.terms.enactmentTimestamp) <
|
||||
Number(updateProposal?.terms.enactmentTimestamp);
|
||||
return related && notCurrent && beforeCurrent;
|
||||
})
|
||||
),
|
||||
[(proposal) => Number(proposal.terms.enactmentTimestamp)],
|
||||
'desc'
|
||||
const {
|
||||
state: { data: enactedProposalData },
|
||||
} = useFetch(
|
||||
`${ENV.rest}governances?proposalState=STATE_ENACTED&proposalType=TYPE_UPDATE_MARKET`,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
|
||||
const latestEnactedProposal =
|
||||
enactedUpdateMarketProposals.length > 0
|
||||
? enactedUpdateMarketProposals[0]
|
||||
: undefined;
|
||||
// @ts-ignore no types here :-/
|
||||
const enacted = enactedProposalData?.connection?.edges
|
||||
.filter(
|
||||
// @ts-ignore no type here
|
||||
({ node }) => node?.proposal?.terms?.updateMarket?.marketId === marketId
|
||||
)
|
||||
// @ts-ignore no type here
|
||||
.sort((a, b) => {
|
||||
return (
|
||||
new Date(a?.node?.terms?.enactmentTimestamp).getTime() -
|
||||
new Date(b?.node?.terms?.enactmentTimestamp).getTime()
|
||||
);
|
||||
});
|
||||
|
||||
let originalProposal;
|
||||
if (isBatchProposalNode(originalProposalData)) {
|
||||
originalProposal = originalProposalData.proposals.find(
|
||||
(proposal) => proposal.id === marketId && proposal.terms.newMarket != null
|
||||
);
|
||||
}
|
||||
if (isSingleProposalNode(originalProposalData)) {
|
||||
originalProposal = originalProposalData.proposal;
|
||||
}
|
||||
const latestEnactedProposal = enacted?.length
|
||||
? enacted[enacted.length - 1]
|
||||
: undefined;
|
||||
|
||||
// LEFT SIDE: update market proposal enacted just before this one
|
||||
// or original new market proposal
|
||||
const left =
|
||||
latestEnactedProposal?.terms.updateMarket?.changes ||
|
||||
originalProposal?.terms.newMarket?.changes;
|
||||
|
||||
// RIGHT SIDE: this update market proposal
|
||||
const right = applyImmutableKeysFromEarlierVersion(
|
||||
left,
|
||||
updateProposal?.terms.updateMarket?.changes
|
||||
);
|
||||
const originalProposal =
|
||||
// @ts-ignore no types with useFetch TODO: check this is good
|
||||
data?.data?.proposal?.terms?.newMarket?.changes;
|
||||
|
||||
return (
|
||||
<section data-testid="proposal-market-changes">
|
||||
@ -140,18 +94,25 @@ export const ProposalMarketChanges = ({
|
||||
setToggleState={setShowChanges}
|
||||
dataTestId={'proposal-market-changes-toggle'}
|
||||
>
|
||||
<SubHeading
|
||||
title={
|
||||
<>
|
||||
{t('UpdateToMarket')}: <MarketName marketId={marketId} truncate />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<SubHeading title={t('updatesToMarket')} />
|
||||
</CollapsibleToggle>
|
||||
|
||||
{showChanges && (
|
||||
<div className="mb-6 bg-vega-cdark-900 p-2 rounded-lg">
|
||||
<JsonDiff left={left as JsonValue} right={right as JsonValue} />
|
||||
<div className="mb-6">
|
||||
<JsonDiff
|
||||
left={latestEnactedProposal || originalProposal}
|
||||
right={
|
||||
latestEnactedProposal
|
||||
? applyImmutableKeysFromEarlierVersion(
|
||||
latestEnactedProposal,
|
||||
updatedProposal
|
||||
)
|
||||
: applyImmutableKeysFromEarlierVersion(
|
||||
originalProposal,
|
||||
updatedProposal
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
@ -2,11 +2,6 @@ import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { ProposalUpdateMarketState } from './proposal-update-market-state';
|
||||
import { MarketUpdateType } from '@vegaprotocol/types';
|
||||
|
||||
jest.mock('../proposal/market-name.tsx', () => ({
|
||||
...jest.requireActual('../proposal/market-name.tsx'),
|
||||
MarketName: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('<ProposalUpdateMarketState />', () => {
|
||||
const suspendProposal = {
|
||||
__typename: 'UpdateMarketState' as const,
|
||||
|
@ -9,8 +9,6 @@ import { useState } from 'react';
|
||||
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { type UpdateMarketStatesFragment } from '../../__generated__/Proposals';
|
||||
import { MarketUpdateTypeMapping } from '@vegaprotocol/types';
|
||||
import { MarketName } from '../proposal/market-name';
|
||||
|
||||
interface ProposalUpdateMarketStateProps {
|
||||
change: UpdateMarketStatesFragment | null;
|
||||
@ -21,18 +19,16 @@ export const ProposalUpdateMarketState = ({
|
||||
}: ProposalUpdateMarketStateProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
let market;
|
||||
let isTerminate = false;
|
||||
|
||||
if (!change || change.__typename !== 'UpdateMarketState') {
|
||||
if (!change) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const market = change?.market;
|
||||
const isTerminate =
|
||||
change?.updateType === 'MARKET_STATE_UPDATE_TYPE_TERMINATE';
|
||||
|
||||
let toggleTitle = t(change.updateType);
|
||||
if (toggleTitle.length === 0) {
|
||||
toggleTitle = t('MarketDetails');
|
||||
if (change.__typename === 'UpdateMarketState') {
|
||||
market = change?.market;
|
||||
isTerminate = change?.updateType === 'MARKET_STATE_UPDATE_TYPE_TERMINATE';
|
||||
}
|
||||
|
||||
return (
|
||||
@ -42,13 +38,7 @@ export const ProposalUpdateMarketState = ({
|
||||
setToggleState={setShowDetails}
|
||||
dataTestId="proposal-market-data-toggle"
|
||||
>
|
||||
<SubHeading
|
||||
title={
|
||||
<>
|
||||
{toggleTitle}: <MarketName marketId={market?.id} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<SubHeading title={t('MarketDetails')} />
|
||||
</CollapsibleToggle>
|
||||
|
||||
{showDetails && (
|
||||
@ -59,12 +49,6 @@ export const ProposalUpdateMarketState = ({
|
||||
{t('marketId')}
|
||||
{market?.id}
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
{t('State')}
|
||||
<span className="bg-vega-green-650 px-1">
|
||||
{MarketUpdateTypeMapping[change.updateType]}
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
{t('marketName')}
|
||||
{market?.tradableInstrument?.instrument?.name}
|
||||
|
@ -1,46 +0,0 @@
|
||||
import classNames from 'classnames';
|
||||
|
||||
// rainbow-ish order
|
||||
const COLOURS = ['red', 'pink', 'orange', 'yellow', 'green', 'blue', 'purple'];
|
||||
|
||||
const getColour = (indicator: number, max = COLOURS.length) => {
|
||||
const available =
|
||||
max < COLOURS.length ? COLOURS.slice(COLOURS.length - max) : COLOURS;
|
||||
const tiers = Object.keys(available).length;
|
||||
let index = Math.abs(indicator - 1);
|
||||
if (indicator >= tiers) {
|
||||
index = index % tiers;
|
||||
}
|
||||
return available[index];
|
||||
};
|
||||
|
||||
export const getStyle = (indicator: number, max = COLOURS.length) =>
|
||||
classNames({
|
||||
'bg-vega-yellow-400 before:bg-vega-yellow-400':
|
||||
'yellow' === getColour(indicator, max),
|
||||
'bg-vega-green-400 before:bg-vega-green-400':
|
||||
'green' === getColour(indicator, max),
|
||||
'bg-vega-blue-400 before:bg-vega-blue-400':
|
||||
'blue' === getColour(indicator, max),
|
||||
'bg-vega-purple-400 before:bg-vega-purple-400':
|
||||
'purple' === getColour(indicator, max),
|
||||
'bg-vega-pink-400 before:bg-vega-pink-400':
|
||||
'pink' === getColour(indicator, max),
|
||||
'bg-vega-orange-400 before:bg-vega-orange-400':
|
||||
'orange' === getColour(indicator, max),
|
||||
'bg-vega-red-400 before:bg-vega-red-400':
|
||||
'red' === getColour(indicator, max),
|
||||
'bg-vega-clight-600 before:bg-vega-clight-600':
|
||||
'none' === getColour(indicator, max),
|
||||
});
|
||||
|
||||
export const getIndicatorStyle = (indicator: number) =>
|
||||
classNames(
|
||||
'rounded-sm text-black inline-block px-1 py-1 font-alpha calt h-8 w-7 text-center',
|
||||
'text-border-1',
|
||||
getStyle(indicator),
|
||||
// Comment below if you want to remove the "chevron"
|
||||
'relative mr-[11px] z-1',
|
||||
'before:absolute before:z-0 before:top-1 before:right-[-11px] before:rounded-sm',
|
||||
"before:w-[22.62px] before:h-[22.62px] before:rotate-45 before:content-['']"
|
||||
);
|
@ -1,9 +0,0 @@
|
||||
import { getIndicatorStyle } from './colours';
|
||||
|
||||
export const Indicator = ({ indicator }: { indicator: number }) => (
|
||||
<div className={getIndicatorStyle(indicator)}>
|
||||
<span className="absolute top-0 left-0 p-1 w-full text-center z-1">
|
||||
{indicator}
|
||||
</span>
|
||||
</div>
|
||||
);
|
@ -1,21 +0,0 @@
|
||||
import { useMarketInfoQuery } from '@vegaprotocol/markets';
|
||||
import { truncateMiddle } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const MarketName = ({
|
||||
marketId,
|
||||
truncate,
|
||||
}: {
|
||||
marketId?: string;
|
||||
truncate?: boolean;
|
||||
}) => {
|
||||
const { data } = useMarketInfoQuery({
|
||||
variables: {
|
||||
marketId: marketId || '',
|
||||
},
|
||||
skip: !marketId,
|
||||
});
|
||||
|
||||
const id = truncate ? truncateMiddle(marketId || '') : marketId;
|
||||
|
||||
return <span>{data?.market?.tradableInstrument.instrument.code || id}</span>;
|
||||
};
|
@ -1,4 +1,3 @@
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { type ProposalTermsFieldsFragment } from '../../__generated__/Proposals';
|
||||
import { type Proposal, type BatchProposal } from '../../types';
|
||||
import { ListAsset } from '../list-asset';
|
||||
@ -13,29 +12,21 @@ import {
|
||||
import { ProposalUpdateBenefitTiers } from '../proposal-update-benefit-tiers';
|
||||
import { ProposalUpdateMarketState } from '../proposal-update-market-state';
|
||||
import { ProposalVolumeDiscountProgramDetails } from '../proposal-volume-discount-program-details';
|
||||
import { type ProposalNode } from './proposal-utils';
|
||||
import { Lozenge } from '@vegaprotocol/ui-toolkit';
|
||||
import { Indicator } from './indicator';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
|
||||
export const ProposalChangeDetails = ({
|
||||
proposal,
|
||||
terms,
|
||||
restData,
|
||||
indicator,
|
||||
}: {
|
||||
proposal: Proposal | BatchProposal;
|
||||
terms: ProposalTermsFieldsFragment;
|
||||
restData: ProposalNode | null;
|
||||
indicator?: number;
|
||||
// eslint-disable-next-line
|
||||
restData: any;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
let details = null;
|
||||
|
||||
switch (terms.change.__typename) {
|
||||
case 'NewAsset': {
|
||||
if (proposal.id && terms.change.source.__typename === 'ERC20') {
|
||||
details = (
|
||||
return (
|
||||
<div>
|
||||
<ListAsset
|
||||
assetId={proposal.id}
|
||||
@ -46,64 +37,62 @@ export const ProposalChangeDetails = ({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
break;
|
||||
return null;
|
||||
}
|
||||
case 'UpdateAsset': {
|
||||
if (proposal.id) {
|
||||
details = (
|
||||
return (
|
||||
<ProposalAssetDetails
|
||||
change={terms.change}
|
||||
assetId={terms.change.assetId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
return null;
|
||||
}
|
||||
case 'NewMarket': {
|
||||
if (proposal.id) {
|
||||
details = <ProposalMarketData proposalId={proposal.id} />;
|
||||
return <ProposalMarketData proposalId={proposal.id} />;
|
||||
}
|
||||
break;
|
||||
return null;
|
||||
}
|
||||
case 'UpdateMarket': {
|
||||
if (proposal.id) {
|
||||
details = (
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<ProposalMarketData proposalId={proposal.id} />
|
||||
<ProposalMarketChanges
|
||||
indicator={indicator}
|
||||
marketId={terms.change.marketId}
|
||||
updateProposalNode={restData}
|
||||
updatedProposal={
|
||||
restData?.data?.proposal?.terms?.updateMarket?.changes
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
return null;
|
||||
}
|
||||
case 'NewTransfer': {
|
||||
if (proposal.id) {
|
||||
details = <ProposalTransferDetails proposalId={proposal.id} />;
|
||||
return <ProposalTransferDetails proposalId={proposal.id} />;
|
||||
}
|
||||
break;
|
||||
return null;
|
||||
}
|
||||
case 'CancelTransfer': {
|
||||
if (proposal.id) {
|
||||
details = <ProposalCancelTransferDetails proposalId={proposal.id} />;
|
||||
return <ProposalCancelTransferDetails proposalId={proposal.id} />;
|
||||
}
|
||||
break;
|
||||
return null;
|
||||
}
|
||||
case 'UpdateMarketState': {
|
||||
details = <ProposalUpdateMarketState change={terms.change} />;
|
||||
break;
|
||||
return <ProposalUpdateMarketState change={terms.change} />;
|
||||
}
|
||||
case 'UpdateReferralProgram': {
|
||||
details = <ProposalReferralProgramDetails change={terms.change} />;
|
||||
break;
|
||||
return <ProposalReferralProgramDetails change={terms.change} />;
|
||||
}
|
||||
case 'UpdateVolumeDiscountProgram': {
|
||||
details = <ProposalVolumeDiscountProgramDetails change={terms.change} />;
|
||||
break;
|
||||
return <ProposalVolumeDiscountProgramDetails change={terms.change} />;
|
||||
}
|
||||
case 'UpdateNetworkParameter': {
|
||||
if (
|
||||
@ -111,48 +100,18 @@ export const ProposalChangeDetails = ({
|
||||
terms.change.networkParameter.key ===
|
||||
'rewards.activityStreak.benefitTiers'
|
||||
) {
|
||||
details = <ProposalUpdateBenefitTiers change={terms.change} />;
|
||||
} else {
|
||||
details = (
|
||||
<div className="mb-4">
|
||||
<SubHeading title={t(terms.change.__typename as string)} />
|
||||
<span>
|
||||
<Trans
|
||||
i18nKey="Change <lozenge>{{key}}</lozenge> to <lozenge>{{value}}</lozenge>"
|
||||
values={{
|
||||
key: terms.change.networkParameter.key,
|
||||
value: terms.change.networkParameter.value,
|
||||
}}
|
||||
components={{
|
||||
// @ts-ignore children passed by i18next
|
||||
lozenge: <Lozenge />,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
return <ProposalUpdateBenefitTiers change={terms.change} />;
|
||||
}
|
||||
|
||||
break;
|
||||
return null;
|
||||
}
|
||||
case 'NewFreeform':
|
||||
case 'NewSpotMarket':
|
||||
case 'UpdateSpotMarket':
|
||||
case 'UpdateSpotMarket': {
|
||||
return null;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (indicator != null && details != null) {
|
||||
details = (
|
||||
<div className="grid grid-cols-[40px_minmax(0,1fr)] grid-rows-1 gap-3 mb-3">
|
||||
<div className="w-10">
|
||||
<Indicator indicator={indicator} />
|
||||
</div>
|
||||
<div>{details}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return details;
|
||||
};
|
||||
|
@ -1,283 +0,0 @@
|
||||
import compact from 'lodash/compact';
|
||||
import { ENV } from '../../../../config';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
type Maybe<T> = T | null | undefined;
|
||||
|
||||
type ProposalState =
|
||||
| 'STATE_UNSPECIFIED'
|
||||
| 'STATE_FAILED'
|
||||
| 'STATE_OPEN'
|
||||
| 'STATE_PASSED'
|
||||
| 'STATE_REJECTED'
|
||||
| 'STATE_DECLINED'
|
||||
| 'STATE_ENACTED'
|
||||
| 'STATE_WAITING_FOR_NODE_VOTE';
|
||||
|
||||
type ProposalType =
|
||||
| 'TYPE_UNSPECIFIED'
|
||||
| 'TYPE_ALL'
|
||||
| 'TYPE_NEW_MARKET'
|
||||
| 'TYPE_UPDATE_MARKET'
|
||||
| 'TYPE_NETWORK_PARAMETERS'
|
||||
| 'TYPE_NEW_ASSET'
|
||||
| 'TYPE_NEW_FREE_FORM'
|
||||
| 'TYPE_UPDATE_ASSET'
|
||||
| 'TYPE_NEW_SPOT_MARKET'
|
||||
| 'TYPE_UPDATE_SPOT_MARKET'
|
||||
| 'TYPE_NEW_TRANSFER'
|
||||
| 'TYPE_CANCEL_TRANSFER'
|
||||
| 'TYPE_UPDATE_MARKET_STATE'
|
||||
| 'TYPE_UPDATE_REFERRAL_PROGRAM'
|
||||
| 'TYPE_UPDATE_VOLUME_DISCOUNT_PROGRAM';
|
||||
|
||||
type ProposalNodeType = 'TYPE_SINGLE_OR_UNSPECIFIED' | 'TYPE_BATCH';
|
||||
|
||||
type ProposalData = {
|
||||
id: string;
|
||||
rationale: {
|
||||
description: string;
|
||||
title: string;
|
||||
};
|
||||
state: ProposalState;
|
||||
timestamp: string;
|
||||
};
|
||||
|
||||
type Terms = {
|
||||
cancelTransfer?: { changes: unknown };
|
||||
enactmentTimestamp: string;
|
||||
newAsset?: { changes: unknown };
|
||||
newFreeform: object;
|
||||
newMarket?: { changes: unknown };
|
||||
newSpotMarket?: { changes: unknown };
|
||||
newTransfer?: { changes: unknown };
|
||||
updateAsset?: { assetId: string; changes: unknown };
|
||||
updateMarket?: { marketId: string; changes: unknown };
|
||||
updateMarketState?: {
|
||||
changes: {
|
||||
marketId: string;
|
||||
price: string;
|
||||
updateType:
|
||||
| 'MARKET_STATE_UPDATE_TYPE_UNSPECIFIED'
|
||||
| 'MARKET_STATE_UPDATE_TYPE_TERMINATE'
|
||||
| 'MARKET_STATE_UPDATE_TYPE_SUSPEND'
|
||||
| 'MARKET_STATE_UPDATE_TYPE_RESUME';
|
||||
};
|
||||
};
|
||||
updateNetworkParameter?: { changes: unknown };
|
||||
updateReferralProgram?: { changes: unknown };
|
||||
updateSpotMarket?: { marketId: string; changes: unknown };
|
||||
updateVolumeDiscountProgram?: { changes: unknown };
|
||||
};
|
||||
|
||||
export type SingleProposalData = ProposalData & {
|
||||
terms: Terms & {
|
||||
closingTimestamp: string;
|
||||
validationTimestamp: string;
|
||||
};
|
||||
};
|
||||
|
||||
type BatchProposalData = ProposalData & {
|
||||
batchTerms: {
|
||||
changes: Terms[];
|
||||
};
|
||||
};
|
||||
|
||||
export type SubProposalData = SingleProposalData & {
|
||||
batchId: string;
|
||||
};
|
||||
|
||||
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 & {
|
||||
proposal: SingleProposalData;
|
||||
proposalType: 'TYPE_SINGLE_OR_UNSPECIFIED';
|
||||
proposals: [];
|
||||
};
|
||||
|
||||
type BatchProposalNode = ProposalNode & {
|
||||
proposal: BatchProposalData;
|
||||
proposalType: 'TYPE_BATCH';
|
||||
};
|
||||
|
||||
export const isProposalNode = (node: unknown): node is ProposalNode =>
|
||||
Boolean(
|
||||
typeof node === 'object' &&
|
||||
node &&
|
||||
'proposal' in node &&
|
||||
typeof node.proposal === 'object' &&
|
||||
node?.proposal &&
|
||||
'id' in node.proposal &&
|
||||
node?.proposal?.id
|
||||
);
|
||||
|
||||
export const isSingleProposalNode = (
|
||||
node: Maybe<ProposalNode>
|
||||
): node is SingleProposalNode =>
|
||||
Boolean(
|
||||
node &&
|
||||
node?.proposalType === 'TYPE_SINGLE_OR_UNSPECIFIED' &&
|
||||
node?.proposal
|
||||
);
|
||||
|
||||
export const isBatchProposalNode = (
|
||||
node: Maybe<ProposalNode>
|
||||
): node is BatchProposalNode =>
|
||||
Boolean(
|
||||
node &&
|
||||
node?.proposalType === 'TYPE_BATCH' &&
|
||||
node?.proposal &&
|
||||
'batchTerms' in node.proposal &&
|
||||
node?.proposals?.length > 0
|
||||
);
|
||||
|
||||
// this includes also batch proposals with `updateMarket`s 👍
|
||||
const PROPOSALS_ENDPOINT = `${ENV.rest}governances?proposalState=:proposalState&proposalType=:proposalType`;
|
||||
|
||||
// this can be queried also by sub proposal id as `proposalId` and it will
|
||||
// return full batch proposal data with all of its sub proposals including
|
||||
// the requested one inside `proposals` array.
|
||||
const PROPOSAL_ENDPOINT = `${ENV.rest}governance?proposalId=:proposalId`;
|
||||
|
||||
export const getProposals = async ({
|
||||
proposalState,
|
||||
proposalType,
|
||||
}: {
|
||||
proposalState: ProposalState;
|
||||
proposalType: ProposalType;
|
||||
}) => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
PROPOSALS_ENDPOINT.replace(':proposalState', proposalState).replace(
|
||||
':proposalType',
|
||||
proposalType
|
||||
)
|
||||
);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (
|
||||
data &&
|
||||
'connection' in data &&
|
||||
data.connection &&
|
||||
'edges' in data.connection &&
|
||||
data.connection.edges?.length > 0
|
||||
) {
|
||||
const nodes = compact(
|
||||
data.connection.edges.map((e: { node?: object }) => e?.node)
|
||||
).filter(isProposalNode);
|
||||
|
||||
return nodes;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// NOOP - ignore errors
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
export const getProposal = async ({ proposalId }: { proposalId: string }) => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
PROPOSAL_ENDPOINT.replace(':proposalId', proposalId)
|
||||
);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data && 'data' in data && isProposalNode(data.data)) {
|
||||
return data.data as ProposalNode;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// NOOP - ignore errors
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const useFetchProposal = ({ proposalId }: { proposalId?: string }) => {
|
||||
const [data, setData] = useState<ProposalNode | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const cb = async () => {
|
||||
if (!proposalId) return;
|
||||
|
||||
setLoading(true);
|
||||
const data = await getProposal({ proposalId });
|
||||
setLoading(false);
|
||||
if (data) {
|
||||
setData(data);
|
||||
}
|
||||
};
|
||||
cb();
|
||||
}, [proposalId]);
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchProposals = ({
|
||||
proposalState,
|
||||
proposalType,
|
||||
}: {
|
||||
proposalState: ProposalState;
|
||||
proposalType: ProposalType;
|
||||
}) => {
|
||||
const [data, setData] = useState<ProposalNode[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const cb = async () => {
|
||||
setLoading(true);
|
||||
const data = await getProposals({ proposalState, proposalType });
|
||||
setLoading(false);
|
||||
if (data) {
|
||||
setData(data);
|
||||
}
|
||||
};
|
||||
cb();
|
||||
}, [proposalState, proposalType]);
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const flatten = (
|
||||
nodes: ProposalNode[]
|
||||
): (SingleProposalData | SubProposalData)[] => {
|
||||
const flattenNodes = [];
|
||||
for (const node of nodes) {
|
||||
if (isSingleProposalNode(node)) {
|
||||
flattenNodes.push(node.proposal);
|
||||
}
|
||||
if (isBatchProposalNode(node)) {
|
||||
for (const sub of node.proposals) {
|
||||
flattenNodes.push(sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
return flattenNodes;
|
||||
};
|
@ -1,12 +1,12 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
||||
import { type VegaWalletConfig } from '@vegaprotocol/wallet';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { generateProposal } from '../../test-helpers/generate-proposals';
|
||||
import { Proposal } from './proposal';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { type Proposal as IProposal } from '../../types';
|
||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
|
||||
|
||||
jest.mock('@vegaprotocol/network-parameters', () => ({
|
||||
...jest.requireActual('@vegaprotocol/network-parameters'),
|
||||
@ -24,44 +24,45 @@ jest.mock('@vegaprotocol/network-parameters', () => ({
|
||||
error: null,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('../proposal-detail-header/proposal-header', () => ({
|
||||
ProposalHeader: () => <div data-testid="proposal-header"></div>,
|
||||
}));
|
||||
|
||||
jest.mock('../proposal-change-table', () => ({
|
||||
ProposalChangeTable: () => <div data-testid="proposal-change-table"></div>,
|
||||
}));
|
||||
|
||||
jest.mock('../proposal-json', () => ({
|
||||
ProposalJson: () => <div data-testid="proposal-json"></div>,
|
||||
}));
|
||||
|
||||
jest.mock('../list-asset', () => ({
|
||||
ListAsset: () => <div data-testid="proposal-list-asset"></div>,
|
||||
}));
|
||||
|
||||
jest.mock('../list-asset', () => ({
|
||||
ListAsset: () => <div data-testid="proposal-list-asset"></div>,
|
||||
}));
|
||||
|
||||
jest.mock('../vote-details', () => ({
|
||||
UserVote: () => <div data-testid="user-vote"></div>,
|
||||
}));
|
||||
|
||||
jest.mock('./proposal-change-details', () => ({
|
||||
ProposalChangeDetails: () => <div data-testid="proposal-change-details" />,
|
||||
ProposalChangeDetails: () => (
|
||||
<div data-testid="proposal-change-details"></div>
|
||||
),
|
||||
}));
|
||||
|
||||
const vegaWalletConfig: VegaWalletConfig = {
|
||||
network: 'TESTNET',
|
||||
vegaUrl: 'https://vega.xyz',
|
||||
vegaWalletServiceUrl: 'https://wallet.vega.xyz',
|
||||
links: {
|
||||
explorer: 'explorer',
|
||||
concepts: 'concepts',
|
||||
chromeExtensionUrl: 'chrome',
|
||||
mozillaExtensionUrl: 'mozilla',
|
||||
},
|
||||
chainId: 'VEGA_CHAIN_ID',
|
||||
};
|
||||
|
||||
const renderComponent = (proposal: IProposal) => {
|
||||
return render(
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<MockedWalletProvider>
|
||||
<AppStateProvider>
|
||||
<Proposal restData={null} proposal={proposal} />
|
||||
</AppStateProvider>
|
||||
</MockedWalletProvider>
|
||||
<VegaWalletProvider config={vegaWalletConfig}>
|
||||
<Proposal restData={{}} proposal={proposal} />
|
||||
</VegaWalletProvider>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
@ -8,22 +8,20 @@ import { ProposalJson } from '../proposal-json';
|
||||
import { UserVote } from '../vote-details';
|
||||
import Routes from '../../../routes';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { type ProposalNode } from './proposal-utils';
|
||||
import { useVoteSubmit } from '@vegaprotocol/proposals';
|
||||
import { useUserVote } from '../vote-details/use-user-vote';
|
||||
import { type Proposal as IProposal, type BatchProposal } from '../../types';
|
||||
import { ProposalChangeDetails } from './proposal-change-details';
|
||||
import { type JsonValue } from 'type-fest';
|
||||
|
||||
export interface ProposalProps {
|
||||
proposal: IProposal | BatchProposal;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
restData: ProposalNode | null;
|
||||
restData: any;
|
||||
}
|
||||
|
||||
export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { submit, finalizedVote, transaction } = useVoteSubmit();
|
||||
const { submit, Dialog, finalizedVote, transaction } = useVoteSubmit();
|
||||
const { voteState, voteDatetime } = useUserVote(proposal?.id, finalizedVote);
|
||||
|
||||
return (
|
||||
@ -48,7 +46,6 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
||||
|
||||
<ProposalHeader
|
||||
proposal={proposal}
|
||||
restData={restData}
|
||||
isListItem={false}
|
||||
voteState={voteState}
|
||||
/>
|
||||
@ -61,7 +58,7 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
||||
<ProposalDescription description={proposal.rationale.description} />
|
||||
</div>
|
||||
|
||||
<div className="mb-4 flex flex-col gap-0">
|
||||
<div className="mb-4">
|
||||
{proposal.__typename === 'Proposal' ? (
|
||||
<ProposalChangeDetails
|
||||
proposal={proposal}
|
||||
@ -73,7 +70,6 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
||||
if (!p?.terms) return null;
|
||||
return (
|
||||
<ProposalChangeDetails
|
||||
indicator={i + 1}
|
||||
key={i}
|
||||
proposal={proposal}
|
||||
terms={p.terms}
|
||||
@ -89,6 +85,7 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
||||
<UserVote
|
||||
proposal={proposal}
|
||||
submit={submit}
|
||||
dialog={Dialog}
|
||||
transaction={transaction}
|
||||
voteState={voteState}
|
||||
voteDatetime={voteDatetime}
|
||||
@ -97,7 +94,7 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
||||
</div>
|
||||
|
||||
<div className="mb-6">
|
||||
<ProposalJson proposal={restData?.proposal as unknown as JsonValue} />
|
||||
<ProposalJson proposal={restData?.data?.proposal} />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||
import { ProposalsListItemDetails } from './proposals-list-item-details';
|
||||
import { mockWalletContext } from '../../test-helpers/mocks';
|
||||
|
||||
const renderComponent = (id: string) =>
|
||||
render(
|
||||
<Router>
|
||||
<AppStateProvider>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<ProposalsListItemDetails id={id} />
|
||||
</AppStateProvider>
|
||||
</VegaWalletContext.Provider>
|
||||
</Router>
|
||||
);
|
||||
|
||||
|
@ -206,7 +206,7 @@ export const ProposalsList = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
<section className="-mx-4 p-4 mb-8 bg-vega-cdark-900 rounded-l-sm">
|
||||
<section className="-mx-4 p-4 mb-8 bg-vega-dark-100">
|
||||
<SubHeading title={t('openProposals')} />
|
||||
|
||||
{sortedProposals.open.length > 0 ||
|
||||
|
@ -1,32 +1,25 @@
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import {
|
||||
MockedWalletProvider,
|
||||
mockConfig,
|
||||
} from '@vegaprotocol/wallet-react/testing';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||
import { ProposalFormSubmit } from './proposal-form-submit';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
|
||||
const renderComponent = (isSubmitting: boolean) => {
|
||||
const renderComponent = (
|
||||
context: VegaWalletContextShape,
|
||||
isSubmitting: boolean
|
||||
) => {
|
||||
render(
|
||||
<MockedWalletProvider>
|
||||
<AppStateProvider>
|
||||
<AppStateProvider>
|
||||
<VegaWalletContext.Provider value={context}>
|
||||
<ProposalFormSubmit isSubmitting={isSubmitting} />
|
||||
</AppStateProvider>
|
||||
</MockedWalletProvider>
|
||||
</VegaWalletContext.Provider>
|
||||
</AppStateProvider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('Proposal Form Submit', () => {
|
||||
const pubKey = { publicKey: '123456__123456', name: 'test' };
|
||||
|
||||
afterEach(() => {
|
||||
act(() => {
|
||||
mockConfig.reset();
|
||||
});
|
||||
});
|
||||
|
||||
it('should display connection message and button if wallet not connected', () => {
|
||||
renderComponent(false);
|
||||
renderComponent({ pubKey: null } as VegaWalletContextShape, false);
|
||||
|
||||
expect(
|
||||
screen.getByText('Connect your wallet to submit a proposal')
|
||||
@ -37,22 +30,28 @@ describe('Proposal Form Submit', () => {
|
||||
});
|
||||
|
||||
it('should display submit button if wallet is connected', () => {
|
||||
mockConfig.store.setState({
|
||||
pubKey: pubKey.publicKey,
|
||||
keys: [pubKey],
|
||||
});
|
||||
renderComponent(false);
|
||||
const pubKey = { publicKey: '123456__123456', name: 'test' };
|
||||
renderComponent(
|
||||
{
|
||||
pubKey: pubKey.publicKey,
|
||||
pubKeys: [pubKey],
|
||||
} as VegaWalletContextShape,
|
||||
false
|
||||
);
|
||||
expect(screen.getByTestId('proposal-submit')).toHaveTextContent(
|
||||
'Submit proposal'
|
||||
);
|
||||
});
|
||||
|
||||
it('should display submitting button text if wallet is connected and submitting', () => {
|
||||
mockConfig.store.setState({
|
||||
pubKey: pubKey.publicKey,
|
||||
keys: [pubKey],
|
||||
});
|
||||
renderComponent(true);
|
||||
const pubKey = { publicKey: '123456__123456', name: 'test' };
|
||||
renderComponent(
|
||||
{
|
||||
pubKey: pubKey.publicKey,
|
||||
pubKeys: [pubKey],
|
||||
} as VegaWalletContextShape,
|
||||
true
|
||||
);
|
||||
expect(screen.getByTestId('proposal-submit')).toHaveTextContent(
|
||||
'Submitting proposal'
|
||||
);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet-react';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContainer } from '../../../../components/vega-wallet-container';
|
||||
|
||||
interface ProposalFormSubmitProps {
|
||||
|
@ -1,24 +1,19 @@
|
||||
import {
|
||||
VegaTransactionDialog,
|
||||
getProposalDialogIcon,
|
||||
getProposalDialogIntent,
|
||||
useGetProposalDialogTitle,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import type {
|
||||
ProposalEventFieldsFragment,
|
||||
VegaTxState,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import type { ProposalEventFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import type { DialogProps } from '@vegaprotocol/proposals';
|
||||
|
||||
interface ProposalFormTransactionDialogProps {
|
||||
finalizedProposal: ProposalEventFieldsFragment | null;
|
||||
transaction: VegaTxState;
|
||||
onChange: (open: boolean) => void;
|
||||
TransactionDialog: (props: DialogProps) => JSX.Element;
|
||||
}
|
||||
|
||||
export const ProposalFormTransactionDialog = ({
|
||||
finalizedProposal,
|
||||
transaction,
|
||||
onChange,
|
||||
TransactionDialog,
|
||||
}: ProposalFormTransactionDialogProps) => {
|
||||
const title = useGetProposalDialogTitle(finalizedProposal?.state);
|
||||
// Render a custom complete UI if the proposal was rejected otherwise
|
||||
@ -29,16 +24,13 @@ export const ProposalFormTransactionDialog = ({
|
||||
|
||||
return (
|
||||
<div data-testid="proposal-transaction-dialog">
|
||||
<VegaTransactionDialog
|
||||
<TransactionDialog
|
||||
title={title}
|
||||
intent={getProposalDialogIntent(finalizedProposal?.state)}
|
||||
icon={getProposalDialogIcon(finalizedProposal?.state)}
|
||||
content={{
|
||||
Complete: completeContent,
|
||||
}}
|
||||
transaction={transaction}
|
||||
isOpen={transaction.dialogOpen}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
lastWeek,
|
||||
mockWalletContext,
|
||||
networkParamsQueryMock,
|
||||
nextWeek,
|
||||
} from '../../test-helpers/mocks';
|
||||
@ -46,7 +48,9 @@ const renderComponent = (
|
||||
render(
|
||||
<Router>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<VoteBreakdown proposal={proposal} />
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<VoteBreakdown proposal={proposal} />
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</Router>
|
||||
);
|
||||
@ -56,7 +60,6 @@ describe('VoteBreakdown', () => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(0);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
@ -15,9 +15,6 @@ import {
|
||||
type VoteFieldsFragment,
|
||||
} from '../../__generated__/Proposals';
|
||||
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
|
||||
@ -42,9 +39,8 @@ const VoteProgress = ({
|
||||
children,
|
||||
}: VoteProgressProps) => {
|
||||
const containerClasses = classNames(
|
||||
'relative h-2 rounded-md overflow-hidden',
|
||||
// 'border border-vega-dark-300',
|
||||
colourfulBg ? 'bg-vega-red' : 'bg-vega-dark-200'
|
||||
'relative h-10 rounded-md border border-vega-dark-300 overflow-hidden',
|
||||
colourfulBg ? 'bg-vega-pink' : 'bg-vega-dark-400'
|
||||
);
|
||||
|
||||
const progressClasses = classNames(
|
||||
@ -53,19 +49,17 @@ const VoteProgress = ({
|
||||
);
|
||||
|
||||
const textClasses = classNames(
|
||||
'w-full flex items-center justify-start text-white text-sm pb-1'
|
||||
'absolute top-0 left-0 w-full h-full flex items-center justify-start px-3 text-black'
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={containerClasses}>
|
||||
<div
|
||||
className={progressClasses}
|
||||
style={{ width: `${percentageFor}%` }}
|
||||
data-testid={testId}
|
||||
/>
|
||||
<div className={textClasses}>{children}</div>
|
||||
<div className={containerClasses}>
|
||||
<div
|
||||
className={progressClasses}
|
||||
style={{ width: `${percentageFor}%` }}
|
||||
data-testid={testId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -84,22 +78,14 @@ const Status = ({ reached, threshold, text, testId }: StatusProps) => {
|
||||
<div data-testid={testId}>
|
||||
{reached ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<VegaIcon
|
||||
name={VegaIconNames.TICK}
|
||||
className="text-vega-green"
|
||||
size={20}
|
||||
/>
|
||||
<VegaIcon name={VegaIconNames.TICK} size={20} />
|
||||
<span>
|
||||
{threshold.toString()}% {text} {t('met')}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<VegaIcon
|
||||
name={VegaIconNames.CROSS}
|
||||
className="text-vega-red"
|
||||
size={20}
|
||||
/>
|
||||
<VegaIcon name={VegaIconNames.CROSS} size={20} />
|
||||
<span>
|
||||
{threshold.toString()}% {text} {t('not met')}
|
||||
</span>
|
||||
@ -111,64 +97,24 @@ const Status = ({ reached, threshold, text, testId }: StatusProps) => {
|
||||
|
||||
export const VoteBreakdown = ({
|
||||
proposal,
|
||||
restData,
|
||||
}: {
|
||||
proposal: Proposal | BatchProposal;
|
||||
restData?: ProposalNode | null;
|
||||
}) => {
|
||||
if (proposal.__typename === 'Proposal') {
|
||||
return <VoteBreakdownNormal proposal={proposal} />;
|
||||
}
|
||||
|
||||
if (proposal.__typename === 'BatchProposal') {
|
||||
return <VoteBreakdownBatch proposal={proposal} restData={restData} />;
|
||||
return <VoteBreakdownBatch proposal={proposal} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const VoteBreakdownBatch = ({
|
||||
proposal,
|
||||
restData,
|
||||
}: {
|
||||
proposal: BatchProposal;
|
||||
restData?: ProposalNode | null;
|
||||
}) => {
|
||||
const VoteBreakdownBatch = ({ proposal }: { proposal: BatchProposal }) => {
|
||||
const [fullBreakdown, setFullBreakdown] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const yesELS =
|
||||
restData?.yes?.reduce((all, y) => {
|
||||
if (y.elsPerMarket) {
|
||||
y.elsPerMarket.forEach((item) => {
|
||||
const share = Number(item.els);
|
||||
if (all[item.marketId]) {
|
||||
all[item.marketId].push(share);
|
||||
} else {
|
||||
all[item.marketId] = [share];
|
||||
}
|
||||
return all;
|
||||
});
|
||||
}
|
||||
return all;
|
||||
}, {} as Record<string, number[]>) || {};
|
||||
|
||||
const noELS =
|
||||
restData?.no?.reduce((all, y) => {
|
||||
if (y.elsPerMarket) {
|
||||
y.elsPerMarket.forEach((item) => {
|
||||
const share = Number(item.els);
|
||||
if (all[item.marketId]) {
|
||||
all[item.marketId].push(share);
|
||||
} else {
|
||||
all[item.marketId] = [share];
|
||||
}
|
||||
return all;
|
||||
});
|
||||
}
|
||||
return all;
|
||||
}, {} as Record<string, number[]>) || {};
|
||||
|
||||
const voteInfo = useBatchVoteInformation({
|
||||
terms: compact(
|
||||
proposal.subProposals ? proposal.subProposals.map((p) => p?.terms) : []
|
||||
@ -205,7 +151,7 @@ const VoteBreakdownBatch = ({
|
||||
<p className="flex gap-2 m-0 items-center">
|
||||
<VegaIcon
|
||||
name={VegaIconNames.CROSS}
|
||||
className="text-vega-red"
|
||||
className="text-vega-pink"
|
||||
size={20}
|
||||
/>
|
||||
{t(
|
||||
@ -230,13 +176,10 @@ const VoteBreakdownBatch = ({
|
||||
if (!p?.terms) return null;
|
||||
return (
|
||||
<VoteBreakdownBatchSubProposal
|
||||
indicator={i + 1}
|
||||
key={i}
|
||||
proposal={proposal}
|
||||
votes={proposal.votes}
|
||||
terms={p.terms}
|
||||
yesELS={yesELS}
|
||||
noELS={noELS}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@ -270,7 +213,7 @@ const VoteBreakdownBatch = ({
|
||||
<p className="flex gap-2 m-0 items-center">
|
||||
<VegaIcon
|
||||
name={VegaIconNames.CROSS}
|
||||
className="text-vega-red"
|
||||
className="text-vega-pink"
|
||||
size={20}
|
||||
/>
|
||||
{t('Proposal failed: {{count}} of {{total}} proposals passed', {
|
||||
@ -292,13 +235,10 @@ const VoteBreakdownBatch = ({
|
||||
if (!p?.terms) return null;
|
||||
return (
|
||||
<VoteBreakdownBatchSubProposal
|
||||
indicator={i + 1}
|
||||
key={i}
|
||||
proposal={proposal}
|
||||
votes={proposal.votes}
|
||||
terms={p.terms}
|
||||
yesELS={yesELS}
|
||||
noELS={noELS}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@ -315,62 +255,28 @@ const VoteBreakdownBatchSubProposal = ({
|
||||
proposal,
|
||||
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';
|
||||
|
||||
let marketId = undefined;
|
||||
if (terms?.change?.__typename === 'UpdateMarket') {
|
||||
marketId = terms.change.marketId;
|
||||
}
|
||||
if (terms?.change?.__typename === 'UpdateMarketState') {
|
||||
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} />
|
||||
</>
|
||||
) : null;
|
||||
|
||||
const indicatorElement = indicator && <Indicator indicator={indicator} />;
|
||||
|
||||
return (
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
{indicatorElement}
|
||||
<h4>
|
||||
{t(terms.change.__typename)} {marketName}
|
||||
</h4>
|
||||
</div>
|
||||
<div className="rounded-sm bg-vega-dark-100 p-3">
|
||||
<VoteBreakDownUI
|
||||
voteInfo={voteInfo}
|
||||
isProposalOpen={isProposalOpen}
|
||||
isUpdateMarket={isUpdateMarket}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h4>{t(terms.change.__typename)}</h4>
|
||||
<VoteBreakDownUI
|
||||
voteInfo={voteInfo}
|
||||
isProposalOpen={isProposalOpen}
|
||||
isUpdateMarket={isUpdateMarket}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -385,13 +291,11 @@ const VoteBreakdownNormal = ({ proposal }: { proposal: Proposal }) => {
|
||||
const isUpdateMarket = proposal?.terms?.change?.__typename === 'UpdateMarket';
|
||||
|
||||
return (
|
||||
<div className="mb-6">
|
||||
<VoteBreakDownUI
|
||||
voteInfo={voteInfo}
|
||||
isProposalOpen={isProposalOpen}
|
||||
isUpdateMarket={isUpdateMarket}
|
||||
/>
|
||||
</div>
|
||||
<VoteBreakDownUI
|
||||
voteInfo={voteInfo}
|
||||
isProposalOpen={isProposalOpen}
|
||||
isUpdateMarket={isUpdateMarket}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -418,6 +322,7 @@ const VoteBreakDownUI = ({
|
||||
noPercentage,
|
||||
noLPPercentage,
|
||||
yesPercentage,
|
||||
yesLPPercentage,
|
||||
yesTokens,
|
||||
noTokens,
|
||||
totalEquityLikeShareWeight,
|
||||
@ -430,7 +335,6 @@ const VoteBreakDownUI = ({
|
||||
majorityLPMet,
|
||||
willPassByTokenVote,
|
||||
willPassByLPVote,
|
||||
lpVoteWeight,
|
||||
} = voteInfo;
|
||||
|
||||
const participationThresholdProgress = BigNumber.min(
|
||||
@ -455,13 +359,13 @@ const VoteBreakDownUI = ({
|
||||
'flex justify-between flex-wrap gap-6'
|
||||
);
|
||||
const sectionClasses = classNames('min-w-[300px] flex-1 flex-grow');
|
||||
const headingClasses = classNames('mb-2 text-sm text-white font-bold');
|
||||
const headingClasses = classNames('mb-2 text-vega-dark-400');
|
||||
const progressDetailsClasses = classNames(
|
||||
'flex justify-between flex-wrap mt-2 text-sm'
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-6">
|
||||
{isProposalOpen && (
|
||||
<div
|
||||
data-testid="vote-status"
|
||||
@ -478,7 +382,7 @@ const VoteBreakDownUI = ({
|
||||
<VegaIcon
|
||||
name={VegaIconNames.CROSS}
|
||||
size={20}
|
||||
className="text-vega-red"
|
||||
className="text-vega-pink"
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
@ -494,13 +398,103 @@ const VoteBreakDownUI = ({
|
||||
<p className="m-0">
|
||||
<Trans
|
||||
i18nKey={'Currently expected to <0>fail</0>'}
|
||||
components={[<span className="text-vega-red" />]}
|
||||
components={[<span className="text-vega-pink" />]}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isUpdateMarket && (
|
||||
<div className="mb-4">
|
||||
<h3 className={headingClasses}>{t('liquidityProviderVote')}</h3>
|
||||
<div className={sectionWrapperClasses}>
|
||||
<section
|
||||
className={sectionClasses}
|
||||
data-testid="lp-majority-breakdown"
|
||||
>
|
||||
<VoteProgress
|
||||
percentageFor={yesLPPercentage}
|
||||
colourfulBg={true}
|
||||
testId="lp-majority-progress"
|
||||
>
|
||||
<Status
|
||||
reached={majorityLPMet}
|
||||
threshold={requiredMajorityLPPercentage}
|
||||
text={t('majorityThreshold')}
|
||||
testId={
|
||||
majorityLPMet ? 'lp-majority-met' : 'lp-majority-not-met'
|
||||
}
|
||||
/>
|
||||
</VoteProgress>
|
||||
|
||||
<div className={progressDetailsClasses}>
|
||||
<div className="flex items-center gap-1">
|
||||
<span>{t('liquidityProviderVotesFor')}:</span>
|
||||
<Tooltip
|
||||
description={
|
||||
<span>{yesLPPercentage.toFixed(defaultDP)}%</span>
|
||||
}
|
||||
>
|
||||
<button>{yesLPPercentage.toFixed(1)}%</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<span>{t('liquidityProviderVotesAgainst')}:</span>
|
||||
<span>
|
||||
<Tooltip
|
||||
description={
|
||||
<span>{noLPPercentage.toFixed(defaultDP)}%</span>
|
||||
}
|
||||
>
|
||||
<button>{noLPPercentage.toFixed(1)}%</button>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
className={sectionClasses}
|
||||
data-testid="lp-participation-breakdown"
|
||||
>
|
||||
<VoteProgress
|
||||
percentageFor={
|
||||
lpParticipationThresholdProgress || new BigNumber(0)
|
||||
}
|
||||
testId="lp-participation-progress"
|
||||
>
|
||||
<Status
|
||||
reached={participationLPMet}
|
||||
threshold={requiredParticipationLP || new BigNumber(1)}
|
||||
text={t('participationThreshold')}
|
||||
testId={
|
||||
participationLPMet
|
||||
? 'lp-participation-met'
|
||||
: 'lp-participation-not-met'
|
||||
}
|
||||
/>
|
||||
</VoteProgress>
|
||||
|
||||
<div className="flex mt-2 text-sm">
|
||||
<div className="flex items-center gap-1">
|
||||
<span>{t('totalLiquidityProviderTokensVoted')}:</span>
|
||||
<Tooltip
|
||||
description={formatNumber(
|
||||
totalEquityLikeShareWeight,
|
||||
defaultDP
|
||||
)}
|
||||
>
|
||||
<span>{totalEquityLikeShareWeight.toFixed(1)}%</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isUpdateMarket && <h3 className={headingClasses}>{t('tokenVote')}</h3>}
|
||||
<div className={sectionWrapperClasses}>
|
||||
<section
|
||||
@ -600,97 +594,6 @@ const VoteBreakDownUI = ({
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{/** Liquidity provider vote */}
|
||||
{isUpdateMarket && (
|
||||
<div className="mt-3">
|
||||
<h3 className={headingClasses}>{t('liquidityProviderVote')}</h3>
|
||||
<div className={sectionWrapperClasses}>
|
||||
<section
|
||||
className={sectionClasses}
|
||||
data-testid="lp-majority-breakdown"
|
||||
>
|
||||
<VoteProgress
|
||||
percentageFor={lpVoteWeight}
|
||||
colourfulBg={true}
|
||||
testId="lp-majority-progress"
|
||||
>
|
||||
<Status
|
||||
reached={majorityLPMet}
|
||||
threshold={requiredMajorityLPPercentage}
|
||||
text={t('majorityThreshold')}
|
||||
testId={
|
||||
majorityLPMet ? 'lp-majority-met' : 'lp-majority-not-met'
|
||||
}
|
||||
/>
|
||||
</VoteProgress>
|
||||
|
||||
<div className={progressDetailsClasses}>
|
||||
<div className="flex items-center gap-1">
|
||||
<span>{t('liquidityProviderVotesFor')}:</span>
|
||||
<Tooltip
|
||||
description={
|
||||
<span>{lpVoteWeight.toFixed(defaultDP)}%</span>
|
||||
}
|
||||
>
|
||||
<button>{lpVoteWeight.toFixed(1)}%</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<span>{t('liquidityProviderVotesAgainst')}:</span>
|
||||
<span>
|
||||
<Tooltip
|
||||
description={
|
||||
<span>{noLPPercentage.toFixed(defaultDP)}%</span>
|
||||
}
|
||||
>
|
||||
<button>{noLPPercentage.toFixed(1)}%</button>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
className={sectionClasses}
|
||||
data-testid="lp-participation-breakdown"
|
||||
>
|
||||
<VoteProgress
|
||||
percentageFor={
|
||||
lpParticipationThresholdProgress || new BigNumber(0)
|
||||
}
|
||||
testId="lp-participation-progress"
|
||||
>
|
||||
<Status
|
||||
reached={participationLPMet}
|
||||
threshold={requiredParticipationLP || new BigNumber(1)}
|
||||
text={t('participationThreshold')}
|
||||
testId={
|
||||
participationLPMet
|
||||
? 'lp-participation-met'
|
||||
: 'lp-participation-not-met'
|
||||
}
|
||||
/>
|
||||
</VoteProgress>
|
||||
|
||||
<div className="flex mt-2 text-sm">
|
||||
<div className="flex items-center gap-1">
|
||||
<span>{t('totalLiquidityProviderTokensVoted')}:</span>
|
||||
<Tooltip
|
||||
description={formatNumber(
|
||||
totalEquityLikeShareWeight,
|
||||
defaultDP
|
||||
)}
|
||||
>
|
||||
<span>{totalEquityLikeShareWeight.toFixed(1)}%</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { captureMessage } from '@sentry/minimal';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet-react';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { VoteValue } from '@vegaprotocol/types';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useUserVoteQuery } from './__generated__/Vote';
|
||||
|
@ -1,19 +1,20 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Icon, ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet-react';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { ConnectToVega } from '../../../../components/connect-to-vega';
|
||||
import { VoteButtonsContainer } from './vote-buttons';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { type VoteValue } from '@vegaprotocol/types';
|
||||
import { type VegaTxState } from '@vegaprotocol/proposals';
|
||||
import { type DialogProps, type VegaTxState } from '@vegaprotocol/proposals';
|
||||
import { type VoteState } from './use-user-vote';
|
||||
import { type Proposal, type BatchProposal } from '../../types';
|
||||
|
||||
interface UserVoteProps {
|
||||
proposal: Proposal | BatchProposal;
|
||||
transaction: VegaTxState;
|
||||
transaction: VegaTxState | null;
|
||||
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
|
||||
dialog: (props: DialogProps) => JSX.Element;
|
||||
voteState: VoteState | null;
|
||||
voteDatetime: Date | null;
|
||||
}
|
||||
@ -22,6 +23,7 @@ export const UserVote = ({
|
||||
proposal,
|
||||
submit,
|
||||
transaction,
|
||||
dialog,
|
||||
voteState,
|
||||
voteDatetime,
|
||||
}: UserVoteProps) => {
|
||||
@ -54,6 +56,7 @@ export const UserVote = ({
|
||||
className="flex"
|
||||
submit={submit}
|
||||
transaction={transaction}
|
||||
dialog={dialog}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
|
@ -2,20 +2,33 @@ import { render, screen } from '@testing-library/react';
|
||||
import { VoteTransactionDialog } from './vote-transaction-dialog';
|
||||
import { VoteState } from './use-user-vote';
|
||||
import { VegaTxStatus } from '@vegaprotocol/proposals';
|
||||
import { ConnectorErrors, unknownError } from '@vegaprotocol/wallet';
|
||||
|
||||
describe('VoteTransactionDialog', () => {
|
||||
const mockTransactionDialog = jest.fn(({ title, content }) => (
|
||||
<div>
|
||||
<div>{title}</div>
|
||||
<div>{content?.Complete}</div>
|
||||
</div>
|
||||
));
|
||||
|
||||
it('renders without crashing', () => {
|
||||
render(
|
||||
<VoteTransactionDialog
|
||||
voteState={VoteState.Yes}
|
||||
transaction={null}
|
||||
TransactionDialog={mockTransactionDialog}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('vote-transaction-dialog')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders with txRequested title when voteState is Requested', () => {
|
||||
render(
|
||||
<VoteTransactionDialog
|
||||
voteState={VoteState.Requested}
|
||||
transaction={{
|
||||
error: null,
|
||||
txHash: null,
|
||||
signature: null,
|
||||
status: VegaTxStatus.Requested,
|
||||
dialogOpen: true,
|
||||
}}
|
||||
transaction={null}
|
||||
TransactionDialog={mockTransactionDialog}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -26,13 +39,8 @@ describe('VoteTransactionDialog', () => {
|
||||
render(
|
||||
<VoteTransactionDialog
|
||||
voteState={VoteState.Pending}
|
||||
transaction={{
|
||||
error: null,
|
||||
txHash: null,
|
||||
signature: null,
|
||||
status: VegaTxStatus.Pending,
|
||||
dialogOpen: true,
|
||||
}}
|
||||
transaction={null}
|
||||
TransactionDialog={mockTransactionDialog}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -43,13 +51,8 @@ describe('VoteTransactionDialog', () => {
|
||||
render(
|
||||
<VoteTransactionDialog
|
||||
voteState={VoteState.Yes} // or any other state other than Requested or Pending
|
||||
transaction={{
|
||||
error: null,
|
||||
txHash: null,
|
||||
signature: null,
|
||||
status: VegaTxStatus.Complete,
|
||||
dialogOpen: true,
|
||||
}}
|
||||
transaction={null}
|
||||
TransactionDialog={mockTransactionDialog}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -62,18 +65,17 @@ describe('VoteTransactionDialog', () => {
|
||||
<VoteTransactionDialog
|
||||
voteState={VoteState.Failed}
|
||||
transaction={{
|
||||
error: unknownError(),
|
||||
error: { message: 'Custom error test message', name: 'blah' },
|
||||
txHash: null,
|
||||
signature: null,
|
||||
status: VegaTxStatus.Error,
|
||||
dialogOpen: true,
|
||||
dialogOpen: false,
|
||||
}}
|
||||
TransactionDialog={mockTransactionDialog}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByText(ConnectorErrors.unknown.message)
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('Custom error test message')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders default error message when voteState is failed and no error message exists on the tx', () => {
|
||||
@ -84,9 +86,10 @@ describe('VoteTransactionDialog', () => {
|
||||
error: null,
|
||||
txHash: null,
|
||||
signature: null,
|
||||
status: VegaTxStatus.Complete,
|
||||
dialogOpen: true,
|
||||
status: VegaTxStatus.Error,
|
||||
dialogOpen: false,
|
||||
}}
|
||||
TransactionDialog={mockTransactionDialog}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -97,13 +100,8 @@ describe('VoteTransactionDialog', () => {
|
||||
render(
|
||||
<VoteTransactionDialog
|
||||
voteState={VoteState.Yes}
|
||||
transaction={{
|
||||
error: null,
|
||||
txHash: null,
|
||||
signature: null,
|
||||
status: VegaTxStatus.Default,
|
||||
dialogOpen: true,
|
||||
}}
|
||||
transaction={null}
|
||||
TransactionDialog={mockTransactionDialog}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -1,77 +1,120 @@
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { VoteButtons, type VoteButtonsProps } from './vote-buttons';
|
||||
import { VoteButtons } from './vote-buttons';
|
||||
import { VoteState } from './use-user-vote';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { mockWalletContext } from '../../test-helpers/mocks';
|
||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||
import { MockedProvider } from '@apollo/react-testing';
|
||||
import { VegaTxStatus } from '@vegaprotocol/proposals';
|
||||
import {
|
||||
MockedWalletProvider,
|
||||
mockConfig,
|
||||
} from '@vegaprotocol/wallet-react/testing';
|
||||
|
||||
describe('Vote buttons', () => {
|
||||
const key = { publicKey: '0x123', name: 'key 1' };
|
||||
const transaction = {
|
||||
status: VegaTxStatus.Default,
|
||||
error: null,
|
||||
txHash: null,
|
||||
signature: null,
|
||||
dialogOpen: false,
|
||||
};
|
||||
const props = {
|
||||
voteState: VoteState.NotCast,
|
||||
voteDatetime: null,
|
||||
proposalState: ProposalState.STATE_OPEN,
|
||||
proposalId: null,
|
||||
minVoterBalance: null,
|
||||
spamProtectionMinTokens: null,
|
||||
currentStakeAvailable: new BigNumber(1),
|
||||
submit: () => Promise.resolve(),
|
||||
transaction,
|
||||
};
|
||||
|
||||
const renderComponent = (testProps?: Partial<VoteButtonsProps>) => {
|
||||
return render(
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(
|
||||
<AppStateProvider>
|
||||
<MockedProvider>
|
||||
<MockedWalletProvider>
|
||||
<VoteButtons {...props} {...testProps} />
|
||||
</MockedWalletProvider>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<VoteButtons
|
||||
voteState={VoteState.NotCast}
|
||||
voteDatetime={null}
|
||||
proposalState={ProposalState.STATE_OPEN}
|
||||
proposalId={null}
|
||||
minVoterBalance={null}
|
||||
spamProtectionMinTokens={null}
|
||||
currentStakeAvailable={new BigNumber(1)}
|
||||
dialog={() => <div>Blah</div>}
|
||||
submit={() => Promise.resolve()}
|
||||
transaction={null}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</AppStateProvider>
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockConfig.store.setState({ pubKey: key.publicKey, keys: [key] });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
act(() => {
|
||||
mockConfig.reset();
|
||||
});
|
||||
});
|
||||
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = renderComponent();
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should explain that voting is closed if the proposal is not open', () => {
|
||||
renderComponent({ proposalState: ProposalState.STATE_PASSED });
|
||||
render(
|
||||
<AppStateProvider>
|
||||
<MockedProvider>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<VoteButtons
|
||||
voteState={VoteState.NotCast}
|
||||
voteDatetime={null}
|
||||
proposalState={ProposalState.STATE_PASSED}
|
||||
proposalId={null}
|
||||
minVoterBalance={null}
|
||||
spamProtectionMinTokens={null}
|
||||
currentStakeAvailable={new BigNumber(1)}
|
||||
dialog={() => <div>Blah</div>}
|
||||
submit={() => Promise.resolve()}
|
||||
transaction={null}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</AppStateProvider>
|
||||
);
|
||||
expect(screen.getByText('Voting has ended.')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should provide a connect wallet prompt if no pubkey', () => {
|
||||
mockConfig.reset();
|
||||
renderComponent();
|
||||
const mockWalletNoPubKeyContext = {
|
||||
pubKey: null,
|
||||
pubKeys: [],
|
||||
isReadOnly: false,
|
||||
sendTx: jest.fn().mockReturnValue(Promise.resolve(null)),
|
||||
connect: jest.fn(),
|
||||
disconnect: jest.fn(),
|
||||
selectPubKey: jest.fn(),
|
||||
connector: null,
|
||||
} as unknown as VegaWalletContextShape;
|
||||
|
||||
render(
|
||||
<AppStateProvider>
|
||||
<MockedProvider>
|
||||
<VegaWalletContext.Provider value={mockWalletNoPubKeyContext}>
|
||||
<VoteButtons
|
||||
voteState={VoteState.NotCast}
|
||||
voteDatetime={null}
|
||||
proposalState={ProposalState.STATE_OPEN}
|
||||
proposalId={null}
|
||||
minVoterBalance={null}
|
||||
spamProtectionMinTokens={null}
|
||||
currentStakeAvailable={new BigNumber(1)}
|
||||
dialog={() => <div>Blah</div>}
|
||||
submit={() => Promise.resolve()}
|
||||
transaction={null}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</AppStateProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('connect-wallet')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should tell the user they need tokens if their current stake is 0', () => {
|
||||
renderComponent({ currentStakeAvailable: new BigNumber(0) });
|
||||
render(
|
||||
<AppStateProvider>
|
||||
<MockedProvider>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<VoteButtons
|
||||
voteState={VoteState.NotCast}
|
||||
voteDatetime={null}
|
||||
proposalState={ProposalState.STATE_OPEN}
|
||||
proposalId={null}
|
||||
minVoterBalance={null}
|
||||
spamProtectionMinTokens={null}
|
||||
currentStakeAvailable={new BigNumber(0)}
|
||||
dialog={() => <div>Blah</div>}
|
||||
submit={() => Promise.resolve()}
|
||||
transaction={null}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</AppStateProvider>
|
||||
);
|
||||
expect(
|
||||
screen.getByText(
|
||||
'You need some VEGA tokens to participate in governance.'
|
||||
@ -80,10 +123,26 @@ describe('Vote buttons', () => {
|
||||
});
|
||||
|
||||
it('should tell the user of the minimum requirements if they have some, but not enough tokens', () => {
|
||||
renderComponent({
|
||||
minVoterBalance: '2000000000000000000',
|
||||
spamProtectionMinTokens: '1000000000000000000',
|
||||
});
|
||||
render(
|
||||
<AppStateProvider>
|
||||
<MockedProvider>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<VoteButtons
|
||||
voteState={VoteState.NotCast}
|
||||
voteDatetime={null}
|
||||
proposalState={ProposalState.STATE_OPEN}
|
||||
proposalId={null}
|
||||
minVoterBalance="2000000000000000000"
|
||||
spamProtectionMinTokens="1000000000000000000"
|
||||
currentStakeAvailable={new BigNumber(1)}
|
||||
dialog={() => <div>Blah</div>}
|
||||
submit={() => Promise.resolve()}
|
||||
transaction={null}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</AppStateProvider>
|
||||
);
|
||||
expect(
|
||||
screen.getByText(
|
||||
'You must have at least 2 VEGA associated to vote on this proposal'
|
||||
@ -92,23 +151,51 @@ describe('Vote buttons', () => {
|
||||
});
|
||||
|
||||
it('should show you voted if vote state is correct, and if the proposal is still open, it will display a change vote button', () => {
|
||||
renderComponent({
|
||||
voteState: VoteState.Yes,
|
||||
minVoterBalance: '2000000000000000000',
|
||||
spamProtectionMinTokens: '1000000000000000000',
|
||||
currentStakeAvailable: new BigNumber(10),
|
||||
});
|
||||
render(
|
||||
<AppStateProvider>
|
||||
<MockedProvider>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<VoteButtons
|
||||
voteState={VoteState.Yes}
|
||||
voteDatetime={null}
|
||||
proposalState={ProposalState.STATE_OPEN}
|
||||
proposalId={null}
|
||||
minVoterBalance="2000000000000000000"
|
||||
spamProtectionMinTokens="1000000000000000000"
|
||||
currentStakeAvailable={new BigNumber(10)}
|
||||
dialog={() => <div>Blah</div>}
|
||||
submit={() => Promise.resolve()}
|
||||
transaction={null}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</AppStateProvider>
|
||||
);
|
||||
expect(screen.getByTestId('you-voted')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('change-vote-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should allow you to change your vote', () => {
|
||||
renderComponent({
|
||||
voteState: VoteState.No,
|
||||
minVoterBalance: '2000000000000000000',
|
||||
spamProtectionMinTokens: '1000000000000000000',
|
||||
currentStakeAvailable: new BigNumber(10),
|
||||
});
|
||||
render(
|
||||
<AppStateProvider>
|
||||
<MockedProvider>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<VoteButtons
|
||||
voteState={VoteState.No}
|
||||
voteDatetime={null}
|
||||
proposalState={ProposalState.STATE_OPEN}
|
||||
proposalId={null}
|
||||
minVoterBalance="2000000000000000000"
|
||||
spamProtectionMinTokens="1000000000000000000"
|
||||
currentStakeAvailable={new BigNumber(10)}
|
||||
dialog={() => <div>Blah</div>}
|
||||
submit={() => Promise.resolve()}
|
||||
transaction={null}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</AppStateProvider>
|
||||
);
|
||||
fireEvent.click(screen.getByTestId('change-vote-button'));
|
||||
expect(screen.getByTestId('vote-buttons')).toBeInTheDocument();
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { format } from 'date-fns';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useVegaWallet, useDialogStore } from '@vegaprotocol/wallet-react';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
AsyncRenderer,
|
||||
Button,
|
||||
@ -17,7 +17,7 @@ import { VoteState } from './use-user-vote';
|
||||
import { ProposalMinRequirements, ProposalUserAction } from '../shared';
|
||||
import { VoteTransactionDialog } from './vote-transaction-dialog';
|
||||
import { useVoteButtonsQuery } from './__generated__/Stake';
|
||||
import type { VegaTxState } from '@vegaprotocol/proposals';
|
||||
import type { DialogProps, VegaTxState } from '@vegaprotocol/proposals';
|
||||
import { filterAcceptableGraphqlErrors } from '../../../../lib/party';
|
||||
import {
|
||||
NetworkParams,
|
||||
@ -32,7 +32,8 @@ interface VoteButtonsContainerProps {
|
||||
proposalId: string | null;
|
||||
proposalState: ProposalState;
|
||||
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
|
||||
transaction: VegaTxState;
|
||||
transaction: VegaTxState | null;
|
||||
dialog: (props: DialogProps) => JSX.Element;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@ -135,16 +136,17 @@ export const VoteButtonsContainer = (props: VoteButtonsContainerProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export interface VoteButtonsProps {
|
||||
interface VoteButtonsProps {
|
||||
voteState: VoteState | null;
|
||||
voteDatetime: Date | null;
|
||||
proposalState: ProposalState;
|
||||
proposalId: string | null;
|
||||
proposalState: ProposalState;
|
||||
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
|
||||
transaction: VegaTxState | null;
|
||||
dialog: (props: DialogProps) => JSX.Element;
|
||||
currentStakeAvailable: BigNumber;
|
||||
minVoterBalance: string | null;
|
||||
spamProtectionMinTokens: string | null;
|
||||
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
|
||||
transaction: VegaTxState;
|
||||
}
|
||||
|
||||
export const VoteButtons = ({
|
||||
@ -157,10 +159,13 @@ export const VoteButtons = ({
|
||||
spamProtectionMinTokens,
|
||||
submit,
|
||||
transaction,
|
||||
dialog: Dialog,
|
||||
}: VoteButtonsProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { pubKey } = useVegaWallet();
|
||||
const openVegaWalletDialog = useDialogStore((store) => store.open);
|
||||
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
|
||||
openVegaWalletDialog: store.openVegaWalletDialog,
|
||||
}));
|
||||
const [changeVote, setChangeVote] = React.useState(false);
|
||||
const proposalVotable = useMemo(
|
||||
() =>
|
||||
@ -179,7 +184,11 @@ export const VoteButtons = ({
|
||||
if (!pubKey) {
|
||||
return (
|
||||
<div data-testid="connect-wallet">
|
||||
<ButtonLink onClick={openVegaWalletDialog}>
|
||||
<ButtonLink
|
||||
onClick={() => {
|
||||
openVegaWalletDialog();
|
||||
}}
|
||||
>
|
||||
{t('connectVegaWallet')}
|
||||
</ButtonLink>{' '}
|
||||
{t('toVote')}
|
||||
@ -292,7 +301,11 @@ export const VoteButtons = ({
|
||||
</p>
|
||||
)
|
||||
)}
|
||||
<VoteTransactionDialog voteState={voteState} transaction={transaction} />
|
||||
<VoteTransactionDialog
|
||||
voteState={voteState}
|
||||
transaction={transaction}
|
||||
TransactionDialog={Dialog}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { VoteState } from './use-user-vote';
|
||||
import {
|
||||
VegaTransactionDialog,
|
||||
type VegaTxState,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import type { DialogProps, VegaTxState } from '@vegaprotocol/proposals';
|
||||
|
||||
interface VoteTransactionDialogProps {
|
||||
voteState: VoteState;
|
||||
transaction: VegaTxState;
|
||||
transaction: VegaTxState | null;
|
||||
TransactionDialog: (props: DialogProps) => JSX.Element;
|
||||
}
|
||||
|
||||
const dialogTitle = (voteState: VoteState): string | undefined => {
|
||||
@ -24,23 +22,22 @@ const dialogTitle = (voteState: VoteState): string | undefined => {
|
||||
export const VoteTransactionDialog = ({
|
||||
voteState,
|
||||
transaction,
|
||||
TransactionDialog,
|
||||
}: VoteTransactionDialogProps) => {
|
||||
// Render a custom message if the voting fails otherwise
|
||||
// pass undefined so that the default vega transaction dialog UI gets used
|
||||
const customMessage =
|
||||
voteState === VoteState.Failed ? (
|
||||
<p>{transaction.error?.message || t('voteError')}</p>
|
||||
<p>{transaction?.error?.message || t('voteError')}</p>
|
||||
) : undefined;
|
||||
|
||||
return (
|
||||
<div data-testid="vote-transaction-dialog">
|
||||
<VegaTransactionDialog
|
||||
<TransactionDialog
|
||||
title={dialogTitle(voteState)}
|
||||
transaction={transaction}
|
||||
content={{
|
||||
Complete: customMessage,
|
||||
}}
|
||||
isOpen={transaction.dialogOpen}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -273,74 +273,4 @@ describe('use-vote-information', () => {
|
||||
expect(current?.willPassByTokenVote).toEqual(false);
|
||||
expect(current?.willPassByLPVote).toEqual(true);
|
||||
});
|
||||
|
||||
it('mainnet recreation: only yes LP votes equal passing', () => {
|
||||
const yesVotes = 0;
|
||||
const noVotes = 70;
|
||||
const yesEquityLikeShareWeight = '0.21';
|
||||
const noEquityLikeShareWeight = '0';
|
||||
const fixedTokenValue = 1000000000000000000;
|
||||
|
||||
const proposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarket',
|
||||
marketId: '12345',
|
||||
},
|
||||
},
|
||||
votes: {
|
||||
__typename: 'ProposalVotes',
|
||||
yes: generateYesVotes(
|
||||
yesVotes,
|
||||
fixedTokenValue,
|
||||
yesEquityLikeShareWeight
|
||||
),
|
||||
no: generateNoVotes(noVotes, fixedTokenValue, noEquityLikeShareWeight),
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() =>
|
||||
useVoteInformation({ terms: proposal.terms, votes: proposal.votes })
|
||||
);
|
||||
|
||||
expect(current?.willPassByTokenVote).toEqual(false);
|
||||
expect(current?.willPassByLPVote).toEqual(true);
|
||||
});
|
||||
|
||||
it('mainnet recreation: mixed yes and no LP votes equal failing', () => {
|
||||
const yesVotes = 0;
|
||||
const noVotes = 70;
|
||||
const yesEquityLikeShareWeight = '0.21';
|
||||
const noEquityLikeShareWeight = '0.22';
|
||||
const fixedTokenValue = 1000000000000000000;
|
||||
|
||||
const proposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarket',
|
||||
marketId: '12345',
|
||||
},
|
||||
},
|
||||
votes: {
|
||||
__typename: 'ProposalVotes',
|
||||
yes: generateYesVotes(
|
||||
yesVotes,
|
||||
fixedTokenValue,
|
||||
yesEquityLikeShareWeight
|
||||
),
|
||||
no: generateNoVotes(noVotes, fixedTokenValue, noEquityLikeShareWeight),
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() =>
|
||||
useVoteInformation({ terms: proposal.terms, votes: proposal.votes })
|
||||
);
|
||||
|
||||
expect(current?.willPassByTokenVote).toEqual(false);
|
||||
expect(current?.willPassByLPVote).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user