Compare commits
40
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7edb5d20c3 | ||
|
|
f7f00dc7bb | ||
|
|
d151b6e923 | ||
|
|
8f8e9c1061 | ||
|
|
b40358a723 | ||
|
|
01f0934da3 | ||
|
|
df7755dbc4 | ||
|
|
9073234040 | ||
|
|
053cd0fa27 | ||
|
|
961dc88dc7 | ||
|
|
b16607a844 | ||
|
|
d0d804631c | ||
|
|
3e778a88dc | ||
|
|
dbb09c9c79 | ||
|
|
06f44b67de | ||
|
|
1861852852 | ||
|
|
3ee0eee80d | ||
|
|
69ce870a81 | ||
|
|
d8467c3206 | ||
|
|
de87f40a01 | ||
|
|
f22fb56afd | ||
|
|
f2627e6ebf | ||
|
|
24e6a13d93 | ||
|
|
b56050ad1c | ||
|
|
cff83ed062 | ||
|
|
8ecce874b0 | ||
|
|
00e319b3c6 | ||
|
|
05559f3ea0 | ||
|
|
4b3b5c322a | ||
|
|
b1280c8285 | ||
|
|
1e0e1c7859 | ||
|
|
ec2bb81ec8 | ||
|
|
fe95c6fcbc | ||
|
|
853ec8f69c | ||
|
|
b68c090ee5 | ||
|
|
cb9b811730 | ||
|
|
98b5260d93 | ||
|
|
613262f7a5 | ||
|
|
9ae6f5201c | ||
|
|
cfb75fcf8f |
@@ -64,12 +64,11 @@ jobs:
|
||||
######
|
||||
|
||||
- name: Run Cypress tests
|
||||
run: yarn nx run ${{ matrix.project }}:e2e ${{ env.SKIP_CACHE }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome
|
||||
run: yarn nx run ${{ matrix.project }}:e2e ${{ env.SKIP_CACHE }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome --env.grepTags="${{ inputs.tags }}"
|
||||
working-directory: frontend-monorepo
|
||||
env:
|
||||
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
|
||||
CYPRESS_VEGA_WALLET_API_TOKEN: ${{ steps.setup-vega.outputs.token }}
|
||||
CYPRESS_grepTags: ${{ inputs.tags }}
|
||||
|
||||
######
|
||||
## Upload logs
|
||||
|
||||
@@ -8,14 +8,16 @@ jobs:
|
||||
lint_pr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Use Node.js 16
|
||||
id: Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16.14.0
|
||||
- name: Install commitlint cli and config
|
||||
run: npm install @commitlint/cli @commitlint/config-conventional
|
||||
- name: Create config
|
||||
run: echo "module.exports = {extends:['@commitlint/config-conventional']};" > commitlint.config.js
|
||||
- name: Install root dependencies
|
||||
run: yarn install
|
||||
- name: Check PR title
|
||||
run: echo "${{ github.event.pull_request.title }}" | npx commitlint
|
||||
run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useExplorerAssetQuery } from './__generated__/Asset';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import Hash from '../hash';
|
||||
|
||||
export type AssetLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
id: string;
|
||||
@@ -27,7 +28,7 @@ const AssetLink = ({ id, ...props }: AssetLinkProps) => {
|
||||
|
||||
return (
|
||||
<Link className="underline" {...props} to={`/${Routes.ASSETS}#${id}`}>
|
||||
{label}
|
||||
<Hash text={label} />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Routes } from '../../../routes/route-names';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import Hash from '../hash';
|
||||
|
||||
export type BlockLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
height: string;
|
||||
@@ -11,7 +12,7 @@ export type BlockLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
const BlockLink = ({ height, ...props }: BlockLinkProps) => {
|
||||
return (
|
||||
<Link className="underline" {...props} to={`/${Routes.BLOCKS}/${height}`}>
|
||||
{height}
|
||||
<Hash text={height} />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { DATA_SOURCES } from '../../../config';
|
||||
import Hash from '../hash';
|
||||
|
||||
export enum EthExplorerLinkTypes {
|
||||
block = 'block',
|
||||
@@ -27,7 +28,7 @@ export const EthExplorerLink = ({
|
||||
{...props}
|
||||
href={link}
|
||||
>
|
||||
{id}
|
||||
<Hash text={id} />
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
export type HashProps = {
|
||||
text: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A simple component that ensures long text things like hashes
|
||||
* are broken when they need to wrap. This will remove the need
|
||||
* for a lot of the overflow scrolling that currently exists.
|
||||
*/
|
||||
const Hash = ({ text }: HashProps) => {
|
||||
return (
|
||||
<code className="break-all font-mono" style={{ wordWrap: 'break-word' }}>
|
||||
{text}
|
||||
</code>
|
||||
);
|
||||
};
|
||||
|
||||
export default Hash;
|
||||
@@ -4,6 +4,7 @@ import { Link } from 'react-router-dom';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import Hash from '../hash';
|
||||
|
||||
export type MarketLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
id: string;
|
||||
@@ -35,7 +36,8 @@ const MarketLink = ({
|
||||
<span role="img" aria-label="Unknown market" className="img">
|
||||
⚠️ {t('Invalid market')}
|
||||
</span>
|
||||
{id}
|
||||
|
||||
<Hash text={id} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -55,7 +57,7 @@ const MarketLink = ({
|
||||
} else {
|
||||
return (
|
||||
<Link className="underline" {...props} to={`/${Routes.MARKETS}#${id}`}>
|
||||
{id}
|
||||
<Hash text={id} />
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useExplorerNodeQuery } from './__generated__/Node';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import Hash from '../hash';
|
||||
|
||||
export type NodeLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
id: string;
|
||||
@@ -22,7 +23,7 @@ const NodeLink = ({ id, ...props }: NodeLinkProps) => {
|
||||
|
||||
return (
|
||||
<Link className="underline" {...props} to={`/${Routes.VALIDATORS}#${id}`}>
|
||||
<code>{label}</code>
|
||||
<Hash text={label} />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Routes } from '../../../routes/route-names';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import Hash from '../hash';
|
||||
|
||||
export type OracleLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
id: string;
|
||||
@@ -14,7 +15,7 @@ const OracleLink = ({ id, ...props }: OracleLinkProps) => {
|
||||
{...props}
|
||||
to={`/${Routes.ORACLES}/${id}`}
|
||||
>
|
||||
{id}
|
||||
<Hash text={id} />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Routes } from '../../../routes/route-names';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import Hash from '../hash';
|
||||
|
||||
export type PartyLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
id: string;
|
||||
@@ -14,7 +15,7 @@ const PartyLink = ({ id, ...props }: PartyLinkProps) => {
|
||||
{...props}
|
||||
to={`/${Routes.PARTIES}/${id}`}
|
||||
>
|
||||
{id}
|
||||
<Hash text={id} />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ describe('Proposal link component', () => {
|
||||
expect(res.getByText('123')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders the ID with an emoji on error', async () => {
|
||||
it('Renders the ID on error', async () => {
|
||||
const mock = {
|
||||
request: {
|
||||
query: ExplorerProposalDocument,
|
||||
@@ -37,9 +37,6 @@ describe('Proposal link component', () => {
|
||||
const res = render(renderComponent('456', [mock]));
|
||||
// The ID
|
||||
expect(res.getByText('456')).toBeInTheDocument();
|
||||
|
||||
// The emoji
|
||||
expect(await res.findByRole('img')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders the proposal title when the query returns a result', async () => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useExplorerProposalQuery } from './__generated__/Proposal';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { ENV } from '../../../config/env';
|
||||
import Hash from '../hash';
|
||||
export type ProposalLinkProps = {
|
||||
id: string;
|
||||
};
|
||||
@@ -17,7 +18,11 @@ const ProposalLink = ({ id }: ProposalLinkProps) => {
|
||||
const base = ENV.dataSources.governanceUrl;
|
||||
const label = data?.proposal?.rationale.title || id;
|
||||
|
||||
return <ExternalLink href={`${base}/proposals/${id}`}>{label}</ExternalLink>;
|
||||
return (
|
||||
<ExternalLink href={`${base}/proposals/${id}`}>
|
||||
<Hash text={label} />
|
||||
</ExternalLink>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProposalLink;
|
||||
|
||||
+10
-4
@@ -63,15 +63,21 @@ describe('Chain Event: Builtin asset deposit', () => {
|
||||
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
|
||||
const partyLink = screen.getByText(`${fullMock.partyId}`);
|
||||
expect(partyLink).toBeInTheDocument();
|
||||
expect(partyLink.tagName).toEqual('A');
|
||||
expect(partyLink.getAttribute('href')).toEqual(
|
||||
if (!partyLink.parentElement) {
|
||||
throw new Error('Party link does not exist');
|
||||
}
|
||||
expect(partyLink.parentElement.tagName).toEqual('A');
|
||||
expect(partyLink.parentElement.getAttribute('href')).toEqual(
|
||||
`/parties/${fullMock.partyId}`
|
||||
);
|
||||
|
||||
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
|
||||
expect(assetLink).toBeInTheDocument();
|
||||
expect(assetLink.tagName).toEqual('A');
|
||||
expect(assetLink.getAttribute('href')).toEqual(
|
||||
if (!assetLink.parentElement) {
|
||||
throw new Error('Asset link does not exist');
|
||||
}
|
||||
expect(assetLink.parentElement.tagName).toEqual('A');
|
||||
expect(assetLink.parentElement.getAttribute('href')).toEqual(
|
||||
`/assets#${fullMock.vegaAssetId}`
|
||||
);
|
||||
});
|
||||
|
||||
+10
-4
@@ -69,15 +69,21 @@ describe('Chain Event: Builtin asset withdrawal', () => {
|
||||
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
|
||||
const partyLink = screen.getByText(`${fullMock.partyId}`);
|
||||
expect(partyLink).toBeInTheDocument();
|
||||
expect(partyLink.tagName).toEqual('A');
|
||||
expect(partyLink.getAttribute('href')).toEqual(
|
||||
if (!partyLink.parentElement) {
|
||||
throw new Error('Party link does not exist');
|
||||
}
|
||||
expect(partyLink.parentElement.tagName).toEqual('A');
|
||||
expect(partyLink.parentElement.getAttribute('href')).toEqual(
|
||||
`/parties/${fullMock.partyId}`
|
||||
);
|
||||
|
||||
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
|
||||
expect(assetLink).toBeInTheDocument();
|
||||
expect(assetLink.tagName).toEqual('A');
|
||||
expect(assetLink.getAttribute('href')).toEqual(
|
||||
if (!assetLink.parentElement) {
|
||||
throw new Error('Asset link does not exist');
|
||||
}
|
||||
expect(assetLink.parentElement.tagName).toEqual('A');
|
||||
expect(assetLink.parentElement.getAttribute('href')).toEqual(
|
||||
`/assets#${fullMock.vegaAssetId}`
|
||||
);
|
||||
});
|
||||
|
||||
+5
-2
@@ -60,8 +60,11 @@ describe('Chain Event: ERC20 Asset Delist', () => {
|
||||
|
||||
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
|
||||
expect(assetLink).toBeInTheDocument();
|
||||
expect(assetLink.tagName).toEqual('A');
|
||||
expect(assetLink.getAttribute('href')).toEqual(
|
||||
if (!assetLink.parentElement) {
|
||||
throw new Error('Asset link does not exist');
|
||||
}
|
||||
expect(assetLink.parentElement.tagName).toEqual('A');
|
||||
expect(assetLink.parentElement.getAttribute('href')).toEqual(
|
||||
`/assets#${fullMock.vegaAssetId}`
|
||||
);
|
||||
});
|
||||
|
||||
+9
-3
@@ -76,14 +76,20 @@ describe('Chain Event: ERC20 Asset limits updated', () => {
|
||||
expect(screen.getByText(t('Vega asset'))).toBeInTheDocument();
|
||||
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
|
||||
expect(assetLink).toBeInTheDocument();
|
||||
expect(assetLink.tagName).toEqual('A');
|
||||
expect(assetLink.getAttribute('href')).toEqual(
|
||||
if (!assetLink.parentElement) {
|
||||
throw new Error('Asset link does not exist');
|
||||
}
|
||||
expect(assetLink.parentElement.tagName).toEqual('A');
|
||||
expect(assetLink.parentElement.getAttribute('href')).toEqual(
|
||||
`/assets#${fullMock.vegaAssetId}`
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('ERC20 asset'))).toBeInTheDocument();
|
||||
const ethLink = screen.getByText(`${fullMock.sourceEthereumAddress}`);
|
||||
expect(ethLink.getAttribute('href')).toContain(
|
||||
if (!ethLink.parentElement) {
|
||||
throw new Error('ETH link does not exist');
|
||||
}
|
||||
expect(ethLink.parentElement.getAttribute('href')).toContain(
|
||||
`/address/${fullMock.sourceEthereumAddress}`
|
||||
);
|
||||
});
|
||||
|
||||
+9
-3
@@ -62,14 +62,20 @@ describe('Chain Event: ERC20 Asset List', () => {
|
||||
expect(screen.getByText(t('Added Vega asset'))).toBeInTheDocument();
|
||||
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
|
||||
expect(assetLink).toBeInTheDocument();
|
||||
expect(assetLink.tagName).toEqual('A');
|
||||
expect(assetLink.getAttribute('href')).toEqual(
|
||||
if (!assetLink.parentElement) {
|
||||
throw new Error('Asset link does not exist');
|
||||
}
|
||||
expect(assetLink.parentElement.tagName).toEqual('A');
|
||||
expect(assetLink.parentElement.getAttribute('href')).toEqual(
|
||||
`/assets#${fullMock.vegaAssetId}`
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Source'))).toBeInTheDocument();
|
||||
const ethLink = screen.getByText(`${fullMock.assetSource}`);
|
||||
expect(ethLink.getAttribute('href')).toContain(
|
||||
if (!ethLink.parentElement) {
|
||||
throw new Error('Asset link does not exist');
|
||||
}
|
||||
expect(ethLink.parentElement.getAttribute('href')).toContain(
|
||||
`/address/${fullMock.assetSource}`
|
||||
);
|
||||
});
|
||||
|
||||
+14
-5
@@ -62,21 +62,30 @@ describe('Chain Event: ERC20 asset deposit', () => {
|
||||
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
|
||||
const partyLink = screen.getByText(`${fullMock.targetPartyId}`);
|
||||
expect(partyLink).toBeInTheDocument();
|
||||
expect(partyLink.tagName).toEqual('A');
|
||||
expect(partyLink.getAttribute('href')).toEqual(
|
||||
if (!partyLink.parentElement) {
|
||||
throw new Error('Party link does not exist');
|
||||
}
|
||||
expect(partyLink.parentElement.tagName).toEqual('A');
|
||||
expect(partyLink.parentElement.getAttribute('href')).toEqual(
|
||||
`/parties/${fullMock.targetPartyId}`
|
||||
);
|
||||
|
||||
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
|
||||
expect(assetLink).toBeInTheDocument();
|
||||
expect(assetLink.tagName).toEqual('A');
|
||||
expect(assetLink.getAttribute('href')).toEqual(
|
||||
if (!assetLink.parentElement) {
|
||||
throw new Error('Asset link does not exist');
|
||||
}
|
||||
expect(assetLink.parentElement.tagName).toEqual('A');
|
||||
expect(assetLink.parentElement.getAttribute('href')).toEqual(
|
||||
`/assets#${fullMock.vegaAssetId}`
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Source'))).toBeInTheDocument();
|
||||
const ethLink = screen.getByText(`${fullMock.sourceEthereumAddress}`);
|
||||
expect(ethLink.getAttribute('href')).toContain(
|
||||
if (!ethLink.parentElement) {
|
||||
throw new Error('ETH link does not exist');
|
||||
}
|
||||
expect(ethLink.parentElement.getAttribute('href')).toContain(
|
||||
`/address/${fullMock.sourceEthereumAddress}`
|
||||
);
|
||||
});
|
||||
|
||||
+9
-3
@@ -57,14 +57,20 @@ describe('Chain Event: ERC20 asset deposit', () => {
|
||||
expect(screen.getByText(t('Asset'))).toBeInTheDocument();
|
||||
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
|
||||
expect(assetLink).toBeInTheDocument();
|
||||
expect(assetLink.tagName).toEqual('A');
|
||||
expect(assetLink.getAttribute('href')).toEqual(
|
||||
if (!assetLink.parentElement) {
|
||||
throw new Error('Asset link does not exist');
|
||||
}
|
||||
expect(assetLink.parentElement.tagName).toEqual('A');
|
||||
expect(assetLink.parentElement.getAttribute('href')).toEqual(
|
||||
`/assets#${fullMock.vegaAssetId}`
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
|
||||
const ethLink = screen.getByText(`${fullMock.targetEthereumAddress}`);
|
||||
expect(ethLink.getAttribute('href')).toContain(
|
||||
if (!ethLink.parentElement) {
|
||||
throw new Error('ETH link does not exist');
|
||||
}
|
||||
expect(ethLink.parentElement.getAttribute('href')).toContain(
|
||||
`/address/${fullMock.targetEthereumAddress}`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -64,14 +64,20 @@ describe('Chain Event: Stake deposit', () => {
|
||||
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
|
||||
const partyLink = screen.getByText(`${fullMock.vegaPublicKey}`);
|
||||
expect(partyLink).toBeInTheDocument();
|
||||
expect(partyLink.tagName).toEqual('A');
|
||||
expect(partyLink.getAttribute('href')).toEqual(
|
||||
if (!partyLink.parentElement) {
|
||||
throw new Error('Party link does not exist');
|
||||
}
|
||||
expect(partyLink.parentElement.tagName).toEqual('A');
|
||||
expect(partyLink.parentElement.getAttribute('href')).toEqual(
|
||||
`/parties/${fullMock.vegaPublicKey}`
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Source'))).toBeInTheDocument();
|
||||
const ethLink = screen.getByText(`${fullMock.ethereumAddress}`);
|
||||
expect(ethLink.getAttribute('href')).toContain(
|
||||
if (!ethLink.parentElement) {
|
||||
throw new Error('ETH link does not exist');
|
||||
}
|
||||
expect(ethLink.parentElement.getAttribute('href')).toContain(
|
||||
`/address/${fullMock.ethereumAddress}`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -64,14 +64,20 @@ describe('Chain Event: Stake remove', () => {
|
||||
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
|
||||
const partyLink = screen.getByText(`${fullMock.vegaPublicKey}`);
|
||||
expect(partyLink).toBeInTheDocument();
|
||||
expect(partyLink.tagName).toEqual('A');
|
||||
expect(partyLink.getAttribute('href')).toEqual(
|
||||
if (!partyLink.parentElement) {
|
||||
throw new Error('Party link does not exist');
|
||||
}
|
||||
expect(partyLink.parentElement.tagName).toEqual('A');
|
||||
expect(partyLink.parentElement.getAttribute('href')).toEqual(
|
||||
`/parties/${fullMock.vegaPublicKey}`
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Source'))).toBeInTheDocument();
|
||||
const ethLink = screen.getByText(`${fullMock.ethereumAddress}`);
|
||||
expect(ethLink.getAttribute('href')).toContain(
|
||||
if (!ethLink.parentElement) {
|
||||
throw new Error('ETH link does not exist');
|
||||
}
|
||||
expect(ethLink.parentElement.getAttribute('href')).toContain(
|
||||
`/address/${fullMock.ethereumAddress}`
|
||||
);
|
||||
});
|
||||
|
||||
+4
-1
@@ -66,7 +66,10 @@ describe('Chain Event: Stake total supply change', () => {
|
||||
|
||||
expect(screen.getByText(t('Source'))).toBeInTheDocument();
|
||||
const ethLink = screen.getByText(`${fullMock.tokenAddress}`);
|
||||
expect(ethLink.getAttribute('href')).toContain(
|
||||
if (!ethLink.parentElement) {
|
||||
throw new Error('ETH link does not exist');
|
||||
}
|
||||
expect(ethLink.parentElement.getAttribute('href')).toContain(
|
||||
`/address/${fullMock.tokenAddress}`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -7,6 +7,8 @@ import type { BlockExplorerTransactionResult } from '../../../../routes/types/bl
|
||||
import type { TendermintBlocksResponse } from '../../../../routes/blocks/tendermint-blocks-response';
|
||||
import { Time } from '../../../time';
|
||||
import { ChainResponseCode } from '../chain-response-code/chain-reponse.code';
|
||||
import { TxDataView } from '../../tx-data-view';
|
||||
import Hash from '../../../links/hash';
|
||||
|
||||
interface TxDetailsSharedProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -46,7 +48,7 @@ export const TxDetailsShared = ({
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Hash')}</TableCell>
|
||||
<TableCell>
|
||||
<code>{txData.hash}</code>
|
||||
<Hash text={txData.hash} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
@@ -82,6 +84,12 @@ export const TxDetailsShared = ({
|
||||
<ChainResponseCode code={txData.code} error={txData.error} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Transaction')}</TableCell>
|
||||
<TableCell>
|
||||
<TxDataView blockData={blockData} txData={txData} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,10 +8,8 @@ import { TxDetailsHeartbeat } from './tx-hearbeat';
|
||||
import { TxDetailsGeneric } from './tx-generic';
|
||||
import { TxDetailsBatch } from './tx-batch';
|
||||
import { TxDetailsChainEvent } from './tx-chain-event';
|
||||
import { TxContent } from '../../../routes/txs/id/tx-content';
|
||||
import { TxDetailsNodeVote } from './tx-node-vote';
|
||||
import { TxDetailsOrderCancel } from './tx-order-cancel';
|
||||
import get from 'lodash/get';
|
||||
import { TxDetailsOrderAmend } from './tx-order-amend';
|
||||
import { TxDetailsWithdrawSubmission } from './tx-withdraw-submission';
|
||||
import { TxDetailsDelegate } from './tx-delegation';
|
||||
@@ -46,23 +44,9 @@ export const TxDetailsWrapper = ({
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const raw = get(blockData, `result.block.data.txs[${txData.index}]`);
|
||||
|
||||
return (
|
||||
<div key={`txd-${txData.hash}`}>
|
||||
<section>{child({ txData, pubKey, blockData })}</section>
|
||||
|
||||
<details title={t('Decoded transaction')} className="mt-3">
|
||||
<summary className="cursor-pointer">{t('Decoded transaction')}</summary>
|
||||
<TxContent data={txData} />
|
||||
</details>
|
||||
|
||||
{raw ? (
|
||||
<details title={t('Raw transaction')} className="mt-3">
|
||||
<summary className="cursor-pointer">{t('Raw transaction')}</summary>
|
||||
<code className="break-all font-mono text-xs">{raw}</code>
|
||||
</details>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ import { TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
|
||||
import DeterministicOrderDetails from '../../order-details/deterministic-order-details';
|
||||
import Hash from '../../links/hash';
|
||||
|
||||
interface TxDetailsOrderProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -47,7 +48,7 @@ export const TxDetailsOrder = ({
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Order')}</TableCell>
|
||||
<TableCell>
|
||||
<code>{deterministicId}</code>
|
||||
<Hash text={deterministicId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { useState } from 'react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import get from 'lodash/get';
|
||||
import { Select } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
|
||||
import type { TendermintBlocksResponse } from '../../routes/blocks/tendermint-blocks-response';
|
||||
|
||||
export function getClassName(showTxData: ShowTxDataType) {
|
||||
const baseClasses =
|
||||
'font-mono bg-neutral-300 text-[11px] leading-3 text-gray-900 w-full p-2 max-w-[615px]';
|
||||
if (showTxData === 'JSON') {
|
||||
return `${baseClasses} whitespace-pre overflow-x-scroll`;
|
||||
} else {
|
||||
return baseClasses;
|
||||
}
|
||||
}
|
||||
|
||||
export function getContents(
|
||||
showTxData: ShowTxDataType,
|
||||
txData: BlockExplorerTransactionResult | null,
|
||||
blockData: TendermintBlocksResponse | null | undefined
|
||||
) {
|
||||
if (showTxData === 'JSON') {
|
||||
if (txData) {
|
||||
return JSON.stringify(txData.command, undefined, 1);
|
||||
}
|
||||
} else {
|
||||
if (txData && blockData) {
|
||||
return get(blockData, `result.block.data.txs[${txData.index}]`);
|
||||
}
|
||||
}
|
||||
|
||||
return '-';
|
||||
}
|
||||
|
||||
type ShowTxDataType = 'JSON' | 'base64';
|
||||
|
||||
interface TxDataViewProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
export const TxDataView = ({ txData, blockData }: TxDataViewProps) => {
|
||||
const [showTxData, setShowTxData] = useState<ShowTxDataType>('JSON');
|
||||
|
||||
if (!txData) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<details title={t('Show raw transaction')}>
|
||||
<summary className="cursor-pointer">{t('Show raw transaction')}</summary>
|
||||
<div className="py-4">
|
||||
<textarea
|
||||
readOnly={true}
|
||||
className={getClassName(showTxData)}
|
||||
rows={12}
|
||||
cols={120}
|
||||
value={getContents(showTxData, txData, blockData)}
|
||||
/>
|
||||
<div className="w-40">
|
||||
<Select
|
||||
placeholder="View as..."
|
||||
onChange={(v) => setShowTxData(v.target.value as ShowTxDataType)}
|
||||
value={showTxData}
|
||||
>
|
||||
<option value={'JSON'}>JSON</option>
|
||||
<option value={'base64'}>Base64</option>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
};
|
||||
@@ -115,7 +115,7 @@ export const TxsInfiniteList = ({
|
||||
className="List"
|
||||
height={995}
|
||||
itemCount={itemCount}
|
||||
itemSize={isStacked ? 134 : 72}
|
||||
itemSize={isStacked ? 134 : 50}
|
||||
onItemsRendered={onItemsRendered}
|
||||
ref={ref}
|
||||
width={'100%'}
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
BlockExplorerTransactions,
|
||||
} from '../routes/types/block-explorer-response';
|
||||
import { DATA_SOURCES } from '../config';
|
||||
import isNumber from 'lodash/isNumber';
|
||||
|
||||
export interface TxsStateProps {
|
||||
txsData: BlockExplorerTransactionResult[];
|
||||
@@ -54,15 +55,15 @@ export const useTxsData = ({ limit, filters }: IUseTxsData) => {
|
||||
} = useFetch<BlockExplorerTransactions>(url, {}, false);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.transactions?.length) {
|
||||
if (data && isNumber(data?.transactions?.length)) {
|
||||
setTxsState((prev) => ({
|
||||
txsData: [...prev.txsData, ...data.transactions],
|
||||
hasMoreTxs: true,
|
||||
hasMoreTxs: data.transactions.length > 0,
|
||||
lastCursor:
|
||||
data.transactions[data.transactions.length - 1].cursor || '',
|
||||
data.transactions[data.transactions.length - 1]?.cursor || '',
|
||||
}));
|
||||
}
|
||||
}, [setTxsState, data?.transactions]);
|
||||
}, [setTxsState, data]);
|
||||
|
||||
const loadTxs = useCallback(() => {
|
||||
return refetch({
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 104 KiB |
File diff suppressed because it is too large
Load Diff
+13
-1
@@ -16,4 +16,16 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
CYPRESS_VEGA_URL=http://localhost:3028/query
|
||||
CYPRESS_VEGA_WALLET_API_TOKEN=
|
||||
CYPRESS_VEGA_WALLET_API_TOKEN=jpeAkxcffzTLCzBX2m5TZIp3hF500YZhHwESwNKOGksdGPXeeIznXypaDfpNe2M9
|
||||
CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
|
||||
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
|
||||
CYPRESS_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint
|
||||
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65
|
||||
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=7f9cf0…c25535
|
||||
CYPRESS_VEGA_ENV=CUSTOM
|
||||
CYPRESS_VEGA_PUBLIC_KEY=02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65
|
||||
CYPRESS_VEGA_PUBLIC_KEY2=7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535
|
||||
CYPRESS_VEGA_TOKEN_URL=https://token.fairground.wtf
|
||||
CYPRESS_VEGA_URL=http://localhost:3028/query
|
||||
CYPRESS_VEGA_WALLET_URL=http://localhost:1789
|
||||
|
||||
@@ -2,56 +2,59 @@
|
||||
"changes": {
|
||||
"decimalPlaces": "5",
|
||||
"positionDecimalPlaces": "5",
|
||||
"lpPriceRange": "10",
|
||||
"instrument": {
|
||||
"name": "Oranges Daily",
|
||||
"code": "ORANGES.24h",
|
||||
"name": "Token test market",
|
||||
"code": "Token.24h",
|
||||
"future": {
|
||||
"settlementAsset": "8b52d4a3a4b0ffe733cddbc2b67be273816cfeb6ca4c8b339bac03ffba08e4e4",
|
||||
"quoteName": "tEuro",
|
||||
"settlementDataDecimals": 5,
|
||||
"settlementAsset": "fBTC",
|
||||
"quoteName": "fBTC",
|
||||
"dataSourceSpecForSettlementData": {
|
||||
"signers": [
|
||||
{
|
||||
"signer": {
|
||||
"__typename": "ETHAddress",
|
||||
"address": "0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "prices.BTC.value",
|
||||
"type": "TYPE_INTEGER"
|
||||
},
|
||||
"conditions": [
|
||||
"external": {
|
||||
"oracle": {
|
||||
"signers": [
|
||||
{
|
||||
"operator": "OPERATOR_GREATER_THAN",
|
||||
"value": "0"
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "prices.ETH.value",
|
||||
"type": "TYPE_INTEGER",
|
||||
"numberDecimalPlaces": "0"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_GREATER_THAN",
|
||||
"value": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"dataSourceSpecForTradingTermination": {
|
||||
"signers": [
|
||||
{
|
||||
"signer": {
|
||||
"__typename": "ETHAddress",
|
||||
"address": "0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC"
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "vegaprotocol.builtin.timestamp",
|
||||
"type": "TYPE_TIMESTAMP"
|
||||
"name": "trading.terminated.ETH5",
|
||||
"type": "TYPE_BOOLEAN"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
|
||||
"value": "1648684800000000000"
|
||||
"operator": "OPERATOR_EQUALS",
|
||||
"value": "true"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -59,7 +62,7 @@
|
||||
},
|
||||
"dataSourceSpecBinding": {
|
||||
"settlementPriceProperty": "prices.BTC.value",
|
||||
"tradingTerminationProperty": "vegaprotocol.builtin.timestamp"
|
||||
"tradingTerminationProperty": "trading.terminated.ETH5"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,83 +1,78 @@
|
||||
{
|
||||
"marketId": "315a8e48db0a292c92b617264728048c82c20efc922c75fd292fc54e5c727c81",
|
||||
"changes": {
|
||||
"instrument": {
|
||||
"code": "ORANGES.24h",
|
||||
"future": {
|
||||
"quoteName": "tEuro",
|
||||
"settlementDataDecimals": 5,
|
||||
"dataSourceSpecForSettlementData": {
|
||||
"signers": [
|
||||
{
|
||||
"signer": {
|
||||
"__typename": "ETHAddress",
|
||||
"address": "0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC"
|
||||
"instrument": {
|
||||
"code": "TEST.24h",
|
||||
"future": {
|
||||
"quoteName": "fUSDC",
|
||||
"settlementDataDecimals": 5,
|
||||
"dataSourceSpecForSettlementData": {
|
||||
"signers": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "prices.ETH.value",
|
||||
"type": "TYPE_INTEGER"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_GREATER_THAN",
|
||||
"value": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"dataSourceSpecForTradingTermination": {
|
||||
"signers": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "prices.BTC.value",
|
||||
"type": "TYPE_INTEGER"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_GREATER_THAN",
|
||||
"value": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"dataSourceSpecForTradingTermination": {
|
||||
"signers": [
|
||||
{
|
||||
"signer": {
|
||||
"__typename": "ETHAddress",
|
||||
"address": "0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC"
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "trading.terminated.ETH5",
|
||||
"type": "TYPE_BOOLEAN"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
|
||||
"value": "1648684800000000000"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "vegaprotocol.builtin.timestamp",
|
||||
"type": "TYPE_TIMESTAMP"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
|
||||
"value": "1648684800000000000"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"dataSourceSpecBinding": {
|
||||
"settlementPriceProperty": "prices.BTC.value",
|
||||
"tradingTerminationProperty": "vegaprotocol.builtin.timestamp"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"dataSourceSpecBinding": {
|
||||
"settlementPriceProperty": "prices.ETH.value",
|
||||
"tradingTerminationProperty": "trading.terminated.ETH5"
|
||||
}
|
||||
},
|
||||
"metadata": ["sector:energy", "sector:food", "source:docs.vega.xyz"],
|
||||
"priceMonitoringParameters": {
|
||||
"triggers": [
|
||||
{
|
||||
"horizon": "43200",
|
||||
"probability": "0.9999999",
|
||||
"auctionExtension": "600"
|
||||
}
|
||||
]
|
||||
},
|
||||
"logNormal": {
|
||||
"tau": 0.0001140771161,
|
||||
"riskAversionParameter": 0.001,
|
||||
"params": {
|
||||
"mu": 0,
|
||||
"r": 0.016,
|
||||
"sigma": 0.3
|
||||
}
|
||||
},
|
||||
"metadata": ["sector:energy", "sector:food", "source:docs.vega.xyz"],
|
||||
"priceMonitoringParameters": {
|
||||
"triggers": [
|
||||
{
|
||||
"horizon": "43200",
|
||||
"probability": "0.9999999",
|
||||
"auctionExtension": "600"
|
||||
}
|
||||
]
|
||||
},
|
||||
"logNormal": {
|
||||
"tau": 0.0001140771161,
|
||||
"riskAversionParameter": 0.001,
|
||||
"params": {
|
||||
"mu": 0,
|
||||
"r": 0.016,
|
||||
"sigma": 0.3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
123
|
||||
0123245
|
||||
|
||||
@@ -11,6 +11,7 @@ const newProposalSubmitButton = '[data-testid="proposal-submit"]';
|
||||
const dialogCloseButton = '[data-testid="dialog-close"]';
|
||||
const viewProposalButton = '[data-testid="view-proposal-btn"]';
|
||||
const openProposals = '[data-testid="open-proposals"]';
|
||||
const closedProposals = '[data-testid="closed-proposals"]';
|
||||
const proposalVoteProgressForPercentage =
|
||||
'[data-testid="vote-progress-indicator-percentage-for"]';
|
||||
const proposalVoteProgressAgainstPercentage =
|
||||
@@ -22,7 +23,9 @@ const proposalVoteProgressAgainstTokens =
|
||||
const changeVoteButton = '[data-testid="change-vote-button"]';
|
||||
const proposalDetailsTitle = '[data-testid="proposal-title"]';
|
||||
const proposalDetailsDescription = '[data-testid="proposal-description"]';
|
||||
const proposalStatus = '[data-testid="proposal-status"]';
|
||||
const rawProposalData = '[data-testid="proposal-data"]';
|
||||
const votesTable = '[data-testid="votes-table"]';
|
||||
const minVoteButton = '[data-testid="min-vote"]';
|
||||
const maxVoteButton = '[data-testid="max-vote"]';
|
||||
const voteButtons = '[data-testid="vote-buttons"]';
|
||||
@@ -37,6 +40,10 @@ const txTimeout = Cypress.env('txTimeout');
|
||||
const epochTimeout = Cypress.env('epochTimeout');
|
||||
const proposalTimeout = { timeout: 14000 };
|
||||
|
||||
const minCloseDays = 2;
|
||||
const maxCloseDays = 3;
|
||||
const requiredParticipation = 0.001;
|
||||
|
||||
const governanceProposalType = {
|
||||
NETWORK_PARAMETER: 'Network parameter',
|
||||
NEW_MARKET: 'New market',
|
||||
@@ -65,21 +72,6 @@ context(
|
||||
network_parameters['governance.proposal.freeform.requiredMajority'] *
|
||||
100
|
||||
).as('requiredMajority');
|
||||
cy.wrap(
|
||||
network_parameters[
|
||||
'governance.proposal.freeform.requiredParticipation'
|
||||
] * 100
|
||||
).as('requiredParticipation');
|
||||
cy.wrap(
|
||||
network_parameters['governance.proposal.freeform.minClose'].split(
|
||||
'h'
|
||||
)[0] / 24
|
||||
).as('minCloseDays');
|
||||
cy.wrap(
|
||||
network_parameters['governance.proposal.freeform.maxClose'].split(
|
||||
'h'
|
||||
)[0] / 24
|
||||
).as('maxCloseDays');
|
||||
cy.wrap(
|
||||
network_parameters['governance.proposal.freeform.minClose'].split(
|
||||
'h'
|
||||
@@ -108,21 +100,6 @@ context(
|
||||
0.00001,
|
||||
'Asserting that value is at least 0.00001 for network parameter minVoterBalance'
|
||||
);
|
||||
assert.isAtLeast(
|
||||
parseFloat(this.requiredParticipation),
|
||||
0.00001,
|
||||
'Asserting that value is at least 0.00001 for network parameter requiredParticipation'
|
||||
);
|
||||
assert.isAtLeast(
|
||||
parseInt(this.minCloseDays),
|
||||
1,
|
||||
'Asserting that value is at least 1 for network parameter minCloseDays'
|
||||
);
|
||||
assert.isAtLeast(
|
||||
parseInt(this.maxCloseDays),
|
||||
parseInt(this.minCloseDays + 1),
|
||||
'Asserting that network parameter maxCloseDays is at least 1 day higher than minCloseDays'
|
||||
);
|
||||
// workaround for first eth tx hanging
|
||||
associateTokenStartOfTests();
|
||||
}
|
||||
@@ -182,10 +159,10 @@ context(
|
||||
this.minProposerBalance
|
||||
);
|
||||
let proposalDays = [
|
||||
this.minCloseDays + 1,
|
||||
this.maxCloseDays,
|
||||
this.minCloseDays + 3,
|
||||
this.minCloseDays + 2,
|
||||
minCloseDays + 1,
|
||||
maxCloseDays,
|
||||
minCloseDays + 3,
|
||||
minCloseDays + 2,
|
||||
];
|
||||
for (var index = 0; index < proposalDays.length; index++) {
|
||||
cy.go_to_make_new_proposal(governanceProposalType.RAW);
|
||||
@@ -227,6 +204,7 @@ context(
|
||||
cy.navigate_to('validators');
|
||||
cy.click_on_validator_from_list(0);
|
||||
cy.staking_validator_page_add_stake('2');
|
||||
cy.close_staking_dialog();
|
||||
|
||||
cy.get(vegaWalletStakedBalances, txTimeout).should('contain', '2');
|
||||
|
||||
@@ -297,10 +275,12 @@ context(
|
||||
});
|
||||
});
|
||||
|
||||
// Skipping test due to bug: #1320
|
||||
it.skip('Newly created freeform proposals list - shows proposal participation - both met and not', function () {
|
||||
createFreeformProposal(this.minProposerBalance);
|
||||
cy.get_submitted_proposal_from_proposal_list()
|
||||
// 3001-VOTE-071
|
||||
it('Newly created freeform proposals list - shows proposal participation - both met and not', function () {
|
||||
const proposalTitle = generateProposalTitle();
|
||||
createFreeformProposal(this.minProposerBalance, proposalTitle);
|
||||
|
||||
cy.get_submitted_proposal_from_proposal_list(proposalTitle)
|
||||
.as('submittedProposal')
|
||||
.within(() => {
|
||||
// 3001-VOTE-039
|
||||
@@ -312,7 +292,7 @@ context(
|
||||
.invoke('text')
|
||||
.then((totalSupply) => {
|
||||
let tokensRequiredToAchieveResult = parseFloat(
|
||||
(totalSupply.replace(/,/g, '') * this.requiredParticipation) / 100
|
||||
(totalSupply.replace(/,/g, '') * requiredParticipation) / 100
|
||||
).toFixed(2);
|
||||
cy.ensure_specified_unstaked_tokens_are_associated(
|
||||
tokensRequiredToAchieveResult
|
||||
@@ -321,16 +301,17 @@ context(
|
||||
cy.get('@submittedProposal').within(() =>
|
||||
cy.get(viewProposalButton).click()
|
||||
);
|
||||
cy.get_proposal_information_from_table('Participation met')
|
||||
cy.get_proposal_information_from_table('Token participation met')
|
||||
.contains('👍')
|
||||
.should('be.visible');
|
||||
cy.navigate_to('proposals');
|
||||
cy.get('@submittedProposal').within(() =>
|
||||
cy.get(voteStatus).should('have.text', 'Participation met')
|
||||
cy.get(voteStatus).should('have.text', 'Set to pass')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// 3001-VOTE-055
|
||||
it('Newly created raw proposal details - shows proposal title and full description', function () {
|
||||
createRawProposal(this.minProposerBalance);
|
||||
cy.get('@rawProposal').then((rawProposal) => {
|
||||
@@ -357,6 +338,7 @@ context(
|
||||
});
|
||||
});
|
||||
|
||||
// 3001-VOTE-043
|
||||
it('Newly created freeform proposal details - shows proposed and closing dates', function () {
|
||||
const closingVoteHrs = '72';
|
||||
const proposalTitle = generateProposalTitle();
|
||||
@@ -399,6 +381,7 @@ context(
|
||||
it('Newly created proposal details - shows default status set to fail', function () {
|
||||
// 3001-VOTE-037
|
||||
// 3001-VOTE-040
|
||||
// 3001-VOTE-067
|
||||
createRawProposal(this.minProposerBalance);
|
||||
cy.get('@rawProposal').then((rawProposal) => {
|
||||
cy.get_submitted_proposal_from_proposal_list(
|
||||
@@ -413,15 +396,17 @@ context(
|
||||
.should('be.visible');
|
||||
// 3001-VOTE-062
|
||||
// 3001-VOTE-040
|
||||
// 3001-VOTE-070
|
||||
cy.get_proposal_information_from_table('Token majority met')
|
||||
.contains('👎')
|
||||
.should('be.visible');
|
||||
// 3001-VOTE-068
|
||||
cy.get_proposal_information_from_table('Token participation met')
|
||||
.contains('👎')
|
||||
.should('be.visible');
|
||||
});
|
||||
|
||||
// 3001-VOTE-080 3001-VOTE-090
|
||||
// 3001-VOTE-080 3001-VOTE-090 3001-VOTE-069 3001-VOTE-072 3001-VOTE-073
|
||||
it('Newly created proposal details - ability to vote for and against proposal - with minimum required tokens associated', function () {
|
||||
createRawProposal(this.minProposerBalance);
|
||||
cy.get('@rawProposal').then((rawProposal) => {
|
||||
@@ -446,7 +431,7 @@ context(
|
||||
.contains(votedDate)
|
||||
.should('be.visible');
|
||||
});
|
||||
cy.get(proposalVoteProgressForPercentage)
|
||||
cy.get(proposalVoteProgressForPercentage) // 3001-VOTE-072
|
||||
.contains('100.00%')
|
||||
.and('be.visible');
|
||||
cy.get(proposalVoteProgressAgainstPercentage)
|
||||
@@ -466,10 +451,10 @@ context(
|
||||
.and('be.visible');
|
||||
// 3001-VOTE-061
|
||||
cy.get_proposal_information_from_table('Participation required')
|
||||
.contains(`${this.requiredParticipation}%`)
|
||||
.contains(`${requiredParticipation}%`)
|
||||
.should('be.visible');
|
||||
// 3001-VOTE-066
|
||||
cy.get_proposal_information_from_table('Majority Required')
|
||||
cy.get_proposal_information_from_table('Majority Required') // 3001-VOTE-073
|
||||
.contains(`${parseFloat(this.requiredMajority).toFixed(2)}%`)
|
||||
.should('be.visible');
|
||||
cy.get_proposal_information_from_table('Number of voting parties')
|
||||
@@ -512,7 +497,7 @@ context(
|
||||
.invoke('text')
|
||||
.then((totalSupply) => {
|
||||
let tokensRequiredToAchieveResult = parseFloat(
|
||||
(totalSupply.replace(/,/g, '') * this.requiredParticipation) / 100
|
||||
(totalSupply.replace(/,/g, '') * requiredParticipation) / 100
|
||||
).toFixed(2);
|
||||
cy.ensure_specified_unstaked_tokens_are_associated(
|
||||
tokensRequiredToAchieveResult
|
||||
@@ -536,6 +521,11 @@ context(
|
||||
cy.get(proposalVoteProgressAgainstTokens)
|
||||
.contains('0.00')
|
||||
.and('be.visible');
|
||||
cy.get_proposal_information_from_table(
|
||||
'Total tokens voted percentage'
|
||||
)
|
||||
.should('have.text', '0.00%')
|
||||
.and('be.visible');
|
||||
cy.get_proposal_information_from_table('Tokens for proposal')
|
||||
.should('have.text', tokensRequiredToAchieveResult)
|
||||
.and('be.visible');
|
||||
@@ -588,6 +578,7 @@ context(
|
||||
);
|
||||
});
|
||||
|
||||
// 3001-VOTE-006
|
||||
it('Creating a proposal - proposal rejected - able to access rejected proposals', function () {
|
||||
cy.ensure_specified_unstaked_tokens_are_associated(
|
||||
this.minProposerBalance
|
||||
@@ -649,6 +640,7 @@ context(
|
||||
cy.get(dialogCloseButton).click();
|
||||
});
|
||||
|
||||
// 3002-PROP-009
|
||||
it('Unable to create a proposal - when some but not enough tokens are associated', function () {
|
||||
cy.ensure_specified_unstaked_tokens_are_associated(
|
||||
this.minProposerBalance - 0.000001
|
||||
@@ -669,7 +661,7 @@ context(
|
||||
});
|
||||
|
||||
it('Unable to create a freeform proposal - when json parent section contains unexpected field', function () {
|
||||
// 3001-VOTE-038
|
||||
// 3001-VOTE-038 3002-PROP-013 3002-PROP-014
|
||||
cy.ensure_specified_unstaked_tokens_are_associated(
|
||||
this.minProposerBalance
|
||||
);
|
||||
@@ -695,6 +687,9 @@ context(
|
||||
'Invalid params: the transaction is malformed'
|
||||
);
|
||||
cy.get(dialogCloseButton).click();
|
||||
cy.get(rawProposalData)
|
||||
.invoke('val')
|
||||
.should('contain', "i shouldn't be here");
|
||||
});
|
||||
|
||||
it('Unable to create a freeform proposal - when json terms section contains unexpected field', function () {
|
||||
@@ -792,6 +787,107 @@ context(
|
||||
cy.contains('You voted: Against').should('be.visible');
|
||||
});
|
||||
|
||||
// 3001-VOTE-006
|
||||
it('Able to view enacted proposal', function () {
|
||||
cy.createMarket();
|
||||
cy.reload();
|
||||
cy.wait_for_spinner();
|
||||
cy.get(closedProposals).within(() => {
|
||||
cy.get(proposalDetailsTitle).should(
|
||||
'have.text',
|
||||
'Add Lorem Ipsum market'
|
||||
);
|
||||
cy.get(proposalStatus).should('have.text', 'Enacted ');
|
||||
cy.get(viewProposalButton).click();
|
||||
});
|
||||
cy.getByTestId('proposal-type').should('have.text', 'New market');
|
||||
cy.get_proposal_information_from_table('State')
|
||||
.contains('Enacted')
|
||||
.and('be.visible');
|
||||
cy.get(votesTable).within(() => {
|
||||
cy.contains('Vote passed.').should('be.visible');
|
||||
cy.contains('Voting has ended.').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
// 3001-VOTE-047
|
||||
it('Able to enact freeform proposal', function () {
|
||||
const proposalTitle = 'Add New free form proposal with short enactment';
|
||||
cy.ensure_specified_unstaked_tokens_are_associated(
|
||||
this.minProposerBalance
|
||||
);
|
||||
cy.sendWalletTxFreeFormProposal();
|
||||
cy.navigate_to('proposals');
|
||||
cy.reload();
|
||||
cy.wait_for_spinner();
|
||||
cy.contains(proposalTitle)
|
||||
.parentsUntil('[data-testid="proposals-list-item"]')
|
||||
.within(() => cy.get(viewProposalButton).click());
|
||||
cy.get_proposal_information_from_table('State')
|
||||
.contains('Open')
|
||||
.and('be.visible');
|
||||
cy.vote_for_proposal('for');
|
||||
cy.get_proposal_information_from_table('State')
|
||||
.contains('Enacted', epochTimeout)
|
||||
.and('be.visible');
|
||||
});
|
||||
|
||||
// 3001-VOTE-046 3001-VOTE-044 3001-VOTE-074 3001-VOTE-074
|
||||
it('Able to enact proposal by voting', function () {
|
||||
const proposalTitle = 'Add New proposal with short enactment';
|
||||
cy.ensure_specified_unstaked_tokens_are_associated(
|
||||
this.minProposerBalance
|
||||
);
|
||||
cy.sendWalletTxUpdateNetworkProposal();
|
||||
cy.navigate_to('proposals');
|
||||
cy.reload();
|
||||
cy.wait_for_spinner();
|
||||
cy.contains(proposalTitle)
|
||||
.parentsUntil('[data-testid="proposals-list-item"]')
|
||||
.within(() => cy.get(viewProposalButton).click());
|
||||
cy.get_proposal_information_from_table('State')
|
||||
.contains('Open')
|
||||
.and('be.visible');
|
||||
cy.vote_for_proposal('for');
|
||||
cy.get_proposal_information_from_table('State') // 3001-VOTE-047
|
||||
.contains('Passed', txTimeout)
|
||||
.and('be.visible');
|
||||
cy.get_proposal_information_from_table('State')
|
||||
.contains('Enacted', epochTimeout)
|
||||
.and('be.visible');
|
||||
cy.get(votesTable).within(() => {
|
||||
cy.contains('Vote passed.').should('be.visible');
|
||||
cy.contains('Voting has ended.').should('be.visible');
|
||||
});
|
||||
cy.get(proposalVoteProgressForPercentage)
|
||||
.contains('100.00%')
|
||||
.and('be.visible');
|
||||
});
|
||||
|
||||
// 3001-VOTE-048 3001-VOTE-049
|
||||
it('Able to fail proposal due to lack of participation', function () {
|
||||
const proposalTitle = 'Add New free form proposal with short enactment';
|
||||
cy.ensure_specified_unstaked_tokens_are_associated(
|
||||
this.minProposerBalance
|
||||
);
|
||||
cy.sendWalletTxFreeFormProposal();
|
||||
cy.navigate_to('proposals');
|
||||
cy.reload();
|
||||
cy.wait_for_spinner();
|
||||
cy.contains(proposalTitle)
|
||||
.parentsUntil('[data-testid="proposals-list-item"]')
|
||||
.within(() => cy.get(viewProposalButton).click());
|
||||
cy.get_proposal_information_from_table('State')
|
||||
.contains('Open')
|
||||
.and('be.visible');
|
||||
cy.get_proposal_information_from_table('State') // 3001-VOTE-047
|
||||
.contains('Declined', txTimeout)
|
||||
.and('be.visible');
|
||||
cy.get_proposal_information_from_table('Rejection reason')
|
||||
.contains('PROPOSAL_ERROR_PARTICIPATION_THRESHOLD_NOT_REACHED')
|
||||
.and('be.visible');
|
||||
});
|
||||
|
||||
function createRawProposal(proposerBalance) {
|
||||
if (proposerBalance)
|
||||
cy.ensure_specified_unstaked_tokens_are_associated(proposerBalance);
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
const proposalListItem = '[data-testid="proposals-list-item"]';
|
||||
const openProposals = '[data-testid="open-proposals"]';
|
||||
const proposalType = '[data-testid="proposal-type"]';
|
||||
const proposalDetails = '[data-testid="proposal-details"]';
|
||||
const newProposalSubmitButton = '[data-testid="proposal-submit"]';
|
||||
const proposalVoteDeadline = '[data-testid="proposal-vote-deadline"]';
|
||||
const proposalValidationDeadline =
|
||||
@@ -209,18 +213,15 @@ context(
|
||||
);
|
||||
});
|
||||
|
||||
// skipped because no markets available to select in capsule
|
||||
it.skip('Able to submit update market proposal', function () {
|
||||
const marketId =
|
||||
'315a8e48db0a292c92b617264728048c82c20efc922c75fd292fc54e5c727c81';
|
||||
cy.go_to_make_new_proposal(governanceProposalType.UPDATE_MARKET);
|
||||
cy.get(newProposalTitle).type('Test update asset proposal');
|
||||
cy.get(newProposalTitle).type('Test update market proposal');
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
cy.get(proposalMarketSelect).select(marketId);
|
||||
cy.get(proposalMarketSelect).select('Test market 1');
|
||||
cy.get('[data-testid="update-market-details"]').within(() => {
|
||||
cy.get('dd').eq(0).should('have.text', 'Oranges Daily');
|
||||
cy.get('dd').eq(1).should('have.text', 'ORANGES.24h');
|
||||
cy.get('dd').eq(2).should('have.text', marketId);
|
||||
cy.get('dd').eq(0).should('have.text', 'Test market 1');
|
||||
cy.get('dd').eq(1).should('have.text', 'TEST.24h');
|
||||
cy.get('dd').eq(2).should('not.be.empty');
|
||||
});
|
||||
cy.fixture('/proposals/update-market').then((updateMarketProposal) => {
|
||||
let newUpdateMarketProposal = JSON.stringify(updateMarketProposal);
|
||||
@@ -281,12 +282,28 @@ context(
|
||||
});
|
||||
|
||||
it('Able to submit update asset proposal using min deadline', function () {
|
||||
const assetId =
|
||||
'ebcd94151ae1f0d39a4bde3b21a9c7ae81a80ea4352fb075a92e07608d9c953d';
|
||||
|
||||
cy.go_to_make_new_proposal(governanceProposalType.UPDATE_ASSET);
|
||||
enterUpdateAssetProposalDetails();
|
||||
cy.get(minVoteDeadline).click();
|
||||
cy.get(minEnactDeadline).click();
|
||||
cy.get(newProposalSubmitButton).should('be.visible').click();
|
||||
cy.wait_for_proposal_submitted();
|
||||
cy.navigate_to('proposals');
|
||||
cy.get(openProposals).within(() => {
|
||||
cy.get(proposalType)
|
||||
.contains('Update asset')
|
||||
.parentsUntil(proposalListItem)
|
||||
.within(() => {
|
||||
cy.get(proposalDetails).should('contain.text', assetId); // 3001-VOTE-029
|
||||
cy.getByTestId('view-proposal-btn').click();
|
||||
});
|
||||
});
|
||||
cy.get_proposal_information_from_table('Proposed enactment') // 3001-VOTE-044
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
});
|
||||
|
||||
it('Able to submit update asset proposal using max deadline', function () {
|
||||
|
||||
@@ -20,7 +20,10 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
|
||||
|
||||
it('Able to connect public key via wallet', function () {
|
||||
verifyConnectedToPubKey();
|
||||
cy.getByTestId('currency-title').should('contain.text', 'USDC (fake)');
|
||||
cy.getByTestId('currency-title', Cypress.env('epochTimeout')).should(
|
||||
'contain.text',
|
||||
'USDC (fake)'
|
||||
);
|
||||
});
|
||||
|
||||
it('Able to connect public key using url', function () {
|
||||
|
||||
@@ -46,6 +46,6 @@ Cypress.Commands.add('verify_page_header', (text) => {
|
||||
});
|
||||
|
||||
Cypress.Commands.add('wait_for_spinner', () => {
|
||||
cy.get(navigation.pageSpinner).should('exist');
|
||||
cy.get(navigation.pageSpinner, Cypress.env('epochTimeout')).should('exist');
|
||||
cy.get(navigation.pageSpinner, { timeout: 20000 }).should('not.exist');
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import './governance.functions.js';
|
||||
import './wallet-eth.functions.js';
|
||||
import './wallet-teardown.functions.js';
|
||||
import './wallet-vega.functions.js';
|
||||
import './proposal.functions.js';
|
||||
import registerCypressGrep from '@cypress/grep';
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import { chainIdQuery, statisticsQuery } from '@vegaprotocol/mock';
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import { addSeconds, millisecondsToSeconds } from 'date-fns';
|
||||
|
||||
const walletName = Cypress.env('vegaWalletName');
|
||||
const walletPubKey = Cypress.env('vegaWalletPublicKey');
|
||||
const walletLocation = Cypress.env('vegaWalletLocation');
|
||||
const walletPassphraseFile = './src/fixtures/wallet/passphrase';
|
||||
|
||||
Cypress.Commands.add('sendWalletTxUpdateNetworkProposal', () => {
|
||||
const MIN_CLOSE_SEC = 8;
|
||||
const MIN_ENACT_SEC = 5;
|
||||
|
||||
const closingDate = addSeconds(new Date(), MIN_CLOSE_SEC);
|
||||
const enactmentDate = addSeconds(closingDate, MIN_ENACT_SEC);
|
||||
const closingTimestamp = millisecondsToSeconds(closingDate.getTime());
|
||||
const enactmentTimestamp = millisecondsToSeconds(enactmentDate.getTime());
|
||||
|
||||
cy.exec(
|
||||
`vegawallet transaction send --wallet ${walletName} --pubkey ${walletPubKey} -p ${walletPassphraseFile} --network DV '{
|
||||
"proposalSubmission": {
|
||||
"rationale": {
|
||||
"title": "Add New proposal with short enactment",
|
||||
"description": "E2E enactment test"
|
||||
},
|
||||
"terms": {
|
||||
"updateNetworkParameter": {
|
||||
"changes": {
|
||||
"key": "governance.proposal.updateNetParam.minProposerBalance",
|
||||
"value": "2"
|
||||
}
|
||||
},
|
||||
"closingTimestamp": ${closingTimestamp},
|
||||
"enactmentTimestamp": ${enactmentTimestamp}
|
||||
}
|
||||
}
|
||||
}' --home ${walletLocation}`,
|
||||
{ failOnNonZeroExit: false }
|
||||
)
|
||||
.its('stderr')
|
||||
.should('contain', '');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('sendWalletTxUpdateAssetProposal', () => {
|
||||
const MIN_CLOSE_SEC = 8;
|
||||
const MIN_ENACT_SEC = 5;
|
||||
|
||||
const closingDate = addSeconds(new Date(), MIN_CLOSE_SEC);
|
||||
const enactmentDate = addSeconds(closingDate, MIN_ENACT_SEC);
|
||||
const closingTimestamp = millisecondsToSeconds(closingDate.getTime());
|
||||
const enactmentTimestamp = millisecondsToSeconds(enactmentDate.getTime());
|
||||
|
||||
cy.exec(
|
||||
`vegawallet transaction send --wallet ${walletName} --pubkey ${walletPubKey} -p ${walletPassphraseFile} --network DV '{
|
||||
"proposalSubmission": {
|
||||
"rationale": {
|
||||
"title": "Update Asset set to fail",
|
||||
"description": "E2E fail test"
|
||||
},
|
||||
"terms": {
|
||||
"updateAsset": {
|
||||
"assetId": "ebcd94151ae1f0d39a4bde3b21a9c7ae81a80ea4352fb075a92e07608d9c953d",
|
||||
"changes": {
|
||||
"quantum": "1",
|
||||
"erc20": {
|
||||
"withdrawThreshold": "10",
|
||||
"lifetimeLimit": "10"
|
||||
}
|
||||
}
|
||||
},
|
||||
"closingTimestamp": ${closingTimestamp},
|
||||
"enactmentTimestamp": ${enactmentTimestamp}
|
||||
}
|
||||
}
|
||||
}' --home ${walletLocation}`,
|
||||
{ failOnNonZeroExit: false }
|
||||
)
|
||||
.its('stderr')
|
||||
.should('contain', '');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('sendWalletTxFreeFormProposal', () => {
|
||||
const MIN_CLOSE_SEC = 5;
|
||||
|
||||
const closingDate = addSeconds(new Date(), MIN_CLOSE_SEC);
|
||||
const closingTimestamp = millisecondsToSeconds(closingDate.getTime());
|
||||
|
||||
cy.exec(
|
||||
`vegawallet transaction send --wallet ${walletName} --pubkey ${walletPubKey} -p ${walletPassphraseFile} --network DV '{
|
||||
"proposalSubmission": {
|
||||
"rationale": {
|
||||
"title": "Add New free form proposal with short enactment",
|
||||
"description": "E2E enactment test"
|
||||
},
|
||||
"terms": {
|
||||
"newFreeform": {},
|
||||
"closingTimestamp": ${closingTimestamp}
|
||||
}
|
||||
}
|
||||
}' --home ${walletLocation}`,
|
||||
{ failOnNonZeroExit: false }
|
||||
)
|
||||
.its('stderr')
|
||||
.should('contain', '');
|
||||
});
|
||||
@@ -72,8 +72,8 @@ export const LockedProgress = ({
|
||||
unlocked,
|
||||
leftLabel,
|
||||
rightLabel,
|
||||
leftColor = Colors.vega.pink,
|
||||
rightColor = Colors.vega.green,
|
||||
leftColor = Colors.vega.pink.DEFAULT,
|
||||
rightColor = Colors.vega.green.DEFAULT,
|
||||
decimals = 2,
|
||||
}: LockedProgressProps) => {
|
||||
const lockedPercentage = React.useMemo(() => {
|
||||
|
||||
@@ -63,7 +63,8 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
|
||||
signer || provider
|
||||
),
|
||||
vesting: new TokenVesting(
|
||||
config.token_vesting_contract.address,
|
||||
config.token_vesting_contract?.address ||
|
||||
'0xadFcb7f93a24F8743a8e548d74d2ecB373c92866',
|
||||
signer || provider
|
||||
),
|
||||
claim: new Claim(ENV.addresses.claimAddress, signer || provider),
|
||||
|
||||
@@ -30,9 +30,12 @@ export function useAnimateValue(
|
||||
) {
|
||||
elRef.current?.animate(
|
||||
[
|
||||
{ backgroundColor: customColors.vega.pink, color: colors.white },
|
||||
{
|
||||
backgroundColor: customColors.vega.pink,
|
||||
backgroundColor: customColors.vega.pink.DEFAULT,
|
||||
color: colors.white,
|
||||
},
|
||||
{
|
||||
backgroundColor: customColors.vega.pink.DEFAULT,
|
||||
color: colors.white,
|
||||
offset: 0.8,
|
||||
},
|
||||
@@ -53,11 +56,11 @@ export function useAnimateValue(
|
||||
elRef.current?.animate(
|
||||
[
|
||||
{
|
||||
backgroundColor: customColors.vega.green,
|
||||
backgroundColor: customColors.vega.green.DEFAULT,
|
||||
color: colors.white,
|
||||
},
|
||||
{
|
||||
backgroundColor: customColors.vega.green,
|
||||
backgroundColor: customColors.vega.green.DEFAULT,
|
||||
color: colors.white,
|
||||
offset: 0.8,
|
||||
},
|
||||
|
||||
@@ -102,7 +102,7 @@ export const VoteDetails = ({
|
||||
</table>
|
||||
</section>
|
||||
)}
|
||||
<section>
|
||||
<section data-testid="votes-table">
|
||||
<SubHeading title={t('tokenVotes')} />
|
||||
<p>
|
||||
<span>
|
||||
|
||||
@@ -67,10 +67,14 @@ export const TokenDetails = ({
|
||||
data-testid="token-contract"
|
||||
title={t('View on Etherscan (opens in a new tab)')}
|
||||
className="font-mono text-white text-right"
|
||||
href={`${ETHERSCAN_URL}/address/${config.token_vesting_contract.address}`}
|
||||
href={`${ETHERSCAN_URL}/address/${
|
||||
config.token_vesting_contract?.address ||
|
||||
'0xadFcb7f93a24F8743a8e548d74d2ecB373c92866'
|
||||
}`}
|
||||
target="_blank"
|
||||
>
|
||||
{config.token_vesting_contract.address}
|
||||
{config.token_vesting_contract?.address ||
|
||||
'0xadFcb7f93a24F8743a8e548d74d2ecB373c92866'}
|
||||
</Link>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
|
||||
@@ -29,7 +29,7 @@ export const TrancheProgress = ({
|
||||
<span className="tranches__progress-title">{t('Locked')}</span>
|
||||
<ProgressBar
|
||||
width={220}
|
||||
color={Colors.vega.pink}
|
||||
color={Colors.vega.pink.DEFAULT}
|
||||
percentage={lockedPercentage}
|
||||
/>
|
||||
<span className="tranches__progress-numbers">
|
||||
@@ -40,7 +40,7 @@ export const TrancheProgress = ({
|
||||
<span className="tranches__progress-title">{t('Redeemed')}</span>
|
||||
<ProgressBar
|
||||
width={220}
|
||||
color={Colors.vega.green}
|
||||
color={Colors.vega.green.DEFAULT}
|
||||
percentage={removedPercentage}
|
||||
/>
|
||||
<span className="tranches__progress-numbers">
|
||||
|
||||
@@ -34,10 +34,10 @@ export const VestingChart = () => {
|
||||
<AreaChart data={data}>
|
||||
<defs>
|
||||
{[
|
||||
['pink', Colors.vega.pink],
|
||||
['green', Colors.vega.green],
|
||||
['pink', Colors.vega.pink.DEFAULT],
|
||||
['green', Colors.vega.green.DEFAULT],
|
||||
['orange', Colors.warning],
|
||||
['yellow', Colors.vega.yellow],
|
||||
['yellow', Colors.vega.yellow.DEFAULT],
|
||||
].map(([key, color]) => (
|
||||
<linearGradient key={key} id={key} x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor={color} stopOpacity={0.85} />
|
||||
@@ -97,7 +97,7 @@ export const VestingChart = () => {
|
||||
dot={false}
|
||||
type="linear"
|
||||
dataKey="team"
|
||||
stroke={Colors.vega.pink}
|
||||
stroke={Colors.vega.pink.DEFAULT}
|
||||
fill="url(#pink)"
|
||||
yAxisId={0}
|
||||
strokeWidth={2}
|
||||
@@ -109,7 +109,7 @@ export const VestingChart = () => {
|
||||
dot={false}
|
||||
type="monotone"
|
||||
dataKey="earlyInvestors"
|
||||
stroke={Colors.vega.green}
|
||||
stroke={Colors.vega.green.DEFAULT}
|
||||
fill="url(#green)"
|
||||
yAxisId={0}
|
||||
strokeWidth={2}
|
||||
@@ -121,7 +121,7 @@ export const VestingChart = () => {
|
||||
dot={false}
|
||||
type="monotone"
|
||||
dataKey="publicSale"
|
||||
stroke={Colors.vega.yellow}
|
||||
stroke={Colors.vega.yellow.DEFAULT}
|
||||
fill="url(#yellow)"
|
||||
yAxisId={0}
|
||||
strokeWidth={2}
|
||||
|
||||
@@ -5,10 +5,12 @@ import { Heading } from '../../components/heading';
|
||||
import { SplashLoader } from '../../components/splash-loader';
|
||||
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
||||
import {
|
||||
useWithdrawals,
|
||||
withdrawalProvider,
|
||||
useWithdrawalDialog,
|
||||
WithdrawalsTable,
|
||||
} from '@vegaprotocol/withdraws';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||
import type { RouteChildProps } from '../index';
|
||||
|
||||
@@ -29,7 +31,12 @@ const Withdrawals = ({ name }: RouteChildProps) => {
|
||||
const WithdrawPendingContainer = () => {
|
||||
const openWithdrawalDialog = useWithdrawalDialog((state) => state.open);
|
||||
const { t } = useTranslation();
|
||||
const { data, loading, error } = useWithdrawals();
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { data, loading, error } = useDataProvider({
|
||||
dataProvider: withdrawalProvider,
|
||||
variables: { partyId: pubKey || '' },
|
||||
skip: !pubKey,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
|
||||
@@ -21,15 +21,21 @@ const orderUpdatedAt = 'updatedAt';
|
||||
const assetSelectField = 'select[name="asset"]';
|
||||
const amountField = 'input[name="amount"]';
|
||||
const txTimeout = Cypress.env('txTimeout');
|
||||
const btcName = 'BTC (local)';
|
||||
const sepoliaUrl = Cypress.env('ETHERSCAN_URL');
|
||||
const btcName =
|
||||
'BTC (local)5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c - tBTC';
|
||||
const btcSymbol = 'tBTC';
|
||||
const usdcSymbol = 'fUSDC';
|
||||
const toastContent = 'toast-content';
|
||||
const ordersTab = 'Orders';
|
||||
const depositsTab = 'Deposits';
|
||||
const toastCloseBtn = 'toast-close';
|
||||
const price = '390';
|
||||
const size = '0.0005';
|
||||
const newPrice = '200';
|
||||
|
||||
// TODO: ensure this test runs only if capsule is running via workflow
|
||||
// Because the tests are run on a live network to optimize time, the tests are interdependent and must be run in the given order.
|
||||
describe('capsule', { tags: '@slow' }, () => {
|
||||
before(() => {
|
||||
cy.createMarket();
|
||||
@@ -50,8 +56,8 @@ describe('capsule', { tags: '@slow' }, () => {
|
||||
marketId: market.id,
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
size: '0.0005',
|
||||
price: '390',
|
||||
size: size,
|
||||
price: price,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||
};
|
||||
const rawPrice = removeDecimal(order.price, market.decimalPlaces);
|
||||
@@ -64,7 +70,8 @@ describe('capsule', { tags: '@slow' }, () => {
|
||||
|
||||
cy.getByTestId(toastContent).should(
|
||||
'contain.text',
|
||||
`ConfirmedYour transaction has been confirmed View in block explorerSubmit order - activeTEST.24h+0.0005 @ 390.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();
|
||||
// orderbook cells are keyed by price level
|
||||
@@ -75,7 +82,7 @@ describe('capsule', { tags: '@slow' }, () => {
|
||||
.should('contain.text', rawSize);
|
||||
|
||||
cy.getByTestId(ordersTab).click();
|
||||
cy.getByTestId('edit').should('contain.text', 'Edit');
|
||||
cy.getByTestId('edit', txTimeout).should('contain.text', 'Edit');
|
||||
cy.getByTestId('tab-orders').within(() => {
|
||||
cy.get('.ag-center-cols-container')
|
||||
.children()
|
||||
@@ -113,35 +120,41 @@ describe('capsule', { tags: '@slow' }, () => {
|
||||
checkIfDataAndTimeOfCreationAndUpdateIsEqual(orderCreatedAt);
|
||||
});
|
||||
});
|
||||
//edit order
|
||||
});
|
||||
|
||||
it('can edit order', function () {
|
||||
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('200');
|
||||
cy.get('#limitPrice').focus().clear().type(newPrice);
|
||||
cy.getByTestId('edit-order').find('[type="submit"]').click();
|
||||
cy.getByTestId(toastContent).should(
|
||||
'contain.text',
|
||||
`ConfirmedYour transaction has been confirmed View in block explorerEdit order - activeTEST.24h+0.0005 @ 200.00 ${usdcSymbol}+0.0005 @ 200.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(ordersTab).click();
|
||||
cy.getByTestId(toastCloseBtn).click();
|
||||
cy.getByTestId(toastCloseBtn).click({ multiple: true });
|
||||
cy.get('.ag-center-cols-container')
|
||||
.children()
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get(`[col-id='${orderPrice}']`).then(($price) => {
|
||||
expect(parseFloat($price.text())).to.equal(parseFloat('200'));
|
||||
expect(parseFloat($price.text())).to.equal(parseFloat(newPrice));
|
||||
});
|
||||
checkIfDataAndTimeOfCreationAndUpdateIsEqual(orderUpdatedAt);
|
||||
});
|
||||
//cancel order
|
||||
});
|
||||
|
||||
it('can cancel order', function () {
|
||||
cy.getByTestId(ordersTab).click();
|
||||
cy.getByTestId('cancel').first().click();
|
||||
cy.getByTestId(toastContent).should(
|
||||
'contain.text',
|
||||
`ConfirmedYour transaction has been confirmed View in block explorerCancel order - cancelledTEST.24h+0.0005 @ 200.00 ${usdcSymbol}`
|
||||
`ConfirmedYour transaction has been confirmed View in block explorerCancel order - cancelledTEST.24h+${size} @ ${newPrice}.00 ${usdcSymbol}`,
|
||||
{ matchCase: false }
|
||||
);
|
||||
cy.getByTestId(toastCloseBtn).click();
|
||||
cy.getByTestId(toastCloseBtn).click({ multiple: true });
|
||||
|
||||
cy.getByTestId('tab-orders')
|
||||
.get('.ag-center-cols-container')
|
||||
@@ -151,7 +164,10 @@ describe('capsule', { tags: '@slow' }, () => {
|
||||
.should('contain.text', OrderStatusMapping.STATUS_CANCELLED);
|
||||
});
|
||||
|
||||
it('can deposit and withdrawal', function () {
|
||||
it('can deposit', function () {
|
||||
cy.visit('/#/portfolio');
|
||||
cy.get('main[data-testid="/portfolio"]').should('exist');
|
||||
|
||||
// 1001-DEPO-001
|
||||
// 1001-DEPO-002
|
||||
// 1001-DEPO-003
|
||||
@@ -160,27 +176,12 @@ describe('capsule', { tags: '@slow' }, () => {
|
||||
// 1001-DEPO-007
|
||||
// 1001-DEPO-008
|
||||
// 1001-DEPO-009
|
||||
// 1002-WITH-001
|
||||
// 1002-WITH-006
|
||||
// 1002-WITH-009
|
||||
// 002-WITH-011
|
||||
// 1002-WITH-024
|
||||
// 1002-WITH-012
|
||||
// 1002-WITH-013
|
||||
// 1002-WITH-014
|
||||
// 1002-WITH-015
|
||||
// 1002-WITH-016
|
||||
// 1002-WITH-019
|
||||
|
||||
cy.visit('/#/portfolio');
|
||||
cy.get('main[data-testid="/portfolio"]').should('exist');
|
||||
|
||||
cy.highlight('creating deposit');
|
||||
// 1001-DEPO-010
|
||||
|
||||
cy.getByTestId(depositsTab).click();
|
||||
cy.getByTestId('deposit-button').click();
|
||||
connectEthereumWallet('Unknown');
|
||||
cy.get(assetSelectField, txTimeout).select(btcName);
|
||||
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
|
||||
cy.getByTestId('deposit-approve-submit').click();
|
||||
cy.getByTestId('dialog-title').should('contain.text', 'Approve complete');
|
||||
cy.get('[data-testid="Return to deposit"]').click();
|
||||
@@ -188,7 +189,8 @@ describe('capsule', { tags: '@slow' }, () => {
|
||||
cy.getByTestId('deposit-submit').click();
|
||||
cy.getByTestId(toastContent, txTimeout).should(
|
||||
'contain.text',
|
||||
`Transaction completedYour transaction has been completedView on EtherscanDeposit 1.00 ${btcSymbol}`
|
||||
`Transaction confirmedYour transaction has been confirmed.View on EtherscanDeposit 1.00 ${btcSymbol}`,
|
||||
{ matchCase: false }
|
||||
);
|
||||
cy.getByTestId(toastCloseBtn).click();
|
||||
cy.getByTestId('Collateral').click();
|
||||
@@ -196,8 +198,6 @@ describe('capsule', { tags: '@slow' }, () => {
|
||||
cy.highlight('deposit verification');
|
||||
|
||||
cy.getByTestId('asset', txTimeout).should('contain.text', btcSymbol);
|
||||
// need to reload page to see deposit history complete
|
||||
cy.reload();
|
||||
cy.getByTestId(depositsTab).click();
|
||||
cy.get('.ag-cell-value', txTimeout).should('contain.text', btcSymbol);
|
||||
cy.get('[col-id="status"]').should('not.have.text', 'Open', txTimeout);
|
||||
@@ -214,15 +214,29 @@ describe('capsule', { tags: '@slow' }, () => {
|
||||
cy.get('[col-id="txHash"]')
|
||||
.find('a')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', 'https://sepolia.etherscan.io/tx');
|
||||
.and('contain', `${sepoliaUrl}/tx/0x`);
|
||||
});
|
||||
});
|
||||
|
||||
cy.highlight('creating withdrawals');
|
||||
it('can withdrawal', function () {
|
||||
// 1002-WITH-001
|
||||
// 1002-WITH-006
|
||||
// 1002-WITH-009
|
||||
// 1002-WITH-011
|
||||
// 1002-WITH-024
|
||||
// 1002-WITH-012
|
||||
// 1002-WITH-013
|
||||
// 1002-WITH-014
|
||||
// 1002-WITH-015
|
||||
// 1002-WITH-016
|
||||
// 1002-WITH-019
|
||||
|
||||
cy.getByTestId('Withdrawals').click();
|
||||
cy.getByTestId('withdraw-dialog-button').click();
|
||||
connectEthereumWallet('Unknown');
|
||||
cy.get(assetSelectField).select(btcName);
|
||||
cy.get(assetSelectField, txTimeout).select(
|
||||
'BTC (local)5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c - tBTC',
|
||||
{ force: true }
|
||||
);
|
||||
cy.get(amountField).clear().type('1');
|
||||
cy.getByTestId('submit-withdrawal').click();
|
||||
cy.getByTestId(toastContent, txTimeout).should(
|
||||
@@ -243,7 +257,7 @@ describe('capsule', { tags: '@slow' }, () => {
|
||||
cy.getByTestId('toast-complete-withdrawal').click();
|
||||
cy.getByTestId(toastContent, txTimeout).should(
|
||||
'contain.text',
|
||||
'Transaction completed'
|
||||
'Transaction confirmed'
|
||||
);
|
||||
|
||||
cy.getByTestId('complete-withdrawal', txTimeout).should('not.exist');
|
||||
@@ -258,17 +272,28 @@ describe('capsule', { tags: '@slow' }, () => {
|
||||
cy.get('[col-id="details.receiverAddress"]')
|
||||
.find('a')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', 'https://sepolia.etherscan.io/address/');
|
||||
.and('contain', `${sepoliaUrl}/address/`);
|
||||
cy.get('[col-id="createdTimestamp"]').should('not.be.empty');
|
||||
cy.get('[col-id="withdrawnTimestamp"]').should('not.be.empty');
|
||||
cy.get('[col-id="status"]').should('have.text', 'Completed');
|
||||
cy.get('[col-id="txHash"]')
|
||||
.find('a')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', 'https://sepolia.etherscan.io/tx/0x');
|
||||
.and('contain', `${sepoliaUrl}/tx/0x`);
|
||||
});
|
||||
});
|
||||
|
||||
it('deposit - if approved amount is less than deposit: must see that an approval is needed and be prompted to approve more', function () {
|
||||
// 1001-DEPO-006
|
||||
|
||||
cy.getByTestId(depositsTab).click();
|
||||
cy.getByTestId('deposit-button').click();
|
||||
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
|
||||
cy.get(amountField).clear().type('20000000');
|
||||
cy.getByTestId('deposit-approve-submit').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
function checkIfDataAndTimeOfCreationAndUpdateIsEqual(date: string) {
|
||||
cy.get(`[col-id='${date}'] .ag-cell-wrapper`)
|
||||
.children('span')
|
||||
|
||||
@@ -56,6 +56,7 @@ describe('deposit form validation', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('insufficient funds', () => {
|
||||
// 1001-DEPO-005
|
||||
// Deposit amount is valid, but less than approved. This will always be the case because our
|
||||
// CI wallet wont have approved any assets
|
||||
cy.get(amountField)
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import { marketQuery } from '@vegaprotocol/mock';
|
||||
import { getDateTimeFormat } from '@vegaprotocol/react-helpers';
|
||||
|
||||
describe('markets table', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
@@ -111,6 +114,53 @@ describe('markets table', { tags: '@smoke' }, () => {
|
||||
`${Cypress.env('VEGA_TOKEN_URL')}/proposals/propose/new-market`
|
||||
);
|
||||
});
|
||||
|
||||
it('opening auction subsets should be properly displayed', () => {
|
||||
cy.mockTradingPage(
|
||||
Schema.MarketState.STATE_ACTIVE,
|
||||
Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION
|
||||
);
|
||||
cy.mockGQL((req) => {
|
||||
const override = {
|
||||
market: {
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: `opening auction MARKET`,
|
||||
},
|
||||
},
|
||||
state: Schema.MarketState.STATE_ACTIVE,
|
||||
tradingMode: Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION,
|
||||
},
|
||||
};
|
||||
const market = marketQuery(override);
|
||||
aliasGQLQuery(req, 'Market', market);
|
||||
aliasGQLQuery(req, 'ProposalOfMarket', {
|
||||
proposal: { terms: { enactmentDatetime: '2023-01-31 12:00:01' } },
|
||||
});
|
||||
});
|
||||
cy.visit('/');
|
||||
cy.visit('#/markets/market-0');
|
||||
cy.getByTestId('item-value').contains('Opening auction').realHover();
|
||||
cy.getByTestId('opening-auction-sub-status').should(
|
||||
'contain.text',
|
||||
'Opening auction: Not enough liquidity to open'
|
||||
);
|
||||
|
||||
const now = new Date(Date.parse('2023-01-30 12:00:01')).getTime();
|
||||
cy.clock(now, ['Date']); // Set "now" to BEFORE reservation
|
||||
cy.visit('/');
|
||||
cy.visit('#/markets/market-0');
|
||||
cy.getByTestId('item-value').contains('Opening auction').realHover();
|
||||
cy.getByTestId('opening-auction-sub-status').should(
|
||||
'contain.text',
|
||||
`Opening auction: Closing on ${getDateTimeFormat().format(
|
||||
new Date('2023-01-31 12:00:01')
|
||||
)}`
|
||||
);
|
||||
cy.clock().then((clock) => {
|
||||
clock.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function openMarketDropDown() {
|
||||
|
||||
@@ -454,8 +454,4 @@ describe('amend and cancel order', { tags: '@smoke' }, () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
it.skip('tbd for 7003-MORD', () => {
|
||||
// NOT COVERED: must see the reference, offset and direction for each part pegged order - waiting for clarification
|
||||
// NOT COVERED: must see the reference, offset and direction for each part liquidity order order - waiting for clarification
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useDepositDialog, DepositsTable } from '@vegaprotocol/deposits';
|
||||
import { useDeposits } from '@vegaprotocol/deposits';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { depositsProvider } from '@vegaprotocol/deposits';
|
||||
import { t, useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
|
||||
export const DepositsContainer = () => {
|
||||
const { deposits, loading, error } = useDeposits();
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const { data, loading, error } = useDataProvider({
|
||||
dataProvider: depositsProvider,
|
||||
variables: { partyId: pubKey || '' },
|
||||
skip: !pubKey,
|
||||
});
|
||||
const openDepositDialog = useDepositDialog((state) => state.open);
|
||||
|
||||
return (
|
||||
<div className="h-full grid grid-rows-[1fr,min-content]">
|
||||
<div className="h-full relative">
|
||||
<DepositsTable
|
||||
rowData={deposits || []}
|
||||
rowData={data || []}
|
||||
noRowsOverlayComponent={() => null}
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<AsyncRenderer
|
||||
data={deposits}
|
||||
data={data}
|
||||
loading={loading}
|
||||
error={error}
|
||||
noDataCondition={(data) => !(data && data.length)}
|
||||
@@ -24,15 +30,17 @@ export const DepositsContainer = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => openDepositDialog()}
|
||||
data-testid="deposit-button"
|
||||
>
|
||||
{t('Deposit')}
|
||||
</Button>
|
||||
</div>
|
||||
{!isReadOnly && (
|
||||
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => openDepositDialog()}
|
||||
data-testid="deposit-button"
|
||||
>
|
||||
{t('Deposit')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
useWithdrawals,
|
||||
withdrawalProvider,
|
||||
useWithdrawalDialog,
|
||||
WithdrawalsTable,
|
||||
} from '@vegaprotocol/withdraws';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { t, useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
||||
|
||||
export const WithdrawalsContainer = () => {
|
||||
const { data, loading, error } = useWithdrawals();
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const { data, loading, error } = useDataProvider({
|
||||
dataProvider: withdrawalProvider,
|
||||
variables: { partyId: pubKey || '' },
|
||||
skip: !pubKey,
|
||||
});
|
||||
const openWithdrawDialog = useWithdrawalDialog((state) => state.open);
|
||||
|
||||
return (
|
||||
@@ -30,15 +36,17 @@ export const WithdrawalsContainer = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => openWithdrawDialog()}
|
||||
data-testid="withdraw-dialog-button"
|
||||
>
|
||||
{t('Make withdrawal')}
|
||||
</Button>
|
||||
</div>
|
||||
{!isReadOnly && (
|
||||
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => openWithdrawDialog()}
|
||||
data-testid="withdraw-dialog-button"
|
||||
>
|
||||
{t('Make withdrawal')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</VegaWalletContainer>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { AccountManager } from '@vegaprotocol/accounts';
|
||||
import { useDepositDialog } from '@vegaprotocol/deposits';
|
||||
|
||||
export const AccountsContainer = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
const openWithdrawalDialog = useWithdrawalDialog((store) => store.open);
|
||||
const openDepositDialog = useDepositDialog((store) => store.open);
|
||||
@@ -37,13 +37,16 @@ export const AccountsContainer = () => {
|
||||
onClickAsset={onClickAsset}
|
||||
onClickWithdraw={openWithdrawalDialog}
|
||||
onClickDeposit={openDepositDialog}
|
||||
isReadOnly={isReadOnly}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end p-2 px-[11px]">
|
||||
<Button size="sm" onClick={() => openDepositDialog()}>
|
||||
{t('Deposit')}
|
||||
</Button>
|
||||
</div>
|
||||
{!isReadOnly && (
|
||||
<div className="flex justify-end p-2 px-[11px]">
|
||||
<Button size="sm" onClick={() => openDepositDialog()}>
|
||||
{t('Deposit')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,26 +1,58 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { Footer } from './footer';
|
||||
import { Footer, NodeHealth } from './footer';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
|
||||
jest.mock('@vegaprotocol/environment');
|
||||
|
||||
describe('Footer', () => {
|
||||
it('renders a button to open node switcher', () => {
|
||||
it('can open node switcher by clicking the node url', () => {
|
||||
const mockOpenNodeSwitcher = jest.fn();
|
||||
const node = 'n99.somenetwork.vega.xyz';
|
||||
const nodeUrl = `https://${node}`;
|
||||
|
||||
// @ts-ignore mock env hook
|
||||
useEnvironment.mockImplementation(() => ({
|
||||
VEGA_URL: `https://api.${node}/graphql`,
|
||||
blockDifference: 0,
|
||||
setNodeSwitcherOpen: mockOpenNodeSwitcher,
|
||||
}));
|
||||
|
||||
render(<Footer />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button'));
|
||||
fireEvent.click(screen.getByText(node));
|
||||
expect(mockOpenNodeSwitcher).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can open node switcher by clicking health', () => {
|
||||
const mockOpenNodeSwitcher = jest.fn();
|
||||
const node = 'n99.somenetwork.vega.xyz';
|
||||
|
||||
// @ts-ignore mock env hook
|
||||
useEnvironment.mockImplementation(() => ({
|
||||
VEGA_URL: `https://api.${node}/graphql`,
|
||||
blockDifference: 0,
|
||||
setNodeSwitcherOpen: mockOpenNodeSwitcher,
|
||||
}));
|
||||
|
||||
render(<Footer />);
|
||||
|
||||
fireEvent.click(screen.getByText('Operational'));
|
||||
expect(mockOpenNodeSwitcher).toHaveBeenCalled();
|
||||
const link = screen.getByText(node);
|
||||
expect(link).toHaveAttribute('href', nodeUrl);
|
||||
});
|
||||
});
|
||||
|
||||
describe('NodeHealth', () => {
|
||||
const cases = [
|
||||
{ diff: 0, classname: 'bg-success', text: 'Operational' },
|
||||
{ diff: 5, classname: 'bg-warning', text: '5 Blocks behind' },
|
||||
{ diff: -1, classname: 'bg-danger', text: 'Non operational' },
|
||||
];
|
||||
it.each(cases)(
|
||||
'renders correct text and indicator color for $diff block difference',
|
||||
(elem) => {
|
||||
console.log(elem);
|
||||
render(<NodeHealth blockDiff={elem.diff} openNodeSwitcher={jest.fn()} />);
|
||||
expect(screen.getByTestId('indicator')).toHaveClass(elem.classname);
|
||||
expect(screen.getByText(elem.text)).toBeInTheDocument();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,28 +1,75 @@
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { ButtonLink, Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { t, useNavigatorOnline } from '@vegaprotocol/react-helpers';
|
||||
import { ButtonLink, Indicator, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const Footer = () => {
|
||||
const { VEGA_URL, setNodeSwitcherOpen } = useEnvironment();
|
||||
const { VEGA_URL, blockDifference, setNodeSwitcherOpen } = useEnvironment();
|
||||
return (
|
||||
<footer className="px-4 py-1 text-xs border-t border-default">
|
||||
<div className="flex justify-between">
|
||||
<div className="flex gap-2">
|
||||
{VEGA_URL && <NodeUrl url={VEGA_URL} />}
|
||||
<ButtonLink onClick={setNodeSwitcherOpen}>{t('Change')}</ButtonLink>
|
||||
{VEGA_URL && (
|
||||
<>
|
||||
<NodeHealth
|
||||
blockDiff={blockDifference}
|
||||
openNodeSwitcher={setNodeSwitcherOpen}
|
||||
/>
|
||||
{' | '}
|
||||
<NodeUrl url={VEGA_URL} openNodeSwitcher={setNodeSwitcherOpen} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
const NodeUrl = ({ url }: { url: string }) => {
|
||||
interface NodeUrlProps {
|
||||
url: string;
|
||||
openNodeSwitcher: () => void;
|
||||
}
|
||||
|
||||
const NodeUrl = ({ url, openNodeSwitcher }: NodeUrlProps) => {
|
||||
// get base url from api url, api sub domain
|
||||
const urlObj = new URL(url);
|
||||
const nodeUrl = urlObj.origin.replace(/^[^.]+\./g, '');
|
||||
return <ButtonLink onClick={openNodeSwitcher}>{nodeUrl}</ButtonLink>;
|
||||
};
|
||||
|
||||
interface NodeHealthProps {
|
||||
openNodeSwitcher: () => void;
|
||||
blockDiff: number;
|
||||
}
|
||||
|
||||
// How many blocks behind the most advanced block that is
|
||||
// deemed acceptable for "Good" status
|
||||
const BLOCK_THRESHOLD = 3;
|
||||
|
||||
export const NodeHealth = ({
|
||||
blockDiff,
|
||||
openNodeSwitcher,
|
||||
}: NodeHealthProps) => {
|
||||
const online = useNavigatorOnline();
|
||||
|
||||
let intent = Intent.Success;
|
||||
let text = 'Operational';
|
||||
|
||||
if (!online) {
|
||||
text = t('Offline');
|
||||
intent = Intent.Danger;
|
||||
} else if (blockDiff < 0) {
|
||||
// Block height query failed and null was returned
|
||||
text = t('Non operational');
|
||||
intent = Intent.Danger;
|
||||
} else if (blockDiff >= BLOCK_THRESHOLD) {
|
||||
text = t(`${blockDiff} Blocks behind`);
|
||||
intent = Intent.Warning;
|
||||
}
|
||||
|
||||
return (
|
||||
<Link href={'https://' + nodeUrl} target="_blank">
|
||||
{nodeUrl}
|
||||
</Link>
|
||||
<span>
|
||||
<Indicator variant={intent} />
|
||||
<ButtonLink onClick={openNodeSwitcher}>{text}</ButtonLink>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -112,7 +112,7 @@ export const SelectMarketPopover = ({
|
||||
} = useMarketList();
|
||||
const variables = useMemo(() => ({ partyId: pubKey }), [pubKey]);
|
||||
const {
|
||||
data: party,
|
||||
data: positions,
|
||||
loading: positionsLoading,
|
||||
reload,
|
||||
} = useDataProvider({
|
||||
@@ -132,11 +132,9 @@ export const SelectMarketPopover = ({
|
||||
const markets = useMemo(
|
||||
() =>
|
||||
data?.filter((market) =>
|
||||
party?.positionsConnection?.edges?.find(
|
||||
(edge) => edge.node.market.id === market.id
|
||||
)
|
||||
positions?.find((node) => node.market.id === market.id)
|
||||
),
|
||||
[data, party]
|
||||
[data, positions]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -172,15 +170,13 @@ export const SelectMarketPopover = ({
|
||||
</div>
|
||||
) : (
|
||||
<table className="relative text-sm w-full whitespace-nowrap">
|
||||
{pubKey && (party?.positionsConnection?.edges?.length ?? 0) > 0 ? (
|
||||
{pubKey && (positions?.length ?? 0) && (markets?.length ?? 0) ? (
|
||||
<>
|
||||
<TableTitle>{t('My markets')}</TableTitle>
|
||||
<SelectAllMarketsTableBody
|
||||
inViewRoot={inViewRoot}
|
||||
markets={markets}
|
||||
positions={party?.positionsConnection?.edges
|
||||
?.filter((edge) => edge.node)
|
||||
.map((edge) => edge.node)}
|
||||
positions={positions || undefined}
|
||||
onSelect={onSelectMarket}
|
||||
onCellClick={onCellClick}
|
||||
headers={columnHeadersPositionMarkets}
|
||||
|
||||
@@ -96,7 +96,7 @@ const EthTxPendingToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||
return (
|
||||
<div>
|
||||
<h3 className="font-bold">{t('Awaiting confirmation')}</h3>
|
||||
<p>{t('Please wait for your transaction to be confirmed')}</p>
|
||||
<p>{t('Please wait for your transaction to be confirmed.')}</p>
|
||||
<EtherscanLink tx={tx} />
|
||||
<EthTransactionDetails tx={tx} />
|
||||
</div>
|
||||
@@ -138,7 +138,7 @@ const EthTxConfirmedToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||
return (
|
||||
<div>
|
||||
<h3 className="font-bold">{t('Transaction confirmed')}</h3>
|
||||
<p>{t('Your transaction has been confirmed')}</p>
|
||||
<p>{t('Your transaction has been confirmed.')}</p>
|
||||
<EtherscanLink tx={tx} />
|
||||
<EthTransactionDetails tx={tx} />
|
||||
</div>
|
||||
@@ -153,7 +153,7 @@ const EthTxCompletedToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||
{t('Processing')} {isDeposit && t('deposit')}
|
||||
</h3>
|
||||
<p>
|
||||
{t('Your transaction has been completed.')}
|
||||
{t('Your transaction has been completed.')}{' '}
|
||||
{isDeposit && t('Waiting for deposit confirmation.')}
|
||||
</p>
|
||||
<EtherscanLink tx={tx} />
|
||||
|
||||
@@ -10,49 +10,49 @@ body,
|
||||
|
||||
/* Styles for allotment */
|
||||
html {
|
||||
--focus-border: theme('colors.vega.pink');
|
||||
--separator-border: theme('colors.neutral.300');
|
||||
--pennant-color-danger: theme('colors.vega.pink');
|
||||
--focus-border: theme('colors.vega.pink.500');
|
||||
--separator-border: theme('colors.vega.light.200');
|
||||
--pennant-color-danger: theme('colors.vega.pink.500');
|
||||
}
|
||||
|
||||
html.dark {
|
||||
--focus-border: theme('colors.vega.yellow');
|
||||
--separator-border: theme('colors.neutral.600');
|
||||
--focus-border: theme('colors.vega.yellow.500');
|
||||
--separator-border: theme('colors.vega.dark.200');
|
||||
}
|
||||
|
||||
.border-default {
|
||||
@apply border-neutral-300 dark:border-neutral-600;
|
||||
@apply border-vega-light-200 dark:border-vega-dark-200;
|
||||
}
|
||||
|
||||
/* Styles for charts */
|
||||
html [data-theme='dark'] {
|
||||
--pennant-color-danger: theme('colors.vega.pink');
|
||||
--pennant-color-danger: theme('colors.vega.pink.DEFAULT');
|
||||
|
||||
/* candles */
|
||||
--pennant-color-buy-fill: theme('colors.vega.green-data-dark');
|
||||
--pennant-color-buy-stroke: theme('colors.vega.green');
|
||||
--pennant-color-buy-fill: theme('colors.vega.green.650');
|
||||
--pennant-color-buy-stroke: theme('colors.vega.green.500');
|
||||
/* sell candles only use stroke as the candle is solid (without border) */
|
||||
--pennant-color-sell-stroke: theme('colors.vega.pink');
|
||||
--pennant-color-sell-stroke: theme('colors.vega.pink.500');
|
||||
|
||||
/* depth chart */
|
||||
--pennant-color-depth-buy-fill: theme('colors.vega.green-data-dark');
|
||||
--pennant-color-depth-buy-stroke: theme('colors.vega.green');
|
||||
--pennant-color-depth-sell-fill: theme('colors.vega.pink-data-dark');
|
||||
--pennant-color-depth-sell-stroke: theme('colors.vega.pink');
|
||||
--pennant-color-depth-buy-fill: theme('colors.vega.green.650');
|
||||
--pennant-color-depth-buy-stroke: theme('colors.vega.green.500');
|
||||
--pennant-color-depth-sell-fill: theme('colors.vega.pink.650');
|
||||
--pennant-color-depth-sell-stroke: theme('colors.vega.pink.500');
|
||||
}
|
||||
|
||||
html [data-theme='light'] {
|
||||
--pennant-color-danger: theme('colors.vega.pink');
|
||||
--pennant-color-danger: theme('colors.vega.pink.500');
|
||||
|
||||
/* candles */
|
||||
--pennant-color-buy-fill: theme('colors.vega.green-data-light');
|
||||
--pennant-color-buy-stroke: theme('colors.vega.green-dark');
|
||||
--pennant-color-buy-fill: theme('colors.vega.green.400');
|
||||
--pennant-color-buy-stroke: theme('colors.vega.green.550');
|
||||
/* sell candles only use stroke as the candle is solid (without border) */
|
||||
--pennant-color-sell-stroke: theme('colors.vega.pink-data-light');
|
||||
--pennant-color-sell-stroke: theme('colors.vega.pink.400');
|
||||
|
||||
/* depth chart */
|
||||
--pennant-color-depth-buy-fill: theme('colors.vega.green-data-light');
|
||||
--pennant-color-depth-buy-stroke: theme('colors.vega.green-dark');
|
||||
--pennant-color-depth-sell-fill: theme('colors.vega.pink-data-light');
|
||||
--pennant-color-depth-sell-stroke: theme('colors.vega.pink-dark');
|
||||
--pennant-color-depth-buy-fill: theme('colors.vega.green.400');
|
||||
--pennant-color-depth-buy-stroke: theme('colors.vega.green.550');
|
||||
--pennant-color-depth-sell-fill: theme('colors.vega.pink.400');
|
||||
--pennant-color-depth-sell-stroke: theme('colors.vega.pink.550');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
const { utils } = require('@commitlint/config-nx-scopes');
|
||||
|
||||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional', '@commitlint/config-nx-scopes'],
|
||||
rules: {
|
||||
'scope-empty': [2, 'never'],
|
||||
'scope-enum': async (ctx) => [
|
||||
2,
|
||||
'always',
|
||||
['ci', 'docs', ...(await utils.getProjects(ctx))],
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -50,10 +50,10 @@ export type Account = Omit<AccountFieldsFragment, 'market' | 'asset'> & {
|
||||
};
|
||||
|
||||
const update = (
|
||||
data: AccountFieldsFragment[],
|
||||
data: AccountFieldsFragment[] | null,
|
||||
deltas: AccountEventsSubscription['accounts']
|
||||
) => {
|
||||
return produce(data, (draft) => {
|
||||
return produce(data || [], (draft) => {
|
||||
deltas.forEach((delta) => {
|
||||
const id = getId(delta);
|
||||
const index = draft.findIndex((a) => getId(a) === id);
|
||||
@@ -73,15 +73,8 @@ const update = (
|
||||
});
|
||||
};
|
||||
|
||||
const getData = (
|
||||
responseData: AccountsQuery
|
||||
): AccountFieldsFragment[] | null => {
|
||||
return (
|
||||
removePaginationWrapper(responseData.party?.accountsConnection?.edges) ??
|
||||
null
|
||||
);
|
||||
};
|
||||
|
||||
const getData = (responseData: AccountsQuery | null): AccountFieldsFragment[] =>
|
||||
removePaginationWrapper(responseData?.party?.accountsConnection?.edges) || [];
|
||||
const getDelta = (
|
||||
subscriptionData: AccountEventsSubscription
|
||||
): AccountEventsSubscription['accounts'] => {
|
||||
|
||||
@@ -23,13 +23,23 @@ jest.mock('@vegaprotocol/react-helpers', () => ({
|
||||
describe('AccountManager', () => {
|
||||
it('change partyId should reload data provider', async () => {
|
||||
const { rerender } = render(
|
||||
<AccountManager partyId="partyOne" onClickAsset={jest.fn} />
|
||||
<AccountManager
|
||||
partyId="partyOne"
|
||||
onClickAsset={jest.fn}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
(helpers.useDataProvider as jest.Mock).mock.calls[0][0].variables.partyId
|
||||
).toEqual('partyOne');
|
||||
await act(() => {
|
||||
rerender(<AccountManager partyId="partyTwo" onClickAsset={jest.fn} />);
|
||||
rerender(
|
||||
<AccountManager
|
||||
partyId="partyTwo"
|
||||
onClickAsset={jest.fn}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
);
|
||||
});
|
||||
expect(
|
||||
(helpers.useDataProvider as jest.Mock).mock.calls[1][0].variables.partyId
|
||||
@@ -38,7 +48,13 @@ describe('AccountManager', () => {
|
||||
|
||||
it('update method should return proper result', async () => {
|
||||
await act(() => {
|
||||
render(<AccountManager partyId="partyOne" onClickAsset={jest.fn} />);
|
||||
render(
|
||||
<AccountManager
|
||||
partyId="partyOne"
|
||||
onClickAsset={jest.fn}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('No accounts')).toBeInTheDocument();
|
||||
|
||||
@@ -16,6 +16,7 @@ interface AccountManagerProps {
|
||||
onClickAsset: (assetId: string) => void;
|
||||
onClickWithdraw?: (assetId?: string) => void;
|
||||
onClickDeposit?: (assetId?: string) => void;
|
||||
isReadOnly: boolean;
|
||||
}
|
||||
|
||||
export const AccountManager = ({
|
||||
@@ -23,6 +24,7 @@ export const AccountManager = ({
|
||||
onClickWithdraw,
|
||||
onClickDeposit,
|
||||
partyId,
|
||||
isReadOnly,
|
||||
}: AccountManagerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const dataRef = useRef<AccountFields[] | null>(null);
|
||||
@@ -58,6 +60,7 @@ export const AccountManager = ({
|
||||
onClickAsset={onClickAsset}
|
||||
onClickDeposit={onClickDeposit}
|
||||
onClickWithdraw={onClickWithdraw}
|
||||
isReadOnly={isReadOnly}
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<AsyncRenderer
|
||||
|
||||
@@ -35,7 +35,11 @@ describe('AccountsTable', () => {
|
||||
it('should render correct columns', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<AccountTable rowData={singleRowData} onClickAsset={() => null} />
|
||||
<AccountTable
|
||||
rowData={singleRowData}
|
||||
onClickAsset={() => null}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
);
|
||||
});
|
||||
const expectedHeaders = ['Asset', 'Total', 'Used', 'Available', ''];
|
||||
@@ -49,7 +53,11 @@ describe('AccountsTable', () => {
|
||||
it('should apply correct formatting', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<AccountTable rowData={singleRowData} onClickAsset={() => null} />
|
||||
<AccountTable
|
||||
rowData={singleRowData}
|
||||
onClickAsset={() => null}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
);
|
||||
});
|
||||
const cells = await screen.findAllByRole('gridcell');
|
||||
|
||||
@@ -29,6 +29,7 @@ export interface AccountTableProps extends AgGridReactProps {
|
||||
onClickAsset: (assetId: string) => void;
|
||||
onClickWithdraw?: (assetId: string) => void;
|
||||
onClickDeposit?: (assetId: string) => void;
|
||||
isReadOnly: boolean;
|
||||
}
|
||||
|
||||
export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
@@ -127,48 +128,50 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
}
|
||||
maxWidth={300}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="breakdown"
|
||||
headerName=""
|
||||
sortable={false}
|
||||
minWidth={200}
|
||||
type="rightAligned"
|
||||
cellRenderer={({
|
||||
data,
|
||||
}: VegaICellRendererParams<AccountFields>) => {
|
||||
return data ? (
|
||||
<>
|
||||
<ButtonLink
|
||||
data-testid="breakdown"
|
||||
onClick={() => {
|
||||
setOpenBreakdown(!openBreakdown);
|
||||
setBreakdown(data.breakdown || null);
|
||||
}}
|
||||
>
|
||||
{t('Breakdown')}
|
||||
</ButtonLink>
|
||||
<span className="mx-1" />
|
||||
<ButtonLink
|
||||
data-testid="deposit"
|
||||
onClick={() => {
|
||||
onClickDeposit && onClickDeposit(data.asset.id);
|
||||
}}
|
||||
>
|
||||
{t('Deposit')}
|
||||
</ButtonLink>
|
||||
<span className="mx-1" />
|
||||
<ButtonLink
|
||||
data-testid="withdraw"
|
||||
onClick={() =>
|
||||
onClickWithdraw && onClickWithdraw(data.asset.id)
|
||||
}
|
||||
>
|
||||
{t('Withdraw')}
|
||||
</ButtonLink>
|
||||
</>
|
||||
) : null;
|
||||
}}
|
||||
/>
|
||||
{!props.isReadOnly && (
|
||||
<AgGridColumn
|
||||
colId="breakdown"
|
||||
headerName=""
|
||||
sortable={false}
|
||||
minWidth={200}
|
||||
type="rightAligned"
|
||||
cellRenderer={({
|
||||
data,
|
||||
}: VegaICellRendererParams<AccountFields>) => {
|
||||
return data ? (
|
||||
<>
|
||||
<ButtonLink
|
||||
data-testid="breakdown"
|
||||
onClick={() => {
|
||||
setOpenBreakdown(!openBreakdown);
|
||||
setBreakdown(data.breakdown || null);
|
||||
}}
|
||||
>
|
||||
{t('Breakdown')}
|
||||
</ButtonLink>
|
||||
<span className="mx-1" />
|
||||
<ButtonLink
|
||||
data-testid="deposit"
|
||||
onClick={() => {
|
||||
onClickDeposit && onClickDeposit(data.asset.id);
|
||||
}}
|
||||
>
|
||||
{t('Deposit')}
|
||||
</ButtonLink>
|
||||
<span className="mx-1" />
|
||||
<ButtonLink
|
||||
data-testid="withdraw"
|
||||
onClick={() =>
|
||||
onClickWithdraw && onClickWithdraw(data.asset.id)
|
||||
}
|
||||
>
|
||||
{t('Withdraw')}
|
||||
</ButtonLink>
|
||||
</>
|
||||
) : null;
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</AgGrid>
|
||||
<Dialog size="medium" open={openBreakdown} onChange={setOpenBreakdown}>
|
||||
<div className="h-[35vh] w-full m-auto flex flex-col">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ApolloError, InMemoryCacheConfig } from '@apollo/client';
|
||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||
import {
|
||||
ApolloClient,
|
||||
from,
|
||||
@@ -13,30 +13,49 @@ import { createClient as createWSClient } from 'graphql-ws';
|
||||
import { onError } from '@apollo/client/link/error';
|
||||
import { RetryLink } from '@apollo/client/link/retry';
|
||||
import ApolloLinkTimeout from 'apollo-link-timeout';
|
||||
import type { GraphQLErrors } from '@apollo/client/errors';
|
||||
import { localLoggerFactory } from '@vegaprotocol/react-helpers';
|
||||
|
||||
const isBrowser = typeof window !== 'undefined';
|
||||
|
||||
const NOT_FOUND = 'NotFound';
|
||||
|
||||
export function createClient(base?: string, cacheConfig?: InMemoryCacheConfig) {
|
||||
if (!base) {
|
||||
throw new Error('Base must be passed into createClient!');
|
||||
export type ClientOptions = {
|
||||
url?: string;
|
||||
cacheConfig?: InMemoryCacheConfig;
|
||||
retry?: boolean;
|
||||
connectToDevTools?: boolean;
|
||||
};
|
||||
|
||||
export function createClient({
|
||||
url,
|
||||
cacheConfig,
|
||||
retry = true,
|
||||
connectToDevTools = true,
|
||||
}: ClientOptions) {
|
||||
if (!url) {
|
||||
throw new Error('url must be passed into createClient!');
|
||||
}
|
||||
const urlHTTP = new URL(base);
|
||||
const urlWS = new URL(base);
|
||||
const urlHTTP = new URL(url);
|
||||
const urlWS = new URL(url);
|
||||
// Replace http with ws, preserving if its a secure connection eg. https => wss
|
||||
urlWS.protocol = urlWS.protocol.replace('http', 'ws');
|
||||
|
||||
const noOpLink = new ApolloLink((operation, forward) => {
|
||||
return forward(operation);
|
||||
});
|
||||
|
||||
const timeoutLink = new ApolloLinkTimeout(10000);
|
||||
const enlargedTimeoutLink = new ApolloLinkTimeout(100000);
|
||||
const retryLink = new RetryLink({
|
||||
delay: {
|
||||
initial: 300,
|
||||
max: 10000,
|
||||
jitter: true,
|
||||
},
|
||||
});
|
||||
|
||||
const retryLink = retry
|
||||
? new RetryLink({
|
||||
delay: {
|
||||
initial: 300,
|
||||
max: 10000,
|
||||
jitter: true,
|
||||
},
|
||||
})
|
||||
: noOpLink;
|
||||
|
||||
const httpLink = new HttpLink({
|
||||
uri: urlHTTP.href,
|
||||
@@ -49,7 +68,7 @@ export function createClient(base?: string, cacheConfig?: InMemoryCacheConfig) {
|
||||
url: urlWS.href,
|
||||
})
|
||||
)
|
||||
: new ApolloLink((operation, forward) => forward(operation));
|
||||
: noOpLink;
|
||||
|
||||
const splitLink = isBrowser
|
||||
? split(
|
||||
@@ -87,23 +106,6 @@ export function createClient(base?: string, cacheConfig?: InMemoryCacheConfig) {
|
||||
return new ApolloClient({
|
||||
link: from([errorLink, composedTimeoutLink, retryLink, splitLink]),
|
||||
cache: new InMemoryCache(cacheConfig),
|
||||
connectToDevTools,
|
||||
});
|
||||
}
|
||||
|
||||
const isApolloGraphQLError = (
|
||||
error: ApolloError | Error | undefined
|
||||
): error is ApolloError => {
|
||||
return !!error && !!(error as ApolloError).graphQLErrors;
|
||||
};
|
||||
|
||||
const hasNotFoundGraphQLErrors = (errors: GraphQLErrors) => {
|
||||
return errors.some((e) => e.extensions && e.extensions['type'] === NOT_FOUND);
|
||||
};
|
||||
|
||||
export const isNotFoundGraphQLError = (
|
||||
error: Error | ApolloError | undefined
|
||||
) => {
|
||||
return (
|
||||
isApolloGraphQLError(error) && hasNotFoundGraphQLErrors(error.graphQLErrors)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,20 +6,15 @@ import { AssetDocument } from './__generated__/Asset';
|
||||
|
||||
export type Asset = AssetFieldsFragment;
|
||||
|
||||
const getData = (responseData: AssetQuery) => {
|
||||
const foundAssets = responseData.assetsConnection?.edges
|
||||
const getData = (responseData: AssetQuery | null) => {
|
||||
const foundAssets = responseData?.assetsConnection?.edges
|
||||
?.filter((e) => Boolean(e?.node))
|
||||
.map((e) => e?.node as Asset);
|
||||
if (foundAssets && foundAssets?.length > 0) return foundAssets[0];
|
||||
return null;
|
||||
};
|
||||
|
||||
export const assetProvider = makeDataProvider<
|
||||
AssetQuery,
|
||||
Asset | null,
|
||||
never,
|
||||
never
|
||||
>({
|
||||
export const assetProvider = makeDataProvider<AssetQuery, Asset, never, never>({
|
||||
query: AssetDocument,
|
||||
getData,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Option } from '@vegaprotocol/ui-toolkit';
|
||||
import type { AssetFieldsFragment } from './__generated__/Asset';
|
||||
|
||||
export const AssetOption = ({ asset }: { asset: AssetFieldsFragment }) => {
|
||||
return (
|
||||
<Option key={asset.id} value={asset.id}>
|
||||
<div className="flex flex-col items-start">
|
||||
<span>{asset.name}</span>
|
||||
<div className="text-[10px] font-mono w-full text-left break-all">
|
||||
<span className="text-gray-500">{asset.id} -</span> {asset.symbol}
|
||||
</div>
|
||||
</div>
|
||||
</Option>
|
||||
);
|
||||
};
|
||||
@@ -16,14 +16,14 @@ export type BuiltinAsset = Omit<Asset, 'source'> & {
|
||||
source: BuiltinAssetSource;
|
||||
};
|
||||
|
||||
const getData = (responseData: AssetsQuery) =>
|
||||
responseData.assetsConnection?.edges
|
||||
const getData = (responseData: AssetsQuery | null) =>
|
||||
responseData?.assetsConnection?.edges
|
||||
?.filter((e) => Boolean(e?.node))
|
||||
.map((e) => e?.node as Asset) ?? [];
|
||||
|
||||
export const assetsProvider = makeDataProvider<
|
||||
AssetsQuery,
|
||||
Asset[] | null,
|
||||
Asset[],
|
||||
never,
|
||||
never
|
||||
>({
|
||||
|
||||
@@ -4,3 +4,4 @@ export * from './asset-data-provider';
|
||||
export * from './assets-data-provider';
|
||||
export * from './asset-details-dialog';
|
||||
export * from './asset-details-table';
|
||||
export * from './asset-option';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
query Chart($marketId: ID!) {
|
||||
market(id: $marketId) {
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
data {
|
||||
priceMonitoringBounds {
|
||||
minValidPrice
|
||||
|
||||
+2
-1
@@ -8,13 +8,14 @@ export type ChartQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ChartQuery = { __typename?: 'Query', market?: { __typename?: 'Market', decimalPlaces: number, data?: { __typename?: 'MarketData', priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string }> | null } | null } | null };
|
||||
export type ChartQuery = { __typename?: 'Query', market?: { __typename?: 'Market', decimalPlaces: number, positionDecimalPlaces: number, data?: { __typename?: 'MarketData', priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string }> | null } | null } | null };
|
||||
|
||||
|
||||
export const ChartDocument = gql`
|
||||
query Chart($marketId: ID!) {
|
||||
market(id: $marketId) {
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
data {
|
||||
priceMonitoringBounds {
|
||||
minValidPrice
|
||||
|
||||
@@ -20,6 +20,7 @@ export const chartQuery = (override?: PartialDeep<ChartQuery>): ChartQuery => {
|
||||
const defaultResult: ChartQuery = {
|
||||
market: {
|
||||
decimalPlaces: 5,
|
||||
positionDecimalPlaces: 0,
|
||||
data: {
|
||||
priceMonitoringBounds: [priceMonitoringBound],
|
||||
__typename: 'MarketData',
|
||||
|
||||
@@ -102,9 +102,11 @@ export class VegaDataSource implements DataSource {
|
||||
|
||||
if (data && data.market && data.market.data) {
|
||||
this._decimalPlaces = data.market.decimalPlaces;
|
||||
this._positionDecimalPlaces = data.market.positionDecimalPlaces;
|
||||
|
||||
return {
|
||||
decimalPlaces: this._decimalPlaces,
|
||||
positionDecimalPlaces: this._positionDecimalPlaces,
|
||||
supportedIntervals: [
|
||||
PennantInterval.I1D,
|
||||
PennantInterval.I6H,
|
||||
|
||||
@@ -48,7 +48,7 @@ function createNewMarketProposal(): ProposalSubmissionBody {
|
||||
signers: [
|
||||
{
|
||||
pubKey: {
|
||||
key: '0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC',
|
||||
key: '70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -45,7 +45,7 @@ export type DealTicketFormFields = OrderSubmissionBody['orderSubmission'] & {
|
||||
};
|
||||
|
||||
export const DealTicket = ({ market, submit }: DealTicketProps) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const { getPersistedOrder, setPersistedOrder } = usePersistedOrderStore(
|
||||
(store) => ({
|
||||
getPersistedOrder: store.getOrder,
|
||||
@@ -74,6 +74,7 @@ export const DealTicket = ({ market, submit }: DealTicketProps) => {
|
||||
|
||||
usePersistedOrderStoreSubscription(market.id, (storedOrder) => {
|
||||
if (order.price !== storedOrder.price) {
|
||||
clearErrors('price');
|
||||
setValue('price', storedOrder.price);
|
||||
}
|
||||
});
|
||||
@@ -157,7 +158,11 @@ export const DealTicket = ({ market, submit }: DealTicketProps) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="p-4" noValidate>
|
||||
<form
|
||||
onSubmit={isReadOnly ? () => null : handleSubmit(onSubmit)}
|
||||
className="p-4"
|
||||
noValidate
|
||||
>
|
||||
<Controller
|
||||
name="type"
|
||||
control={control}
|
||||
@@ -219,13 +224,14 @@ export const DealTicket = ({ market, submit }: DealTicketProps) => {
|
||||
/>
|
||||
)}
|
||||
<DealTicketButton
|
||||
disabled={Object.keys(errors).length >= 1}
|
||||
disabled={Object.keys(errors).length >= 1 || isReadOnly}
|
||||
variant={order.side === Schema.Side.SIDE_BUY ? 'ternary' : 'secondary'}
|
||||
/>
|
||||
<SummaryMessage
|
||||
errorMessage={errors.summary?.message}
|
||||
market={market}
|
||||
order={order}
|
||||
isReadOnly={isReadOnly}
|
||||
/>
|
||||
<DealTicketFeeDetails order={order} market={market} />
|
||||
</form>
|
||||
@@ -240,9 +246,10 @@ interface SummaryMessageProps {
|
||||
errorMessage?: string;
|
||||
market: MarketDealTicket;
|
||||
order: OrderSubmissionBody['orderSubmission'];
|
||||
isReadOnly: boolean;
|
||||
}
|
||||
const SummaryMessage = memo(
|
||||
({ errorMessage, market, order }: SummaryMessageProps) => {
|
||||
({ errorMessage, market, order, isReadOnly }: SummaryMessageProps) => {
|
||||
// Specific error UI for if balance is so we can
|
||||
// render a deposit dialog
|
||||
const asset = market.tradableInstrument.instrument.product.settlementAsset;
|
||||
@@ -250,6 +257,17 @@ const SummaryMessage = memo(
|
||||
market,
|
||||
order,
|
||||
});
|
||||
if (isReadOnly) {
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<InputError data-testid="dealticket-error-message-summary">
|
||||
{
|
||||
'You need to connect your own wallet to start trading on this market'
|
||||
}
|
||||
</InputError>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (errorMessage === SummaryValidationType.NoCollateral) {
|
||||
return (
|
||||
<ZeroBalanceError
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { useMemo } from 'react';
|
||||
import { parseISO, isValid, isAfter } from 'date-fns';
|
||||
import classNames from 'classnames';
|
||||
import { useProposalOfMarketQuery } from '@vegaprotocol/governance';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { DataGrid, t } from '@vegaprotocol/react-helpers';
|
||||
import { DataGrid, getDateTimeFormat, t } from '@vegaprotocol/react-helpers';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { createDocsLinks } from '@vegaprotocol/react-helpers';
|
||||
@@ -21,14 +24,24 @@ export const TradingModeTooltip = ({
|
||||
const { VEGA_DOCS_URL } = useEnvironment();
|
||||
const market = useMarket(marketId);
|
||||
const marketData = useStaticMarketData(marketId, skip);
|
||||
const { marketTradingMode: tradingMode, trigger } = marketData || {};
|
||||
const variables = useMemo(() => ({ marketId: marketId || '' }), [marketId]);
|
||||
const { data: proposalData } = useProposalOfMarketQuery({
|
||||
variables,
|
||||
skip:
|
||||
!tradingMode ||
|
||||
Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION !== tradingMode,
|
||||
});
|
||||
|
||||
if (!market || !marketData) {
|
||||
return null;
|
||||
}
|
||||
const enactmentDate = parseISO(
|
||||
proposalData?.proposal?.terms.enactmentDatetime
|
||||
);
|
||||
|
||||
const compiledGrid =
|
||||
onSelect && compileGridData(market, marketData, onSelect);
|
||||
const { marketTradingMode: tradingMode, trigger } = marketData;
|
||||
|
||||
switch (tradingMode) {
|
||||
case Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS: {
|
||||
@@ -43,12 +56,47 @@ export const TradingModeTooltip = ({
|
||||
case Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION: {
|
||||
return (
|
||||
<section data-testid="trading-mode-tooltip">
|
||||
<p className={classNames({ 'mb-4': Boolean(compiledGrid) })}>
|
||||
<span>
|
||||
{t(
|
||||
'This new market is in an opening auction to determine a fair mid-price before starting continuous trading.'
|
||||
)}
|
||||
</span>{' '}
|
||||
<p
|
||||
className={classNames('flex flex-col', {
|
||||
'mb-4': Boolean(compiledGrid),
|
||||
})}
|
||||
>
|
||||
{isValid(enactmentDate) && isAfter(new Date(), enactmentDate) ? (
|
||||
<>
|
||||
<span
|
||||
className="justify-center font-bold my-2"
|
||||
data-testid="opening-auction-sub-status"
|
||||
>
|
||||
{`${Schema.MarketTradingModeMapping[tradingMode]}: ${t(
|
||||
'Not enough liquidity to open'
|
||||
)}`}
|
||||
</span>
|
||||
<span>
|
||||
{t(
|
||||
'This market is in opening auction until it has reached enough liquidity to move into continuous trading.'
|
||||
)}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{isValid(enactmentDate) && (
|
||||
<span
|
||||
className="justify-center font-bold my-2"
|
||||
data-testid="opening-auction-sub-status"
|
||||
>
|
||||
{`${Schema.MarketTradingModeMapping[tradingMode]}: ${t(
|
||||
'Closing on %s',
|
||||
getDateTimeFormat().format(enactmentDate)
|
||||
)}`}
|
||||
</span>
|
||||
)}
|
||||
<span>
|
||||
{t(
|
||||
'This is a new market in an opening auction to determine a fair mid-price before starting continuous trading.'
|
||||
)}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{VEGA_DOCS_URL && (
|
||||
<ExternalLink
|
||||
href={createDocsLinks(VEGA_DOCS_URL).AUCTION_TYPE_OPENING}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Asset } from '@vegaprotocol/assets';
|
||||
import { AssetOption } from '@vegaprotocol/assets';
|
||||
import {
|
||||
ethereumAddress,
|
||||
t,
|
||||
@@ -17,7 +18,6 @@ import {
|
||||
Input,
|
||||
InputError,
|
||||
RichSelect,
|
||||
Option,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
@@ -185,14 +185,7 @@ export const DepositForm = ({
|
||||
hasError={Boolean(errors.asset?.message)}
|
||||
>
|
||||
{assets.filter(isAssetTypeERC20).map((a) => (
|
||||
<Option key={a.id} value={a.id}>
|
||||
<div className="flex flex-col items-start">
|
||||
<span>{a.name}</span>
|
||||
<span className="text-[10px] font-mono">
|
||||
<span className="text-gray-500">{a.id} -</span> {a.symbol}
|
||||
</span>
|
||||
</div>
|
||||
</Option>
|
||||
<AssetOption asset={a} />
|
||||
))}
|
||||
</RichSelect>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import uniqBy from 'lodash/uniqBy';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import {
|
||||
getEvents,
|
||||
makeDataProvider,
|
||||
removePaginationWrapper,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import {
|
||||
DepositsDocument,
|
||||
DepositEventDocument,
|
||||
} from './__generated__/Deposit';
|
||||
import type {
|
||||
DepositFieldsFragment,
|
||||
DepositsQuery,
|
||||
DepositEventSubscription,
|
||||
DepositEventSubscriptionVariables,
|
||||
} from './__generated__/Deposit';
|
||||
|
||||
export const depositsProvider = makeDataProvider<
|
||||
DepositsQuery,
|
||||
DepositFieldsFragment[],
|
||||
DepositEventSubscription,
|
||||
DepositEventSubscription,
|
||||
DepositEventSubscriptionVariables
|
||||
>({
|
||||
query: DepositsDocument,
|
||||
subscriptionQuery: DepositEventDocument,
|
||||
getData: (data: DepositsQuery | null) =>
|
||||
orderBy(
|
||||
removePaginationWrapper(data?.party?.depositsConnection?.edges || []),
|
||||
['createdTimestamp'],
|
||||
['desc']
|
||||
),
|
||||
getDelta: (data: DepositEventSubscription) => data,
|
||||
update: (
|
||||
data: DepositFieldsFragment[] | null,
|
||||
delta: DepositEventSubscription
|
||||
) => {
|
||||
if (!delta.busEvents?.length) {
|
||||
return data;
|
||||
}
|
||||
const incoming = getEvents<DepositFieldsFragment>(
|
||||
Schema.BusEventType.Deposit,
|
||||
delta.busEvents
|
||||
);
|
||||
return uniqBy([...incoming, ...(data || [])], 'id');
|
||||
},
|
||||
});
|
||||
@@ -5,7 +5,7 @@ export * from './deposit-limits';
|
||||
export * from './deposit-manager';
|
||||
export * from './deposits-table';
|
||||
export * from './use-deposit-balances';
|
||||
export * from './use-deposits';
|
||||
export * from './deposits-provider';
|
||||
export * from './use-get-allowance';
|
||||
export * from './use-get-balance-of-erc20-token';
|
||||
export * from './use-get-deposit-maximum';
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
import uniqBy from 'lodash/uniqBy';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { getNodes, getEvents } from '@vegaprotocol/react-helpers';
|
||||
import type { UpdateQueryFn } from '@apollo/client/core/watchQueryOptions';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import {
|
||||
useDepositsQuery,
|
||||
DepositEventDocument,
|
||||
} from './__generated__/Deposit';
|
||||
import type {
|
||||
DepositFieldsFragment,
|
||||
DepositsQuery,
|
||||
DepositEventSubscription,
|
||||
DepositEventSubscriptionVariables,
|
||||
} from './__generated__/Deposit';
|
||||
|
||||
export const useDeposits = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { data, loading, error, subscribeToMore } = useDepositsQuery({
|
||||
variables: { partyId: pubKey || '' },
|
||||
skip: !pubKey,
|
||||
});
|
||||
|
||||
const deposits = useMemo(() => {
|
||||
if (!data?.party?.depositsConnection?.edges?.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return orderBy(
|
||||
getNodes<DepositFieldsFragment>(data.party?.depositsConnection),
|
||||
['createdTimestamp'],
|
||||
['desc']
|
||||
);
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!pubKey) return;
|
||||
|
||||
const unsub = subscribeToMore<
|
||||
DepositEventSubscription,
|
||||
DepositEventSubscriptionVariables
|
||||
>({
|
||||
document: DepositEventDocument,
|
||||
variables: { partyId: pubKey },
|
||||
updateQuery,
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsub();
|
||||
};
|
||||
}, [pubKey, subscribeToMore]);
|
||||
|
||||
return { data, loading, error, deposits };
|
||||
};
|
||||
|
||||
const updateQuery: UpdateQueryFn<
|
||||
DepositsQuery,
|
||||
DepositEventSubscriptionVariables,
|
||||
DepositEventSubscription
|
||||
> = (prev, { subscriptionData, variables }) => {
|
||||
if (!subscriptionData.data.busEvents?.length || !variables?.partyId) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
const curr = getNodes<DepositFieldsFragment>(prev.party?.depositsConnection);
|
||||
const incoming = getEvents<DepositFieldsFragment>(
|
||||
Schema.BusEventType.Deposit,
|
||||
subscriptionData.data.busEvents
|
||||
);
|
||||
|
||||
const deposits = uniqBy([...incoming, ...curr], 'id');
|
||||
|
||||
if (!prev.party) {
|
||||
return {
|
||||
...prev,
|
||||
party: {
|
||||
__typename: 'Party',
|
||||
id: variables?.partyId,
|
||||
depositsConnection: {
|
||||
__typename: 'DepositsConnection',
|
||||
edges: deposits.map((d) => ({ __typename: 'DepositEdge', node: d })),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...prev,
|
||||
party: {
|
||||
...prev.party,
|
||||
id: variables?.partyId,
|
||||
depositsConnection: {
|
||||
__typename: 'DepositsConnection',
|
||||
edges: deposits.map((d) => ({ __typename: 'DepositEdge', node: d })),
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -46,7 +46,10 @@ describe('Network loader', () => {
|
||||
render(
|
||||
<NetworkLoader skeleton={SKELETON_TEXT}>{SUCCESS_TEXT}</NetworkLoader>
|
||||
);
|
||||
expect(createClient).toHaveBeenCalledWith('http://vega.node', undefined);
|
||||
expect(createClient).toHaveBeenCalledWith({
|
||||
url: 'http://vega.node',
|
||||
cacheConfig: undefined,
|
||||
});
|
||||
expect(await screen.findByText(SUCCESS_TEXT)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,7 +20,10 @@ export function NetworkLoader({
|
||||
|
||||
const client = useMemo(() => {
|
||||
if (VEGA_URL) {
|
||||
return createClient(VEGA_URL, cache);
|
||||
return createClient({
|
||||
url: VEGA_URL,
|
||||
cacheConfig: cache,
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
}, [VEGA_URL, cache]);
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './use-environment';
|
||||
export * from './use-links';
|
||||
export * from './use-node-health';
|
||||
|
||||
@@ -18,6 +18,9 @@ type UseConfigOptions = {
|
||||
defaultConfig?: Configuration;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch list of hosts from the VEGA_CONFIG_URL
|
||||
*/
|
||||
export const useConfig = (
|
||||
{ environment, defaultConfig }: UseConfigOptions,
|
||||
onError: (errorType: ErrorType) => void
|
||||
|
||||
@@ -101,6 +101,16 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
describe('throws error', () => {
|
||||
const consoleError = console.error;
|
||||
|
||||
beforeAll(() => {
|
||||
console.error = jest.fn();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
console.error = consoleError;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// @ts-ignore: typescript doesn't recognize the mock implementation
|
||||
global.fetch.mockImplementation(setupFetch());
|
||||
@@ -127,6 +137,7 @@ describe('throws error', () => {
|
||||
});
|
||||
|
||||
beforeEach(() => jest.resetModules()); // clears the cache of the modules
|
||||
|
||||
it('throws a validation error when NX_ETHERSCAN_URL is not a valid url', () => {
|
||||
process.env['NX_ETHERSCAN_URL'] = 'invalid-url';
|
||||
const result = () =>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// having the node switcher dialog in the environment provider breaks the test renderer
|
||||
// workaround based on: https://github.com/facebook/react/issues/11565
|
||||
import type { ComponentProps, ReactNode } from 'react';
|
||||
import { renderHook, waitFor, act } from '@testing-library/react';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import type { ClientOptions } from '@vegaprotocol/apollo-client';
|
||||
import { createClient } from '@vegaprotocol/apollo-client';
|
||||
import { useEnvironment, EnvironmentProvider } from './use-environment';
|
||||
import { Networks, ErrorType } from '../types';
|
||||
@@ -32,6 +33,7 @@ const mockEnvironmentState = {
|
||||
VEGA_ENV: Networks.TESTNET,
|
||||
VEGA_CONFIG_URL: 'https://vega.xyz/testnet-config.json',
|
||||
VEGA_NETWORKS: {
|
||||
DEVNET: 'https://devnet.url',
|
||||
TESTNET: 'https://testnet.url',
|
||||
STAGNET3: 'https://stagnet3.url',
|
||||
MAINNET: 'https://mainnet.url',
|
||||
@@ -42,6 +44,10 @@ const mockEnvironmentState = {
|
||||
GIT_ORIGIN_URL: 'https://github.com/test/repo',
|
||||
GIT_COMMIT_HASH: 'abcde01234',
|
||||
GITHUB_FEEDBACK_URL: 'https://github.com/test/feedback',
|
||||
MAINTENANCE_PAGE: false,
|
||||
configLoading: false,
|
||||
blockDifference: 0,
|
||||
nodeSwitcherOpen: false,
|
||||
setNodeSwitcherOpen: noop,
|
||||
networkError: undefined,
|
||||
};
|
||||
@@ -96,7 +102,7 @@ const getQuickestNode = (mockNodes: Record<string, MockRequestConfig>) => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// @ts-ignore: typescript doesn't recognize the mock implementation
|
||||
// @ts-ignore: typscript doesn't recognise the mock implementation
|
||||
global.fetch.mockImplementation(setupFetch());
|
||||
|
||||
window.localStorage.clear();
|
||||
@@ -129,6 +135,7 @@ describe('useEnvironment hook', () => {
|
||||
const { result } = renderHook(() => useEnvironment(), {
|
||||
wrapper: MockWrapper,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current).toEqual({
|
||||
...mockEnvironmentState,
|
||||
@@ -136,39 +143,67 @@ describe('useEnvironment hook', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('allows for the VEGA_CONFIG_URL to be missing when there is a VEGA_URL present', async () => {
|
||||
delete process.env['NX_VEGA_CONFIG_URL'];
|
||||
const { result } = renderHook(() => useEnvironment(), {
|
||||
wrapper: MockWrapper,
|
||||
});
|
||||
|
||||
expect(result.current).toEqual({
|
||||
...mockEnvironmentState,
|
||||
VEGA_CONFIG_URL: undefined,
|
||||
setNodeSwitcherOpen: result.current.setNodeSwitcherOpen,
|
||||
});
|
||||
});
|
||||
|
||||
it('allows for the VEGA_NETWORKS to be missing from the environment', async () => {
|
||||
act(async () => {
|
||||
delete process.env['NX_VEGA_NETWORKS'];
|
||||
it('allows for the VEGA_CONFIG_URL to be missing when there is a VEGA_URL present', async () => {
|
||||
delete process.env['NX_VEGA_CONFIG_URL'];
|
||||
const { result } = renderHook(() => useEnvironment(), {
|
||||
wrapper: MockWrapper,
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(result.current).toEqual({
|
||||
...mockEnvironmentState,
|
||||
VEGA_NETWORKS: {},
|
||||
VEGA_CONFIG_URL: undefined,
|
||||
setNodeSwitcherOpen: result.current.setNodeSwitcherOpen,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('when VEGA_NETWORKS is not a valid json, prints a warning and continues without using the value from it', async () => {
|
||||
act(async () => {
|
||||
it('allows for the VEGA_NETWORKS to be missing from the environment', async () => {
|
||||
delete process.env['NX_VEGA_NETWORKS'];
|
||||
const { result } = renderHook(() => useEnvironment(), {
|
||||
wrapper: MockWrapper,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current).toEqual({
|
||||
...mockEnvironmentState,
|
||||
VEGA_NETWORKS: {
|
||||
TESTNET: window.location.origin,
|
||||
},
|
||||
setNodeSwitcherOpen: result.current.setNodeSwitcherOpen,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('throws a validation error when NX_VEGA_ENV is not found in the environment', async () => {
|
||||
delete process.env['NX_VEGA_ENV'];
|
||||
const consoleError = console.error;
|
||||
console.error = noop;
|
||||
expect(() => {
|
||||
renderHook(() => useEnvironment(), {
|
||||
wrapper: MockWrapper,
|
||||
});
|
||||
}).toThrowError(
|
||||
`NX_VEGA_ENV is invalid, received "undefined" instead of: 'CUSTOM' | 'SANDBOX' | 'TESTNET' | 'STAGNET1' | 'STAGNET3' | 'DEVNET' | 'MAINNET' | 'MIRROR'`
|
||||
);
|
||||
console.error = consoleError;
|
||||
});
|
||||
|
||||
it('throws a validation error when VEGA_ENV is not a valid network', async () => {
|
||||
process.env['NX_VEGA_ENV'] = 'SOMETHING';
|
||||
const consoleError = console.error;
|
||||
console.error = noop;
|
||||
expect(() => {
|
||||
renderHook(() => useEnvironment(), {
|
||||
wrapper: MockWrapper,
|
||||
});
|
||||
}).toThrowError(
|
||||
`NX_VEGA_ENV is invalid, received "SOMETHING" instead of: CUSTOM | SANDBOX | TESTNET | STAGNET1 | STAGNET3 | DEVNET | MAINNET | MIRROR`
|
||||
);
|
||||
console.error = consoleError;
|
||||
});
|
||||
|
||||
it('when VEGA_NETWORKS is not a valid json, prints a warning and continues without using the value from it', async () => {
|
||||
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(noop);
|
||||
process.env['NX_VEGA_NETWORKS'] = '{not:{valid:json';
|
||||
const { result } = renderHook(() => useEnvironment(), {
|
||||
@@ -177,26 +212,56 @@ it('when VEGA_NETWORKS is not a valid json, prints a warning and continues witho
|
||||
await waitFor(() => {
|
||||
expect(result.current).toEqual({
|
||||
...mockEnvironmentState,
|
||||
VEGA_NETWORKS: {},
|
||||
VEGA_NETWORKS: {
|
||||
TESTNET: window.location.origin,
|
||||
},
|
||||
setNodeSwitcherOpen: result.current.setNodeSwitcherOpen,
|
||||
});
|
||||
});
|
||||
|
||||
expect(consoleWarnSpy).toHaveBeenCalled();
|
||||
consoleWarnSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
it.each`
|
||||
env | etherscanUrl | providerUrl
|
||||
${Networks.DEVNET} | ${'https://sepolia.etherscan.io'} | ${'https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8'}
|
||||
${Networks.TESTNET} | ${'https://sepolia.etherscan.io'} | ${'https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8'}
|
||||
${Networks.STAGNET3} | ${'https://sepolia.etherscan.io'} | ${'https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8'}
|
||||
${Networks.MAINNET} | ${'https://etherscan.io'} | ${'https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8'}
|
||||
`(
|
||||
'uses correct default ethereum connection variables in $env',
|
||||
async ({ env, etherscanUrl, providerUrl }) => {
|
||||
act(async () => {
|
||||
it('throws a validation error when VEGA_NETWORKS has an invalid network as a key', async () => {
|
||||
process.env['NX_VEGA_NETWORKS'] = JSON.stringify({
|
||||
NOT_A_NETWORK: 'https://somewhere.url',
|
||||
});
|
||||
const consoleError = console.error;
|
||||
console.error = noop;
|
||||
expect(() => {
|
||||
renderHook(() => useEnvironment(), {
|
||||
wrapper: MockWrapper,
|
||||
});
|
||||
}).toThrowError(
|
||||
`All keys in NX_VEGA_NETWORKS must represent a valid environment: CUSTOM | SANDBOX | TESTNET | STAGNET1 | STAGNET3 | DEVNET | MAINNET | MIRROR`
|
||||
);
|
||||
console.error = consoleError;
|
||||
});
|
||||
|
||||
it('throws a validation error when both VEGA_URL and VEGA_CONFIG_URL are missing in the environment', async () => {
|
||||
delete process.env['NX_VEGA_URL'];
|
||||
delete process.env['NX_VEGA_CONFIG_URL'];
|
||||
const consoleError = console.error;
|
||||
console.error = noop;
|
||||
expect(() => {
|
||||
renderHook(() => useEnvironment(), {
|
||||
wrapper: MockWrapper,
|
||||
});
|
||||
}).toThrowError(
|
||||
`Must provide either NX_VEGA_CONFIG_URL or NX_VEGA_URL in the environment.`
|
||||
);
|
||||
console.error = consoleError;
|
||||
});
|
||||
|
||||
it.each`
|
||||
env | etherscanUrl | providerUrl
|
||||
${Networks.DEVNET} | ${'https://sepolia.etherscan.io'} | ${'https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8'}
|
||||
${Networks.TESTNET} | ${'https://sepolia.etherscan.io'} | ${'https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8'}
|
||||
${Networks.STAGNET3} | ${'https://sepolia.etherscan.io'} | ${'https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8'}
|
||||
${Networks.MAINNET} | ${'https://etherscan.io'} | ${'https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8'}
|
||||
`(
|
||||
'uses correct default ethereum connection variables in $env',
|
||||
async ({ env, etherscanUrl, providerUrl }) => {
|
||||
// @ts-ignore allow adding a mock return value to mocked module
|
||||
createClient.mockImplementation(() => createMockClient({ network: env }));
|
||||
|
||||
@@ -215,13 +280,39 @@ it.each`
|
||||
setNodeSwitcherOpen: result.current.setNodeSwitcherOpen,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
describe('node selection', () => {
|
||||
it('updates the VEGA_URL from the config when it is missing from the environment', async () => {
|
||||
act(async () => {
|
||||
it('throws a validation error when NX_ETHERSCAN_URL is not a valid url', async () => {
|
||||
process.env['NX_ETHERSCAN_URL'] = 'invalid-url';
|
||||
const consoleError = console.error;
|
||||
console.error = noop;
|
||||
expect(() => {
|
||||
renderHook(() => useEnvironment(), {
|
||||
wrapper: MockWrapper,
|
||||
});
|
||||
}).toThrowError(
|
||||
`The NX_ETHERSCAN_URL environment variable must be a valid url`
|
||||
);
|
||||
console.error = consoleError;
|
||||
});
|
||||
|
||||
it('throws a validation error when NX_ETHEREUM_PROVIDER_URL is not a valid url', async () => {
|
||||
process.env['NX_ETHEREUM_PROVIDER_URL'] = 'invalid-url';
|
||||
const consoleError = console.error;
|
||||
console.error = noop;
|
||||
expect(() => {
|
||||
renderHook(() => useEnvironment(), {
|
||||
wrapper: MockWrapper,
|
||||
});
|
||||
}).toThrow(
|
||||
`The NX_ETHEREUM_PROVIDER_URL environment variable must be a valid url`
|
||||
);
|
||||
console.error = consoleError;
|
||||
});
|
||||
|
||||
describe('node selection', () => {
|
||||
it('updates the VEGA_URL from the config when it is missing from the environment', async () => {
|
||||
delete process.env['NX_VEGA_URL'];
|
||||
const { result } = renderHook(() => useEnvironment(), {
|
||||
wrapper: MockWrapper,
|
||||
@@ -235,10 +326,9 @@ describe('node selection', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('updates the VEGA_URL with the quickest node to respond from the config urls', async () => {
|
||||
act(async () => {
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('updates the VEGA_URL with the quickest node to respond from the config urls', async () => {
|
||||
delete process.env['NX_VEGA_URL'];
|
||||
|
||||
const mockNodes: Record<string, MockRequestConfig> = {
|
||||
@@ -248,13 +338,14 @@ describe('node selection', () => {
|
||||
'https://mock-node-4.com': { hasError: false, delay: 0 },
|
||||
};
|
||||
|
||||
// @ts-ignore: typescript doesn't recognize the mock implementation
|
||||
// @ts-ignore: typscript doesn't recognise the mock implementation
|
||||
global.fetch.mockImplementation(
|
||||
setupFetch(mockEnvironmentState.VEGA_CONFIG_URL, Object.keys(mockNodes))
|
||||
);
|
||||
// @ts-ignore allow adding a mock return value to mocked module
|
||||
createClient.mockImplementation((url: keyof typeof mockNodes) => {
|
||||
return createMockClient({ statistics: mockNodes[url] });
|
||||
createClient.mockImplementation((cfg: ClientOptions) => {
|
||||
// eslint-disable-next-line
|
||||
return createMockClient({ statistics: mockNodes[cfg.url!] });
|
||||
});
|
||||
|
||||
const nodeUrl = getQuickestNode(mockNodes);
|
||||
@@ -271,10 +362,8 @@ describe('node selection', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('ignores failing nodes and selects the first successful one to use', async () => {
|
||||
act(async () => {
|
||||
it('ignores failing nodes and selects the first successful one to use', async () => {
|
||||
delete process.env['NX_VEGA_URL'];
|
||||
|
||||
const mockNodes: Record<string, MockRequestConfig> = {
|
||||
@@ -284,13 +373,14 @@ describe('node selection', () => {
|
||||
'https://mock-node-4.com': { hasError: true, delay: 0 },
|
||||
};
|
||||
|
||||
// @ts-ignore: typescript doesn't recognize the mock implementation
|
||||
// @ts-ignore: typscript doesn't recognise the mock implementation
|
||||
global.fetch.mockImplementation(
|
||||
setupFetch(mockEnvironmentState.VEGA_CONFIG_URL, Object.keys(mockNodes))
|
||||
);
|
||||
// @ts-ignore allow adding a mock return value to mocked module
|
||||
createClient.mockImplementation((url: keyof typeof mockNodes) => {
|
||||
return createMockClient({ statistics: mockNodes[url] });
|
||||
createClient.mockImplementation((cfg: ClientOptions) => {
|
||||
// eslint-disable-next-line
|
||||
return createMockClient({ statistics: mockNodes[cfg.url!] });
|
||||
});
|
||||
|
||||
const nodeUrl = getQuickestNode(mockNodes);
|
||||
@@ -307,10 +397,8 @@ describe('node selection', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('has a network error when cannot connect to any nodes', async () => {
|
||||
act(async () => {
|
||||
it('has a network error when cannot connect to any nodes', async () => {
|
||||
delete process.env['NX_VEGA_URL'];
|
||||
|
||||
const mockNodes: Record<string, MockRequestConfig> = {
|
||||
@@ -320,13 +408,14 @@ describe('node selection', () => {
|
||||
'https://mock-node-4.com': { hasError: true, delay: 0 },
|
||||
};
|
||||
|
||||
// @ts-ignore: typescript doesn't recognize the mock implementation
|
||||
// @ts-ignore: typscript doesn't recognise the mock implementation
|
||||
global.fetch.mockImplementation(
|
||||
setupFetch(mockEnvironmentState.VEGA_CONFIG_URL, Object.keys(mockNodes))
|
||||
);
|
||||
// @ts-ignore allow adding a mock return value to mocked module
|
||||
createClient.mockImplementation((url: keyof typeof mockNodes) => {
|
||||
return createMockClient({ statistics: mockNodes[url] });
|
||||
createClient.mockImplementation((cfg: ClientOptions) => {
|
||||
// eslint-disable-next-line
|
||||
return createMockClient({ statistics: mockNodes[cfg.url!] });
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useEnvironment(), {
|
||||
@@ -338,17 +427,16 @@ describe('node selection', () => {
|
||||
...mockEnvironmentState,
|
||||
VEGA_URL: undefined,
|
||||
networkError: ErrorType.CONNECTION_ERROR_ALL,
|
||||
nodeSwitcherOpen: true,
|
||||
setNodeSwitcherOpen: result.current.setNodeSwitcherOpen,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('has a network error when it cannot fetch the network config and there is no VEGA_URL in the environment', async () => {
|
||||
act(async () => {
|
||||
it('has a network error when it cannot fetch the network config and there is no VEGA_URL in the environment', async () => {
|
||||
delete process.env['NX_VEGA_URL'];
|
||||
|
||||
// @ts-ignore: typescript doesn't recognize the mock implementation
|
||||
// @ts-ignore: typscript doesn't recognise the mock implementation
|
||||
global.fetch.mockImplementation(() => {
|
||||
throw new Error('Cannot fetch');
|
||||
});
|
||||
@@ -362,19 +450,18 @@ describe('node selection', () => {
|
||||
...mockEnvironmentState,
|
||||
VEGA_URL: undefined,
|
||||
networkError: ErrorType.CONFIG_LOAD_ERROR,
|
||||
nodeSwitcherOpen: true,
|
||||
setNodeSwitcherOpen: result.current.setNodeSwitcherOpen,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('logs an error when it cannot fetch the network config and there is a VEGA_URL in the environment', async () => {
|
||||
act(async () => {
|
||||
it('logs an error when it cannot fetch the network config and there is a VEGA_URL in the environment', async () => {
|
||||
const consoleWarnSpy = jest
|
||||
.spyOn(console, 'warn')
|
||||
.mockImplementation(noop);
|
||||
|
||||
// @ts-ignore: typescript doesn't recognize the mock implementation
|
||||
// @ts-ignore: typscript doesn't recognise the mock implementation
|
||||
global.fetch.mockImplementation(() => {
|
||||
throw new Error('Cannot fetch');
|
||||
});
|
||||
@@ -386,6 +473,7 @@ describe('node selection', () => {
|
||||
await waitFor(() => {
|
||||
expect(result.current).toEqual({
|
||||
...mockEnvironmentState,
|
||||
nodeSwitcherOpen: false,
|
||||
setNodeSwitcherOpen: result.current.setNodeSwitcherOpen,
|
||||
});
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||
@@ -396,15 +484,13 @@ describe('node selection', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// SKIP due to https://github.com/facebook/jest/issues/12670
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('has a network error when the config is invalid and there is no VEGA_URL in the environment', async () => {
|
||||
act(async () => {
|
||||
// SKIP due to https://github.com/facebook/jest/issues/12670
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('has a network error when the config is invalid and there is no VEGA_URL in the environment', async () => {
|
||||
delete process.env['NX_VEGA_URL'];
|
||||
|
||||
// @ts-ignore: typescript doesn't recognize the mock implementation
|
||||
// @ts-ignore: typscript doesn't recognise the mock implementation
|
||||
global.fetch.mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
@@ -421,21 +507,20 @@ describe('node selection', () => {
|
||||
...mockEnvironmentState,
|
||||
VEGA_URL: undefined,
|
||||
networkError: ErrorType.CONFIG_VALIDATION_ERROR,
|
||||
nodeSwitcherOpen: true,
|
||||
setNodeSwitcherOpen: result.current.setNodeSwitcherOpen,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// SKIP due to https://github.com/facebook/jest/issues/12670
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('logs an error when the network config in invalid and there is a VEGA_URL in the environment', async () => {
|
||||
act(async () => {
|
||||
// SKIP due to https://github.com/facebook/jest/issues/12670
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('logs an error when the network config is invalid and there is a VEGA_URL in the environment', async () => {
|
||||
const consoleWarnSpy = jest
|
||||
.spyOn(console, 'warn')
|
||||
.mockImplementation(noop);
|
||||
|
||||
// @ts-ignore: typescript doesn't recognize the mock implementation
|
||||
// @ts-ignore: typscript doesn't recognise the mock implementation
|
||||
global.fetch.mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
@@ -447,6 +532,8 @@ describe('node selection', () => {
|
||||
wrapper: MockWrapper,
|
||||
});
|
||||
|
||||
expect(result.current.configLoading).toBe(true);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current).toEqual({
|
||||
...mockEnvironmentState,
|
||||
@@ -460,12 +547,8 @@ describe('node selection', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// SKIP due to https://github.com/facebook/jest/issues/12670
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('has a network error when the selected node is not a valid url', async () => {
|
||||
act(async () => {
|
||||
it('has a network error when the selected node is not a valid url', async () => {
|
||||
process.env['NX_VEGA_URL'] = 'not-url';
|
||||
|
||||
const { result } = renderHook(() => useEnvironment(), {
|
||||
@@ -475,15 +558,15 @@ describe('node selection', () => {
|
||||
await waitFor(() => {
|
||||
expect(result.current).toEqual({
|
||||
...mockEnvironmentState,
|
||||
VEGA_URL: 'not-url',
|
||||
nodeSwitcherOpen: true,
|
||||
networkError: ErrorType.INVALID_URL,
|
||||
setNodeSwitcherOpen: result.current.setNodeSwitcherOpen,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('has a network error when cannot connect to the selected node', async () => {
|
||||
act(async () => {
|
||||
it('has a network error when cannot connect to the selected node', async () => {
|
||||
// @ts-ignore allow adding a mock return value to mocked module
|
||||
createClient.mockImplementation(() => {
|
||||
return createMockClient({ statistics: { hasError: true } });
|
||||
@@ -496,15 +579,14 @@ describe('node selection', () => {
|
||||
await waitFor(() => {
|
||||
expect(result.current).toEqual({
|
||||
...mockEnvironmentState,
|
||||
nodeSwitcherOpen: true,
|
||||
networkError: ErrorType.CONNECTION_ERROR,
|
||||
setNodeSwitcherOpen: result.current.setNodeSwitcherOpen,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('has a network error when the selected node has not subscription available', async () => {
|
||||
act(async () => {
|
||||
it('has a network error when the selected node has no subscription available', async () => {
|
||||
// @ts-ignore allow adding a mock return value to mocked module
|
||||
createClient.mockImplementation(() => {
|
||||
return createMockClient({ busEvents: { hasError: true } });
|
||||
@@ -519,6 +601,7 @@ describe('node selection', () => {
|
||||
...mockEnvironmentState,
|
||||
networkError: ErrorType.SUBSCRIPTION_ERROR,
|
||||
setNodeSwitcherOpen: result.current.setNodeSwitcherOpen,
|
||||
nodeSwitcherOpen: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,6 +25,7 @@ import type {
|
||||
NodeData,
|
||||
Configuration,
|
||||
} from '../types';
|
||||
import { useNodeHealth } from './use-node-health';
|
||||
|
||||
type EnvironmentProviderProps = {
|
||||
config?: Configuration;
|
||||
@@ -33,7 +34,10 @@ type EnvironmentProviderProps = {
|
||||
};
|
||||
|
||||
export type EnvironmentState = Environment & {
|
||||
configLoading: boolean;
|
||||
networkError?: ErrorType;
|
||||
blockDifference: number;
|
||||
nodeSwitcherOpen: boolean;
|
||||
setNodeSwitcherOpen: () => void;
|
||||
};
|
||||
|
||||
@@ -76,10 +80,14 @@ export const EnvironmentProvider = ({
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const { state: nodes, clients } = useNodes(
|
||||
config,
|
||||
environment.MAINTENANCE_PAGE
|
||||
);
|
||||
|
||||
const blockDifference = useNodeHealth(clients, environment.VEGA_URL);
|
||||
|
||||
const nodeKeys = Object.keys(nodes);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -89,9 +97,10 @@ export const EnvironmentProvider = ({
|
||||
);
|
||||
if (successfulNodeKey && nodes[successfulNodeKey]) {
|
||||
Object.keys(clients).forEach((node) => clients[node]?.stop());
|
||||
const url = nodes[successfulNodeKey].url;
|
||||
updateEnvironment((prevEnvironment) => ({
|
||||
...prevEnvironment,
|
||||
VEGA_URL: nodes[successfulNodeKey].url,
|
||||
VEGA_URL: url,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -139,7 +148,10 @@ export const EnvironmentProvider = ({
|
||||
<EnvironmentContext.Provider
|
||||
value={{
|
||||
...environment,
|
||||
configLoading: loading,
|
||||
networkError,
|
||||
blockDifference,
|
||||
nodeSwitcherOpen: isNodeSwitcherOpen,
|
||||
setNodeSwitcherOpen: () => setNodeSwitcherOpen(true),
|
||||
}}
|
||||
>
|
||||
@@ -149,9 +161,9 @@ export const EnvironmentProvider = ({
|
||||
setDialogOpen={setNodeSwitcherOpen}
|
||||
loading={loading}
|
||||
config={config}
|
||||
onConnect={(url) =>
|
||||
updateEnvironment((env) => ({ ...env, VEGA_URL: url }))
|
||||
}
|
||||
onConnect={(url) => {
|
||||
updateEnvironment((env) => ({ ...env, VEGA_URL: url }));
|
||||
}}
|
||||
/>
|
||||
{children}
|
||||
</EnvironmentContext.Provider>
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import {
|
||||
useNodeHealth,
|
||||
NODE_SUBSET_COUNT,
|
||||
INTERVAL_TIME,
|
||||
} from './use-node-health';
|
||||
import type { createClient } from '@vegaprotocol/apollo-client';
|
||||
import type { ClientCollection } from './use-nodes';
|
||||
|
||||
function setup(...args: Parameters<typeof useNodeHealth>) {
|
||||
return renderHook(() => useNodeHealth(...args));
|
||||
}
|
||||
|
||||
function createMockClient(blockHeight: number) {
|
||||
return {
|
||||
query: jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
statistics: {
|
||||
chainId: 'chain-id',
|
||||
blockHeight: blockHeight.toString(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
} as unknown as ReturnType<typeof createClient>;
|
||||
}
|
||||
|
||||
function createRejectingClient() {
|
||||
return {
|
||||
query: () => Promise.reject(new Error('request failed')),
|
||||
} as unknown as ReturnType<typeof createClient>;
|
||||
}
|
||||
|
||||
function createErroringClient() {
|
||||
return {
|
||||
query: () =>
|
||||
Promise.resolve({
|
||||
error: new Error('failed'),
|
||||
}),
|
||||
} as unknown as ReturnType<typeof createClient>;
|
||||
}
|
||||
|
||||
const CURRENT_URL = 'https://current.test.com';
|
||||
|
||||
describe('useNodeHealth', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
it('provides difference between the highest block and the current block', async () => {
|
||||
const highest = 100;
|
||||
const curr = 97;
|
||||
const clientCollection: ClientCollection = {
|
||||
[CURRENT_URL]: createMockClient(curr),
|
||||
'https://n02.test.com': createMockClient(98),
|
||||
'https://n03.test.com': createMockClient(highest),
|
||||
};
|
||||
const { result } = setup(clientCollection, CURRENT_URL);
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(INTERVAL_TIME);
|
||||
});
|
||||
expect(result.current).toBe(highest - curr);
|
||||
});
|
||||
|
||||
it('returns -1 if the current node query fails', async () => {
|
||||
const clientCollection: ClientCollection = {
|
||||
[CURRENT_URL]: createRejectingClient(),
|
||||
'https://n02.test.com': createMockClient(200),
|
||||
'https://n03.test.com': createMockClient(102),
|
||||
};
|
||||
const { result } = setup(clientCollection, CURRENT_URL);
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(INTERVAL_TIME);
|
||||
});
|
||||
expect(result.current).toBe(-1);
|
||||
});
|
||||
|
||||
it('returns -1 if the current node query returns an error', async () => {
|
||||
const clientCollection: ClientCollection = {
|
||||
[CURRENT_URL]: createErroringClient(),
|
||||
'https://n02.test.com': createMockClient(200),
|
||||
'https://n03.test.com': createMockClient(102),
|
||||
};
|
||||
const { result } = setup(clientCollection, CURRENT_URL);
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(INTERVAL_TIME);
|
||||
});
|
||||
expect(result.current).toBe(-1);
|
||||
});
|
||||
|
||||
it('queries against 5 random nodes along with the current url', async () => {
|
||||
const clientCollection: ClientCollection = new Array(20)
|
||||
.fill(null)
|
||||
.reduce((obj, x, i) => {
|
||||
obj[`https://n${i}.test.com`] = createMockClient(100);
|
||||
return obj;
|
||||
}, {} as ClientCollection);
|
||||
clientCollection[CURRENT_URL] = createMockClient(100);
|
||||
const spyOnCurrent = jest.spyOn(clientCollection[CURRENT_URL], 'query');
|
||||
|
||||
const { result } = setup(clientCollection, CURRENT_URL);
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(INTERVAL_TIME);
|
||||
});
|
||||
|
||||
let count = 0;
|
||||
Object.values(clientCollection).forEach((client) => {
|
||||
// @ts-ignore jest.fn() in client setup means mock will be present
|
||||
if (client?.query.mock.calls.length) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
|
||||
expect(count).toBe(NODE_SUBSET_COUNT + 1);
|
||||
expect(spyOnCurrent).toHaveBeenCalledTimes(1);
|
||||
expect(result.current).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
import compact from 'lodash/compact';
|
||||
import shuffle from 'lodash/shuffle';
|
||||
import type { createClient } from '@vegaprotocol/apollo-client';
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { StatisticsQuery } from '../utils/__generated__/Node';
|
||||
import { StatisticsDocument } from '../utils/__generated__/Node';
|
||||
import type { ClientCollection } from './use-nodes';
|
||||
|
||||
// How often to query other nodes
|
||||
export const INTERVAL_TIME = 30 * 1000;
|
||||
// Number of nodes to query against
|
||||
export const NODE_SUBSET_COUNT = 5;
|
||||
|
||||
// Queries all nodes from the environment provider via an interval
|
||||
// to calculate and return the difference between the most advanced block
|
||||
// and the block height of the current node
|
||||
export const useNodeHealth = (clients: ClientCollection, vegaUrl?: string) => {
|
||||
const [blockDiff, setBlockDiff] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!clients || !vegaUrl) return;
|
||||
|
||||
const fetchBlockHeight = async (
|
||||
client?: ReturnType<typeof createClient>
|
||||
) => {
|
||||
try {
|
||||
const result = await client?.query<StatisticsQuery>({
|
||||
query: StatisticsDocument,
|
||||
fetchPolicy: 'no-cache', // always fetch and never cache
|
||||
});
|
||||
|
||||
if (!result) return null;
|
||||
if (result.error) return null;
|
||||
return result;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const getBlockHeights = async () => {
|
||||
const nodes = Object.keys(clients).filter((key) => key !== vegaUrl);
|
||||
// make sure that your current vega url is always included
|
||||
// so we can compare later
|
||||
const testNodes = [vegaUrl, ...randomSubset(nodes, NODE_SUBSET_COUNT)];
|
||||
const result = await Promise.all(
|
||||
testNodes.map((node) => fetchBlockHeight(clients[node]))
|
||||
);
|
||||
const blockHeights: { [node: string]: number | null } = {};
|
||||
testNodes.forEach((node, i) => {
|
||||
const data = result[i];
|
||||
const blockHeight = data
|
||||
? Number(data?.data.statistics.blockHeight)
|
||||
: null;
|
||||
blockHeights[node] = blockHeight;
|
||||
});
|
||||
return blockHeights;
|
||||
};
|
||||
|
||||
// Every INTERVAL_TIME get block heights of a random subset
|
||||
// of nodes and determine if your current node is falling behind
|
||||
const interval = setInterval(async () => {
|
||||
const blockHeights = await getBlockHeights();
|
||||
const highestBlock = Math.max.apply(
|
||||
null,
|
||||
compact(Object.values(blockHeights))
|
||||
);
|
||||
const currNodeBlock = blockHeights[vegaUrl];
|
||||
|
||||
if (!currNodeBlock) {
|
||||
// Block height query failed and null was returned
|
||||
setBlockDiff(-1);
|
||||
} else {
|
||||
setBlockDiff(highestBlock - currNodeBlock);
|
||||
}
|
||||
}, INTERVAL_TIME);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [clients, vegaUrl]);
|
||||
|
||||
return blockDiff;
|
||||
};
|
||||
|
||||
const randomSubset = (arr: string[], size: number) => {
|
||||
const shuffled = shuffle(arr);
|
||||
return shuffled.slice(0, size);
|
||||
};
|
||||
@@ -74,7 +74,7 @@ const getInitialState = (config?: Configuration) =>
|
||||
{}
|
||||
);
|
||||
|
||||
type ClientCollection = Record<
|
||||
export type ClientCollection = Record<
|
||||
string,
|
||||
undefined | ReturnType<typeof createClient>
|
||||
>;
|
||||
@@ -178,6 +178,10 @@ const reducer = (state: Record<string, NodeData>, action: Action) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Tests each node to see if its suitable for connecting to and returns that data
|
||||
* as a map of node urls to an object of that data
|
||||
*/
|
||||
export const useNodes = (config?: Configuration, skip?: boolean) => {
|
||||
const [clients, setClients] = useState<ClientCollection>({});
|
||||
const [state, dispatch] = useReducer(reducer, getInitialState(config));
|
||||
|
||||
@@ -83,9 +83,7 @@ const getBundledEnvironmentValue = (key: EnvKey) => {
|
||||
case 'ETH_WALLET_MNEMONIC':
|
||||
return process.env['NX_ETH_WALLET_MNEMONIC'];
|
||||
case 'MAINTENANCE_PAGE':
|
||||
return (
|
||||
process.env['MAINTENANCE_PAGE'] || process.env['NX_MAINTENANCE_PAGE']
|
||||
);
|
||||
return process.env['NX_MAINTENANCE_PAGE'];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -110,7 +108,7 @@ export const compileEnvironment = (
|
||||
const environment = ENV_KEYS.reduce((acc, key) => {
|
||||
const value = getValue(key, definitions);
|
||||
|
||||
if (value) {
|
||||
if (value !== undefined && value !== null) {
|
||||
return {
|
||||
...acc,
|
||||
[key]: value,
|
||||
|
||||
@@ -38,7 +38,11 @@ export const requestNode = (
|
||||
|
||||
let subscriptionSucceeded = false;
|
||||
|
||||
const client = createClient(url);
|
||||
const client = createClient({
|
||||
url,
|
||||
retry: false,
|
||||
connectToDevTools: false,
|
||||
});
|
||||
|
||||
// make a query for block height
|
||||
client
|
||||
|
||||
@@ -59,8 +59,8 @@ const update = (
|
||||
export type Trade = Omit<FillFieldsFragment, 'market'> & { market?: Market };
|
||||
export type TradeEdge = Edge<Trade>;
|
||||
|
||||
const getData = (responseData: FillsQuery): FillEdgeFragment[] =>
|
||||
responseData.party?.tradesConnection?.edges || [];
|
||||
const getData = (responseData: FillsQuery | null): FillEdgeFragment[] =>
|
||||
responseData?.party?.tradesConnection?.edges || [];
|
||||
|
||||
const getPageInfo = (responseData: FillsQuery): PageInfo | null =>
|
||||
responseData.party?.tradesConnection?.pageInfo || null;
|
||||
|
||||
@@ -5,8 +5,8 @@ import type {
|
||||
} from './__generated__/Proposals';
|
||||
import { ProposalsListDocument } from './__generated__/Proposals';
|
||||
|
||||
const getData = (responseData: ProposalsListQuery) =>
|
||||
responseData.proposalsConnection?.edges
|
||||
const getData = (responseData: ProposalsListQuery | null) =>
|
||||
responseData?.proposalsConnection?.edges
|
||||
?.filter((edge) => Boolean(edge?.node))
|
||||
.map((edge) => edge?.node as ProposalListFieldsFragment) || null;
|
||||
|
||||
|
||||
@@ -43,3 +43,12 @@ subscription OnUpdateNetworkParameters {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query ProposalOfMarket($marketId: ID!) {
|
||||
proposal(id: $marketId) {
|
||||
id
|
||||
terms {
|
||||
enactmentDatetime
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,13 @@ export type OnUpdateNetworkParametersSubscriptionVariables = Types.Exact<{ [key:
|
||||
|
||||
export type OnUpdateNetworkParametersSubscription = { __typename?: 'Subscription', busEvents?: Array<{ __typename?: 'BusEvent', event: { __typename?: 'AccountEvent' } | { __typename?: 'Asset' } | { __typename?: 'AuctionEvent' } | { __typename?: 'Deposit' } | { __typename?: 'LiquidityProvision' } | { __typename?: 'LossSocialization' } | { __typename?: 'MarginLevels' } | { __typename?: 'Market' } | { __typename?: 'MarketData' } | { __typename?: 'MarketEvent' } | { __typename?: 'MarketTick' } | { __typename?: 'NodeSignature' } | { __typename?: 'OracleSpec' } | { __typename?: 'Order' } | { __typename?: 'Party' } | { __typename?: 'PositionResolution' } | { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, datetime: any, terms: { __typename?: 'ProposalTerms', enactmentDatetime?: any | null, change: { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } } } | { __typename?: 'RiskFactor' } | { __typename?: 'SettleDistressed' } | { __typename?: 'SettlePosition' } | { __typename?: 'TimeUpdate' } | { __typename?: 'Trade' } | { __typename?: 'TransactionResult' } | { __typename?: 'TransferResponses' } | { __typename?: 'Vote' } | { __typename?: 'Withdrawal' } }> | null };
|
||||
|
||||
export type ProposalOfMarketQueryVariables = Types.Exact<{
|
||||
marketId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ProposalOfMarketQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, terms: { __typename?: 'ProposalTerms', enactmentDatetime?: any | null } } | null };
|
||||
|
||||
export const ProposalEventFieldsFragmentDoc = gql`
|
||||
fragment ProposalEventFields on Proposal {
|
||||
id
|
||||
@@ -113,4 +120,42 @@ export function useOnUpdateNetworkParametersSubscription(baseOptions?: Apollo.Su
|
||||
return Apollo.useSubscription<OnUpdateNetworkParametersSubscription, OnUpdateNetworkParametersSubscriptionVariables>(OnUpdateNetworkParametersDocument, options);
|
||||
}
|
||||
export type OnUpdateNetworkParametersSubscriptionHookResult = ReturnType<typeof useOnUpdateNetworkParametersSubscription>;
|
||||
export type OnUpdateNetworkParametersSubscriptionResult = Apollo.SubscriptionResult<OnUpdateNetworkParametersSubscription>;
|
||||
export type OnUpdateNetworkParametersSubscriptionResult = Apollo.SubscriptionResult<OnUpdateNetworkParametersSubscription>;
|
||||
export const ProposalOfMarketDocument = gql`
|
||||
query ProposalOfMarket($marketId: ID!) {
|
||||
proposal(id: $marketId) {
|
||||
id
|
||||
terms {
|
||||
enactmentDatetime
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useProposalOfMarketQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useProposalOfMarketQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useProposalOfMarketQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useProposalOfMarketQuery({
|
||||
* variables: {
|
||||
* marketId: // value for 'marketId'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useProposalOfMarketQuery(baseOptions: Apollo.QueryHookOptions<ProposalOfMarketQuery, ProposalOfMarketQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ProposalOfMarketQuery, ProposalOfMarketQueryVariables>(ProposalOfMarketDocument, options);
|
||||
}
|
||||
export function useProposalOfMarketLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ProposalOfMarketQuery, ProposalOfMarketQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ProposalOfMarketQuery, ProposalOfMarketQueryVariables>(ProposalOfMarketDocument, options);
|
||||
}
|
||||
export type ProposalOfMarketQueryHookResult = ReturnType<typeof useProposalOfMarketQuery>;
|
||||
export type ProposalOfMarketLazyQueryHookResult = ReturnType<typeof useProposalOfMarketLazyQuery>;
|
||||
export type ProposalOfMarketQueryResult = Apollo.QueryResult<ProposalOfMarketQuery, ProposalOfMarketQueryVariables>;
|
||||
@@ -35,12 +35,12 @@ export type LedgerEntry = LedgerEntryFragment & {
|
||||
|
||||
export type AggregatedLedgerEntriesEdge = Schema.AggregatedLedgerEntriesEdge;
|
||||
|
||||
const getData = (responseData: LedgerEntriesQuery) => {
|
||||
return responseData.ledgerEntries?.edges || [];
|
||||
const getData = (responseData: LedgerEntriesQuery | null) => {
|
||||
return responseData?.ledgerEntries?.edges || [];
|
||||
};
|
||||
|
||||
export const update = (
|
||||
data: ReturnType<typeof getData>,
|
||||
data: ReturnType<typeof getData> | null,
|
||||
delta: ReturnType<typeof getData>,
|
||||
reload: () => void,
|
||||
variables?: LedgerEntriesQueryVariables
|
||||
|
||||
@@ -35,10 +35,10 @@ export const liquidityProvisionsDataProvider = makeDataProvider<
|
||||
query: LiquidityProvisionsDocument,
|
||||
subscriptionQuery: LiquidityProvisionsUpdateDocument,
|
||||
update: (
|
||||
data: LiquidityProvisionFieldsFragment[],
|
||||
data: LiquidityProvisionFieldsFragment[] | null,
|
||||
deltas: LiquidityProvisionsUpdateSubscription['liquidityProvisions']
|
||||
) => {
|
||||
return produce(data, (draft) => {
|
||||
return produce(data || [], (draft) => {
|
||||
deltas?.forEach((delta) => {
|
||||
const id = getId(delta);
|
||||
const index = draft.findIndex((a) => getId(a) === id);
|
||||
@@ -63,9 +63,9 @@ export const liquidityProvisionsDataProvider = makeDataProvider<
|
||||
});
|
||||
});
|
||||
},
|
||||
getData: (responseData: LiquidityProvisionsQuery) => {
|
||||
getData: (responseData: LiquidityProvisionsQuery | null) => {
|
||||
return (
|
||||
responseData.market?.liquidityProvisionsConnection?.edges?.map(
|
||||
responseData?.market?.liquidityProvisionsConnection?.edges?.map(
|
||||
(e) => e?.node
|
||||
) ?? []
|
||||
).filter((e) => !!e) as LiquidityProvisionFieldsFragment[];
|
||||
@@ -105,7 +105,7 @@ export const marketLiquidityDataProvider = makeDataProvider<
|
||||
never
|
||||
>({
|
||||
query: MarketLpDocument,
|
||||
getData: (responseData: MarketLpQuery) => {
|
||||
getData: (responseData: MarketLpQuery | null) => {
|
||||
return responseData;
|
||||
},
|
||||
});
|
||||
@@ -119,10 +119,10 @@ export const liquidityFeeShareDataProvider = makeDataProvider<
|
||||
query: LiquidityProviderFeeShareDocument,
|
||||
subscriptionQuery: LiquidityProviderFeeShareUpdateDocument,
|
||||
update: (
|
||||
data: LiquidityProviderFeeShareFieldsFragment[],
|
||||
data: LiquidityProviderFeeShareFieldsFragment[] | null,
|
||||
deltas: LiquidityProviderFeeShareUpdateSubscription['marketsData'][0]['liquidityProviderFeeShare']
|
||||
) => {
|
||||
return produce(data, (draft) => {
|
||||
return produce(data || [], (draft) => {
|
||||
deltas?.forEach((delta) => {
|
||||
const id = delta.partyId;
|
||||
const index = draft.findIndex((a) => a.party.id === id);
|
||||
@@ -143,7 +143,7 @@ export const liquidityFeeShareDataProvider = makeDataProvider<
|
||||
});
|
||||
},
|
||||
getData: (data) => {
|
||||
return data.market?.data?.liquidityProviderFeeShare || [];
|
||||
return data?.market?.data?.liquidityProviderFeeShare || [];
|
||||
},
|
||||
getDelta: (subscriptionData: LiquidityProviderFeeShareUpdateSubscription) => {
|
||||
return subscriptionData.marketsData[0].liquidityProviderFeeShare;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user