Compare commits

..
151 changed files with 1559 additions and 2316 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
NX_TENDERMINT_URL=https://mainnet-observer-proxy01.ops.vega.xyz/
NX_TENDERMINT_WEBSOCKET_URL=wss://mainnet-observer-proxy01.ops.vega.xyz/websocket
NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_URL=https://api.vega.xyz/query
NX_VEGA_ENV=MAINNET
NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/rest
@@ -10,8 +10,6 @@
"governance.proposal.updateMarket.minVoterBalance",
"governance.proposal.updateNetParam.minProposerBalance",
"governance.proposal.updateNetParam.minVoterBalance",
"governance.proposal.updateAsset.minProposerBalance",
"governance.proposal.updateAsset.minVoterBalance",
"reward.staking.delegation.maxPayoutPerEpoch",
"reward.staking.delegation.maxPayoutPerParticipant",
"reward.staking.delegation.minimumValidatorStake",
@@ -21,6 +19,9 @@
"validators.delegation.minAmount"
],
"fiveDecimal": [
"governance.proposal.updateAsset.minProposerBalance",
"governance.proposal.updateAsset.minVoterBalance",
"governance.proposal.updateAsset.requiredParticipation",
"market.fee.factors.infrastructureFee",
"market.fee.factors.makerFee",
"market.liquidity.bondPenaltyParameter",
@@ -76,7 +77,6 @@
"governance.proposal.updateNetParam.requiredMajority",
"governance.proposal.updateNetParam.requiredParticipation",
"governance.proposal.updateMarket.minProposerEquityLikeShare",
"governance.proposal.updateAsset.requiredParticipation",
"validators.vote.required"
],
"duration": [
@@ -138,7 +138,7 @@ context('Network parameters page', { tags: '@smoke' }, function () {
});
});
it.skip('should list each network parameter displayed as a currency value with four decimals - in the correct format', function () {
it('should list each network parameter displayed as a currency value with four decimals - in the correct format', function () {
cy.get_network_parameters().then((network_parameters) => {
network_parameters = Object.entries(network_parameters);
network_parameters.forEach((network_parameter) => {
-1
View File
@@ -4,7 +4,6 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
NX_VEGA_ENV=CUSTOM
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
NX_VEGA_EXPLORER_URL=/
# App flags
NX_EXPLORER_TXS_LIST=0
+1 -2
View File
@@ -8,5 +8,4 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://dev.token.vega.xyz
NX_VEGA_URL=https://api.devnet1.vega.xyz/graphql
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_EXPLORER_URL=/
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
+4 -5
View File
@@ -1,10 +1,9 @@
# App configuration variables
NX_TENDERMINT_URL=https://be.vega.community
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
NX_TENDERMINT_URL=https://be.explorer.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.explorer.vega.xyz/websocket
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_ENV=MAINNET
NX_BLOCK_EXPLORER=https://be.vega.community/rest/
NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/rest/
NX_ETHERSCAN_URL=https://etherscan.io
NX_VEGA_GOVERNANCE_URL=https://token.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz/
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
+1 -2
View File
@@ -8,5 +8,4 @@ NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://token.fairground.wtf
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
-1
View File
@@ -11,4 +11,3 @@ NX_VEGA_GOVERNANCE_URL=https://validator-testnet.governance.vega.xyz
NX_TENDERMINT_URL=https://tm-be.validators-testnet.vega.rocks
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/rest
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket
NX_VEGA_EXPLORER_URL=https://validator-testnet.explorer.vega.xyz/
-1
View File
@@ -5,4 +5,3 @@ NX_VEGA_ENV=CUSTOM
NX_BLOCK_EXPLORER=
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
NX_VEGA_EXPLORER_URL=/
@@ -6,7 +6,6 @@ export type AssetBalanceProps = {
assetId: string;
price: string;
showAssetLink?: boolean;
showAssetSymbol?: boolean;
};
/**
@@ -17,21 +16,18 @@ const AssetBalance = ({
assetId,
price,
showAssetLink = true,
showAssetSymbol = false,
}: AssetBalanceProps) => {
const { data: asset, loading } = useAssetDataProvider(assetId);
const { data: asset } = useAssetDataProvider(assetId);
const label =
!loading && asset && asset.decimals
asset && asset.decimals
? addDecimalsFormatNumber(price, asset.decimals)
: price;
return (
<div className="inline-block">
<span>{label}</span>{' '}
{showAssetLink && asset?.id ? (
<AssetLink showAssetSymbol={showAssetSymbol} assetId={assetId} />
) : null}
{showAssetLink && asset?.id ? <AssetLink assetId={assetId} /> : null}
</div>
);
};
@@ -13,17 +13,11 @@ const DEFAULT_DECIMALS = 18;
* the governance asset first, which is set by a network parameter
*/
const GovernanceAssetBalance = ({ price }: GovernanceAssetBalanceProps) => {
const { data, loading } = useExplorerGovernanceAssetQuery();
const { data } = useExplorerGovernanceAssetQuery();
if (!loading && data && data.networkParameter?.value) {
if (data && data.networkParameter?.value) {
const governanceAssetId = data.networkParameter.value;
return (
<AssetBalance
price={price}
showAssetSymbol={true}
assetId={governanceAssetId}
/>
);
return <AssetBalance price={price} assetId={governanceAssetId} />;
} else {
return (
<div className="inline-block">
@@ -20,7 +20,7 @@ export const PageHeader = ({
copy = false,
className,
}: PageHeaderProps) => {
const titleClasses = 'text-xl uppercase font-alpha calt';
const titleClasses = 'text-4xl xl:text-5xl uppercase font-alpha calt';
return (
<header className={className}>
<span className={`${titleClasses} block`}>{prefix}</span>
@@ -30,7 +30,7 @@ export const BundleExists = ({
// Note if this is wrong, the wrong decoder will be used which will give incorrect data
return (
<div className="w-auto h-10 max-w-lg border-2 border-solid border-vega-light-100 dark:border-vega-dark-200 p-5 mt-5">
<div className="w-auto max-w-lg border-2 border-solid border-vega-light-100 dark:border-vega-dark-200 p-5 mt-5">
<IconForBundleStatus status={status} />
<h1 className="text-xl pb-1">
{status === 'STATUS_ENABLED'
@@ -1,3 +1,4 @@
export { TxList } from './tx-list';
export { TxOrderType } from './tx-order-type';
export { TxsInfiniteList } from './txs-infinite-list';
export { TxsInfiniteListItem } from './txs-infinite-list-item';
@@ -0,0 +1,14 @@
import { t } from '@vegaprotocol/i18n';
import type { TendermintUnconfirmedTransactionsResponse } from '../../routes/txs/tendermint-unconfirmed-transactions-response.d';
interface TxsProps {
data: TendermintUnconfirmedTransactionsResponse | undefined;
}
export const TxList = ({ data }: TxsProps) => {
if (!data) {
return <div>{t('Awaiting transactions')}</div>;
}
return <div>{JSON.stringify(data, null, ' ')}</div>;
};
@@ -7,7 +7,7 @@ import { toHex } from '../search/detect-search';
import { ChainResponseCode } from './details/chain-response-code/chain-reponse.code';
import isNumber from 'lodash/isNumber';
const TRUNCATE_LENGTH = 10;
const TRUNCATE_LENGTH = 5;
export const TxsInfiniteListItem = ({
hash,
@@ -34,10 +34,10 @@ export const TxsInfiniteListItem = ({
className="flex items-center h-full border-t border-neutral-600 dark:border-neutral-800 txs-infinite-list-item grid grid-cols-10 py-2"
>
<div
className="text-sm col-span-10 md:col-span-3 leading-none"
className="text-sm col-span-10 xl:col-span-3 leading-none"
data-testid="tx-hash"
>
<span className="md:hidden uppercase text-vega-dark-300">
<span className="xl:hidden uppercase text-vega-dark-300">
ID:&nbsp;
</span>
<TruncatedLink
@@ -48,10 +48,10 @@ export const TxsInfiniteListItem = ({
/>
</div>
<div
className="text-sm col-span-10 md:col-span-3 leading-none"
className="text-sm col-span-10 xl:col-span-3 leading-none"
data-testid="pub-key"
>
<span className="md:hidden uppercase text-vega-dark-300">
<span className="xl:hidden uppercase text-vega-dark-300">
By:&nbsp;
</span>
<TruncatedLink
@@ -61,14 +61,14 @@ export const TxsInfiniteListItem = ({
endChars={TRUNCATE_LENGTH}
/>
</div>
<div className="text-sm col-span-5 md:col-span-2 leading-none flex items-center">
<div className="text-sm col-span-5 xl:col-span-2 leading-none flex items-center">
<TxOrderType orderType={type} command={command} />
</div>
<div
className="text-sm col-span-3 md:col-span-1 leading-none flex items-center"
className="text-sm col-span-3 xl:col-span-1 leading-none flex items-center"
data-testid="tx-block"
>
<span className="md:hidden uppercase text-vega-dark-300">
<span className="xl:hidden uppercase text-vega-dark-300">
Block:&nbsp;
</span>
<TruncatedLink
@@ -79,10 +79,10 @@ export const TxsInfiniteListItem = ({
/>
</div>
<div
className="text-sm col-span-2 md:col-span-1 leading-none flex items-center"
className="text-sm col-span-2 xl:col-span-1 leading-none flex items-center"
data-testid="tx-success"
>
<span className="md:hidden uppercase text-vega-dark-300">
<span className="xl:hidden uppercase text-vega-dark-300">
Success:&nbsp;
</span>
{isNumber(code) ? (
@@ -64,7 +64,9 @@ describe('Txs infinite list', () => {
error={Error('test error!')}
/>
);
expect(screen.getByText('Cannot fetch transaction')).toBeInTheDocument();
expect(
screen.getByText('Cannot fetch transaction: Error: test error!')
).toBeInTheDocument();
});
it('item renders data of n length into list of n length', () => {
@@ -30,7 +30,7 @@ const NOOP = () => {};
const Item = ({ index, style, isLoading, error }: ItemProps) => {
let content;
if (error) {
content = t(`Cannot fetch transaction`);
content = t(`Cannot fetch transaction: ${error}`);
} else if (isLoading) {
content = <Loader />;
} else {
@@ -68,7 +68,7 @@ export const TxsInfiniteList = ({
className,
}: TxsInfiniteListProps) => {
const { screenSize } = useScreenDimensions();
const isStacked = ['xs', 'sm'].includes(screenSize);
const isStacked = ['xs', 'sm', 'md', 'lg'].includes(screenSize);
if (!txs) {
if (!areTxsLoading) {
@@ -95,15 +95,15 @@ export const TxsInfiniteList = ({
return (
<div className={className} data-testid="transactions-list">
<div className="lg:grid grid-cols-10 w-full mb-3 hidden text-vega-dark-300 uppercase">
<div className="xl:grid grid-cols-10 w-full mb-3 hidden text-vega-dark-300 uppercase">
<div className="col-span-3">
<span className="hidden xl:inline">{t('Transaction')} &nbsp;</span>
<span className="hidden xl:inline">Transaction &nbsp;</span>
<span>ID</span>
</div>
<div className="col-span-3">{t('Submitted By')}</div>
<div className="col-span-2">{t('Type')}</div>
<div className="col-span-1">{t('Block')}</div>
<div className="col-span-1">{t('Success')}</div>
<div className="col-span-3">Submitted By</div>
<div className="col-span-2">Type</div>
<div className="col-span-1">Block</div>
<div className="col-span-1">Success</div>
</div>
<div data-testid="infinite-scroll-wrapper">
<InfiniteLoader
@@ -1,4 +1,5 @@
import { Routes } from '../../routes/route-names';
import { RenderFetched } from '../render-fetched';
import { TruncatedLink } from '../truncate/truncated-link';
import { TxOrderType } from './tx-order-type';
import { Table, TableRow, TableCell } from '../table';
@@ -8,7 +9,7 @@ import type { BlockExplorerTransactions } from '../../routes/types/block-explore
import isNumber from 'lodash/isNumber';
import { ChainResponseCode } from './details/chain-response-code/chain-reponse.code';
import { getTxsDataUrl } from '../../hooks/use-txs-data';
import { AsyncRenderer, Loader } from '@vegaprotocol/ui-toolkit';
import { Loader } from '@vegaprotocol/ui-toolkit';
import EmptyList from '../empty-list/empty-list';
interface TxsPerBlockProps {
@@ -26,7 +27,7 @@ export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => {
} = useFetch<BlockExplorerTransactions>(url);
return (
<AsyncRenderer data={data} error={error} loading={!!loading}>
<RenderFetched error={error} loading={loading} className="text-body-large">
{data && data.transactions.length > 0 ? (
<div className="overflow-x-auto whitespace-nowrap mb-28">
<Table>
@@ -94,6 +95,6 @@ export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => {
label={t('0 transactions')}
/>
)}
</AsyncRenderer>
</RenderFetched>
);
};
@@ -144,12 +144,12 @@ afterEach(() => {
describe('Block', () => {
it('renders error state if error is present', async () => {
(useFetch as jest.Mock).mockReturnValue({
state: { data: null, loading: false, error: new Error('asd') },
state: { data: null, loading: false, error: 'asd' },
});
render(renderComponent());
expect(screen.getByText(`BLOCK ${blockId}`)).toBeInTheDocument();
expect(screen.getByText('Something went wrong: asd')).toBeInTheDocument();
expect(screen.getByText('Error retrieving data')).toBeInTheDocument();
});
it('renders loading state if present', async () => {
@@ -1,3 +1,4 @@
import React from 'react';
import { Link, useParams } from 'react-router-dom';
import { DATA_SOURCES } from '../../../config';
import { getDateTimeFormat } from '@vegaprotocol/utils';
@@ -11,8 +12,9 @@ import {
TableCell,
} from '../../../components/table';
import { TxsPerBlock } from '../../../components/txs/txs-per-block';
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import { Button } from '@vegaprotocol/ui-toolkit';
import { Routes } from '../../route-names';
import { RenderFetched } from '../../../components/render-fetched';
import { t } from '@vegaprotocol/i18n';
import { useFetch } from '@vegaprotocol/react-helpers';
import { NodeLink } from '../../../components/links';
@@ -32,7 +34,7 @@ const Block = () => {
return (
<section>
<RouteTitle data-testid="block-header">{t(`BLOCK ${block}`)}</RouteTitle>
<AsyncRenderer data={blockData} error={error} loading={!!loading}>
<RenderFetched error={error} loading={loading}>
<>
<div className="grid grid-cols-2 gap-2 mb-8">
<Link
@@ -121,7 +123,7 @@ const Block = () => {
</>
)}
</>
</AsyncRenderer>
</RenderFetched>
</section>
);
};
+11 -16
View File
@@ -1,7 +1,7 @@
import { t } from '@vegaprotocol/i18n';
import { useFetch } from '@vegaprotocol/react-helpers';
import { RouteTitle } from '../../components/route-title';
import { AsyncRenderer, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { Loader, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { DATA_SOURCES } from '../../config';
import type { TendermintGenesisResponse } from './tendermint-genesis-response';
import { useDocumentTitle } from '../../hooks/use-document-title';
@@ -10,26 +10,21 @@ const Genesis = () => {
useDocumentTitle(['Genesis']);
const {
state: { data, loading, error },
state: { data: genesis, loading },
} = useFetch<TendermintGenesisResponse>(
`${DATA_SOURCES.tendermintUrl}/genesis`
);
if (!genesis?.result.genesis) {
if (loading) {
return <Loader />;
}
return null;
}
return (
<>
<section>
<RouteTitle data-testid="genesis-header">{t('Genesis')}</RouteTitle>
<AsyncRenderer
data={data}
error={error}
loading={!!loading}
loadingMessage={t('Loading genesis information...')}
errorMessage={t('Could not fetch genesis data')}
>
<section>
<SyntaxHighlighter data={data?.result.genesis} />
</section>
</AsyncRenderer>
</>
<SyntaxHighlighter data={genesis?.result.genesis} />
</section>
);
};
@@ -49,7 +49,6 @@ export const MarketPage = () => {
/>
<AsyncRenderer
noDataMessage={t('This chain has no markets')}
errorMessage={t('Could not fetch market') + ' ' + marketId}
data={data}
loading={loading}
error={error}
@@ -21,7 +21,6 @@ import { useDocumentTitle } from '../../hooks/use-document-title';
const PERCENTAGE_PARAMS = [
'governance.proposal.asset.requiredMajority',
'governance.proposal.asset.requiredParticipation',
'governance.proposal.updateAsset.requiredParticipation',
'governance.proposal.freeform.requiredMajority',
'governance.proposal.freeform.requiredParticipation',
'governance.proposal.market.requiredMajority',
@@ -54,8 +53,6 @@ const BIG_NUMBER_PARAMS = [
'governance.proposal.asset.minProposerBalance',
'governance.proposal.market.minProposerBalance',
'governance.proposal.market.minVoterBalance',
'governance.proposal.updateAsset.minProposerBalance',
'governance.proposal.updateAsset.minVoterBalance',
];
export const NetworkParameterRow = ({
@@ -1,4 +1,4 @@
import { AsyncRenderer, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { Loader, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { RouteTitle } from '../../../components/route-title';
import { t } from '@vegaprotocol/i18n';
import { useExplorerOracleSpecsQuery } from '../__generated__/Oracles';
@@ -8,7 +8,7 @@ import { useScrollToLocation } from '../../../hooks/scroll-to-location';
import filter from 'recursive-key-filter';
const Oracles = () => {
const { data, loading, error } = useExplorerOracleSpecsQuery();
const { data, loading } = useExplorerOracleSpecsQuery();
useDocumentTitle(['Oracles']);
useScrollToLocation();
@@ -16,40 +16,28 @@ const Oracles = () => {
return (
<section>
<RouteTitle data-testid="oracle-specs-heading">{t('Oracles')}</RouteTitle>
<AsyncRenderer
data={data}
loading={loading}
error={error}
loadingMessage={t('Loading oracle data...')}
errorMessage={t('Oracle data could not be loaded')}
noDataMessage={t('No oracles found')}
noDataCondition={(data) =>
!data?.oracleSpecsConnection?.edges ||
data.oracleSpecsConnection.edges?.length === 0
}
>
{data?.oracleSpecsConnection?.edges
? data.oracleSpecsConnection.edges.map((o) => {
const id = o?.node.dataSourceSpec.spec.id;
if (!id) {
return null;
}
return (
<div id={id} key={id} className="mb-10">
<OracleDetails
id={id}
dataSource={o?.node}
showBroadcasts={false}
/>
<details>
<summary className="pointer">JSON</summary>
<SyntaxHighlighter data={filter(o, ['__typename'])} />
</details>
</div>
);
})
: null}
</AsyncRenderer>
{loading ? <Loader /> : null}
{data?.oracleSpecsConnection?.edges
? data.oracleSpecsConnection.edges.map((o) => {
const id = o?.node.dataSourceSpec.spec.id;
if (!id) {
return null;
}
return (
<div id={id} key={id} className="mb-10">
<OracleDetails
id={id}
dataSource={o?.node}
showBroadcasts={false}
/>
<details>
<summary className="pointer">JSON</summary>
<SyntaxHighlighter data={filter(o, ['__typename'])} />
</details>
</div>
);
})
: null}
</section>
);
};
@@ -1,11 +1,12 @@
import { RouteTitle } from '../../../components/route-title';
import { RenderFetched } from '../../../components/render-fetched';
import { truncateByChars } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useDocumentTitle } from '../../../hooks/use-document-title';
import { useParams } from 'react-router-dom';
import { useExplorerOracleSpecByIdQuery } from '../__generated__/Oracles';
import { OracleDetails } from '../components/oracle';
import { AsyncRenderer, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import filter from 'recursive-key-filter';
import { TruncateInline } from '../../../components/truncate/truncate';
@@ -26,14 +27,7 @@ export const Oracle = () => {
{t(`Oracle `)}
<TruncateInline startChars={5} endChars={5} text={id || '1'} />
</RouteTitle>
<AsyncRenderer
data={data}
error={error}
loading={loading}
noDataCondition={(data) => !data?.oracleSpec}
errorMessage={t('Could not load oracle data')}
loadingMessage={t('Loading oracle data...')}
>
<RenderFetched error={error} loading={loading}>
{data?.oracleSpec ? (
<div id={id} key={id} className="mb-10">
<OracleDetails
@@ -50,7 +44,7 @@ export const Oracle = () => {
) : (
<span></span>
)}
</AsyncRenderer>
</RenderFetched>
</section>
);
};
@@ -48,13 +48,6 @@ query ExplorerPartyAssets($partyId: ID!) {
}
stakingSummary {
currentStakeAvailable
linkings(pagination: { first: 100 }) {
edges {
node {
amount
}
}
}
}
accountsConnection {
edges {
@@ -10,7 +10,7 @@ export type ExplorerPartyAssetsQueryVariables = Types.Exact<{
}>;
export type ExplorerPartyAssetsQuery = { __typename?: 'Query', partiesConnection?: { __typename?: 'PartyConnection', edges: Array<{ __typename?: 'PartyEdge', node: { __typename?: 'Party', id: string, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number, node: { __typename?: 'Node', id: string, name: string } } } | null> | null } | null, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string, linkings: { __typename?: 'StakesConnection', edges?: Array<{ __typename?: 'StakeLinkingEdge', node: { __typename?: 'StakeLinking', amount: string } } | null> | null } }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', name: string, id: string, decimals: number, symbol: string, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string } }, market?: { __typename?: 'Market', id: string, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } | null } } | null> | null } | null } }> } | null };
export type ExplorerPartyAssetsQuery = { __typename?: 'Query', partiesConnection?: { __typename?: 'PartyConnection', edges: Array<{ __typename?: 'PartyEdge', node: { __typename?: 'Party', id: string, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number, node: { __typename?: 'Node', id: string, name: string } } } | null> | null } | null, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', name: string, id: string, decimals: number, symbol: string, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string } }, market?: { __typename?: 'Market', id: string, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } | null } } | null> | null } | null } }> } | null };
export const ExplorerPartyAssetsAccountsFragmentDoc = gql`
fragment ExplorerPartyAssetsAccounts on AccountBalance {
@@ -64,13 +64,6 @@ export const ExplorerPartyAssetsDocument = gql`
}
stakingSummary {
currentStakeAvailable
linkings(pagination: {first: 100}) {
edges {
node {
amount
}
}
}
}
accountsConnection {
edges {
@@ -1,24 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { useParams } from 'react-router-dom';
import { toNonHex } from '../../../../components/search/detect-search';
import { PageHeader } from '../../../../components/page-header';
import { useDocumentTitle } from '../../../../hooks/use-document-title';
import { PartyAccounts } from '../components/party-accounts';
const PartyAccountsByAsset = () => {
const { party } = useParams<{ party: string }>();
useDocumentTitle(['Public keys', party || '-']);
const partyId = toNonHex(party ? party : '');
return (
<section>
<PageHeader title={t('Balances by asset')} />
<PartyAccounts partyId={partyId} />
</section>
);
};
export { PartyAccountsByAsset };
@@ -1,9 +1,32 @@
import { AccountManager } from '@vegaprotocol/accounts';
import { useCallback } from 'react';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/i18n';
import get from 'lodash/get';
import AssetBalance from '../../../../components/asset-balance/asset-balance';
import { AssetLink, MarketLink } from '../../../../components/links';
import { Table, TableRow } from '../../../../components/table';
import type * as Schema from '@vegaprotocol/types';
import type { ExplorerPartyAssetsAccountsFragment } from '../__generated__/Party-assets';
const accountTypeString: Record<Schema.AccountType, string> = {
ACCOUNT_TYPE_BOND: t('Bond'),
ACCOUNT_TYPE_EXTERNAL: t('External'),
ACCOUNT_TYPE_FEES_INFRASTRUCTURE: t('Fees (Infrastructure)'),
ACCOUNT_TYPE_FEES_LIQUIDITY: t('Fees (Liquidity)'),
ACCOUNT_TYPE_FEES_MAKER: t('Fees (Maker)'),
ACCOUNT_TYPE_GENERAL: t('General'),
ACCOUNT_TYPE_GLOBAL_INSURANCE: t('Global Insurance Pool'),
ACCOUNT_TYPE_GLOBAL_REWARD: t('Global Reward Pool'),
ACCOUNT_TYPE_INSURANCE: t('Insurance'),
ACCOUNT_TYPE_MARGIN: t('Margin'),
ACCOUNT_TYPE_PENDING_TRANSFERS: t('Pending Transfers'),
ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES: t('Reward - LP Fees received'),
ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES: t('Reward - Maker fees paid'),
ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES: t('Reward - Maker fees received'),
ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS: t('Reward - Market proposers'),
ACCOUNT_TYPE_SETTLEMENT: t('Settlement'),
};
interface PartyAccountsProps {
partyId: string;
accounts: ExplorerPartyAssetsAccountsFragment[];
}
/**
@@ -11,22 +34,49 @@ interface PartyAccountsProps {
* probably do with sorting by asset, and then within asset, by type with general
* appearing first and... tbd
*/
export const PartyAccounts = ({ partyId }: PartyAccountsProps) => {
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
const onClickAsset = useCallback(
(assetId?: string) => {
assetId && openAssetDetailsDialog(assetId);
},
[openAssetDetailsDialog]
);
export const PartyAccounts = ({ accounts }: PartyAccountsProps) => {
return (
<div className="block min-h-44 h-60 4 w-full border-red-800 relative">
<AccountManager
partyId={partyId}
onClickAsset={onClickAsset}
isReadOnly={true}
/>
</div>
<Table className="max-w-5xl min-w-fit">
<thead>
<TableRow modifier="bordered" className="font-mono">
<td>{t('Type')}</td>
<td>{t('Market')}</td>
<td className="text-right pr-2">{t('Balance')}</td>
<td>{t('Asset')}</td>
</TableRow>
</thead>
<tbody>
{accounts.map((account) => {
const m = get(account, 'market.tradableInstrument.instrument.name');
return (
<TableRow
key={`pa-${account.asset.id}-${account.type}`}
title={account.asset.name}
id={`${accountTypeString[account.type]} ${m ? ` - ${m}` : ''}`}
>
<td className="text-md">{accountTypeString[account.type]}</td>
<td className="text-md">
{account.market?.id ? (
<MarketLink id={account.market?.id} />
) : (
<p>-</p>
)}
</td>
<td className="text-md text-right pr-2">
<AssetBalance
assetId={account.asset.id}
price={account.balance}
showAssetLink={false}
/>
</td>
<td className="text-md">
<AssetLink assetId={account.asset.id} asDialog={true} />
</td>
</TableRow>
);
})}
</tbody>
</Table>
);
};
@@ -1,63 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { useNavigate } from 'react-router-dom';
import { Routes } from '../../../../routes/route-names';
import { Button, Icon, Loader } from '@vegaprotocol/ui-toolkit';
import { PartyBlock } from './party-block';
import type { AccountFields } from '@vegaprotocol/accounts';
export interface PartyBlockAccountProps {
partyId: string;
accountData: AccountFields[] | null;
accountLoading: boolean;
accountError?: Error;
}
/**
* Displays an overview of a party's assets. This uses existing data
* providers to structure the details by asset, rather than looking at
* it by account. The assumption is that this is a more natural way to
* get an idea of the assets and activity of a party.
*/
export const PartyBlockAccounts = ({
partyId,
accountData,
accountLoading,
accountError,
}: PartyBlockAccountProps) => {
const navigate = useNavigate();
const shouldShowActionButton =
accountData && accountData.length > 0 && !accountLoading && !accountError;
const action = shouldShowActionButton ? (
<Button
size="sm"
onClick={() => navigate(`/${Routes.PARTIES}/${partyId}/assets`)}
>
{t('Show all')}
</Button>
) : null;
return (
<PartyBlock title={t('Assets')} action={action}>
{accountData && accountData.length > 0 ? (
<p>
{accountData.length} {t('assets, including')}{' '}
{accountData
.map((a) => a.asset.symbol)
.slice(0, 3)
.join(', ')}
</p>
) : accountLoading && !accountError ? (
<Loader size="small" />
) : accountData && accountData.length === 0 ? (
<p>{t('No accounts found')}</p>
) : (
<p>
<Icon className="mr-1" name="error" />
<span className="text-sm">{t('Could not load assets')}</span>
</p>
)}
</PartyBlock>
);
};
@@ -1,83 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { useExplorerPartyAssetsQuery } from '../__generated__/Party-assets';
import GovernanceAssetBalance from '../../../../components/asset-balance/governance-asset-balance';
import {
Icon,
KeyValueTable,
KeyValueTableRow,
Loader,
} from '@vegaprotocol/ui-toolkit';
import { PartyBlock } from './party-block';
import BigNumber from 'bignumber.js';
export interface PartyBlockStakeProps {
partyId: string;
accountLoading: boolean;
accountError?: Error;
}
/**
* Displays an overview of a single party's staking balance, importantly maintaining'
* the same height before and after the details are loaded in.
*
* Unlike PartyBlockAccounts there is not action button in the title of this block as
* there is no page for it to link to currently. That's a future task.
*/
export const PartyBlockStake = ({
partyId,
accountLoading,
accountError,
}: PartyBlockStakeProps) => {
const partyRes = useExplorerPartyAssetsQuery({
// Don't cache data for this query, party information can move quite quickly
fetchPolicy: 'network-only',
variables: { partyId: partyId },
skip: !partyId,
});
const p = partyRes.data?.partiesConnection?.edges[0].node;
const linkedLength = p?.stakingSummary?.linkings?.edges?.length;
const linkedStake =
linkedLength && linkedLength > 0
? p?.stakingSummary?.linkings?.edges
?.reduce((total, e) => {
return new BigNumber(total).plus(
new BigNumber(e?.node.amount || 0)
);
}, new BigNumber(0))
.toString()
: '0';
return (
<PartyBlock title={t('Staking')}>
{p?.stakingSummary.currentStakeAvailable ? (
<KeyValueTable>
<KeyValueTableRow noBorder={true}>
<div>{t('Available stake')}</div>
<div>
<GovernanceAssetBalance
price={p.stakingSummary.currentStakeAvailable}
/>
</div>
</KeyValueTableRow>
<KeyValueTableRow noBorder={true}>
<div>{t('Active stake')}</div>
<div>
<GovernanceAssetBalance price={linkedStake || '0'} />
</div>
</KeyValueTableRow>
</KeyValueTable>
) : accountLoading && !accountError ? (
<Loader size="small" />
) : !accountError ? (
<p>{t('No staking balance')}</p>
) : (
<p>
<Icon className="mr-1" name="error" />
<span className="text-sm">{t('Could not load stake details')}</span>
</p>
)}
</PartyBlock>
);
};
@@ -1,24 +0,0 @@
import type { ReactNode } from 'react';
export interface PartyBlockProps {
children: ReactNode;
title: string;
action?: ReactNode;
}
export function PartyBlock({ children, title, action }: PartyBlockProps) {
return (
<div className="border-2 min-h-[138px] border-solid border-vega-light-100 dark:border-vega-dark-200 p-5 mt-5">
<div
className="flex flex-col md:flex-row gap-1 justify-between content-start mb-2"
data-testid="page-title"
>
<h3 className="font-semibold text-lg">{title}</h3>
{action ? action : null}
</div>
{children}
</div>
);
}
@@ -1,26 +1,24 @@
import { getNodes } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
import { useMemo } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useParams } from 'react-router-dom';
import { SubHeading } from '../../../components/sub-heading';
import { Panel } from '../../../components/panel';
import { toNonHex } from '../../../components/search/detect-search';
import { useTxsData } from '../../../hooks/use-txs-data';
import { TxsInfiniteList } from '../../../components/txs';
import { PageHeader } from '../../../components/page-header';
import { useExplorerPartyAssetsQuery } from './__generated__/Party-assets';
import type { ExplorerPartyAssetsAccountsFragment } from './__generated__/Party-assets';
import { useDocumentTitle } from '../../../hooks/use-document-title';
import { Icon, Intent, Notification, Splash } from '@vegaprotocol/ui-toolkit';
import { aggregatedAccountsDataProvider } from '@vegaprotocol/accounts';
import { PartyBlockStake } from './components/party-block-stake';
import { PartyBlockAccounts } from './components/party-block-accounts';
import { isValidPartyId } from './components/party-id-error';
import { useDataProvider } from '@vegaprotocol/data-provider';
import GovernanceAssetBalance from '../../../components/asset-balance/governance-asset-balance';
import { PartyAccounts } from './components/party-accounts';
const Party = () => {
const { party } = useParams<{ party: string }>();
useDocumentTitle(['Public keys', party || '-']);
const navigate = useNavigate();
const partyId = toNonHex(party ? party : '');
const { isMobile } = useScreenDimensions();
const visibleChars = useMemo(() => (isMobile ? 10 : 14), [isMobile]);
@@ -30,72 +28,71 @@ const Party = () => {
filters,
});
const variables = useMemo(() => ({ partyId }), [partyId]);
const {
data: AccountData,
loading: AccountLoading,
error: AccountError,
} = useDataProvider({
dataProvider: aggregatedAccountsDataProvider,
variables,
const partyRes = useExplorerPartyAssetsQuery({
// Don't cache data for this query, party information can move quite quickly
fetchPolicy: 'network-only',
variables: { partyId: partyId },
skip: !party,
});
if (!isValidPartyId(partyId)) {
return (
<div className="max-w-sm mx-auto">
<Notification
message={t('Invalid party ID')}
intent={Intent.Danger}
buttonProps={{
text: t('Go back'),
action: () => navigate(-1),
className: 'py-1',
size: 'sm',
}}
/>
</div>
);
}
const p = partyRes.data?.partiesConnection?.edges[0].node;
const header = p?.id ? (
<PageHeader
title={p.id}
copy
truncateStart={visibleChars}
truncateEnd={visibleChars}
/>
) : (
<Panel>
<p>No data found for public key {party}</p>
</Panel>
);
const staking = (
<section>
{p?.stakingSummary?.currentStakeAvailable ? (
<div className="mt-4 leading-3">
<strong className="font-semibold">{t('Staking Balance: ')}</strong>
<GovernanceAssetBalance
price={p.stakingSummary.currentStakeAvailable}
/>
</div>
) : null}
</section>
);
const accounts = getNodes<ExplorerPartyAssetsAccountsFragment>(
p?.accountsConnection
);
return (
<section>
<PageHeader
title={partyId}
copy
truncateStart={visibleChars}
truncateEnd={visibleChars}
/>
<h1
className="font-alpha calt uppercase font-xl mb-4 text-vega-dark-100 dark:text-vega-light-100"
data-testid="parties-header"
>
{t('Public key')}
</h1>
{partyRes.data ? (
<>
{header}
<SubHeading>{t('Asset data')}</SubHeading>
{accounts ? <PartyAccounts accounts={accounts} /> : null}
{staking}
<div className="grid md:grid-flow-col grid-flow-row md:space-x-4 grid-cols-1 md:grid-cols-2 w-full">
<PartyBlockAccounts
accountError={AccountError}
accountLoading={AccountLoading}
accountData={AccountData}
partyId={partyId}
/>
<PartyBlockStake
accountError={AccountError}
accountLoading={AccountLoading}
partyId={partyId}
/>
</div>
<SubHeading>{t('Transactions')}</SubHeading>
{!error && txsData ? (
<TxsInfiniteList
hasMoreTxs={hasMoreTxs}
areTxsLoading={loading}
txs={txsData}
loadMoreTxs={loadTxs}
error={error}
className="mb-28"
/>
) : (
<Splash>
<Icon name="error" className="mr-1" />
&nbsp;{t('Could not load transaction list for party')}
</Splash>
)}
<SubHeading>{t('Transactions')}</SubHeading>
<TxsInfiniteList
hasMoreTxs={hasMoreTxs}
areTxsLoading={loading}
txs={txsData}
loadMoreTxs={loadTxs}
error={error}
className="mb-28"
/>
</>
) : null}
</section>
);
};
@@ -1,7 +0,0 @@
import { Outlet } from 'react-router-dom';
const PartiesSubPage = () => {
return <Outlet />;
};
export default PartiesSubPage;
@@ -0,0 +1,35 @@
import React from 'react';
import { DATA_SOURCES } from '../../config';
import type { TendermintUnconfirmedTransactionsResponse } from '../txs/tendermint-unconfirmed-transactions-response.d';
import { TxList } from '../../components/txs';
import { RouteTitle } from '../../components/route-title';
import { t } from '@vegaprotocol/i18n';
import { useFetch } from '@vegaprotocol/react-helpers';
import { useDocumentTitle } from '../../hooks/use-document-title';
const PendingTxs = () => {
const {
state: { data: unconfirmedTransactions },
} = useFetch<TendermintUnconfirmedTransactionsResponse>(
`${DATA_SOURCES.tendermintUrl}/unconfirmed_txs`
);
useDocumentTitle(['Pending transactions']);
return (
<section>
<RouteTitle data-testid="unconfirmed-transactions-header">
{t('Unconfirmed transactions')}
</RouteTitle>
<br />
<div>{t(`Number: ${unconfirmedTransactions?.result?.n_txs || 0}`)}</div>
<br />
<div>
<br />
<TxList data={unconfirmedTransactions} />
</div>
</section>
);
};
export { PendingTxs };
+20 -38
View File
@@ -14,6 +14,7 @@ import { Block } from './blocks/id';
import { Blocks } from './blocks/home';
import { Tx } from './txs/id';
import { TxsList } from './txs/home';
import { PendingTxs } from './pending';
import flags from '../config/flags';
import { t } from '@vegaprotocol/i18n';
import { Routes } from './route-names';
@@ -28,7 +29,6 @@ import compact from 'lodash/compact';
import { AssetLink, MarketLink } from '../components/links';
import { truncateMiddle } from '@vegaprotocol/ui-toolkit';
import { remove0x } from '@vegaprotocol/utils';
import { PartyAccountsByAsset } from './parties/id/accounts';
export type Navigable = {
path: string;
@@ -75,43 +75,14 @@ const partiesRoutes: Route[] = flags.parties
},
{
path: ':party',
element: <Party />,
children: [
{
index: true,
element: <PartySingle />,
handle: {
breadcrumb: (params: Params<string>) => (
<Link to={linkTo(Routes.PARTIES, params.party)}>
{truncateMiddle(params.party as string)}
</Link>
),
},
},
{
path: 'assets',
element: <Party />,
handle: {
breadcrumb: (params: Params<string>) => (
<Link to={linkTo(Routes.PARTIES, params.party)}>
{truncateMiddle(params.party as string)}
</Link>
),
},
children: [
{
index: true,
element: <PartyAccountsByAsset />,
handle: {
breadcrumb: () => {
return t('Assets');
},
},
},
],
},
],
element: <PartySingle />,
handle: {
breadcrumb: (params: Params<string>) => (
<Link to={linkTo(Routes.PARTIES, params.party)}>
{truncateMiddle(params.party as string)}
</Link>
),
},
},
],
},
@@ -266,6 +237,17 @@ export const routerConfig: Route[] = [
breadcrumb: () => <Link to={Routes.TX}>{t('Transactions')}</Link>,
},
children: [
{
path: 'pending',
element: <PendingTxs />,
handle: {
breadcrumb: () => (
<Link to={linkTo(Routes.TX, 'pending')}>
{t('Pending transactions')}
</Link>
),
},
},
{
path: ':txHash',
element: <Tx />,
@@ -10,7 +10,7 @@ interface TxDetailsProps {
export const txDetailsTruncateLength = 30;
export const TxDetails = ({ txData, pubKey }: TxDetailsProps) => {
export const TxDetails = ({ txData, pubKey, className }: TxDetailsProps) => {
if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
@@ -28,7 +28,6 @@ import {
} from '@vegaprotocol/environment';
import classNames from 'classnames';
import { NodeStatus, NodeStatusMapping } from '@vegaprotocol/types';
import { PartyLink } from '../../components/links';
type RateProps = {
value: BigNumber | number | undefined;
@@ -192,9 +191,7 @@ export const ValidatorsPage = () => {
<KeyValueTable>
<KeyValueTableRow>
<div>{t('ID')}</div>
<div className="break-all text-xs font-mono">
{v.id}
</div>
<div className="break-all text-xs">{v.id}</div>
</KeyValueTableRow>
<KeyValueTableRow>
<div>{t('Status')}</div>
@@ -221,14 +218,12 @@ export const ValidatorsPage = () => {
</div>
</KeyValueTableRow>
<KeyValueTableRow>
<div>{t('Key')}</div>
<div className="break-all text-xs">
<PartyLink id={v.pubkey} />
</div>
<div>{t('Public key')}</div>
<div className="break-all text-xs">{v.pubkey}</div>
</KeyValueTableRow>
<KeyValueTableRow>
<div>{t('Ethereum address')}</div>
<div className="break-all text-xs font-mono">
<div className="break-all text-xs">
<EtherscanLink address={v.ethereumAddress} />{' '}
<CopyWithTooltip text={v.ethereumAddress}>
<button title={t('Copy address to clipboard')}>
@@ -239,9 +234,7 @@ export const ValidatorsPage = () => {
</KeyValueTableRow>
<KeyValueTableRow>
<div>{t('Tendermint public key')}</div>
<div className="break-all text-xs font-mono">
{v.tmPubkey}
</div>
<div className="break-all text-xs">{v.tmPubkey}</div>
</KeyValueTableRow>
<KeyValueTableRow>
-5
View File
@@ -32,11 +32,6 @@
line-height: calc(min(var(--ag-line-height, 26px), 26px) - 4px);
}
.vega-ag-grid .ag-row {
border-width: 1px 0;
border-bottom: 1px solid transparent;
}
/* Light variables */
.ag-theme-balham {
--ag-background-color: theme(colors.white);
+1 -1
View File
@@ -1,5 +1,5 @@
# App configuration variables
NX_VEGA_ENV=MAINNET
NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_URL=https://api.vega.xyz/query
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://etherscan.io
@@ -4,11 +4,12 @@ import {
navigation,
} from '../../support/common.functions';
import {
convertUnixTimestampToDateformat,
createRawProposal,
createTenDigitUnixTimeStampForSpecifiedDays,
enterUniqueFreeFormProposalBody,
generateFreeFormProposalTitle,
getDateFormatForSpecifiedDays,
getGovernanceProposalDateFormatForSpecifiedDays,
getProposalIdFromList,
getProposalInformationFromTable,
getSubmittedProposalFromProposalList,
@@ -22,7 +23,6 @@ import { ensureSpecifiedUnstakedTokensAreAssociated } from '../../../../governan
import { ethereumWalletConnect } from '../../../../governance-e2e/src/support/wallet-eth.functions';
import { vegaWalletSetSpecifiedApprovalAmount } from '../../../../governance-e2e/src/support/wallet-teardown.functions';
import type { testFreeformProposal } from '../../support/common-interfaces';
import { formatDateWithLocalTimezone } from '@vegaprotocol/utils';
const proposalVoteProgressForPercentage =
'[data-testid="vote-progress-indicator-percentage-for"]';
@@ -99,22 +99,20 @@ describe(
getSubmittedProposalFromProposalList(proposalTitle).within(() =>
cy.get(viewProposalButton).click()
);
cy.wrap(
formatDateWithLocalTimezone(new Date(proposalTimeStamp * 1000))
).then((closingDate) => {
getProposalInformationFromTable('Closes on')
.contains(closingDate)
.should('be.visible');
});
cy.wrap(
formatDateWithLocalTimezone(
new Date(createTenDigitUnixTimeStampForSpecifiedDays(0) * 1000)
)
).then((proposalDate) => {
getProposalInformationFromTable('Proposed on')
.contains(proposalDate)
.should('be.visible');
});
convertUnixTimestampToDateformat(proposalTimeStamp).then(
(closingDate) => {
getProposalInformationFromTable('Closes on')
.contains(closingDate)
.should('be.visible');
}
);
getGovernanceProposalDateFormatForSpecifiedDays(0).then(
(proposalDate) => {
getProposalInformationFromTable('Proposed on')
.contains(proposalDate)
.should('be.visible');
}
);
});
it('Newly created proposal details - shows default status set to fail', function () {
@@ -157,16 +155,18 @@ describe(
cy.getByTestId('vote-buttons').contains('against').should('be.visible');
cy.getByTestId('vote-buttons').contains('for').should('be.visible');
voteForProposal('for');
getDateFormatForSpecifiedDays(0).then((votedDate) => {
// 3001-VOTE-051
// 3001-VOTE-093
cy.contains('You voted:')
.siblings()
.contains('For')
.siblings()
.contains(votedDate)
.should('be.visible');
});
getGovernanceProposalDateFormatForSpecifiedDays(0, 'shortMonth').then(
(votedDate) => {
// 3001-VOTE-051
// 3001-VOTE-093
cy.contains('You voted:')
.siblings()
.contains('For')
.siblings()
.contains(votedDate)
.should('be.visible');
}
);
cy.get(proposalVoteProgressForPercentage) // 3001-VOTE-072
.contains('100.00%')
.and('be.visible');
@@ -29,8 +29,6 @@ const tableReceiverAddress = '[col-id="details.receiverAddress"]';
const tableWithdrawnTimeStamp = '[col-id="withdrawnTimestamp"]';
const tableWithdrawnStatus = '[col-id="status"]';
const tableCreatedTimeStamp = '[col-id="createdTimestamp"]';
const toastContent = 'toast-content';
const toastPanel = 'toast-panel';
const usdtName = 'USDC (local)';
const usdcEthAddress = '0x1b8a1B6CBE5c93609b46D1829Cc7f3Cb8eeE23a0';
const usdcSymbol = 'tUSDC';
@@ -151,6 +149,7 @@ context(
});
});
// Skipping because of bug #1857
it('Able to withdraw asset: -eth wallet not connected', function () {
const ethWalletAddress = Cypress.env('ethWalletPublicKey');
cy.reload();
@@ -188,26 +187,8 @@ context(
.should('have.attr', 'href')
.and('contain', 'https://sepolia.etherscan.io/address/');
cy.get(tableCreatedTimeStamp).should('not.be.empty');
});
ethereumWalletConnect();
cy.getByTestId(completeWithdrawalButton).click();
cy.getByTestId(toastContent)
.last()
.should('contain.text', 'Awaiting confirmation')
.within(() => {
cy.getByTestId('external-link').should('exist');
});
cy.getByTestId(toastContent)
.first()
.should('contain.text', 'The withdrawal has been approved.')
.within(() => {
cy.getByTestId(toastPanel).should('contain.text', '110.00', 'tUSDC');
});
cy.getByTestId(toastContent)
.last()
.should('contain.text', 'Transaction confirmed')
.within(() => {
cy.getByTestId('external-link').should('exist');
cy.getByTestId(completeWithdrawalButton).click();
// Unable to complete withdrawal in Capsule
});
});
@@ -237,10 +218,7 @@ context(
});
function waitForAssetsDisplayed(expectedAsset: string) {
cy.getByTestId('currency-title', txTimeout).should(
'contain.text',
expectedAsset
);
cy.getByTestId('currency-title').should('contain.text', expectedAsset);
}
}
);
@@ -27,7 +27,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
cy.getByTestId('app-announcement').should('not.exist');
});
it('should show open or enacted proposals without proposal summary', function () {
it('should show open or enacted proposals with proposal summary', function () {
cy.get('body').then(($body) => {
if (!$body.find('[data-testid="proposals-list-item"]').length) {
cy.createMarket();
@@ -43,6 +43,12 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
.invoke('text')
.should('not.be.empty');
cy.getByTestId('proposal-type').invoke('text').should('not.be.empty');
cy.getByTestId('proposal-description')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('proposal-details')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('proposal-status')
.invoke('text')
.should('not.be.empty');
@@ -101,15 +101,15 @@ context(
);
cy.getByTestId('protocol-upgrade-proposal-release-tag').should(
'have.text',
'Vega release tag: v1'
'Vega release tagv1'
);
cy.getByTestId('protocol-upgrade-proposal-block-height').should(
'have.text',
'Upgrade block height: 2015942'
'Upgrade block height2015942'
);
cy.getByTestId('protocol-upgrade-proposal-status').should(
'have.text',
'Approved '
'Approved'
);
});
});
@@ -1,4 +1,3 @@
import { format } from 'date-fns';
import { closeDialog, navigateTo, navigation } from './common.functions';
import { ensureSpecifiedUnstakedTokensAreAssociated } from './staking.functions';
@@ -16,6 +15,35 @@ const newProposalSubmitButton = '[data-testid="proposal-submit"]';
const epochTimeout = Cypress.env('epochTimeout');
const proposalTimeout = { timeout: 14000 };
export function convertUnixTimestampToDateformat(
unixTimestamp: number,
monthTextLength = 'longMonth'
) {
const dateSupplied = new Date(unixTimestamp * 1000);
const year = dateSupplied.getFullYear();
const months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];
const month = months[dateSupplied.getMonth()];
const shortMonth = months[dateSupplied.getMonth()].substring(0, 3),
date = dateSupplied.getDate();
if (monthTextLength === 'longMonth') {
return cy.wrap(`${date} ${month} ${year}`);
} else return cy.wrap(`${date} ${shortMonth} ${year}`);
}
export function createTenDigitUnixTimeStampForSpecifiedDays(
durationDays: number
) {
@@ -24,13 +52,6 @@ export function createTenDigitUnixTimeStampForSpecifiedDays(
return (timestamp = Math.floor(timestamp / 1000));
}
export function getDateFormatForSpecifiedDays(days: number) {
const date = new Date(
createTenDigitUnixTimeStampForSpecifiedDays(days) * 1000
);
return cy.wrap(format(date, 'dd MMM yyyy'));
}
export function enterRawProposalBody(timestamp: number) {
cy.fixture('/proposals/raw.json').then((rawProposal) => {
rawProposal.terms.closingTimestamp = timestamp;
@@ -83,6 +104,16 @@ export function getProposalIdFromList(proposalTitle: string) {
});
}
export function getGovernanceProposalDateFormatForSpecifiedDays(
days: number,
shortOrLong?: string
) {
return convertUnixTimestampToDateformat(
createTenDigitUnixTimeStampForSpecifiedDays(days),
shortOrLong
);
}
export function getProposalInformationFromTable(heading: string) {
return cy.get(proposalInformationTableRows).contains(heading).siblings();
}
+1 -1
View File
@@ -1,7 +1,7 @@
# App configuration variables
NX_VEGA_ENV=MAINNET
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_URL=https://api.vega.xyz/query
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://etherscan.io
-5
View File
@@ -1,5 +0,0 @@
function ReactMarkdown({ children }) {
return <div>{children}</div>;
}
export default ReactMarkdown;
@@ -23,7 +23,6 @@ export const ConnectToVega = () => {
openVegaWalletDialog();
}}
data-testid="connect-to-vega-wallet-btn"
variant="primary"
>
{t('connectVegaWallet')}
</Button>
@@ -23,7 +23,7 @@ export const Heading = ({
})}
>
<h1
className={classNames('font-alpha calt text-5xl break-words', {
className={classNames('font-alpha calt text-5xl', {
'mt-0': !marginTop,
'mb-0': !marginBottom,
})}
@@ -21,10 +21,10 @@ export const useGetUserBalances = (account: string | undefined) => {
token.allowance(account, config.staking_bridge_contract.address),
]);
const balance = toBigNum(b.toString(), decimals);
const walletBalance = toBigNum(w.toString(), decimals);
const lien = toBigNum(stats.lien.toString(), decimals);
const allowance = toBigNum(a.toString(), decimals);
const balance = toBigNum(b, decimals);
const walletBalance = toBigNum(w, decimals);
const lien = toBigNum(stats.lien, decimals);
const allowance = toBigNum(a, decimals);
return {
balanceFormatted: balance,
@@ -21,14 +21,8 @@ export function useRefreshAssociatedBalances() {
]);
updateBalances({
walletAssociatedBalance: toBigNum(
walletAssociatedBalance.toString(),
decimals
),
vestingAssociatedBalance: toBigNum(
vestingAssociatedBalance.toString(),
decimals
),
walletAssociatedBalance: toBigNum(walletAssociatedBalance, decimals),
vestingAssociatedBalance: toBigNum(vestingAssociatedBalance, decimals),
});
},
[staking, vesting, updateBalances, decimals]
@@ -31,18 +31,12 @@ export const useRefreshBalances = (address: string) => {
pubKey ? vesting.stake_balance(address, pubKey) : null,
]);
const balance = toBigNum(b.toString(), decimals);
const walletBalance = toBigNum(w.toString(), decimals);
const lien = toBigNum(stats.lien.toString(), decimals);
const allowance = toBigNum(a.toString(), decimals);
const walletAssociatedBalance = toBigNum(
walletStakeBalance ? walletStakeBalance.toString() : 0,
decimals
);
const vestingAssociatedBalance = toBigNum(
vestingStakeBalance ? vestingStakeBalance.toString() : 0,
decimals
);
const balance = toBigNum(b, decimals);
const walletBalance = toBigNum(w, decimals);
const lien = toBigNum(stats.lien, decimals);
const allowance = toBigNum(a, decimals);
const walletAssociatedBalance = toBigNum(walletStakeBalance, decimals);
const vestingAssociatedBalance = toBigNum(vestingStakeBalance, decimals);
updateBalances({
balanceFormatted: balance,
+3 -11
View File
@@ -201,7 +201,7 @@
"NewFreeform": "Freeform",
"tokenVotes": "Token votes",
"liquidityVotes": "Liquidity votes",
"castYourVote": "Cast your vote",
"yourVote": "Your vote",
"for": "For",
"against": "Against",
"majorityRequired": "Majority Required",
@@ -587,7 +587,7 @@
"tokensAgainstProposal": "Tokens against proposal",
"participationRequired": "Participation required",
"numberOfVotingParties": "Number of voting parties",
"totalTokensVotes": "Total tokens voted",
"totalTokensVotes": "Total yes tokens",
"totalTokenVotedPercentage": "Total tokens voted percentage",
"numberOfForVotes": "Number of votes for",
"numberOfAgainstVotes": "Number of votes against",
@@ -631,10 +631,7 @@
"New market": "New market",
"Market change": "Market change",
"Network parameter": "Network parameter",
"Change": "Change",
"Unknown proposal": "Unknown proposal",
"ERC20ContractAddress": "ERC20 contract address",
"MaxFaucetAmountMint": "Max faucet amount mint",
"Code": "Code",
"settled future": "settled future",
"Symbol": "Symbol",
@@ -680,7 +677,6 @@
"NewProposal": "New proposal",
"ProposalTypeQuestion": "What type of proposal would you like to make?",
"NetworkParameterProposal": "Update network parameter proposal",
"parameter": "parameter",
"NewMarketProposal": "New market proposal",
"UpdateMarketProposal": "Update market proposal",
"NewAssetProposal": "New asset proposal",
@@ -698,7 +694,6 @@
"UpdateMarket": "Update market",
"NewAsset": "New asset",
"UpdateAsset": "Update asset",
"AssetID": "Asset ID",
"Freeform": "Freeform",
"RawProposal": "Let me choose (raw proposal)",
"UseMin": "Use minimum",
@@ -742,7 +737,6 @@
"ProposalNotFound": "Proposal not found",
"ProposalNotFoundDetails": "The proposal you are looking for is not here, it may have been enacted before the last chain restore. You could check the Vega forums/discord instead for information about it.",
"FreeformProposal": "Freeform proposal",
"Id": "ID",
"unknownReason": "unknown reason",
"votingEnded": "Voting has ended.",
"STATUS": "STATUS",
@@ -797,7 +791,5 @@
"67% voting power required": "67% voting power required",
"Token": "Token",
"associateVegaNow": "Associate $VEGA now",
"disconnectedNotice": "You have been disconnected. Connect your ETH wallet to the {{correctNetwork}} network to use this app.",
"connectAVegaWalletToVote": "Connect a Vega wallet with $VEGA tokens to vote on a proposal.",
"findOutMoreAboutHowToVote": "Find out more about how to vote on Vega"
"disconnectedNotice": "You have been disconnected. Connect your ETH wallet to the {{correctNetwork}} network to use this app."
}
@@ -1,11 +1,7 @@
import { useTranslation } from 'react-i18next';
import { Icon } from '@vegaprotocol/ui-toolkit';
import * as Schema from '@vegaprotocol/types';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { ProposalState } from '@vegaprotocol/types';
import { ProposalInfoLabel } from '../proposal-info-label';
import type { ReactNode } from 'react';
import type { ProposalInfoLabelVariant } from '../proposal-info-label';
export const CurrentProposalState = ({
proposal,
@@ -13,63 +9,19 @@ export const CurrentProposalState = ({
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
}) => {
const { t } = useTranslation();
let proposalStatus: ReactNode;
let variant = 'tertiary' as ProposalInfoLabelVariant;
let className = 'text-white';
switch (proposal?.state) {
case ProposalState.STATE_ENACTED: {
proposalStatus = (
<>
<span className="mr-2">{t('voteState_Enacted')}</span>
<Icon name={'tick'} />
</>
);
break;
}
case ProposalState.STATE_PASSED: {
proposalStatus = (
<>
<span className="mr-2">{t('voteState_Passed')}</span>
<Icon name={'tick'} />
</>
);
break;
}
case ProposalState.STATE_WAITING_FOR_NODE_VOTE: {
proposalStatus = (
<>
<span className="mr-2">{t('voteState_WaitingForNodeVote')}</span>
<Icon name={'time'} />
</>
);
break;
}
case ProposalState.STATE_OPEN: {
variant = 'primary' as ProposalInfoLabelVariant;
proposalStatus = <>{t('voteState_Open')}</>;
break;
}
case ProposalState.STATE_DECLINED: {
proposalStatus = (
<>
<span className="mr-2">{t('voteState_Declined')}</span>
<Icon name={'cross'} />
</>
);
break;
}
case ProposalState.STATE_REJECTED: {
proposalStatus = (
<>
<span className="mr-2">{t('voteState_Rejected')}</span>
<Icon name={'warning-sign'} />
</>
);
break;
}
if (
proposal?.state === Schema.ProposalState.STATE_DECLINED ||
proposal?.state === Schema.ProposalState.STATE_FAILED ||
proposal?.state === Schema.ProposalState.STATE_REJECTED
) {
className = 'text-danger';
} else if (
proposal?.state === Schema.ProposalState.STATE_ENACTED ||
proposal?.state === Schema.ProposalState.STATE_PASSED
) {
className = 'text-white';
}
return (
<ProposalInfoLabel variant={variant}>{proposalStatus}</ProposalInfoLabel>
);
return <span className={className}>{t(`${proposal?.state}`)}</span>;
};
@@ -16,12 +16,17 @@ it('Renders all data for table', () => {
render(<ProposalChangeTable proposal={proposal} />);
expect(screen.getByText('ID')).toBeInTheDocument();
expect(screen.getByText(proposal?.id as string)).toBeInTheDocument();
expect(screen.getByText('State')).toBeInTheDocument();
expect(screen.getByText('Open')).toBeInTheDocument();
expect(screen.getByText('Closes on')).toBeInTheDocument();
expect(
screen.getByText(
formatDateWithLocalTimezone(new Date(proposal?.terms.closingDatetime))
)
).toBeInTheDocument();
expect(screen.getByText('Proposed enactment')).toBeInTheDocument();
expect(
screen.getByText(
@@ -30,12 +35,17 @@ it('Renders all data for table', () => {
)
)
).toBeInTheDocument();
expect(screen.getByText('Proposed by')).toBeInTheDocument();
expect(screen.getByText(proposal?.party.id ?? '')).toBeInTheDocument();
expect(screen.getByText('Proposed on')).toBeInTheDocument();
expect(
screen.getByText(formatDateWithLocalTimezone(new Date(proposal?.datetime)))
).toBeInTheDocument();
expect(screen.getByText('Type')).toBeInTheDocument();
expect(screen.getByText('Network parameter')).toBeInTheDocument();
});
it('Changes data based on if data is in future or past', () => {
@@ -43,12 +53,17 @@ it('Changes data based on if data is in future or past', () => {
state: ProposalState.STATE_ENACTED,
});
render(<ProposalChangeTable proposal={proposal} />);
expect(screen.getByText('State')).toBeInTheDocument();
expect(screen.getByText('Enacted')).toBeInTheDocument();
expect(screen.getByText('Closed on')).toBeInTheDocument();
expect(
screen.getByText(
formatDateWithLocalTimezone(new Date(proposal?.terms.closingDatetime))
)
).toBeInTheDocument();
expect(screen.getByText('Enacted on')).toBeInTheDocument();
expect(
screen.getByText(
@@ -89,6 +104,7 @@ it('Renders error details and rejection reason if present', () => {
render(<ProposalChangeTable proposal={proposal} />);
expect(screen.getByText('Error details')).toBeInTheDocument();
expect(screen.getByText(errorDetails)).toBeInTheDocument();
expect(screen.getByText('Rejection reason')).toBeInTheDocument();
expect(
screen.getByText(ProposalRejectionReason.PROPOSAL_ERROR_CLOSE_TIME_TOO_LATE)
@@ -6,6 +6,7 @@ import {
KeyValueTableRow,
RoundedWrapper,
} from '@vegaprotocol/ui-toolkit';
import { CurrentProposalState } from '../current-proposal-state';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
@@ -19,12 +20,16 @@ export const ProposalChangeTable = ({ proposal }: ProposalChangeTableProps) => {
const terms = proposal?.terms;
return (
<RoundedWrapper paddingBottom={true}>
<RoundedWrapper>
<KeyValueTable data-testid="proposal-change-table">
<KeyValueTableRow>
{t('id')}
{proposal?.id}
</KeyValueTableRow>
<KeyValueTableRow>
{t('state')}
<CurrentProposalState proposal={proposal} />
</KeyValueTableRow>
<KeyValueTableRow>
{isFuture(new Date(terms?.closingDatetime))
? t('closesOn')
@@ -45,24 +50,26 @@ export const ProposalChangeTable = ({ proposal }: ProposalChangeTableProps) => {
{t('proposedBy')}
<span style={{ wordBreak: 'break-word' }}>{proposal?.party.id}</span>
</KeyValueTableRow>
<KeyValueTableRow
noBorder={!proposal?.rejectionReason && !proposal?.errorDetails}
>
<KeyValueTableRow>
{t('proposedOn')}
{formatDateWithLocalTimezone(new Date(proposal?.datetime))}
</KeyValueTableRow>
{proposal?.rejectionReason ? (
<KeyValueTableRow noBorder={!proposal?.errorDetails}>
<KeyValueTableRow>
{t('rejectionReason')}
{proposal.rejectionReason}
</KeyValueTableRow>
) : null}
{proposal?.errorDetails ? (
<KeyValueTableRow noBorder={true}>
<KeyValueTableRow>
{t('errorDetails')}
{proposal.errorDetails}
</KeyValueTableRow>
) : null}
<KeyValueTableRow>
{t('type')}
{t(`${proposal?.terms.change.__typename}`)}
</KeyValueTableRow>
</KeyValueTable>
</RoundedWrapper>
);
@@ -1,68 +1,68 @@
import { render, screen } from '@testing-library/react';
import {
generateNoVotes,
generateProposal,
generateYesVotes,
} from '../../test-helpers/generate-proposals';
import { generateProposal } from '../../test-helpers/generate-proposals';
import { ProposalHeader } from './proposal-header';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { ProposalRejectionReason, ProposalState } from '@vegaprotocol/types';
import { lastWeek, nextWeek } from '../../test-helpers/mocks';
const renderComponent = (
proposal: ProposalQuery['proposal'],
isListItem = true
) => render(<ProposalHeader proposal={proposal} isListItem={isListItem} />);
const renderComponent = (proposal: ProposalQuery['proposal']) => (
<ProposalHeader proposal={proposal} />
);
describe('Proposal header', () => {
it('Renders New market proposal', () => {
renderComponent(
generateProposal({
rationale: {
title: 'New some market',
description: 'A new some market',
},
terms: {
change: {
__typename: 'NewMarket',
instrument: {
__typename: 'InstrumentConfiguration',
name: 'Some market',
code: 'FX:BTCUSD/DEC99',
futureProduct: {
__typename: 'FutureProduct',
settlementAsset: {
__typename: 'Asset',
symbol: 'tGBP',
render(
renderComponent(
generateProposal({
rationale: {
title: 'New some market',
description: 'A new some market',
},
terms: {
change: {
__typename: 'NewMarket',
instrument: {
__typename: 'InstrumentConfiguration',
name: 'Some market',
code: 'FX:BTCUSD/DEC99',
futureProduct: {
__typename: 'FutureProduct',
settlementAsset: {
__typename: 'Asset',
symbol: 'tGBP',
},
},
},
},
},
},
})
})
)
);
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'New some market'
);
expect(screen.getByTestId('proposal-type')).toHaveTextContent('New market');
expect(screen.getByTestId('proposal-description')).toHaveTextContent(
'A new some market'
);
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
'tGBP settled future.'
);
});
it('Renders Update market proposal', () => {
renderComponent(
generateProposal({
rationale: {
title: 'New market id',
},
terms: {
change: {
__typename: 'UpdateMarket',
marketId: 'MarketId',
render(
renderComponent(
generateProposal({
rationale: {
title: 'New market id',
},
},
})
terms: {
change: {
__typename: 'UpdateMarket',
marketId: 'MarketId',
},
},
})
)
);
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'New market id'
@@ -79,49 +79,53 @@ describe('Proposal header', () => {
});
it('Renders New asset proposal - ERC20', () => {
renderComponent(
generateProposal({
rationale: {
title: 'New asset: Fake currency',
description: '',
},
terms: {
change: {
__typename: 'NewAsset',
name: 'Fake currency',
symbol: 'FAKE',
source: {
__typename: 'ERC20',
contractAddress: '0x0',
render(
renderComponent(
generateProposal({
rationale: {
title: 'New asset: Fake currency',
description: '',
},
terms: {
change: {
__typename: 'NewAsset',
name: 'Fake currency',
symbol: 'FAKE',
source: {
__typename: 'ERC20',
contractAddress: '0x0',
},
},
},
},
})
})
)
);
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'New asset: Fake currency'
);
expect(screen.getByTestId('proposal-type')).toHaveTextContent('New asset');
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
'Symbol: FAKE. ERC20 contract address: 0x0'
'Symbol: FAKE. ERC20 0x0'
);
});
it('Renders New asset proposal - BuiltInAsset', () => {
renderComponent(
generateProposal({
terms: {
change: {
__typename: 'NewAsset',
name: 'Fake currency',
symbol: 'BIA',
source: {
__typename: 'BuiltinAsset',
maxFaucetAmountMint: '300',
render(
renderComponent(
generateProposal({
terms: {
change: {
__typename: 'NewAsset',
name: 'Fake currency',
symbol: 'BIA',
source: {
__typename: 'BuiltinAsset',
maxFaucetAmountMint: '300',
},
},
},
},
})
})
)
);
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'Unknown proposal'
@@ -133,22 +137,24 @@ describe('Proposal header', () => {
});
it('Renders Update network', () => {
renderComponent(
generateProposal({
rationale: {
title: 'Network parameter',
},
terms: {
change: {
__typename: 'UpdateNetworkParameter',
networkParameter: {
__typename: 'NetworkParameter',
key: 'Network key',
value: 'Network value',
render(
renderComponent(
generateProposal({
rationale: {
title: 'Network parameter',
},
terms: {
change: {
__typename: 'UpdateNetworkParameter',
networkParameter: {
__typename: 'NetworkParameter',
key: 'Network key',
value: 'Network value',
},
},
},
},
})
})
)
);
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'Network parameter'
@@ -161,211 +167,122 @@ describe('Proposal header', () => {
);
});
it('Renders Freeform proposal - short rationale', () => {
renderComponent(
generateProposal({
id: 'short',
rationale: {
title: '0x0',
},
terms: {
change: {
__typename: 'NewFreeform',
it('Renders Freeform network - short rationale', () => {
render(
renderComponent(
generateProposal({
id: 'short',
rationale: {
title: '0x0',
},
},
})
terms: {
change: {
__typename: 'NewFreeform',
},
},
})
)
);
expect(screen.getByTestId('proposal-title')).toHaveTextContent('0x0');
expect(screen.getByTestId('proposal-type')).toHaveTextContent('Freeform');
expect(
screen.queryByTestId('proposal-description')
).not.toBeInTheDocument();
expect(screen.getByTestId('proposal-details')).toHaveTextContent('short');
});
it('Renders Freeform proposal - long rationale (105 chars) - listing', () => {
renderComponent(
generateProposal({
id: 'long',
rationale: {
title: '0x0',
description:
'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean dolor.',
},
terms: {
change: {
__typename: 'NewFreeform',
it('Renders Freeform proposal - long rationale (105 chars)', () => {
render(
renderComponent(
generateProposal({
id: 'long',
rationale: {
title: '0x0',
description:
'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean dolor.',
},
},
})
terms: {
change: {
__typename: 'NewFreeform',
},
},
})
)
);
// For a rationale over 100 chars, we expect the header to be truncated at
// 100 chars with ellipsis and the details-one element to contain the rest.
expect(screen.getByTestId('proposal-title')).toHaveTextContent('0x0');
expect(screen.getByTestId('proposal-type')).toHaveTextContent('Freeform');
// Rationale in list view is not rendered
expect(
screen.queryByTestId('proposal-description')
).not.toBeInTheDocument();
});
it('Renders Freeform proposal - long rationale (105 chars) - details', () => {
renderComponent(
generateProposal({
id: 'long',
rationale: {
title: '0x0',
description:
'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean dolor.',
},
terms: {
change: {
__typename: 'NewFreeform',
},
},
}),
false
);
expect(screen.getByTestId('proposal-description')).toHaveTextContent(
/Class aptent/
'Class aptent taciti sociosqu ad litora torquent per conubia'
);
expect(screen.getByTestId('proposal-details')).toHaveTextContent('long');
});
// Remove once proposals have rationale and re-enable above tests
it('Renders Freeform proposal - id for title', () => {
renderComponent(
generateProposal({
id: 'freeform id',
rationale: {
title: 'freeform',
},
terms: {
change: {
__typename: 'NewFreeform',
render(
renderComponent(
generateProposal({
id: 'freeform id',
rationale: {
title: 'freeform',
},
},
})
terms: {
change: {
__typename: 'NewFreeform',
},
},
})
)
);
expect(screen.getByTestId('proposal-title')).toHaveTextContent('freeform');
expect(screen.getByTestId('proposal-type')).toHaveTextContent('Freeform');
expect(
screen.queryByTestId('proposal-description')
).not.toBeInTheDocument();
expect(screen.queryByTestId('proposal-details')).toHaveTextContent(
'freeform id'
);
});
it('Renders asset change proposal header', () => {
renderComponent(
generateProposal({
terms: {
change: {
__typename: 'UpdateAsset',
assetId: 'foo',
render(
renderComponent(
generateProposal({
terms: {
change: {
__typename: 'UpdateAsset',
assetId: 'foo',
},
},
},
})
})
)
);
expect(screen.getByTestId('proposal-type')).toHaveTextContent(
'Update asset'
);
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
'Update asset'
);
expect(screen.getByText('foo')).toBeInTheDocument();
});
it("Renders unknown proposal if it's a different proposal type", () => {
renderComponent(
generateProposal({
terms: {
change: {
// @ts-ignore unknown proposal
__typename: 'Foo',
render(
renderComponent(
generateProposal({
terms: {
change: {
// @ts-ignore unknown proposal
__typename: 'Foo',
},
},
},
})
})
)
);
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'Unknown proposal'
);
});
it('Renders proposal state: Enacted', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_ENACTED,
terms: {
enactmentDatetime: lastWeek.toString(),
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Enacted');
});
it('Renders proposal state: Passed', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_PASSED,
terms: {
closingDatetime: lastWeek.toString(),
enactmentDatetime: nextWeek.toString(),
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Passed');
});
it('Renders proposal state: Waiting for node vote', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_WAITING_FOR_NODE_VOTE,
terms: {
enactmentDatetime: nextWeek.toString(),
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent(
'Waiting for node vote'
);
});
it('Renders proposal state: Open', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_OPEN,
votes: {
__typename: 'ProposalVotes',
yes: generateYesVotes(3000, 1000000000000000000),
no: generateNoVotes(0),
},
terms: {
closingDatetime: nextWeek.toString(),
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
});
it('Renders proposal state: Declined - majority not reached', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_DECLINED,
terms: {
enactmentDatetime: lastWeek.toString(),
},
votes: {
no: generateNoVotes(1, 1000000000000000000),
yes: generateYesVotes(1, 1000000000000000000),
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Declined');
});
it('Renders proposal state: Rejected', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_REJECTED,
terms: {
enactmentDatetime: lastWeek.toString(),
},
rejectionReason:
ProposalRejectionReason.PROPOSAL_ERROR_INVALID_FUTURE_PRODUCT,
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Rejected');
});
});
@@ -1,27 +1,23 @@
import { useTranslation } from 'react-i18next';
import { Lozenge } from '@vegaprotocol/ui-toolkit';
import { Intent, Lozenge } from '@vegaprotocol/ui-toolkit';
import { shorten } from '@vegaprotocol/utils';
import { Heading, SubHeading } from '../../../../components/heading';
import type { ReactNode } from 'react';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import ReactMarkdown from 'react-markdown';
import { truncateMiddle } from '../../../../lib/truncate-middle';
import { CurrentProposalState } from '../current-proposal-state';
import { ProposalInfoLabel } from '../proposal-info-label';
export const ProposalHeader = ({
proposal,
isListItem = true,
useSubHeading = true,
}: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
isListItem?: boolean;
useSubHeading?: boolean;
}) => {
const { t } = useTranslation();
const change = proposal?.terms.change;
let details: ReactNode;
let proposalType = '';
let proposalType: ReactNode;
let title = proposal?.rationale.title.trim();
let description = proposal?.rationale.description.trim();
@@ -34,12 +30,10 @@ export const ProposalHeader = ({
switch (change?.__typename) {
case 'NewMarket': {
proposalType = 'NewMarket';
proposalType = t('NewMarket');
details = (
<>
<span>
{t('Code')}: {change.instrument.code}.
</span>{' '}
{t('Code')}: {change.instrument.code}.{' '}
{change.instrument.futureProduct?.settlementAsset.symbol ? (
<>
<span className="font-semibold">
@@ -55,61 +49,54 @@ export const ProposalHeader = ({
break;
}
case 'UpdateMarket': {
proposalType = 'UpdateMarket';
details = (
<>
<span>{t('Market change')}:</span>{' '}
<span>{truncateMiddle(change.marketId)}</span>
</>
);
proposalType = t('UpdateMarket');
details = `${t('Market change')}: ${change.marketId}`;
break;
}
case 'NewAsset': {
proposalType = 'NewAsset';
proposalType = t('NewAsset');
details = (
<>
<span>{t('Symbol')}:</span> <Lozenge>{change.symbol}.</Lozenge>{' '}
{change.source.__typename === 'ERC20' && (
<>
<span>{t('ERC20ContractAddress')}:</span>{' '}
<Lozenge>{change.source.contractAddress}</Lozenge>
</>
)}{' '}
{change.source.__typename === 'BuiltinAsset' && (
<>
<span>{t('MaxFaucetAmountMint')}:</span>{' '}
<Lozenge>{change.source.maxFaucetAmountMint}</Lozenge>
</>
)}
{t('Symbol')}: {change.symbol}.{' '}
<Lozenge>
{change.source.__typename === 'ERC20' &&
`ERC20 ${change.source.contractAddress}`}
{change.source.__typename === 'BuiltinAsset' &&
`${t('Max faucet amount mint')}: ${
change.source.maxFaucetAmountMint
}`}
</Lozenge>
</>
);
break;
}
case 'UpdateNetworkParameter': {
proposalType = 'NetworkParameter';
proposalType = t('NetworkParameter');
const parametersClasses = 'font-mono leading-none';
details = (
<>
<span>{t('Change')}:</span>{' '}
<Lozenge>{change.networkParameter.key}</Lozenge>{' '}
<span>{t('to')}</span>{' '}
<span className="whitespace-nowrap">
<Lozenge>{change.networkParameter.value}</Lozenge>
<span className={`${parametersClasses} mr-2`}>
{change.networkParameter.key}
</span>{' '}
{t('to')}{' '}
<span className={`${parametersClasses} ml-2`}>
{change.networkParameter.value}
</span>
</>
);
break;
}
case 'NewFreeform': {
proposalType = 'Freeform';
details = <span />;
proposalType = t('Freeform');
details = `${t('FreeformProposal')}: ${proposal?.id}`;
break;
}
case 'UpdateAsset': {
proposalType = 'UpdateAsset';
proposalType = t('UpdateAsset');
details = (
<>
<span>{t('AssetID')}:</span>{' '}
<Lozenge>{truncateMiddle(change.assetId)}</Lozenge>
`${t('Update asset')}`;
<Lozenge>{change.assetId}</Lozenge>
</>
);
break;
@@ -117,9 +104,9 @@ export const ProposalHeader = ({
}
return (
<>
<div className="text-sm mb-2">
<div data-testid="proposal-title">
{isListItem ? (
{useSubHeading ? (
<header>
<SubHeading title={titleContent || t('Unknown proposal')} />
</header>
@@ -129,39 +116,18 @@ export const ProposalHeader = ({
</div>
<div className="flex items-center gap-2 mb-4">
<div data-testid="proposal-type">
<ProposalInfoLabel variant="secondary">
{t(`${proposalType}`)}
</ProposalInfoLabel>
</div>
{proposalType && (
<div data-testid="proposal-type">
<Lozenge variant={Intent.None}>{proposalType}</Lozenge>
</div>
)}
<div data-testid="proposal-status">
<CurrentProposalState proposal={proposal} />
</div>
{description && (
<div data-testid="proposal-description">{description}</div>
)}
</div>
{details && (
<div data-testid="proposal-details" className="break-words my-10">
{details}
</div>
)}
{description && !isListItem && (
<div data-testid="proposal-description">
{/*<div className="uppercase mr-2">{t('ProposalDescription')}:</div>*/}
<SubHeading title={t('ProposalDescription')} />
<ReactMarkdown
className="react-markdown-container"
/* Prevents HTML embedded in the description from rendering */
skipHtml={true}
/* Stops users embedding images which could be used for tracking */
disallowedElements={['img']}
linkTarget="_blank"
>
{description}
</ReactMarkdown>
</div>
)}
</>
{details && <div data-testid="proposal-details">{details}</div>}
</div>
);
};
@@ -1 +0,0 @@
export * from './proposal-info-label';
@@ -1,35 +0,0 @@
import classNames from 'classnames';
import type { ReactNode } from 'react';
export type ProposalInfoLabelVariant =
| 'primary'
| 'secondary'
| 'tertiary'
| 'highlight';
const base = 'rounded-md px-2 py-1 font-alpha';
const primary = 'bg-vega-light-150 text-black';
const secondary = 'bg-vega-dark-200 text-white';
const tertiary = 'bg-vega-dark-150 text-white';
const highlight = 'bg-vega-yellow text-black';
const getClassname = (variant: ProposalInfoLabelVariant) => {
return classNames(base, {
[primary]: variant === 'primary',
[secondary]: variant === 'secondary',
[tertiary]: variant === 'tertiary',
[highlight]: variant === 'highlight',
});
};
interface ProposalInfoLabelProps {
children: ReactNode;
variant?: ProposalInfoLabelVariant;
}
export const ProposalInfoLabel = ({
children,
variant = 'primary',
}: ProposalInfoLabelProps) => {
return <div className={getClassname(variant)}>{children}</div>;
};
@@ -1,10 +1,8 @@
import { useTranslation } from 'react-i18next';
import { Icon, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { SubHeading } from '../../../../components/heading';
import type { PartialDeep } from 'type-fest';
import type * as Schema from '@vegaprotocol/types';
import { useState } from 'react';
import classnames from 'classnames';
export const ProposalTermsJson = ({
terms,
@@ -12,26 +10,10 @@ export const ProposalTermsJson = ({
terms: PartialDeep<Schema.ProposalTerms>;
}) => {
const { t } = useTranslation();
const [showDetails, setShowDetails] = useState(false);
const showDetailsIconClasses = classnames('mb-4', {
'rotate-180': showDetails,
});
return (
<section>
<button
onClick={() => setShowDetails(!showDetails)}
data-testid="proposal-terms-toggle"
>
<div className="flex items-center gap-3">
<SubHeading title={t('proposalTerms')} />
<div className={showDetailsIconClasses}>
<Icon name="chevron-down" size={8} />
</div>
</div>
</button>
{showDetails && <SyntaxHighlighter data={terms} />}
<SubHeading title={t('proposalTerms')} />
<SyntaxHighlighter data={terms} />
</section>
);
};
@@ -1,4 +1,4 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import { MockedProvider } from '@apollo/client/testing';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { ProposalVotesTable } from './proposal-votes-table';
@@ -46,7 +46,6 @@ describe('Proposal Votes Table', () => {
it('should show vote breakdown fields, excluding custom update market fields', () => {
renderComponent();
fireEvent.click(screen.getByTestId('vote-breakdown-toggle'));
expect(screen.getByText('Expected to pass')).toBeInTheDocument();
expect(screen.getByText('Token majority met')).toBeInTheDocument();
expect(screen.getByText('Token participation met')).toBeInTheDocument();
@@ -56,7 +55,7 @@ describe('Proposal Votes Table', () => {
expect(screen.getByText('Participation required')).toBeInTheDocument();
expect(screen.getByText('Majority Required')).toBeInTheDocument();
expect(screen.getByText('Number of voting parties')).toBeInTheDocument();
expect(screen.getByText('Total tokens voted')).toBeInTheDocument();
expect(screen.getByText('Total yes tokens')).toBeInTheDocument();
expect(
screen.getByText('Total tokens voted percentage')
).toBeInTheDocument();
@@ -71,14 +70,13 @@ describe('Proposal Votes Table', () => {
it('displays different breakdown fields for update market proposal', () => {
renderComponent(updateMarketProposal, updateMarketProposalType);
fireEvent.click(screen.getByTestId('vote-breakdown-toggle'));
expect(screen.getByText('Liquidity majority met')).toBeInTheDocument();
expect(screen.getByText('Liquidity participation met')).toBeInTheDocument();
expect(
screen.getByText('Liquidity shares for proposal')
).toBeInTheDocument();
expect(screen.queryByText('Number of voting parties')).toBeNull();
expect(screen.queryByText('Total tokens voted')).toBeNull();
expect(screen.queryByText('Total yes tokens')).toBeNull();
expect(screen.queryByText('Total tokens voted percentage')).toBeNull();
expect(screen.queryByText('Number of votes for')).toBeNull();
expect(screen.queryByText('Number of votes against')).toBeNull();
@@ -88,7 +86,6 @@ describe('Proposal Votes Table', () => {
it('displays if an update market proposal will pass by token vote', () => {
renderComponent(updateMarketProposal, updateMarketProposalType);
fireEvent.click(screen.getByTestId('vote-breakdown-toggle'));
expect(screen.getByText('👍 by token vote')).toBeInTheDocument();
});
@@ -113,7 +110,6 @@ describe('Proposal Votes Table', () => {
}),
updateMarketProposalType
);
fireEvent.click(screen.getByTestId('vote-breakdown-toggle'));
expect(screen.getByText('👍 by liquidity vote')).toBeInTheDocument();
});
});
@@ -1,12 +1,9 @@
import classnames from 'classnames';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
KeyValueTable,
KeyValueTableRow,
Thumbs,
RoundedWrapper,
Icon,
} from '@vegaprotocol/ui-toolkit';
import { formatNumber, formatNumberPercentage } from '@vegaprotocol/utils';
import { SubHeading } from '../../../../components/heading';
@@ -29,7 +26,6 @@ export const ProposalVotesTable = ({
const {
appState: { totalSupply },
} = useAppState();
const [showDetails, setShowDetails] = useState(false);
const {
willPassByTokenVote,
willPassByLPVote,
@@ -57,130 +53,113 @@ export const ProposalVotesTable = ({
? t('byTokenVote')
: t('byLiquidityVote');
const showDetailsIconClasses = classnames('mb-4', {
'rotate-180': showDetails,
});
return (
<>
<button
onClick={() => setShowDetails(!showDetails)}
data-testid="vote-breakdown-toggle"
>
<div className="flex items-center gap-3">
<SubHeading title={t('voteBreakdown')} />
<div className={showDetailsIconClasses}>
<Icon name="chevron-down" size={8} />
</div>
</div>
</button>
{showDetails && (
<RoundedWrapper marginBottomLarge={true} paddingBottom={true}>
<KeyValueTable
data-testid="proposal-votes-table"
numerical={true}
headingLevel={4}
>
<SubHeading title={t('voteBreakdown')} />
<RoundedWrapper>
<KeyValueTable
data-testid="proposal-votes-table"
numerical={true}
headingLevel={4}
>
<KeyValueTableRow>
{t('expectedToPass')}
{isUpdateMarket ? (
updateMarketWillPass ? (
<Thumbs up={true} text={updateMarketVotePassMethod} />
) : (
<Thumbs up={false} />
)
) : willPassByTokenVote ? (
<Thumbs up={true} />
) : (
<Thumbs up={false} />
)}
</KeyValueTableRow>
<KeyValueTableRow>
{t('majorityMet')}
{majorityMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
</KeyValueTableRow>
{isUpdateMarket && (
<KeyValueTableRow>
{t('expectedToPass')}
{isUpdateMarket ? (
updateMarketWillPass ? (
<Thumbs up={true} text={updateMarketVotePassMethod} />
) : (
<Thumbs up={false} />
)
) : willPassByTokenVote ? (
{t('majorityLPMet')}
{majorityLPMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
</KeyValueTableRow>
)}
<KeyValueTableRow>
{t('participationMet')}
{participationMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
</KeyValueTableRow>
{isUpdateMarket && (
<KeyValueTableRow>
{t('participationLPMet')}
{participationLPMet ? (
<Thumbs up={true} />
) : (
<Thumbs up={false} />
)}
</KeyValueTableRow>
)}
<KeyValueTableRow>
{t('tokenForProposal')}
{formatNumber(yesTokens, 2)}
</KeyValueTableRow>
{isUpdateMarket && (
<KeyValueTableRow>
{t('majorityMet')}
{majorityMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
{t('tokenLPForProposal')}
{formatNumber(yesEquityLikeShareWeight, 2)}
</KeyValueTableRow>
{isUpdateMarket && (
)}
<KeyValueTableRow>
{t('totalSupply')}
{formatNumber(totalSupply, 2)}
</KeyValueTableRow>
<KeyValueTableRow>
{t('tokensAgainstProposal')}
{formatNumber(noTokens, 2)}
</KeyValueTableRow>
<KeyValueTableRow>
{t('participationRequired')}
{formatNumberPercentage(requiredParticipation)}
</KeyValueTableRow>
<KeyValueTableRow>
{t('majorityRequired')}
{formatNumberPercentage(requiredMajorityPercentage)}
</KeyValueTableRow>
{!isUpdateMarket && (
<>
<KeyValueTableRow>
{t('majorityLPMet')}
{majorityLPMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
{t('numberOfVotingParties')}
{formatNumber(totalVotes, 0)}
</KeyValueTableRow>
)}
<KeyValueTableRow>
{t('participationMet')}
{participationMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
</KeyValueTableRow>
{isUpdateMarket && (
<KeyValueTableRow>
{t('participationLPMet')}
{participationLPMet ? (
<Thumbs up={true} />
) : (
<Thumbs up={false} />
)}
{t('totalTokensVotes')}
{formatNumber(totalTokensVoted, 2)}
</KeyValueTableRow>
)}
<KeyValueTableRow>
{t('tokenForProposal')}
{formatNumber(yesTokens, 2)}
</KeyValueTableRow>
{isUpdateMarket && (
<KeyValueTableRow>
{t('tokenLPForProposal')}
{formatNumber(yesEquityLikeShareWeight, 2)}
{t('totalTokenVotedPercentage')}
{formatNumberPercentage(totalTokensPercentage, 2)}
</KeyValueTableRow>
)}
<KeyValueTableRow>
{t('totalSupply')}
{formatNumber(totalSupply, 2)}
</KeyValueTableRow>
<KeyValueTableRow>
{t('tokensAgainstProposal')}
{formatNumber(noTokens, 2)}
</KeyValueTableRow>
<KeyValueTableRow>
{t('participationRequired')}
{formatNumberPercentage(requiredParticipation)}
</KeyValueTableRow>
<KeyValueTableRow>
{t('majorityRequired')}
{formatNumberPercentage(requiredMajorityPercentage)}
</KeyValueTableRow>
{!isUpdateMarket && (
<>
<KeyValueTableRow>
{t('numberOfVotingParties')}
{formatNumber(totalVotes, 0)}
</KeyValueTableRow>
<KeyValueTableRow>
{t('totalTokensVotes')}
{formatNumber(totalTokensVoted, 2)}
</KeyValueTableRow>
<KeyValueTableRow>
{t('totalTokenVotedPercentage')}
{formatNumberPercentage(totalTokensPercentage, 2)}
</KeyValueTableRow>
<KeyValueTableRow>
{t('numberOfForVotes')}
{formatNumber(yesVotes, 0)}
</KeyValueTableRow>
<KeyValueTableRow>
{t('numberOfAgainstVotes')}
{formatNumber(noVotes, 0)}
</KeyValueTableRow>
<KeyValueTableRow>
{t('yesPercentage')}
{formatNumberPercentage(yesPercentage, 2)}
</KeyValueTableRow>
<KeyValueTableRow noBorder={true}>
{t('noPercentage')}
{formatNumberPercentage(noPercentage, 2)}
</KeyValueTableRow>
</>
)}
</KeyValueTable>
</RoundedWrapper>
)}
<KeyValueTableRow>
{t('numberOfForVotes')}
{formatNumber(yesVotes, 0)}
</KeyValueTableRow>
<KeyValueTableRow>
{t('numberOfAgainstVotes')}
{formatNumber(noVotes, 0)}
</KeyValueTableRow>
<KeyValueTableRow>
{t('yesPercentage')}
{formatNumberPercentage(yesPercentage, 2)}
</KeyValueTableRow>
<KeyValueTableRow noBorder={true}>
{t('noPercentage')}
{formatNumberPercentage(noPercentage, 2)}
</KeyValueTableRow>
</>
)}
</KeyValueTable>
</RoundedWrapper>
</>
);
};
@@ -2,7 +2,7 @@ import {
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { AsyncRenderer, RoundedWrapper } from '@vegaprotocol/ui-toolkit';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { ProposalHeader } from '../proposal-detail-header/proposal-header';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
@@ -77,8 +77,8 @@ export const Proposal = ({ proposal }: ProposalProps) => {
return (
<AsyncRenderer data={params} loading={loading} error={error}>
<section data-testid="proposal">
<ProposalHeader proposal={proposal} isListItem={false} />
<div className="my-10">
<ProposalHeader proposal={proposal} useSubHeading={false} />
<div className="mb-10">
<ProposalChangeTable proposal={proposal} />
</div>
{proposal.terms.change.__typename === 'NewAsset' &&
@@ -91,18 +91,14 @@ export const Proposal = ({ proposal }: ProposalProps) => {
/>
) : null}
<div className="mb-12">
<RoundedWrapper paddingBottom={true}>
<VoteDetails
proposal={proposal}
proposalType={proposalType}
minVoterBalance={minVoterBalance}
spamProtectionMinTokens={
params?.spam_protection_voting_min_tokens
}
/>
</RoundedWrapper>
<VoteDetails
proposal={proposal}
proposalType={proposalType}
minVoterBalance={minVoterBalance}
spamProtectionMinTokens={params?.spam_protection_voting_min_tokens}
/>
</div>
<div className="mb-4">
<div className="mb-10">
<ProposalVotesTable proposal={proposal} proposalType={proposalType} />
</div>
<ProposalTermsJson terms={proposal.terms} />
@@ -97,6 +97,7 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Enacted');
expect(screen.getByTestId('vote-details')).toHaveTextContent(
format(lastWeek, DATE_FORMAT_DETAILED)
);
@@ -112,6 +113,7 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Passed');
expect(screen.getByTestId('vote-details')).toHaveTextContent(
`Enacts on ${format(nextWeek, DATE_FORMAT_DETAILED)}`
);
@@ -126,6 +128,9 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent(
'Waiting for node vote'
);
expect(screen.getByTestId('vote-details')).toHaveTextContent(
`Enacts on ${format(nextWeek, DATE_FORMAT_DETAILED)}`
);
@@ -216,6 +221,7 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
expect(screen.getByTestId('vote-details')).toHaveTextContent(
'5 minutes left to vote'
);
@@ -230,6 +236,7 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
expect(screen.getByTestId('vote-details')).toHaveTextContent(
'5 hours left to vote'
);
@@ -244,6 +251,7 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
expect(screen.getByTestId('vote-details')).toHaveTextContent(
'5 days left to vote'
);
@@ -260,7 +268,10 @@ describe('Proposals list item details', () => {
networkParamsQueryMock,
createUserVoteQueryMock(proposal?.id, VoteValue.VALUE_YES),
]);
expect(await screen.findByText('You voted For')).toBeInTheDocument();
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
expect(await screen.findByText('You voted')).toBeInTheDocument();
expect(await screen.findByText('For')).toBeInTheDocument();
});
it('Renders proposal state: Open - user voted against', async () => {
@@ -274,7 +285,9 @@ describe('Proposals list item details', () => {
networkParamsQueryMock,
createUserVoteQueryMock(proposal?.id, VoteValue.VALUE_NO),
]);
expect(await screen.findByText('You voted Against')).toBeInTheDocument();
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
expect(await screen.findByText('You voted')).toBeInTheDocument();
expect(await screen.findByText('Against')).toBeInTheDocument();
});
it('Renders proposal state: Open - participation not reached', () => {
@@ -290,6 +303,7 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
expect(screen.getByTestId('vote-status')).toHaveTextContent(
'Participation not reached'
);
@@ -308,6 +322,7 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
expect(screen.getByTestId('vote-status')).toHaveTextContent(
'Majority not reached'
);
@@ -327,6 +342,7 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
expect(screen.getByTestId('vote-status')).toHaveTextContent('Set to pass');
});
@@ -343,6 +359,7 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Declined');
expect(screen.getByTestId('vote-status')).toHaveTextContent(
'Participation not reached'
);
@@ -361,6 +378,7 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Declined');
expect(screen.getByTestId('vote-status')).toHaveTextContent(
'Majority not reached'
);
@@ -377,6 +395,7 @@ describe('Proposals list item details', () => {
ProposalRejectionReason.PROPOSAL_ERROR_INVALID_FUTURE_PRODUCT,
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Rejected');
expect(screen.getByTestId('vote-status')).toHaveTextContent(
'Invalid future product'
);
@@ -1,8 +1,11 @@
import { Link } from 'react-router-dom';
import { Button } from '@vegaprotocol/ui-toolkit';
import { Button, Icon } from '@vegaprotocol/ui-toolkit';
import { useVoteInformation } from '../../hooks';
import { useUserVote } from '../vote-details/use-user-vote';
import { StatusPass } from '../current-proposal-status/current-proposal-status';
import {
StatusPass,
StatusFail,
} from '../current-proposal-status/current-proposal-status';
import { format, formatDistanceToNowStrict } from 'date-fns';
import { useTranslation } from 'react-i18next';
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
@@ -19,7 +22,7 @@ const MajorityNotReached = () => {
const { t } = useTranslation();
return (
<>
{t('Majority')} {t('not reached')}
{t('Majority')} <StatusFail>{t('not reached')}</StatusFail>
</>
);
};
@@ -27,7 +30,7 @@ const ParticipationNotReached = () => {
const { t } = useTranslation();
return (
<>
{t('Participation')} {t('not reached')}
{t('Participation')} <StatusFail>{t('not reached')}</StatusFail>
</>
);
};
@@ -54,11 +57,17 @@ export const ProposalsListItemDetails = ({
? t('byTokenVote')
: t('byLPVote');
let proposalStatus: ReactNode;
let voteDetails: ReactNode;
let voteStatus: ReactNode;
switch (state) {
case ProposalState.STATE_ENACTED: {
proposalStatus = (
<>
{t('voteState_Enacted')} <Icon name={'tick'} />
</>
);
voteDetails = proposal?.terms.enactmentDatetime && (
<>
{format(
@@ -70,6 +79,11 @@ export const ProposalsListItemDetails = ({
break;
}
case ProposalState.STATE_PASSED: {
proposalStatus = (
<>
{t('voteState_Passed')} <Icon name={'tick'} />
</>
);
voteDetails = proposal?.terms.change.__typename !== 'NewFreeform' && (
<>
{t('toEnactOn')}{' '}
@@ -83,6 +97,11 @@ export const ProposalsListItemDetails = ({
break;
}
case ProposalState.STATE_WAITING_FOR_NODE_VOTE: {
proposalStatus = (
<>
{t('voteState_WaitingForNodeVote')} <Icon name={'time'} />
</>
);
voteDetails = proposal?.terms.change.__typename !== 'NewFreeform' && (
<>
{t('toEnactOn')}{' '}
@@ -96,14 +115,19 @@ export const ProposalsListItemDetails = ({
break;
}
case ProposalState.STATE_OPEN: {
proposalStatus = (
<>
{t('voteState_Open')} <Icon name={'hand'} />
</>
);
voteDetails = (voteState === 'Yes' && (
<>
{t('youVoted')} {t('voteState_Yes')}
{t('youVoted')} <StatusPass>{t('voteState_Yes')}</StatusPass>
</>
)) ||
(voteState === 'No' && (
<>
{t('youVoted')} {t('voteState_No')}
{t('youVoted')} <StatusFail>{t('voteState_No')}</StatusFail>
</>
)) || (
<>
@@ -124,29 +148,40 @@ export const ProposalsListItemDetails = ({
</>
) : (
<>
{t('Set to')} {t('fail')}
{t('Set to')} <StatusFail>{t('fail')}</StatusFail>
</>
))) ||
(!participationMet && <ParticipationNotReached />) ||
(!majorityMet && <MajorityNotReached />) ||
(willPassByTokenVote ? (
<>
{t('Set to')} {t('pass')}
{t('Set to')} <StatusPass>{t('pass')}</StatusPass>
</>
) : (
<>
{t('Set to')} {t('fail')}
{t('Set to')} <StatusFail>{t('fail')}</StatusFail>
</>
));
break;
}
case ProposalState.STATE_DECLINED: {
proposalStatus = (
<>
{t('voteState_Declined')} <Icon name={'cross'} />
</>
);
voteStatus =
(!participationMet && <ParticipationNotReached />) ||
(!majorityMet && <MajorityNotReached />);
break;
}
case ProposalState.STATE_REJECTED: {
proposalStatus = (
<>
<StatusFail>{t('voteState_Rejected')}</StatusFail>{' '}
<Icon name={'warning-sign'} />
</>
);
voteStatus = proposal?.rejectionReason && (
<>{t(ProposalRejectionReasonMapping[proposal.rejectionReason])}</>
);
@@ -155,10 +190,16 @@ export const ProposalsListItemDetails = ({
}
return (
<div className="grid grid-cols-[1fr_auto] mt-4 items-start gap-2 text-sm">
<div className="grid grid-cols-[1fr_auto] mt-2 items-start gap-2 text-sm">
<div
className="col-start-1 row-start-1 flex items-center gap-2 text-white"
data-testid="proposal-status"
>
{proposalStatus}
</div>
{voteDetails && (
<div
className="col-start-1 row-start-2 text-vega-light-300"
className="col-start-1 row-start-2 text-neutral-500"
data-testid="vote-details"
>
{voteDetails}
@@ -175,7 +216,9 @@ export const ProposalsListItemDetails = ({
{proposal?.id && (
<div className="col-start-2 row-start-2 justify-self-end">
<Link to={`${Routes.PROPOSALS}/${proposal.id}`}>
<Button data-testid="view-proposal-btn">{t('View')}</Button>
<Button data-testid="view-proposal-btn" size="sm">
{t('View')}
</Button>
</Link>
</div>
)}
@@ -106,7 +106,7 @@ export const ProposalsList = ({
{proposals.length > 0 && (
<ProposalsListFilter setFilterString={setFilterString} />
)}
<section className="-mx-4 p-4 mb-8 bg-vega-dark-100">
<section className="-mx-4 p-4 mb-8 bg-neutral-800">
<SubHeading title={t('openProposals')} />
{sortedProposals.open.length > 0 ||
sortedProtocolUpgradeProposals.open.length > 0 ? (
@@ -3,15 +3,15 @@ import { Link } from 'react-router-dom';
import {
Button,
Icon,
Intent,
Lozenge,
RoundedWrapper,
} from '@vegaprotocol/ui-toolkit';
import { stripFullStops } from '@vegaprotocol/utils';
import { ProtocolUpgradeProposalStatus } from '@vegaprotocol/types';
import { SubHeading } from '../../../../components/heading';
import { ProposalInfoLabel } from '../proposal-info-label';
import Routes from '../../../routes';
import type { ReactNode } from 'react';
import Routes from '../../../routes';
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
interface ProtocolProposalsListItemProps {
@@ -29,30 +29,30 @@ export const ProtocolUpgradeProposalsListItem = ({
switch (proposal.status) {
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED:
proposalStatusIcon = (
<span data-testid="protocol-upgrade-proposal-status-icon-rejected">
<div data-testid="protocol-upgrade-proposal-status-icon-rejected">
<Icon name={'cross'} />
</span>
</div>
);
break;
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING:
proposalStatusIcon = (
<span data-testid="protocol-upgrade-proposal-status-icon-pending">
<div data-testid="protocol-upgrade-proposal-status-icon-pending">
<Icon name={'time'} />
</span>
</div>
);
break;
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED:
proposalStatusIcon = (
<span data-testid="protocol-upgrade-proposal-status-icon-approved">
<div data-testid="protocol-upgrade-proposal-status-icon-approved">
<Icon name={'tick'} />
</span>
</div>
);
break;
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED:
proposalStatusIcon = (
<span data-testid="protocol-upgrade-proposal-status-icon-unspecified">
<div data-testid="protocol-upgrade-proposal-status-icon-unspecified">
<Icon name={'disable'} />
</span>
</div>
);
break;
}
@@ -71,28 +71,18 @@ export const ProtocolUpgradeProposalsListItem = ({
</div>
<div className="text-sm">
<div className="flex gap-2">
<div
data-testid="protocol-upgrade-proposal-type"
className="flex items-center gap-2 mb-4"
>
<ProposalInfoLabel variant="highlight">
{t('networkUpgrade')}
</ProposalInfoLabel>
</div>
<div data-testid="protocol-upgrade-proposal-status">
<ProposalInfoLabel>
{t(`${proposal.status}`)} {proposalStatusIcon}
</ProposalInfoLabel>
</div>
<div
data-testid="protocol-upgrade-proposal-type"
className="flex items-center gap-2 mb-4"
>
<Lozenge variant={Intent.Success}>{t('networkUpgrade')}</Lozenge>
</div>
<div
data-testid="protocol-upgrade-proposal-release-tag"
className="mb-2"
>
<span>{t('vegaReleaseTag')}:</span>{' '}
<span className="pr-2">{t('vegaReleaseTag')}</span>
<Lozenge>{proposal.vegaReleaseTag}</Lozenge>
</div>
@@ -100,18 +90,30 @@ export const ProtocolUpgradeProposalsListItem = ({
data-testid="protocol-upgrade-proposal-block-height"
className="mb-2"
>
<span>{t('upgradeBlockHeight')}:</span>{' '}
<span className="pr-2">{t('upgradeBlockHeight')}</span>
<Lozenge>{proposal.upgradeBlockHeight}</Lozenge>
</div>
<div className="grid grid-cols-1 mt-3">
<div className="justify-self-end">
<div className="grid grid-cols-[1fr_auto] mt-3 items-start gap-2">
<div className="col-start-1 row-start-1 text-white">
<div
data-testid="protocol-upgrade-proposal-status"
className="flex items-center gap-2"
>
<span>{t(`${proposal.status}`)}</span>
<span>{proposalStatusIcon}</span>
</div>
</div>
<div className="col-start-2 row-start-2 justify-self-end">
<Link
to={`${Routes.PROPOSALS}/protocol-upgrade/${stripFullStops(
proposal.vegaReleaseTag
)}`}
>
<Button data-testid="view-proposal-btn">{t('View')}</Button>
<Button data-testid="view-proposal-btn" size="sm">
{t('View')}
</Button>
</Link>
</div>
</div>
@@ -107,6 +107,10 @@ export const VoteButtons = ({
);
}
if (currentStakeAvailable.isLessThanOrEqualTo(0)) {
return t('noGovernanceTokens');
}
if (minVoterBalance && spamProtectionMinTokens) {
const formattedMinVoterBalance = new BigNumber(
addDecimal(minVoterBalance, 18)
@@ -159,30 +163,24 @@ export const VoteButtons = ({
return (
<>
{changeVote || (voteState === VoteState.NotCast && proposalVotable) ? (
<>
{currentStakeAvailable.isLessThanOrEqualTo(0) && (
<p data-testid="no-stake-available">{t('noGovernanceTokens')}</p>
)}
<div className="flex gap-4" data-testid="vote-buttons">
<div className="flex gap-4" data-testid="vote-buttons">
<div className="flex-1">
<Button
data-testid="vote-for"
onClick={() => submitVote(VoteValue.VALUE_YES)}
variant="primary"
disabled={currentStakeAvailable.isLessThanOrEqualTo(0)}
>
{t('voteFor')}
</Button>
</div>
<div className="flex-1">
<Button
data-testid="vote-against"
onClick={() => submitVote(VoteValue.VALUE_NO)}
variant="primary"
disabled={currentStakeAvailable.isLessThanOrEqualTo(0)}
>
{t('voteAgainst')}
</Button>
</div>
</>
</div>
) : (
(voteState === VoteState.Yes || voteState === VoteState.No) && (
<p data-testid="you-voted">
@@ -1,6 +1,5 @@
import { useTranslation } from 'react-i18next';
import { formatDistanceToNow } from 'date-fns';
import { RoundedWrapper, Icon } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { ProposalState } from '@vegaprotocol/types';
import { useVoteSubmit, VoteProgress } from '@vegaprotocol/proposals';
@@ -200,11 +199,10 @@ export const VoteDetails = ({
{proposalType === ProposalType.PROPOSAL_UPDATE_MARKET && (
<p>{t('votingThresholdInfo')}</p>
)}
<section className="mt-10">
<SubHeading title={t('castYourVote')} />
{pubKey ? (
proposal && (
{pubKey ? (
<section className="mt-10">
<SubHeading title={t('yourVote')} />
{proposal && (
<VoteButtonsContainer
voteState={voteState}
voteDatetime={voteDatetime}
@@ -216,19 +214,11 @@ export const VoteDetails = ({
submit={submit}
dialog={Dialog}
/>
)
) : (
<RoundedWrapper paddingBottom={true}>
<div className="mb-4">
<div className="flex items-center gap-2 mb-2">
<Icon name={'info-sign'} />
<div>{t('connectAVegaWalletToVote')}</div>
</div>
</div>
<ConnectToVega />
</RoundedWrapper>
)}
</section>
)}
</section>
) : (
<ConnectToVega />
)}
</section>
</>
);
@@ -34,9 +34,8 @@ export const useUserTrancheBalances = (address: string | undefined) => {
vesting.get_vested_for_tranche(address, tId),
]);
// Convert t and v EthersBigNumbers to regular BigNumbers
const total = toBigNum(t.toString(), decimals);
const vested = toBigNum(v.toString(), decimals);
const total = toBigNum(t, decimals);
const vested = toBigNum(v, decimals);
return {
id: tId,
@@ -54,7 +54,7 @@ export const WalletAssociate = ({
address,
ethereumConfig.staking_bridge_contract.address
);
const allowance = toBigNum(a.toString(), decimals);
const allowance = toBigNum(a, decimals);
setAllowance(allowance);
}
};
-3
View File
@@ -6,7 +6,6 @@ import '@testing-library/jest-dom';
import dev from './i18n/translations/dev.json';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import ResizeObserver from 'resize-observer-polyfill';
// Set up i18n instance so that components have the correct default
// en translations
@@ -23,5 +22,3 @@ i18n.use(initReactI18next).init({
ns: ['translations'],
defaultNS: 'translations',
});
global.ResizeObserver = ResizeObserver;
-41
View File
@@ -40,11 +40,6 @@
line-height: calc(min(var(--ag-line-height, 26px), 26px) - 4px);
}
.vega-ag-grid .ag-row {
border-width: 1px 0;
border-bottom: 1px solid transparent;
}
/* Dark variables */
.ag-theme-balham-dark {
--ag-background-color: theme(colors.black);
@@ -63,39 +58,3 @@
.validators-table .ag-theme-balham-dark *:hover {
cursor: pointer;
}
/* Styles required to (effectively) un-override the
* reset styles so that the Proposal description fields
* render as you'd expect them to.
*
* Notes:
* - image embeds are disabled, so no styles are required
* - strong may not be required
* - skipHTML is enabled, so no nested HTML will be rendered. Only
* . valid markdown
*/
.dark .react-markdown-container,
.dark .react-markdown-container li,
.dark .react-markdown-container p {
color: #fff;
}
.react-markdown-container strong {
font-weight: bold;
}
.react-markdown-container ol {
margin-left: 1em;
}
.react-markdown-container li {
margin-left: 1em;
padding-left: 1em;
}
.react-markdown-container ol li {
list-style: decimal;
}
.react-markdown-container ul li {
list-style: circle;
}
@@ -1,6 +1,6 @@
# App configuration variables
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_URL=https://api.vega.xyz/query
NX_VEGA_ENV=MAINNET
NX_VEGA_NETWORKS={\"MAINNET\":\"https://alpha.console.vega.xyz\",\"TESTNET\":\"https://console.fairground.wtf\"}
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
+1 -1
View File
@@ -1,5 +1,5 @@
# App configuration variables
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_URL=https://api.vega.xyz/query
NX_VEGA_NETWORKS='{"TESTNET":"https://multisig-signer.fairground.wtf","MAINNET":"https://multisig-signer.vega.xyz"}'
NX_VEGA_ENV=MAINNET
+43 -190
View File
@@ -48,8 +48,8 @@
"tranche_start": "2023-04-20T00:00:00.000Z",
"tranche_end": "2023-05-20T00:00:00.000Z",
"total_added": "19242.125",
"total_removed": "1523.8177488329475",
"locked_amount": "6891.9471601080244395475",
"total_removed": "1214.9959502191275",
"locked_amount": "9295.5944273726850283975",
"deposits": [
{
"amount": "188",
@@ -223,11 +223,6 @@
}
],
"withdrawals": [
{
"amount": "308.82179861382",
"user": "0xDd7a98557586ce21f770662319C2047C5a3bD605",
"tx": "0x2b3571c143ecebddf91fb62f402d516d51110edfe37b13200a6e5cf682dc5bb0"
},
{
"amount": "202.093666077975",
"user": "0x1447Efc62d5077d7AbB20492dF831Dd6c9Eb1756",
@@ -356,12 +351,6 @@
}
],
"withdrawals": [
{
"amount": "308.82179861382",
"user": "0xDd7a98557586ce21f770662319C2047C5a3bD605",
"tranche_id": 56,
"tx": "0x2b3571c143ecebddf91fb62f402d516d51110edfe37b13200a6e5cf682dc5bb0"
},
{
"amount": "195.89040769089",
"user": "0xDd7a98557586ce21f770662319C2047C5a3bD605",
@@ -370,8 +359,8 @@
}
],
"total_tokens": "914.25",
"withdrawn_tokens": "504.71220630471",
"remaining_tokens": "409.53779369529"
"withdrawn_tokens": "195.89040769089",
"remaining_tokens": "718.35959230911"
},
{
"address": "0x9573BDF7FfC5519912d293e4D1f750eab2E471E7",
@@ -876,15 +865,10 @@
"tranche_id": 54,
"tranche_start": "2023-04-06T00:00:00.000Z",
"tranche_end": "2023-05-06T00:00:00.000Z",
"total_added": "14610",
"total_removed": "6141.45090157707",
"locked_amount": "0",
"total_added": "14520",
"total_removed": "5079.76864115505",
"locked_amount": "238.4036111111108718",
"deposits": [
{
"amount": "90",
"user": "0x05659B08a8079E003eE37F26c29e52532728c034",
"tx": "0x83cb92910e0725b82b3459e025260c448f931224f7cb00a376351dbd75ae2733"
},
{
"amount": "111",
"user": "0x90Cf7B9958C3BbBCddAb52Ecc408535D4f3C4241",
@@ -1037,46 +1021,6 @@
}
],
"withdrawals": [
{
"amount": "191.85071991228",
"user": "0x6F32AA5A6198329c16e438512F992a0548C856f9",
"tx": "0x1438ad7d4c51ec6cb9b8f9d67b05583915099427035179baf996fd208fd76603"
},
{
"amount": "309",
"user": "0x21770cCAb229Bd1509609fFFa35bEB16b73A657c",
"tx": "0x264f4b3c5dc43cd5afaa5954b50ba7885ce3091b11ae51ba19b8a5851a5a4fc7"
},
{
"amount": "116.83154050974",
"user": "0x15024E62134A8BFFCce11f5ce58CeCDe853038D7",
"tx": "0x130c439ea764588c13cd825ae89767bea04064bd3d76b762b7148e5373857b9b"
},
{
"amount": "0",
"user": "0x15024E62134A8BFFCce11f5ce58CeCDe853038D7",
"tx": "0x9be84231ff156bc8b8de9a99250f03b8ebf9d59c252e3b778f10051489e5758a"
},
{
"amount": "111",
"user": "0x90Cf7B9958C3BbBCddAb52Ecc408535D4f3C4241",
"tx": "0xdfae5add3e12eed919c0481d9289aa33688884db68ca90332d1df31107e4fbc6"
},
{
"amount": "111",
"user": "0xAc1c2783d64c29CDB79608f5B66a37Fd1ea35dD6",
"tx": "0x7195dc5808870b0f00824c6593eda1e994976189cf3373cf84c5dae7a60d8b62"
},
{
"amount": "90",
"user": "0x05659B08a8079E003eE37F26c29e52532728c034",
"tx": "0x016970cfdaaab7a663a5c8b897b44c5d302e5cd110638de231073c3b5e23ddf8"
},
{
"amount": "132",
"user": "0x9E53F72210479BF46aE7ABF77c48B0d611b3c3dB",
"tx": "0x1dbcf713b48965a82aa2e17cb3e7db9a491668d859d408a39c8a74b0ea860b6b"
},
{
"amount": "106.53500000286",
"user": "0x6F32AA5A6198329c16e438512F992a0548C856f9",
@@ -1124,28 +1068,6 @@
}
],
"users": [
{
"address": "0x05659B08a8079E003eE37F26c29e52532728c034",
"deposits": [
{
"amount": "90",
"user": "0x05659B08a8079E003eE37F26c29e52532728c034",
"tranche_id": 54,
"tx": "0x83cb92910e0725b82b3459e025260c448f931224f7cb00a376351dbd75ae2733"
}
],
"withdrawals": [
{
"amount": "90",
"user": "0x05659B08a8079E003eE37F26c29e52532728c034",
"tranche_id": 54,
"tx": "0x016970cfdaaab7a663a5c8b897b44c5d302e5cd110638de231073c3b5e23ddf8"
}
],
"total_tokens": "90",
"withdrawn_tokens": "90",
"remaining_tokens": "0"
},
{
"address": "0x90Cf7B9958C3BbBCddAb52Ecc408535D4f3C4241",
"deposits": [
@@ -1156,17 +1078,10 @@
"tx": "0x1229e077f48b796678436bfa8143cea3ae71df58b589ac922be205c4482be235"
}
],
"withdrawals": [
{
"amount": "111",
"user": "0x90Cf7B9958C3BbBCddAb52Ecc408535D4f3C4241",
"tranche_id": 54,
"tx": "0xdfae5add3e12eed919c0481d9289aa33688884db68ca90332d1df31107e4fbc6"
}
],
"withdrawals": [],
"total_tokens": "111",
"withdrawn_tokens": "111",
"remaining_tokens": "0"
"withdrawn_tokens": "0",
"remaining_tokens": "111"
},
{
"address": "0x83D7eD53E7CB97b542F1F71f40561d51F8019C8B",
@@ -1411,12 +1326,6 @@
}
],
"withdrawals": [
{
"amount": "191.85071991228",
"user": "0x6F32AA5A6198329c16e438512F992a0548C856f9",
"tranche_id": 54,
"tx": "0x1438ad7d4c51ec6cb9b8f9d67b05583915099427035179baf996fd208fd76603"
},
{
"amount": "106.53500000286",
"user": "0x6F32AA5A6198329c16e438512F992a0548C856f9",
@@ -1455,8 +1364,8 @@
}
],
"total_tokens": "858",
"withdrawn_tokens": "858",
"remaining_tokens": "0"
"withdrawn_tokens": "666.14928008772",
"remaining_tokens": "191.85071991228"
},
{
"address": "0x0bBf7580e036eA5D69ABe679CC90117EeC2e3dc1",
@@ -1484,18 +1393,6 @@
}
],
"withdrawals": [
{
"amount": "116.83154050974",
"user": "0x15024E62134A8BFFCce11f5ce58CeCDe853038D7",
"tranche_id": 54,
"tx": "0x130c439ea764588c13cd825ae89767bea04064bd3d76b762b7148e5373857b9b"
},
{
"amount": "0",
"user": "0x15024E62134A8BFFCce11f5ce58CeCDe853038D7",
"tranche_id": 54,
"tx": "0x9be84231ff156bc8b8de9a99250f03b8ebf9d59c252e3b778f10051489e5758a"
},
{
"amount": "60.16845949026",
"user": "0x15024E62134A8BFFCce11f5ce58CeCDe853038D7",
@@ -1504,8 +1401,8 @@
}
],
"total_tokens": "177",
"withdrawn_tokens": "177",
"remaining_tokens": "0"
"withdrawn_tokens": "60.16845949026",
"remaining_tokens": "116.83154050974"
},
{
"address": "0xAc1c2783d64c29CDB79608f5B66a37Fd1ea35dD6",
@@ -1517,17 +1414,10 @@
"tx": "0x99aeaedef27b5fb693485f4e21d434e343b30fa100945a4ff65386de8801c87e"
}
],
"withdrawals": [
{
"amount": "111",
"user": "0xAc1c2783d64c29CDB79608f5B66a37Fd1ea35dD6",
"tranche_id": 54,
"tx": "0x7195dc5808870b0f00824c6593eda1e994976189cf3373cf84c5dae7a60d8b62"
}
],
"withdrawals": [],
"total_tokens": "111",
"withdrawn_tokens": "111",
"remaining_tokens": "0"
"withdrawn_tokens": "0",
"remaining_tokens": "111"
},
{
"address": "0x8D416A61bCccf4E6aF5598302BC4e41f97401652",
@@ -1554,17 +1444,10 @@
"tx": "0xcb0f255003872ac506798efa97744868e11ceab2f2f03d96da605d8674f783f6"
}
],
"withdrawals": [
{
"amount": "132",
"user": "0x9E53F72210479BF46aE7ABF77c48B0d611b3c3dB",
"tranche_id": 54,
"tx": "0x1dbcf713b48965a82aa2e17cb3e7db9a491668d859d408a39c8a74b0ea860b6b"
}
],
"withdrawals": [],
"total_tokens": "132",
"withdrawn_tokens": "132",
"remaining_tokens": "0"
"withdrawn_tokens": "0",
"remaining_tokens": "132"
},
{
"address": "0xC0f02AA8bA8b509D223C9De6108AFA1C03f03341",
@@ -1591,17 +1474,10 @@
"tx": "0xf6467d54c2de5c5d21fd3ac174ac6bf51b6fc675cf81dedcb143679f28caa46a"
}
],
"withdrawals": [
{
"amount": "309",
"user": "0x21770cCAb229Bd1509609fFFa35bEB16b73A657c",
"tranche_id": 54,
"tx": "0x264f4b3c5dc43cd5afaa5954b50ba7885ce3091b11ae51ba19b8a5851a5a4fc7"
}
],
"withdrawals": [],
"total_tokens": "309",
"withdrawn_tokens": "309",
"remaining_tokens": "0"
"withdrawn_tokens": "0",
"remaining_tokens": "309"
},
{
"address": "0x0629D3C608e1E8e9B88a1B00e1422e4B33276a98",
@@ -4861,7 +4737,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "86666.297",
"total_removed": "0",
"locked_amount": "49802.2753121154213651374",
"locked_amount": "50692.0833719598561288655",
"deposits": [
{
"amount": "86666.297",
@@ -4927,7 +4803,7 @@
"tranche_end": "2023-06-01T00:00:00.000Z",
"total_added": "2500",
"total_removed": "0",
"locked_amount": "312.4325905575906",
"locked_amount": "363.908920940171075",
"deposits": [
{
"amount": "2500",
@@ -4960,7 +4836,7 @@
"tranche_end": "2023-11-01T00:00:00.000Z",
"total_added": "15000.000000000000015",
"total_removed": "0",
"locked_amount": "14327.0455917874395143270455917874395",
"locked_amount": "14632.5464221014495146325464221014495",
"deposits": [
{
"amount": "1.5e-14",
@@ -5048,7 +4924,7 @@
"tranche_end": "2023-09-01T00:00:00.000Z",
"total_added": "17500",
"total_removed": "0",
"locked_amount": "10913.25608896940375",
"locked_amount": "11269.67372433574875",
"deposits": [
{
"amount": "12500",
@@ -5315,7 +5191,7 @@
"tranche_end": "2023-08-01T00:00:00.000Z",
"total_added": "37500",
"total_removed": "18077.0118744",
"locked_amount": "17350.5026089625535",
"locked_amount": "18126.91355893186125",
"deposits": [
{
"amount": "7500",
@@ -5734,7 +5610,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "129999.45",
"total_removed": "0",
"locked_amount": "49756.837002255673748325",
"locked_amount": "50645.8332242054391795015",
"deposits": [
{
"amount": "129999.45",
@@ -5767,7 +5643,7 @@
"tranche_end": "2024-04-01T00:00:00.000Z",
"total_added": "54144.7663",
"total_removed": "0",
"locked_amount": "48485.46842731635829207292",
"locked_amount": "49039.85711135509392504979",
"deposits": [
{
"amount": "54144.7663",
@@ -5800,7 +5676,7 @@
"tranche_end": "2023-09-03T00:00:00.000Z",
"total_added": "62600",
"total_removed": "0",
"locked_amount": "20022.5830035514954",
"locked_amount": "20665.30095129376038",
"deposits": [
{
"amount": "10000",
@@ -5993,7 +5869,7 @@
"tranche_end": "2023-09-17T00:00:00.000Z",
"total_added": "5000",
"total_removed": "0",
"locked_amount": "1791.028665651953",
"locked_amount": "1842.3639649923895",
"deposits": [
{
"amount": "5000",
@@ -7062,7 +6938,7 @@
"tranche_end": "2023-06-02T00:00:00.000Z",
"total_added": "1939928.38",
"total_removed": "1709370.7872515768348",
"locked_amount": "126202.1342632826843379468",
"locked_amount": "146119.4950805440611341152",
"deposits": [
{
"amount": "1852091.69",
@@ -40934,7 +40810,7 @@
"tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "3732368.4671",
"total_removed": "715655.108029600523393",
"locked_amount": "218430.066175628697078272948",
"locked_amount": "249036.115263241476836903328",
"deposits": [
{
"amount": "1998.95815",
@@ -42326,8 +42202,8 @@
"tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "15870102.715470999700000001",
"total_removed": "871680.07831804700259352",
"locked_amount": "6074226.5757489411253544394273819693295485",
"total_removed": "870712.39693462448509352",
"locked_amount": "6182753.66071744239145030977234379973803727",
"deposits": [
{
"amount": "16249.93",
@@ -42831,11 +42707,6 @@
}
],
"withdrawals": [
{
"amount": "967.6813834225175",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0x625e0c8fb8d5e2dd7fef0359bb396571f7d8aed05a601ddf0f6e6577edb26a13"
},
{
"amount": "10150.87581603206683",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
@@ -45004,12 +44875,6 @@
}
],
"withdrawals": [
{
"amount": "967.6813834225175",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 2,
"tx": "0x625e0c8fb8d5e2dd7fef0359bb396571f7d8aed05a601ddf0f6e6577edb26a13"
},
{
"amount": "913.910324501590625",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -46566,8 +46431,8 @@
}
],
"total_tokens": "259998.8875",
"withdrawn_tokens": "159590.522104386423875",
"remaining_tokens": "100408.365395613576125"
"withdrawn_tokens": "158622.840720963906375",
"remaining_tokens": "101376.046779036093625"
},
{
"address": "0x89051CAb67Bc7F8CC44F7e270c6EDaf1EC57676c",
@@ -59069,8 +58934,8 @@
"tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "472355.6199999996",
"total_removed": "44479.0535455583416",
"locked_amount": "34611.49258501772999518148756976",
"total_removed": "44110.3307271943416",
"locked_amount": "39461.19601458522069398354033484",
"deposits": [
{
"amount": "3000",
@@ -65689,11 +65554,6 @@
}
],
"withdrawals": [
{
"amount": "368.722818364",
"user": "0x0436E2E4EA467f9958403dc56635404EE1bb1E94",
"tx": "0x97f5b5ec5ac839a8d182b9267b5d052cb70f9a18b648874337e3d5360b21a5fb"
},
{
"amount": "182.662252664",
"user": "0xb1169C6daAc76bAcaf0D8f87641Fc38fbabe569F",
@@ -81903,17 +81763,10 @@
"tx": "0xb1425d9b0d5f10c5b06236d6f9d61ebddd21c4027e0f41deb5ec227a1753c715"
}
],
"withdrawals": [
{
"amount": "368.722818364",
"user": "0x0436E2E4EA467f9958403dc56635404EE1bb1E94",
"tranche_id": 5,
"tx": "0x97f5b5ec5ac839a8d182b9267b5d052cb70f9a18b648874337e3d5360b21a5fb"
}
],
"withdrawals": [],
"total_tokens": "400",
"withdrawn_tokens": "368.722818364",
"remaining_tokens": "31.277181636"
"withdrawn_tokens": "0",
"remaining_tokens": "400"
},
{
"address": "0x6672F8789C54b49C596E6e7Ca3A72726Fc4C400E",
+49 -48
View File
@@ -9,7 +9,6 @@ import {
import { isBefore, isAfter, addSeconds, subSeconds } from 'date-fns';
import { createOrder } from '../support/create-order';
import { connectEthereumWallet } from '../support/ethereum-wallet';
import { selectAsset } from '../support/helpers';
const orderSize = 'size';
const orderType = 'type';
@@ -23,13 +22,15 @@ const assetSelectField = 'select[name="asset"]';
const amountField = 'input[name="amount"]';
const txTimeout = Cypress.env('txTimeout');
const sepoliaUrl = Cypress.env('ETHERSCAN_URL');
const btcName = 0;
const vegaName = 4;
const btcName =
'BTC (local)5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c - tBTC';
const vegaName =
'Vegab4f2726571fbe8e33b442dc92ed2d7f0d810e21835b7371a7915a365f07ccd9b - VEGA';
const btcSymbol = 'tBTC';
const vegaSymbol = 'VEGA';
const usdcSymbol = 'fUSDC';
const toastContent = 'toast-content';
const openOrdersTab = 'Open';
const ordersTab = 'Orders';
const depositsTab = 'Deposits';
const collateralTab = 'Collateral';
const toastCloseBtn = 'toast-close';
@@ -49,7 +50,6 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
cy.get('@markets').then((markets) => {
cy.wrap(markets[0]).as('market');
});
cy.visit('/#/portfolio');
});
beforeEach(() => {
@@ -73,7 +73,7 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown');
selectAsset(btcName);
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
cy.getByTestId('approve-default').should(
'contain.text',
`Before you can make a deposit of your chosen asset, ${btcSymbol}, you need to approve its use in your Ethereum wallet`
@@ -81,7 +81,6 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
cy.getByTestId(approveSubmit).click();
cy.getByTestId('approve-pending').should('exist');
cy.getByTestId('approve-confirmed').should('exist');
cy.get(amountField).focus();
cy.get(amountField).clear().type('10');
cy.getByTestId(depositSubmit).click();
cy.getByTestId(toastContent, txTimeout).should(
@@ -116,6 +115,7 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
});
it('can key to key transfers', function () {
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId(collateralTab).click();
@@ -147,8 +147,8 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
cy.getByTestId('Withdrawals').click();
cy.getByTestId('withdraw-dialog-button').click();
selectAsset(0);
cy.get(amountField).focus();
connectEthereumWallet('Unknown');
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
cy.get(amountField).clear().type('1');
cy.getByTestId('submit-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
@@ -170,7 +170,7 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
'Error occurredcannot estimate gas'
'Error occurredprocessing response error'
);
cy.getByTestId(toastCloseBtn).click({ multiple: true });
cy.getByTestId(completeWithdrawalBtn).should(
@@ -180,7 +180,7 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
});
});
describe('capsule', { tags: '@slow', testIsolation: true }, () => {
describe('capsule', { tags: '@slow' }, () => {
before(() => {
cy.updateCapsuleMultiSig();
});
@@ -231,7 +231,7 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
cy.getByTestId(toastContent).should(
'contain.text',
`Order submittedYour transaction has been confirmed View in block explorerSubmit order - activeTEST.24h+${order.size} @ ${order.price}.00 ${usdcSymbol}`,
`ConfirmedYour transaction has been confirmed View in block explorerSubmit order - activeTEST.24h+${order.size} @ ${order.price}.00 ${usdcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click();
@@ -242,9 +242,9 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
.get(`[data-testid="bid-vol-${rawPrice}"]`)
.should('contain.text', order.size);
cy.getByTestId(openOrdersTab).click();
cy.getByTestId(ordersTab).click();
cy.getByTestId('edit', txTimeout).should('contain.text', 'Edit');
cy.getByTestId('tab-open-orders').within(() => {
cy.getByTestId('tab-orders').within(() => {
cy.get('.ag-center-cols-container')
.children()
.first()
@@ -283,9 +283,7 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
});
});
it('can edit order', function () {
const market = this.market;
cy.visit(`/#/markets/${market.id}`);
cy.getByTestId(openOrdersTab).click();
cy.getByTestId(ordersTab).click();
cy.getByTestId('edit').first().should('be.visible').click();
cy.getByTestId('dialog-title').should('contain.text', 'Edit order');
cy.get('#limitPrice').focus().clear().type(newPrice);
@@ -293,12 +291,12 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
cy.getByTestId(toastContent).should(
'contain.text',
`Order submittedYour transaction has been confirmed View in block explorerEdit order - activeTEST.24h+${size} @ ${price}.00 ${usdcSymbol}+${size} @ ${newPrice}.00 ${usdcSymbol}`,
`ConfirmedYour transaction has been confirmed View in block explorerEdit order - activeTEST.24h+${size} @ ${price}.00 ${usdcSymbol}+${size} @ ${newPrice}.00 ${usdcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click({ multiple: true });
cy.getByTestId(openOrdersTab).click();
cy.getByTestId(ordersTab).click();
cy.get('.ag-center-cols-container')
.children()
.first()
@@ -309,20 +307,18 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
checkIfDataAndTimeOfCreationAndUpdateIsEqual(orderUpdatedAt);
});
});
it('can cancel order', function () {
const market = this.market;
cy.visit(`/#/markets/${market.id}`);
cy.getByTestId(openOrdersTab).click();
// comment because of bug #2695
it.skip('can cancel order', function () {
cy.getByTestId(ordersTab).click();
cy.getByTestId('cancel').first().click();
cy.getByTestId(toastContent).should(
'contain.text',
`Order cancelledYour transaction has been confirmed View in block explorerCancel order - cancelledTEST.24h+${size} @ ${newPrice}.00 ${usdcSymbol}`,
`ConfirmedYour transaction has been confirmed View in block explorerCancel order - cancelledTEST.24h+${size} @ ${newPrice}.00 ${usdcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click({ multiple: true });
cy.getByTestId('Closed').click();
cy.getByTestId('tab-closed-orders')
cy.getByTestId('tab-orders')
.get('.ag-center-cols-container')
.children()
.first()
@@ -352,7 +348,7 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
cy.getByTestId('Withdrawals').click();
cy.getByTestId('withdraw-dialog-button').click();
connectEthereumWallet('Unknown');
selectAsset(btcName);
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
cy.get(amountField).clear().type('1');
cy.getByTestId('submit-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
@@ -375,15 +371,19 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
'contain.text',
'Transaction confirmed'
);
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
'Funds unlockedYour funds have been unlocked for withdrawalView in block explorerWithdraw 1.00 tBTCComplete withdrawal'
);
cy.getByTestId(toastCloseBtn).click({ multiple: true });
cy.get('.ag-center-cols-container')
.find('[col-id="status"]')
.eq(0, txTimeout)
.should('contain.text', 'Completed');
cy.wrap(null).then(() => {
try {
cy.getByTestId(completeWithdrawalBtn)
.eq(0, txTimeout)
.should('not.exist');
} catch (error) {
console.log(
'Assertion failed, but we are continuing because this is our wait to complete transaction'
);
}
});
cy.get('[col-id="txHash"]', txTimeout)
.should('have.length.above', 1)
@@ -404,20 +404,17 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
.should('have.attr', 'href')
.and('contain', `${sepoliaUrl}/tx/0x`);
});
cy.getByTestId('withdraw-dialog-button').click({ force: true });
cy.getByTestId('BALANCE_AVAILABLE_value').should('have.text', '7.999');
// comment because of bug #2819
// cy.getByTestId('withdraw-dialog-button').click();
// cy.getByTestId('BALANCE_AVAILABLE_value').should('have.text', '0')
});
it('approved amount is less than deposit', function () {
// 1001-DEPO-006
// 1001-DEPO-007
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown');
selectAsset(btcName);
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
cy.contains('Deposits of tBTC not approved').should('not.exist');
cy.contains('Use maximum').should('be.visible');
cy.get(amountField).clear().type('20000000');
@@ -425,7 +422,11 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
cy.getByTestId(depositSubmit).click();
cy.getByTestId('input-error-text').should(
'contain.text',
`You can't deposit more than you have in your Ethereum wallet`
'Amount is above approved amount'
);
cy.getByTestId('reapprove-default').should(
'contain.text',
'Approve again to deposit more than'
);
});
@@ -439,11 +440,11 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown');
selectAsset(vegaName);
cy.get(assetSelectField, txTimeout).select(vegaName, { force: true });
cy.getByTestId('approve-submit').click();
cy.getByTestId('approve-confirmed').should(
'contain.text',
'You approved deposits of up to VEGA'
'You can now make deposits in VEGA, up to a maximum of'
);
cy.get(amountField).clear().type('10000');
cy.getByTestId('deposit-submit').click();
@@ -477,9 +478,9 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
.and('contain', `${sepoliaUrl}/tx/0x`);
});
cy.getByTestId('Withdrawals').click(txTimeout);
cy.getByTestId('Withdrawals').click();
cy.getByTestId('withdraw-dialog-button').click();
selectAsset(1);
cy.get(assetSelectField, txTimeout).select(vegaName, { force: true });
cy.get(amountField).clear().type('10000');
cy.getByTestId('DELAY_TIME_value').should('have.text', '5 days');
cy.getByTestId('submit-withdrawal').click();
+179 -195
View File
@@ -14,226 +14,210 @@ const itemHeader = 'item-header';
const itemValue = 'item-value';
const marketListContent = 'popover-content';
describe(
'Console - market list - live env',
{ tags: '@live', testIsolation: true },
() => {
beforeEach(() => {
cy.visit('/');
describe('Console - market list - live env', { tags: '@live' }, () => {
beforeEach(() => {
cy.visit('/');
});
it('shows the market list page', () => {
cy.get('main', { timeout: 20000 });
// Overlay should be shown
cy.getByTestId(selectMarketOverlay).should('exist');
cy.contains('Select a market to get started').should('be.visible');
// I expect the market overlay table to contain at least one row
cy.getByTestId(selectMarketOverlay)
.get('table tr')
.should('have.length.greaterThan', 1);
// each market shown in overlay table contains content under the last price and change fields
cy.getByTestId(selectMarketOverlay)
.get('table tr')
.getByTestId('price')
.should('not.be.empty');
});
it('redirects to a default market', () => {
cy.getByTestId('dialog-close').click();
cy.getByTestId(selectMarketOverlay).should('not.exist');
// the choose market overlay is no longer showing
cy.contains('Select a market to get started').should('not.exist');
cy.contains('Loading...').should('not.exist');
cy.getByTestId('popover-trigger').should('not.be.empty');
});
});
describe('Console - market info - live env', { tags: '@live' }, () => {
before(() => {
cy.visit('/');
cy.contains('Loading market data...').should('not.exist');
cy.getByTestId('link').should('be.visible');
cy.getByTestId('dialog-close').click();
cy.getByTestId(marketInfoBtn).click();
});
const titles = ['Market data', 'Market specification', 'Market governance'];
const subtitles = [
'Current fees',
'Market price',
'Market volume',
'Insurance pool',
'Key details',
'Instrument',
'Settlement asset',
'Metadata',
'Risk model',
'Risk parameters',
'Risk factors',
'Price monitoring bounds 1',
'Liquidity monitoring parameters',
'Liquidity',
'Liquidity price range',
'Oracle',
'Proposal',
];
it('market info titles are displayed', () => {
cy.getByTestId('split-view-view')
.find('.text-lg')
.each((element, index) => {
cy.wrap(element).should('have.text', titles[index]);
});
});
it('market info subtitles are displayed', () => {
cy.getByTestId('popover-trigger').click();
cy.contains('Loading market data...').should('not.exist');
cy.contains('[data-testid="link"]', 'AAVEDAI.MF21').click();
cy.getByTestId(marketInfoBtn).click();
cy.getByTestId(marketInfoSubtitle).each((element, index) => {
cy.wrap(element).should('have.text', subtitles[index]);
});
});
it('shows the market list page', () => {
cy.get('main', { timeout: 20000 });
// Overlay should be shown
cy.getByTestId(selectMarketOverlay).should('exist');
cy.contains('Select a market to get started').should('be.visible');
// I expect the market overlay table to contain at least one row
cy.getByTestId(selectMarketOverlay)
.get('table tr')
.should('have.length.greaterThan', 1);
// each market shown in overlay table contains content under the last price and change fields
cy.getByTestId(selectMarketOverlay)
.get('table tr')
.getByTestId('price')
.should('not.be.empty');
it('renders correctly liquidity in trading tab', () => {
cy.getByTestId('Liquidity').click();
cy.contains('Loading').should('not.exist');
cy.contains('Something went wrong').should('not.exist');
cy.contains('Application error').should('not.exist');
cy.getByTestId('tab-liquidity').within(() => {
cy.get('[col-id="party.id"]').eq(1).should('not.be.empty');
});
});
});
it('redirects to a default market', () => {
cy.getByTestId('dialog-close').click();
cy.getByTestId(selectMarketOverlay).should('not.exist');
describe('Console - market summary - live env', { tags: '@live' }, () => {
before(() => {
cy.visit('/');
cy.getByTestId('dialog-close').click();
cy.getByTestId(marketSummaryBlock).should('be.visible');
});
// the choose market overlay is no longer showing
cy.contains('Select a market to get started').should('not.exist');
cy.contains('Loading...').should('not.exist');
cy.getByTestId('popover-trigger').should('not.be.empty');
});
}
);
it('must display market name', () => {
cy.getByTestId('popover-trigger').should('not.be.empty');
});
describe(
'Console - market info - live env',
{ tags: '@live', testIsolation: true },
() => {
before(() => {
cy.visit('/');
cy.contains('Loading market data...').should('not.exist');
cy.getByTestId('link').should('be.visible');
cy.getByTestId('dialog-close').click();
cy.getByTestId(marketInfoBtn).click();
});
const titles = ['Market data', 'Market specification', 'Market governance'];
const subtitles = [
'Current fees',
'Market price',
'Market volume',
'Insurance pool',
'Key details',
'Instrument',
'Settlement asset',
'Metadata',
'Risk model',
'Risk parameters',
'Risk factors',
'Price monitoring bounds 1',
'Liquidity monitoring parameters',
'Liquidity',
'Liquidity price range',
'Oracle',
'Proposal',
];
it('market info titles are displayed', () => {
cy.getByTestId('split-view-view')
.find('.text-lg')
.each((element, index) => {
cy.wrap(element).should('have.text', titles[index]);
});
});
it('market info subtitles are displayed', () => {
cy.getByTestId('popover-trigger').click();
cy.contains('Loading market data...').should('not.exist');
cy.contains('[data-testid="link"]', 'AAVEDAI.MF21').click();
cy.getByTestId(marketInfoBtn).click();
cy.getByTestId(marketInfoSubtitle).each((element, index) => {
cy.wrap(element).should('have.text', subtitles[index]);
it('must see market expiry', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketExpiry).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Expiry');
cy.getByTestId(itemValue).should('not.be.empty');
});
});
});
it('renders correctly liquidity in trading tab', () => {
cy.getByTestId('Liquidity').click();
cy.contains('Loading').should('not.exist');
cy.contains('Something went wrong').should('not.exist');
cy.contains('Application error').should('not.exist');
cy.getByTestId('tab-liquidity').within(() => {
cy.get('[col-id="party.id"]').eq(1).should('not.be.empty');
it('must see market price', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketPrice).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Price');
cy.getByTestId(itemValue).should('not.be.empty');
});
});
}
);
});
describe(
'Console - market summary - live env',
{ tags: '@live', testIsolation: true },
() => {
before(() => {
cy.visit('/');
cy.getByTestId('dialog-close').click();
cy.getByTestId(marketSummaryBlock).should('be.visible');
});
it('must display market name', () => {
cy.getByTestId('popover-trigger').should('not.be.empty');
});
it('must see market expiry', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketExpiry).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Expiry');
cy.getByTestId(itemValue).should('not.be.empty');
});
it('must see market change', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketChange).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Change (24h)');
cy.getByTestId(percentageValue).should('not.be.empty');
cy.getByTestId(priceChangeValue).should('not.be.empty');
});
});
});
it('must see market price', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketPrice).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Price');
cy.getByTestId(itemValue).should('not.be.empty');
});
it('must see market volume', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketVolume).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Volume (24h)');
cy.getByTestId(itemValue).should('not.be.empty');
});
});
});
it('must see market change', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketChange).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Change (24h)');
cy.getByTestId(percentageValue).should('not.be.empty');
cy.getByTestId(priceChangeValue).should('not.be.empty');
});
it('must see market mode', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketMode).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Trading mode');
cy.getByTestId(itemValue).should('not.be.empty');
});
});
});
it('must see market volume', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketVolume).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Volume (24h)');
cy.getByTestId(itemValue).should('not.be.empty');
});
it('must see market settlement', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketSettlement).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Settlement asset');
cy.getByTestId(itemValue).should('not.be.empty');
});
});
});
});
it('must see market mode', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketMode).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Trading mode');
cy.getByTestId(itemValue).should('not.be.empty');
});
describe('Console - markets table - live env', { tags: '@live' }, () => {
beforeEach(() => {
cy.visit('/');
});
it('renders markets correctly', () => {
cy.get('[data-testid^="market-link-"]').should('not.be.empty');
cy.getByTestId('price').invoke('text').should('not.be.empty');
cy.getByTestId('settlement-asset').should('not.be.empty');
cy.getByTestId('price-change-percentage').should('not.be.empty');
cy.getByTestId('price-change').should('not.be.empty');
cy.getByTestId('sparkline-svg').should('be.visible');
});
it('renders market list drop down', () => {
openMarketDropDown();
cy.getByTestId(marketListContent)
.find('[data-testid="price"]')
.invoke('text')
.should('not.be.empty');
cy.getByTestId(marketListContent)
.find('[data-testid="trading-mode-col"]')
.should('not.be.empty');
cy.getByTestId(marketListContent)
.find('[data-testid="taker-fee"]')
.should('contain.text', '%');
cy.getByTestId(marketListContent)
.find('[data-testid="market-volume"]')
.should('not.be.empty');
cy.getByTestId(marketListContent)
.find('[data-testid="market-name"]')
.should('not.be.empty');
});
it('Able to select market from dropdown', () => {
cy.getByTestId('popover-trigger')
.invoke('text')
.then((marketName) => {
openMarketDropDown();
cy.get('[data-testid^=market-link]').eq(1).click();
cy.getByTestId('popover-trigger').should('not.be.equal', marketName);
});
});
it('must see market settlement', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketSettlement).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Settlement asset');
cy.getByTestId(itemValue).should('not.be.empty');
});
});
});
}
);
describe(
'Console - markets table - live env',
{ tags: '@live', testIsolation: true },
() => {
beforeEach(() => {
cy.visit('/');
});
it('renders markets correctly', () => {
cy.get('[data-testid^="market-link-"]').should('not.be.empty');
cy.getByTestId('price').invoke('text').should('not.be.empty');
cy.getByTestId('settlement-asset').should('not.be.empty');
cy.getByTestId('price-change-percentage').should('not.be.empty');
cy.getByTestId('price-change').should('not.be.empty');
cy.getByTestId('sparkline-svg').should('be.visible');
});
it('renders market list drop down', () => {
openMarketDropDown();
cy.getByTestId(marketListContent)
.find('[data-testid="price"]')
.invoke('text')
.should('not.be.empty');
cy.getByTestId(marketListContent)
.find('[data-testid="trading-mode-col"]')
.should('not.be.empty');
cy.getByTestId(marketListContent)
.find('[data-testid="taker-fee"]')
.should('contain.text', '%');
cy.getByTestId(marketListContent)
.find('[data-testid="market-volume"]')
.should('not.be.empty');
cy.getByTestId(marketListContent)
.find('[data-testid="market-name"]')
.should('not.be.empty');
});
it('Able to select market from dropdown', () => {
cy.getByTestId('popover-trigger')
.invoke('text')
.then((marketName) => {
openMarketDropDown();
cy.get('[data-testid^=market-link]').eq(1).click();
cy.getByTestId('popover-trigger').should('not.be.equal', marketName);
});
});
}
);
});
});
function openMarketDropDown() {
cy.contains('Loading...').should('not.exist');
@@ -12,7 +12,7 @@ import {
formatNumberPercentage,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { updateGridData } from '@vegaprotocol/datagrid';
import { updateGridData } from '@vegaprotocol/react-helpers';
import {
NetworkParams,
useNetworkParams,
@@ -155,7 +155,6 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketOpenOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -167,7 +166,6 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketClosedOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -179,7 +177,6 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketRejectOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -190,7 +187,6 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketAllOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -199,7 +195,6 @@ const MarketBottomPanel = memo(
<TradingViews.fills.component
marketId={marketId}
onMarketClick={onMarketClick}
storeKey="marketFills"
/>
</VegaWalletContainer>
</Tab>
@@ -218,7 +213,6 @@ const MarketBottomPanel = memo(
<TradingViews.positions.component
onMarketClick={onMarketClick}
noBottomPlaceholder
storeKey="marketPositions"
/>
</VegaWalletContainer>
</Tab>
@@ -228,7 +222,6 @@ const MarketBottomPanel = memo(
pinnedAsset={pinnedAsset}
noBottomPlaceholder
hideButtons
storeKey="marketCollateral"
/>
</VegaWalletContainer>
</Tab>
@@ -241,10 +234,7 @@ const MarketBottomPanel = memo(
<Tabs storageKey="console-trade-grid-bottom">
<Tab id="positions" name={t('Positions')}>
<VegaWalletContainer>
<TradingViews.positions.component
onMarketClick={onMarketClick}
storeKey="marketPositions"
/>
<TradingViews.positions.component onMarketClick={onMarketClick} />
</VegaWalletContainer>
</Tab>
<Tab id="open-orders" name={t('Open')}>
@@ -255,7 +245,6 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketOpenOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -267,7 +256,6 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketClosedOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -279,7 +267,6 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketRejectedOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -290,7 +277,6 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketAllOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -299,7 +285,6 @@ const MarketBottomPanel = memo(
<TradingViews.fills.component
marketId={marketId}
onMarketClick={onMarketClick}
storeKey="marketFills"
/>
</VegaWalletContainer>
</Tab>
@@ -308,7 +293,6 @@ const MarketBottomPanel = memo(
<TradingViews.collateral.component
pinnedAsset={pinnedAsset}
hideButtons
storeKey="marketCollateral"
/>
</VegaWalletContainer>
</Tab>
+1 -2
View File
@@ -264,7 +264,6 @@ const ClosedMarketsDataGrid = ({ rowData }: { rowData: Row[] }) => {
{
headerName: t('Market ID'),
field: 'id',
flex: 1,
},
];
return cols;
@@ -277,10 +276,10 @@ const ClosedMarketsDataGrid = ({ rowData }: { rowData: Row[] }) => {
columnDefs={colDefs}
getRowId={({ data }) => data.id}
defaultColDef={{
flex: 1,
resizable: true,
}}
overlayNoRowsTemplate="No data"
storeKey="closedMarkets"
/>
);
};
@@ -2,7 +2,7 @@ import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import { useDepositDialog, DepositsTable } from '@vegaprotocol/deposits';
import { depositsProvider } from '@vegaprotocol/deposits';
import { t } from '@vegaprotocol/i18n';
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
import { useBottomPlaceholder } from '@vegaprotocol/react-helpers';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useRef } from 'react';
@@ -53,7 +53,6 @@ export const Portfolio = () => {
<PositionsContainer
onMarketClick={onMarketClick}
noBottomPlaceholder
storeKey="portfolioPositions"
/>
</VegaWalletContainer>
</Tab>
@@ -62,16 +61,12 @@ export const Portfolio = () => {
<OrderListContainer
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
storeKey="portfolioOrders"
/>
</VegaWalletContainer>
</Tab>
<Tab id="fills" name={t('Fills')}>
<VegaWalletContainer>
<FillsContainer
onMarketClick={onMarketClick}
storeKey="portfolioFills"
/>
<FillsContainer onMarketClick={onMarketClick} />
</VegaWalletContainer>
</Tab>
<Tab id="ledger-entries" name={t('Ledger entries')}>
@@ -91,7 +86,7 @@ export const Portfolio = () => {
<Tabs storageKey="console-portfolio-bottom">
<Tab id="collateral" name={t('Collateral')}>
<VegaWalletContainer>
<AccountsContainer storeKey="portfolioCollateral" />
<AccountsContainer />
</VegaWalletContainer>
</Tab>
<Tab id="deposits" name={t('Deposits')}>
@@ -13,12 +13,10 @@ export const AccountsContainer = ({
pinnedAsset,
hideButtons,
noBottomPlaceholder,
storeKey,
}: {
pinnedAsset?: PinnedAsset;
hideButtons?: boolean;
noBottomPlaceholder?: boolean;
storeKey?: string;
}) => {
const { pubKey, isReadOnly } = useVegaWallet();
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
@@ -51,7 +49,6 @@ export const AccountsContainer = ({
isReadOnly={isReadOnly}
pinnedAsset={pinnedAsset}
noBottomPlaceholder={noBottomPlaceholder}
storeKey={storeKey}
/>
{!isReadOnly && !hideButtons && (
<div className="flex gap-2 justify-end p-2 px-[11px] absolute lg:fixed bottom-0 right-3 dark:bg-black/75 bg-white/75 rounded">
-11
View File
@@ -93,11 +93,6 @@ html [data-theme='light'] {
line-height: calc(min(var(--ag-line-height, 26px), 26px) - 4px);
}
.vega-ag-grid .ag-row {
border-width: 1px 0;
border-bottom: 1px solid transparent;
}
/* Light variables */
.ag-theme-balham {
--ag-background-color: theme(colors.white);
@@ -121,9 +116,3 @@ html [data-theme='light'] {
--ag-row-hover-color: theme(colors.neutral[800]);
--ag-font-size: 12px;
}
.ag-theme-balham-dark .ag-row.no-hover,
.ag-theme-balham-dark .ag-row.no-hover:hover,
.ag-theme-balham .ag-row.no-hover,
.ag-theme-balham .ag-row.no-hover:hover {
background: var(--ag-background-color);
}
+1 -4
View File
@@ -1,6 +1,6 @@
import { useRef, useMemo, memo, useCallback } from 'react';
import { t } from '@vegaprotocol/i18n';
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
import { useBottomPlaceholder } from '@vegaprotocol/react-helpers';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
@@ -17,7 +17,6 @@ interface AccountManagerProps {
isReadOnly: boolean;
pinnedAsset?: PinnedAsset;
noBottomPlaceholder?: boolean;
storeKey?: string;
}
export const AccountManager = ({
@@ -28,7 +27,6 @@ export const AccountManager = ({
isReadOnly,
pinnedAsset,
noBottomPlaceholder,
storeKey,
}: AccountManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const variables = useMemo(() => ({ partyId }), [partyId]);
@@ -61,7 +59,6 @@ export const AccountManager = ({
suppressLoadingOverlay
suppressNoRowsOverlay
pinnedAsset={pinnedAsset}
storeKey={storeKey}
{...bottomPlaceholderProps}
/>
<div className="pointer-events-none absolute inset-0">
+2 -2
View File
@@ -100,7 +100,6 @@ export interface AccountTableProps extends AgGridReactProps {
onClickDeposit?: (assetId: string) => void;
isReadOnly: boolean;
pinnedAsset?: PinnedAsset;
storeKey?: string;
}
export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
@@ -156,6 +155,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
(data) => data.asset.id !== pinnedAssetId
)}
defaultColDef={{
flex: 1,
resizable: true,
tooltipComponent: TooltipCellComponent,
sortable: true,
@@ -187,6 +187,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
</ButtonLink>
);
}}
maxWidth={300}
/>
<AgGridColumn
headerName={t('Used')}
@@ -361,7 +362,6 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
);
}
}}
flex={1}
/>
}
</AgGrid>
@@ -9,8 +9,9 @@ declare global {
}
export function addGetNetworkParameters() {
// @ts-ignore - ignoring Cypress type error which gets resolved when Cypress uses the command
Cypress.Commands.add('get_network_parameters', () => {
const query = `
const mutation = `
{
networkParametersConnection {
edges {
@@ -25,17 +26,17 @@ export function addGetNetworkParameters() {
method: 'POST',
url: `http://localhost:3008/graphql`,
body: {
query,
query: mutation,
},
headers: { 'content-type': 'application/json' },
})
.its('body.data.networkParametersConnection.edges')
.then(function (response) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const object = response.reduce(function (obj: any, edge: any) {
const { value, key } = edge.node;
obj[key] = value;
return obj;
// @ts-ignore - ignoring Cypress type error which gets resolved when Cypress uses the command
const object = response.reduce(function (r, e) {
const { value, key } = e.node;
r[key] = value;
return r;
}, {});
return cy.wrap(object);
});
-5
View File
@@ -1,5 +1,4 @@
export * from './lib/ag-grid/ag-grid-lazy';
export * from './lib/ag-grid/use-column-sizes';
export * from './lib/cells/cumulative-vol-cell';
export * from './lib/cells/flash-cell';
@@ -11,7 +10,6 @@ export * from './lib/cells/vol-cell';
export * from './lib/cells/centered-grid-cell';
export * from './lib/cells/market-name-cell';
export * from './lib/cells/order-type-cell';
export * from './lib/cells/size';
export * from './lib/filters/date-range-filter';
export * from './lib/filters/set-filter';
@@ -20,6 +18,3 @@ export * from './lib/cell-class-rules';
export * from './lib/type-helpers';
export * from './lib/cells/grid-progress-bar';
export * from './lib/ag-grid-update';
export * from './lib/use-bottom-placeholder';
@@ -1,30 +1,22 @@
import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
import { AgGridReact } from 'ag-grid-react';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { useColumnSizes } from './use-column-sizes';
import classNames from 'classnames';
export const AgGridThemed = ({
style,
gridRef,
storeKey,
...props
}: (AgGridReactProps | AgReactUiProps) & {
style?: React.CSSProperties;
gridRef?: React.ForwardedRef<AgGridReact>;
storeKey?: string;
}) => {
const commonColumnCallbacks = useColumnSizes({
storeKey,
props,
});
const { theme } = useThemeSwitcher();
const defaultProps = {
rowHeight: 22,
headerHeight: 22,
enableCellTextSelection: true,
};
const wrapperClasses = classNames('vega-ag-grid', {
'ag-theme-balham': theme === 'light',
'ag-theme-balham-dark': theme === 'dark',
@@ -32,12 +24,7 @@ export const AgGridThemed = ({
return (
<div className={wrapperClasses} style={style}>
<AgGridReact
{...defaultProps}
{...props}
{...commonColumnCallbacks}
ref={gridRef}
/>
<AgGridReact {...defaultProps} {...props} ref={gridRef} />
</div>
);
};
@@ -4,7 +4,6 @@ import type { AgGridReactProps, AgGridReact } from 'ag-grid-react';
type Props = AgGridReactProps & {
style?: React.CSSProperties;
gridRef?: React.Ref<AgGridReact>;
storeKey?: string;
};
export const AgGridLazyInternal = lazy(() =>
@@ -1,117 +0,0 @@
import type {
Column,
ColumnResizedEvent,
GridSizeChangedEvent,
GridReadyEvent,
} from 'ag-grid-community';
import { renderHook, act, waitFor } from '@testing-library/react';
import { useColumnSizes } from './use-column-sizes';
const mockApis = {
api: {
sizeColumnsToFit: jest.fn(),
},
columnApi: {
setColumnWidths: jest.fn(),
},
};
const mockValueSetter = jest.fn();
const mockStore = {
sizes: { testid: { col1: 100 } },
valueSetter: mockValueSetter,
};
jest.mock('zustand', () => ({
...jest.requireActual('zustand'),
create: () =>
jest.fn(() =>
jest.fn().mockImplementation((creator) => {
return creator(mockStore);
})
),
}));
describe('UseColumnSizes hook', () => {
const storeKey = 'testid';
beforeEach(() => {
jest.clearAllMocks();
});
it('should return proper methods', () => {
const { result } = renderHook(() =>
useColumnSizes({ storeKey, props: {} })
);
expect(Object.keys(result.current)).toHaveLength(3);
expect(result.current).toStrictEqual({
onColumnResized: expect.any(Function),
onGridReady: expect.any(Function),
onGridSizeChanged: expect.any(Function),
});
});
it('onGridSizeChanged should call setSize', async () => {
const { result } = renderHook(() =>
useColumnSizes({ storeKey, props: {} })
);
await act(() => {
result.current.onGridSizeChanged?.({
clientWidth: 1000,
...mockApis,
} as GridSizeChangedEvent);
});
await waitFor(() => {
expect(mockApis.columnApi.setColumnWidths).toHaveBeenCalledWith([
{ key: 'col1', newWidth: 100 },
]);
});
});
it('onColumnResized should fill up store', async () => {
const columns: Column[] = [
{ getColId: () => 'col1', getActualWidth: () => 100 },
{ getColId: () => 'col2', getActualWidth: () => 200 },
] as Column[];
const sizeObj = { col1: 100, col2: 200, clientWidth: 1000 };
const { result } = renderHook(() =>
useColumnSizes({ storeKey, props: {} })
);
await act(() => {
result.current.onGridSizeChanged?.({
clientWidth: 1000,
...mockApis,
} as GridSizeChangedEvent);
});
await act(() => {
result.current.onColumnResized?.({
columns,
finished: true,
source: 'uiColumnDragged',
...mockApis,
} as ColumnResizedEvent);
});
await waitFor(() => {
expect(mockValueSetter).toHaveBeenCalledWith(storeKey, sizeObj);
});
});
it('onGridReady should call setSizes', async () => {
const props = { onGridReady: jest.fn() };
const { result } = renderHook(() => useColumnSizes({ storeKey, props }));
const obTest = { cool: 1, ...mockApis };
await act(() => {
result.current.onGridReady?.(obTest as GridReadyEvent);
});
expect(props.onGridReady).toHaveBeenCalledWith(obTest);
expect(mockApis.api.sizeColumnsToFit).toHaveBeenCalledWith();
});
it('if no storeKey should be transparent', () => {
const { result } = renderHook(() =>
useColumnSizes({ storeKey: '', props: {} })
);
expect(result.current).toStrictEqual({
onColumnResized: undefined,
onGridReady: undefined,
onGridSizeChanged: undefined,
});
});
});
@@ -1,143 +0,0 @@
import { useCallback, useRef } from 'react';
import type {
GridSizeChangedEvent,
GridReadyEvent,
ColumnResizedEvent,
} from 'ag-grid-community';
import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
const STORAGE_KEY = 'vega_columns_sizes_store';
export const useColumnSizesStore = create<{
sizes: Record<string, Record<string, number>>;
valueSetter: (storeKey: string, value: Record<string, number>) => void;
}>()(
persist(
immer((set) => ({
sizes: {},
valueSetter: (storeKey, value) =>
set((state) => {
state.sizes[storeKey] = {
...(state.sizes[storeKey] || {}),
...value,
};
return state;
}),
})),
{ name: STORAGE_KEY }
)
);
interface UseColumnSizesProps {
props: AgGridReactProps | AgReactUiProps;
storeKey?: string;
}
export const useColumnSizes = ({
storeKey = '',
props,
}: UseColumnSizesProps): {
onColumnResized?: (event: ColumnResizedEvent) => void;
onGridReady?: (event: GridReadyEvent) => void;
onGridSizeChanged?: (event: GridSizeChangedEvent) => void;
} => {
const sizes = useColumnSizesStore((store) => store.sizes[storeKey] || {});
const valueSetter = useColumnSizesStore((store) => store.valueSetter);
const widthRef = useRef(sizes['clientWidth'] || 0);
const {
onColumnResized: parentOnColumnResized,
onGridReady: parentOnGridReady,
onGridSizeChanged: parentOnGridSizeChanged,
} = props;
const recalculateSizes = useCallback((sizes: Record<string, number>) => {
if (
widthRef.current &&
sizes['clientWidth'] &&
widthRef.current !== sizes['clientWidth']
) {
const oldWidth = sizes['clientWidth'];
const ratio = widthRef.current / oldWidth;
return {
...Object.entries(sizes).reduce((agg, [key, value]) => {
agg[key] = value * ratio;
return agg;
}, {} as Record<string, number>),
width: widthRef.current,
} as Record<string, number>;
}
return sizes;
}, []);
const onColumnResized = useCallback(
(event: ColumnResizedEvent) => {
parentOnColumnResized?.(event);
if (
storeKey &&
event.source === 'uiColumnDragged' &&
event.finished &&
widthRef.current
) {
const { columns } = event;
if (columns?.length) {
const sizesObj = columns.reduce((aggr, column) => {
aggr[column.getColId()] = column.getActualWidth();
return aggr;
}, {} as Record<string, number>);
sizesObj['clientWidth'] = widthRef.current;
valueSetter(storeKey, sizesObj);
}
}
},
[valueSetter, storeKey, parentOnColumnResized]
);
const setSizes = useCallback(
(apiEvent: GridReadyEvent | GridSizeChangedEvent) => {
if (!storeKey || !Object.keys(sizes).length || !widthRef.current) {
apiEvent.api.sizeColumnsToFit();
} else {
const recalculatedSizes = recalculateSizes(sizes);
const newSizes = Object.entries(recalculatedSizes).map(
([key, size]) => ({
key,
newWidth: size,
})
);
apiEvent.columnApi.setColumnWidths(newSizes);
}
},
[storeKey, recalculateSizes, sizes]
);
const onGridReady = useCallback(
(event: GridReadyEvent) => {
parentOnGridReady?.(event);
setSizes(event);
},
[setSizes, parentOnGridReady]
);
const onGridSizeChanged = useCallback(
(event: GridSizeChangedEvent) => {
parentOnGridSizeChanged?.(event);
widthRef.current = event.clientWidth;
setSizes(event);
},
[parentOnGridSizeChanged, setSizes]
);
if (storeKey) {
return {
onGridReady,
onGridSizeChanged,
onColumnResized,
};
}
return {
onGridReady: parentOnGridReady,
onGridSizeChanged: parentOnGridSizeChanged,
onColumnResized: parentOnColumnResized,
};
};
-3
View File
@@ -1,4 +1 @@
import '@testing-library/jest-dom';
import ResizeObserver from 'resize-observer-polyfill';
global.ResizeObserver = ResizeObserver;
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { act, render, screen } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { generateMarket, generateMarketData } from '../../test-helpers';
import { DealTicket } from './deal-ticket';
@@ -40,21 +40,12 @@ describe('DealTicket', () => {
});
it('should display ticket defaults', () => {
const { container } = render(generateJsx());
render(generateJsx());
// Assert defaults are used
expect(
screen.getByTestId(`order-type-${Schema.OrderType.TYPE_MARKET}`)
).toBeInTheDocument();
expect(
screen.getByTestId(`order-type-${Schema.OrderType.TYPE_LIMIT}`)
).toBeInTheDocument();
const oderTypeLimitToggle = container.querySelector(
`[data-testid="order-type-${Schema.OrderType.TYPE_LIMIT}"] input[type="radio"]`
);
expect(oderTypeLimitToggle).toBeChecked();
expect(
screen.queryByTestId('order-side-SIDE_BUY')?.querySelector('input')
).toBeChecked();
@@ -63,15 +54,8 @@ describe('DealTicket', () => {
).not.toBeChecked();
expect(screen.getByTestId('order-size')).toHaveDisplayValue('0');
expect(screen.getByTestId('order-tif')).toHaveValue(
Schema.OrderTimeInForce.TIME_IN_FORCE_GTC
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC
);
});
it('should display last price for market type order', () => {
render(generateJsx());
act(() => {
screen.getByTestId(`order-type-${Schema.OrderType.TYPE_MARKET}`).click();
});
// Assert last price is shown
expect(screen.getByTestId('last-price')).toHaveTextContent(
// eslint-disable-next-line
@@ -222,11 +206,7 @@ describe('DealTicket', () => {
it('handles TIF select box dependent on order type', async () => {
render(generateJsx());
act(() => {
screen.getByTestId(`order-type-${Schema.OrderType.TYPE_MARKET}`).click();
});
// Only FOK and IOC should be present for type market order
// Only FOK and IOC should be present by default (type market order)
expect(
Array.from(screen.getByTestId('order-tif').children).map(
(o) => o.textContent
@@ -20,8 +20,8 @@ interface TypeSelectorProps {
}
const toggles = [
{ label: t('Limit'), value: Schema.OrderType.TYPE_LIMIT },
{ label: t('Market'), value: Schema.OrderType.TYPE_MARKET },
{ label: t('Limit'), value: Schema.OrderType.TYPE_LIMIT },
];
export const TypeSelector = ({
+1 -3
View File
@@ -26,10 +26,9 @@ export const DepositsTable = forwardRef<
<AgGrid
ref={ref}
overlayNoRowsTemplate={t('No deposits')}
defaultColDef={{ resizable: true }}
defaultColDef={{ flex: 1, resizable: true }}
style={{ width: '100%', height: '100%' }}
suppressCellFocus={true}
storeKey="depositTable"
{...props}
>
<AgGridColumn headerName="Asset" field="asset.symbol" />
@@ -81,7 +80,6 @@ export const DepositsTable = forwardRef<
</EtherscanLink>
);
}}
flex={1}
/>
</AgGrid>
);
+15 -12
View File
@@ -34,22 +34,25 @@ fragment FillEdge on TradeEdge {
cursor
}
query Fills($filter: TradesFilter, $pagination: Pagination) {
trades(filter: $filter, pagination: $pagination) {
edges {
...FillEdge
}
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
query Fills($partyId: ID!, $marketId: ID, $pagination: Pagination) {
party(id: $partyId) {
id
tradesConnection(marketId: $marketId, pagination: $pagination) {
edges {
...FillEdge
}
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
}
}
}
subscription FillsEvent($filter: TradesSubscriptionFilter!) {
tradesStream(filter: $filter) {
subscription FillsEvent($partyId: ID!) {
trades(partyId: $partyId) {
id
marketId
buyOrder

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