Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca3b2dfa4d | ||
|
|
7ac4fbaa67 | ||
|
|
87fbcb2994 | ||
|
|
7c632e8867 | ||
|
|
42c477763c | ||
|
|
853ec8f69c | ||
|
|
b68c090ee5 | ||
|
|
cb9b811730 | ||
|
|
98b5260d93 | ||
|
|
613262f7a5 | ||
|
|
9ae6f5201c | ||
|
|
cfb75fcf8f | ||
|
|
ae6ccdfb52 | ||
|
|
9e73c6a462 | ||
|
|
24c017bc2b | ||
|
|
3188f23439 | ||
|
|
33e528c569 | ||
|
|
e10945eeda | ||
|
|
47b2a2fdcb | ||
|
|
860938f26c | ||
|
|
5766d84804 | ||
|
|
6362c022b5 | ||
|
|
a6118c14dd | ||
|
|
d384fb35f1 | ||
|
|
3ed3714e79 | ||
|
|
3893b26d30 | ||
|
|
1ba0ad234b | ||
|
|
9f6a5aac39 |
@@ -68,7 +68,6 @@ jobs:
|
||||
working-directory: frontend-monorepo
|
||||
env:
|
||||
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
|
||||
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPRESS_ETH_WALLET_MNEMONIC }}
|
||||
CYPRESS_VEGA_WALLET_API_TOKEN: ${{ steps.setup-vega.outputs.token }}
|
||||
CYPRESS_grepTags: ${{ inputs.tags }}
|
||||
|
||||
|
||||
@@ -6,4 +6,5 @@ NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https:
|
||||
NX_VEGA_ENV=DEVNET
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_BLOCK_EXPLORER=https://be.devnet1.vega.xyz/rest
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://dev.token.vega.xyz
|
||||
@@ -7,3 +7,4 @@ NX_VEGA_ENV=MAINNET
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/rest/
|
||||
NX_ETHERSCAN_URL=https://etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://token.vega.xyz
|
||||
|
||||
@@ -7,6 +7,7 @@ NX_VEGA_ENV=MIRROR
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_BLOCK_EXPLORER=https://be.mainnet-mirror.vega.xyz/rest/
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://mainnet-mirror.token.vega.xyz
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS=1
|
||||
|
||||
@@ -9,3 +9,4 @@ NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https:
|
||||
NX_TENDERMINT_URL=https://tm.n01.sandbox.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.sandbox.vega.xyz/websocket
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://sandbox.token.vega.xyz
|
||||
|
||||
@@ -11,3 +11,4 @@ NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
NX_BLOCK_EXPLORER=https://be.stagnet1.vega.xyz/rest
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://stagnet1.token.vega.xyz
|
||||
|
||||
@@ -4,4 +4,5 @@ NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_ENV=STAGNET3
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
|
||||
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
|
||||
NX_VEGA_GOVERNANCE_URL=https://stagnet3.token.vega.xyz
|
||||
|
||||
@@ -10,3 +10,4 @@ NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_VEGA_NETWORKS={}
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://token.fairground.wtf
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
"executor": "@nrwl/workspace:run-commands",
|
||||
"options": {
|
||||
"commands": [
|
||||
"npx openapi-typescript https://raw.githubusercontent.com/vegaprotocol/documentation/main/specs/v0.66.1/blockexplorer.openapi.json --output apps/explorer/src/types/explorer.d.ts --immutable-types"
|
||||
"npx openapi-typescript https://raw.githubusercontent.com/vegaprotocol/documentation/main/specs/v0.67.3/blockexplorer.openapi.json --output apps/explorer/src/types/explorer.d.ts --immutable-types"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -8,6 +8,9 @@ query ExplorerMarket($id: ID!) {
|
||||
product {
|
||||
... on Future {
|
||||
quoteName
|
||||
settlementAsset {
|
||||
decimals
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -8,7 +8,7 @@ export type ExplorerMarketQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } | null };
|
||||
export type ExplorerMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', decimals: number } } } } } | null };
|
||||
|
||||
|
||||
export const ExplorerMarketDocument = gql`
|
||||
@@ -22,6 +22,9 @@ export const ExplorerMarketDocument = gql`
|
||||
product {
|
||||
... on Future {
|
||||
quoteName
|
||||
settlementAsset {
|
||||
decimals
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { Routes } from '../../../routes/route-names';
|
||||
import { useExplorerMarketQuery } from './__generated__/Market';
|
||||
import { Link } from 'react-router-dom';
|
||||
@@ -8,6 +7,7 @@ import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export type MarketLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
id: string;
|
||||
showMarketName?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -15,7 +15,11 @@ export type MarketLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
* with a link to the markets list. If the name does not come back
|
||||
* it will use the ID instead
|
||||
*/
|
||||
const MarketLink = ({ id, ...props }: MarketLinkProps) => {
|
||||
const MarketLink = ({
|
||||
id,
|
||||
showMarketName = true,
|
||||
...props
|
||||
}: MarketLinkProps) => {
|
||||
const { data, error, loading } = useExplorerMarketQuery({
|
||||
variables: { id },
|
||||
});
|
||||
@@ -37,11 +41,24 @@ const MarketLink = ({ id, ...props }: MarketLinkProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Link className="underline" {...props} to={`/${Routes.MARKETS}#${id}`}>
|
||||
{label}
|
||||
</Link>
|
||||
);
|
||||
if (showMarketName) {
|
||||
return (
|
||||
<Link
|
||||
className="underline"
|
||||
{...props}
|
||||
to={`/${Routes.MARKETS}#${id}`}
|
||||
title={id}
|
||||
>
|
||||
{label}
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Link className="underline" {...props} to={`/${Routes.MARKETS}#${id}`}>
|
||||
{id}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default MarketLink;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Routes } from '../../../routes/route-names';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
|
||||
export type OracleLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
id: string;
|
||||
};
|
||||
|
||||
const OracleLink = ({ id, ...props }: OracleLinkProps) => {
|
||||
return (
|
||||
<Link
|
||||
className="underline font-mono"
|
||||
{...props}
|
||||
to={`/${Routes.ORACLES}/${id}`}
|
||||
>
|
||||
{id}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default OracleLink;
|
||||
@@ -0,0 +1,9 @@
|
||||
query ExplorerProposal($id: ID!) {
|
||||
proposal(id: $id) {
|
||||
id
|
||||
rationale {
|
||||
title
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerProposalQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerProposalQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null };
|
||||
|
||||
|
||||
export const ExplorerProposalDocument = gql`
|
||||
query ExplorerProposal($id: ID!) {
|
||||
proposal(id: $id) {
|
||||
id
|
||||
rationale {
|
||||
title
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerProposalQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerProposalQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerProposalQuery` 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 } = useExplorerProposalQuery({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerProposalQuery(baseOptions: Apollo.QueryHookOptions<ExplorerProposalQuery, ExplorerProposalQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerProposalQuery, ExplorerProposalQueryVariables>(ExplorerProposalDocument, options);
|
||||
}
|
||||
export function useExplorerProposalLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerProposalQuery, ExplorerProposalQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerProposalQuery, ExplorerProposalQueryVariables>(ExplorerProposalDocument, options);
|
||||
}
|
||||
export type ExplorerProposalQueryHookResult = ReturnType<typeof useExplorerProposalQuery>;
|
||||
export type ExplorerProposalLazyQueryHookResult = ReturnType<typeof useExplorerProposalLazyQuery>;
|
||||
export type ExplorerProposalQueryResult = Apollo.QueryResult<ExplorerProposalQuery, ExplorerProposalQueryVariables>;
|
||||
@@ -0,0 +1,85 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { render } from '@testing-library/react';
|
||||
import ProposalLink from './proposal-link';
|
||||
import { ExplorerProposalDocument } from './__generated__/Proposal';
|
||||
import { GraphQLError } from 'graphql';
|
||||
|
||||
function renderComponent(id: string, mocks: MockedResponse[]) {
|
||||
return (
|
||||
<MockedProvider mocks={mocks} addTypename={false}>
|
||||
<MemoryRouter>
|
||||
<ProposalLink id={id} />
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
}
|
||||
|
||||
describe('Proposal link component', () => {
|
||||
it('Renders the ID at first', () => {
|
||||
const res = render(renderComponent('123', []));
|
||||
expect(res.getByText('123')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders the ID with an emoji on error', async () => {
|
||||
const mock = {
|
||||
request: {
|
||||
query: ExplorerProposalDocument,
|
||||
variables: {
|
||||
id: '456',
|
||||
},
|
||||
},
|
||||
result: {
|
||||
errors: [new GraphQLError('No such proposal')],
|
||||
},
|
||||
};
|
||||
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 () => {
|
||||
const mock = {
|
||||
request: {
|
||||
query: ExplorerProposalDocument,
|
||||
variables: {
|
||||
id: '123',
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
proposal: {
|
||||
id: '123',
|
||||
rationale: {
|
||||
title: 'test-title',
|
||||
description: 'test description',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const res = render(renderComponent('123', [mock]));
|
||||
expect(res.getByText('123')).toBeInTheDocument();
|
||||
expect(await res.findByText('test-title')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Leaves the proposal id when the market is not found', async () => {
|
||||
const mock = {
|
||||
request: {
|
||||
query: ExplorerProposalDocument,
|
||||
variables: {
|
||||
id: '123',
|
||||
},
|
||||
},
|
||||
error: new Error('No such asset'),
|
||||
};
|
||||
|
||||
const res = render(renderComponent('123', [mock]));
|
||||
expect(await res.findByText('123')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import { useExplorerProposalQuery } from './__generated__/Proposal';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { ENV } from '../../../config/env';
|
||||
export type ProposalLinkProps = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a proposal ID, generates an external link over to
|
||||
* the Governance page for more information
|
||||
*/
|
||||
const ProposalLink = ({ id }: ProposalLinkProps) => {
|
||||
const { data } = useExplorerProposalQuery({
|
||||
variables: { id },
|
||||
});
|
||||
|
||||
const base = ENV.dataSources.governanceUrl;
|
||||
const label = data?.proposal?.rationale.title || id;
|
||||
|
||||
return <ExternalLink href={`${base}/proposals/${id}`}>{label}</ExternalLink>;
|
||||
};
|
||||
|
||||
export default ProposalLink;
|
||||
@@ -97,6 +97,10 @@ describe('Order TX Summary component', () => {
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: 'TEST',
|
||||
settlementAsset: {
|
||||
__typeName: 'SettlementAsset',
|
||||
decimals: 18,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -3,61 +3,78 @@ import { MockedProvider } from '@apollo/client/testing';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { render } from '@testing-library/react';
|
||||
import PriceInMarket from './price-in-market';
|
||||
import type { DecimalSource } from './price-in-market';
|
||||
import { ExplorerMarketDocument } from '../links/market-link/__generated__/Market';
|
||||
|
||||
function renderComponent(
|
||||
price: string,
|
||||
marketId: string,
|
||||
mocks: MockedResponse[]
|
||||
mocks: MockedResponse[],
|
||||
decimalSource: DecimalSource = 'MARKET'
|
||||
) {
|
||||
return (
|
||||
<MockedProvider mocks={mocks} addTypename={false}>
|
||||
<MemoryRouter>
|
||||
<PriceInMarket marketId={marketId} price={price} />
|
||||
<PriceInMarket
|
||||
marketId={marketId}
|
||||
price={price}
|
||||
decimalSource={decimalSource}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const fullMock = {
|
||||
request: {
|
||||
query: ExplorerMarketDocument,
|
||||
variables: {
|
||||
id: '123',
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
market: {
|
||||
id: '123',
|
||||
decimalPlaces: 2,
|
||||
state: 'irrelevant-test-data',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'test dai',
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: 'dai',
|
||||
settlementAsset: {
|
||||
decimals: 18,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('Price in Market component', () => {
|
||||
it('Renders the raw price when there is no market data', () => {
|
||||
const res = render(renderComponent('100', '123', []));
|
||||
expect(res.getByText('100')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders the formatted price when market data is fetched', async () => {
|
||||
const mock = {
|
||||
request: {
|
||||
query: ExplorerMarketDocument,
|
||||
variables: {
|
||||
id: '123',
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
market: {
|
||||
id: '123',
|
||||
decimalPlaces: 2,
|
||||
state: 'irrelevant-test-data',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'test dai',
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: 'dai',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const res = render(renderComponent('100', '123', [mock]));
|
||||
it('Renders the formatted price when market data is fetched, using market decimals by default', async () => {
|
||||
const res = render(renderComponent('100', '123', [fullMock]));
|
||||
expect(await res.findByText('1.00')).toBeInTheDocument();
|
||||
expect(await res.findByText('dai')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders the formatted price when market data is fetched, using settlement decimals', async () => {
|
||||
const res = render(
|
||||
renderComponent('100', '123', [fullMock], 'SETTLEMENT_ASSET')
|
||||
);
|
||||
expect(await res.findByText('0.0000000000000001')).toBeInTheDocument();
|
||||
expect(await res.findByText('dai')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Leaves the market id when the market is not found', async () => {
|
||||
const mock = {
|
||||
request: {
|
||||
|
||||
@@ -3,16 +3,23 @@ import isUndefined from 'lodash/isUndefined';
|
||||
import { useExplorerMarketQuery } from '../links/market-link/__generated__/Market';
|
||||
import get from 'lodash/get';
|
||||
|
||||
export type DecimalSource = 'MARKET' | 'SETTLEMENT_ASSET';
|
||||
|
||||
export type PriceInMarketProps = {
|
||||
marketId: string;
|
||||
price: string;
|
||||
decimalSource?: DecimalSource;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a market ID and a price it will fetch the market
|
||||
* and format the price in that market's decimal places.
|
||||
*/
|
||||
const PriceInMarket = ({ marketId, price }: PriceInMarketProps) => {
|
||||
const PriceInMarket = ({
|
||||
marketId,
|
||||
price,
|
||||
decimalSource = 'MARKET',
|
||||
}: PriceInMarketProps) => {
|
||||
const { data } = useExplorerMarketQuery({
|
||||
variables: { id: marketId },
|
||||
fetchPolicy: 'cache-first',
|
||||
@@ -20,8 +27,19 @@ const PriceInMarket = ({ marketId, price }: PriceInMarketProps) => {
|
||||
|
||||
let label = price;
|
||||
|
||||
if (data && data.market?.decimalPlaces) {
|
||||
label = addDecimalsFormatNumber(price, data.market.decimalPlaces);
|
||||
if (data) {
|
||||
if (decimalSource === 'MARKET' && data.market?.decimalPlaces) {
|
||||
label = addDecimalsFormatNumber(price, data.market.decimalPlaces);
|
||||
} else if (
|
||||
decimalSource === 'SETTLEMENT_ASSET' &&
|
||||
data.market?.tradableInstrument.instrument.product.settlementAsset
|
||||
) {
|
||||
label = addDecimalsFormatNumber(
|
||||
price,
|
||||
data.market?.tradableInstrument.instrument.product.settlementAsset
|
||||
.decimals
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const suffix = get(
|
||||
|
||||
@@ -20,6 +20,7 @@ import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
|
||||
import { TxDetailsLiquidityAmendment } from './tx-liquidity-amend';
|
||||
import { TxDetailsLiquidityCancellation } from './tx-liquidity-cancel';
|
||||
import { TxDetailsDataSubmission } from './tx-data-submission';
|
||||
import { TxProposalVote } from './tx-proposal-vote';
|
||||
|
||||
interface TxDetailsWrapperProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -91,6 +92,8 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
|
||||
return TxDetailsOrderAmend;
|
||||
case 'Validator Heartbeat':
|
||||
return TxDetailsHeartbeat;
|
||||
case 'Vote on Proposal':
|
||||
return TxProposalVote;
|
||||
case 'Batch Market Instructions':
|
||||
return TxDetailsBatch;
|
||||
case 'Chain Event':
|
||||
|
||||
@@ -55,6 +55,7 @@ export const TxDetailsLiquidityAmendment = ({
|
||||
<PriceInMarket
|
||||
price={amendment.commitmentAmount}
|
||||
marketId={marketId}
|
||||
decimalSource="SETTLEMENT_ASSET"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -54,6 +54,7 @@ export const TxDetailsLiquiditySubmission = ({
|
||||
<PriceInMarket
|
||||
price={submission.commitmentAmount}
|
||||
marketId={marketId}
|
||||
decimalSource="SETTLEMENT_ASSET"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -50,6 +50,12 @@ export const TxDetailsOrder = ({
|
||||
<code>{deterministicId}</code>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Market ID')}</TableCell>
|
||||
<TableCell>
|
||||
<MarketLink id={marketId} showMarketName={false} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Market')}</TableCell>
|
||||
<TableCell>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
import ProposalLink from '../../links/proposal-link/proposal-link';
|
||||
|
||||
interface TxProposalVoteProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* A vote on a proposal.
|
||||
*
|
||||
* One inconsistency here that feels right but should be standardised is that there are two rows
|
||||
* for the proposal ID, one that creates a link with the text of the proposal title that takes
|
||||
* a user out to the governance site, and the other that just shows the ID. Both are useful, but
|
||||
* doesn't feel quite right. This could be fixed with a separate component to display a preview
|
||||
* of the proposal and link off to the governance site, removing the title from the header. Or
|
||||
* something else. For now, this is more useful than the default view
|
||||
*/
|
||||
export const TxProposalVote = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
}: TxProposalVoteProps) => {
|
||||
if (!txData || !txData.command.voteSubmission) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const vote = txData.command.voteSubmission.value ? '👍' : '👎';
|
||||
return (
|
||||
<TableWithTbody className="mb-8">
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Proposal ID')}</TableCell>
|
||||
<TableCell>{txData.command.voteSubmission.proposalId}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Proposal details')}</TableCell>
|
||||
<TableCell>
|
||||
<ProposalLink id={txData.command.voteSubmission.proposalId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Proposal')}</TableCell>
|
||||
<TableCell>{txData.command.voteSubmission.proposalId}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Vote')}</TableCell>
|
||||
<TableCell>{vote}</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
);
|
||||
};
|
||||
@@ -16,6 +16,7 @@ export const ENV = {
|
||||
tendermintUrl: windowOrDefault('NX_TENDERMINT_URL'),
|
||||
tendermintWebsocketUrl: windowOrDefault('NX_TENDERMINT_WEBSOCKET_URL'),
|
||||
ethExplorerUrl: windowOrDefault('NX_ETHERSCAN_URL'),
|
||||
governanceUrl: windowOrDefault('NX_VEGA_GOVERNANCE_URL'),
|
||||
},
|
||||
flags: {
|
||||
assets: truthy.includes(windowOrDefault('NX_EXPLORER_ASSETS')),
|
||||
|
||||
@@ -51,6 +51,9 @@ fragment ExplorerOracleDataSource on OracleSpec {
|
||||
|
||||
fragment ExplorerOracleDataConnection on OracleSpec {
|
||||
dataConnection {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
externalData {
|
||||
@@ -79,12 +82,21 @@ fragment ExplorerOracleDataConnection on OracleSpec {
|
||||
}
|
||||
|
||||
query ExplorerOracleSpecs {
|
||||
oracleSpecsConnection {
|
||||
oracleSpecsConnection(pagination: { first: 50 }) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
...ExplorerOracleDataSource
|
||||
...ExplorerOracleDataConnection
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query ExplorerOracleSpecById($id: ID!) {
|
||||
oracleSpec(oracleSpecId: $id) {
|
||||
...ExplorerOracleDataSource
|
||||
...ExplorerOracleDataConnection
|
||||
}
|
||||
}
|
||||
|
||||
+55
-5
@@ -5,12 +5,19 @@ import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerOracleDataSourceFragment = { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } } };
|
||||
|
||||
export type ExplorerOracleDataConnectionFragment = { __typename?: 'OracleSpec', dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } };
|
||||
export type ExplorerOracleDataConnectionFragment = { __typename?: 'OracleSpec', dataConnection: { __typename?: 'OracleDataConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } };
|
||||
|
||||
export type ExplorerOracleSpecsQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
|
||||
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
|
||||
|
||||
export type ExplorerOracleSpecByIdQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerOracleSpecByIdQuery = { __typename?: 'Query', oracleSpec?: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } | null };
|
||||
|
||||
export const ExplorerOracleDataSourceFragmentDoc = gql`
|
||||
fragment ExplorerOracleDataSource on OracleSpec {
|
||||
@@ -66,7 +73,10 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
|
||||
`;
|
||||
export const ExplorerOracleDataConnectionFragmentDoc = gql`
|
||||
fragment ExplorerOracleDataConnection on OracleSpec {
|
||||
dataConnection {
|
||||
dataConnection(pagination: {first: 1}) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
externalData {
|
||||
@@ -96,7 +106,10 @@ export const ExplorerOracleDataConnectionFragmentDoc = gql`
|
||||
`;
|
||||
export const ExplorerOracleSpecsDocument = gql`
|
||||
query ExplorerOracleSpecs {
|
||||
oracleSpecsConnection {
|
||||
oracleSpecsConnection(pagination: {first: 50}) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
...ExplorerOracleDataSource
|
||||
@@ -133,4 +146,41 @@ export function useExplorerOracleSpecsLazyQuery(baseOptions?: Apollo.LazyQueryHo
|
||||
}
|
||||
export type ExplorerOracleSpecsQueryHookResult = ReturnType<typeof useExplorerOracleSpecsQuery>;
|
||||
export type ExplorerOracleSpecsLazyQueryHookResult = ReturnType<typeof useExplorerOracleSpecsLazyQuery>;
|
||||
export type ExplorerOracleSpecsQueryResult = Apollo.QueryResult<ExplorerOracleSpecsQuery, ExplorerOracleSpecsQueryVariables>;
|
||||
export type ExplorerOracleSpecsQueryResult = Apollo.QueryResult<ExplorerOracleSpecsQuery, ExplorerOracleSpecsQueryVariables>;
|
||||
export const ExplorerOracleSpecByIdDocument = gql`
|
||||
query ExplorerOracleSpecById($id: ID!) {
|
||||
oracleSpec(oracleSpecId: $id) {
|
||||
...ExplorerOracleDataSource
|
||||
...ExplorerOracleDataConnection
|
||||
}
|
||||
}
|
||||
${ExplorerOracleDataSourceFragmentDoc}
|
||||
${ExplorerOracleDataConnectionFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useExplorerOracleSpecByIdQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerOracleSpecByIdQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerOracleSpecByIdQuery` 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 } = useExplorerOracleSpecByIdQuery({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerOracleSpecByIdQuery(baseOptions: Apollo.QueryHookOptions<ExplorerOracleSpecByIdQuery, ExplorerOracleSpecByIdQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerOracleSpecByIdQuery, ExplorerOracleSpecByIdQueryVariables>(ExplorerOracleSpecByIdDocument, options);
|
||||
}
|
||||
export function useExplorerOracleSpecByIdLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerOracleSpecByIdQuery, ExplorerOracleSpecByIdQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerOracleSpecByIdQuery, ExplorerOracleSpecByIdQueryVariables>(ExplorerOracleSpecByIdDocument, options);
|
||||
}
|
||||
export type ExplorerOracleSpecByIdQueryHookResult = ReturnType<typeof useExplorerOracleSpecByIdQuery>;
|
||||
export type ExplorerOracleSpecByIdLazyQueryHookResult = ReturnType<typeof useExplorerOracleSpecByIdLazyQuery>;
|
||||
export type ExplorerOracleSpecByIdQueryResult = Apollo.QueryResult<ExplorerOracleSpecByIdQuery, ExplorerOracleSpecByIdQueryVariables>;
|
||||
@@ -37,7 +37,7 @@ describe('Oracle Data view', () => {
|
||||
dataConnection: {
|
||||
edges: [],
|
||||
},
|
||||
} as ExplorerOracleDataConnectionFragment)
|
||||
} as unknown as ExplorerOracleDataConnectionFragment)
|
||||
);
|
||||
expect(res.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
@@ -67,7 +67,7 @@ describe('Oracle Signers component', () => {
|
||||
__typename: 'Signer',
|
||||
signer: {
|
||||
__typename: 'PubKey',
|
||||
key: '123',
|
||||
key: '1234567891234567789123456789123456778912345678912345677891234567',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
EthExplorerLinkTypes,
|
||||
} from '../../../components/links/eth-explorer-link/eth-explorer-link';
|
||||
import { TableRow, TableCell, TableHeader } from '../../../components/table';
|
||||
import { remove0x } from '@vegaprotocol/react-helpers';
|
||||
|
||||
import type { SourceType } from './oracle';
|
||||
|
||||
@@ -14,7 +15,15 @@ export type Signer = {
|
||||
};
|
||||
|
||||
export function getAddressTypeLabel(signer: Signer) {
|
||||
return signer.__typename === 'ETHAddress' ? 'ETH' : 'Vega';
|
||||
const res = signer.__typename === 'ETHAddress' ? 'ETH' : 'Vega';
|
||||
|
||||
// This is a hack: some older oracles were submitted before proper checks stopped
|
||||
// ETH addresses being returned as Vega addresses
|
||||
if (res === 'Vega' && signer?.key?.length !== 64) {
|
||||
return 'ETH';
|
||||
} else {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
export function getAddress(signer: Signer) {
|
||||
@@ -29,6 +38,16 @@ export function getAddressLink(signer: Signer) {
|
||||
|
||||
if (signer.__typename === 'ETHAddress') {
|
||||
return <EthExplorerLink id={address} type={EthExplorerLinkTypes.address} />;
|
||||
} else if (signer.__typename === 'PubKey' && address.length !== 64) {
|
||||
// This is a hack: some older oracles were submitted before proper checks stopped
|
||||
// ETH addresses being returned as Vega addresses
|
||||
// Hacky 0x prefixing as a bonus
|
||||
return (
|
||||
<EthExplorerLink
|
||||
id={`0x${remove0x(address)}`}
|
||||
type={EthExplorerLinkTypes.address}
|
||||
/>
|
||||
);
|
||||
} else if (signer.__typename === 'PubKey') {
|
||||
return <PartyLink id={address} />;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import { OracleData } from './oracle-data';
|
||||
import { OracleFilter } from './oracle-filter';
|
||||
import { OracleDetailsType } from './oracle-details-type';
|
||||
import { OracleMarkets } from './oracle-markets';
|
||||
import { OracleSigners } from './oracle-signers';
|
||||
import OracleLink from '../../../components/links/oracle-link/oracle-link';
|
||||
|
||||
export type SourceType =
|
||||
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType'];
|
||||
@@ -21,36 +23,47 @@ interface OracleDetailsProps {
|
||||
id: string;
|
||||
dataSource: ExplorerOracleDataSourceFragment;
|
||||
dataConnection: ExplorerOracleDataConnectionFragment;
|
||||
// Defaults to false. Hides the count of 'broadcasts' this oracle has seen
|
||||
showBroadcasts?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notes:
|
||||
* - Matched data is really 'Data that matched this oracle' and given oracles are unique
|
||||
* to each market, and each serves either as trading termination or settlement, really
|
||||
* they will only ever see 1 match (most likely). So it should be more like 'Has seen
|
||||
* data' vs 'Has not yet seen data'
|
||||
*/
|
||||
export const OracleDetails = ({
|
||||
id,
|
||||
dataSource,
|
||||
dataConnection,
|
||||
showBroadcasts = false,
|
||||
}: OracleDetailsProps) => {
|
||||
const sourceType = dataSource.dataSourceSpec.spec.data.sourceType;
|
||||
const reportsCount: number = dataConnection.dataConnection.edges?.length || 0;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TableWithTbody>
|
||||
<TableWithTbody className="mb-2">
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">{t('ID')}</TableHeader>
|
||||
<TableCell modifier="bordered">{id}</TableCell>
|
||||
<TableCell modifier="bordered">
|
||||
<OracleLink id={id} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<OracleDetailsType type={sourceType.__typename} />
|
||||
{
|
||||
// Disabled until https://github.com/vegaprotocol/vega/issues/7286 is released
|
||||
/*<OracleSigners sourceType={sourceType} />*/
|
||||
}
|
||||
<OracleSigners sourceType={sourceType} />
|
||||
<OracleMarkets id={id} />
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">{t('Broadcasts')}</TableHeader>
|
||||
<TableCell modifier="bordered">{reportsCount}</TableCell>
|
||||
<TableHeader scope="row">{t('Matched data')}</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
{showBroadcasts ? reportsCount : reportsCount > 0 ? '✅' : '❌'}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
<OracleFilter data={dataSource} />
|
||||
<OracleData data={dataConnection} />
|
||||
{showBroadcasts ? <OracleData data={dataConnection} /> : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Loader, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import { RouteTitle } from '../../../components/route-title';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { useExplorerOracleSpecsQuery } from '../__generated__/Oracles';
|
||||
import { useDocumentTitle } from '../../../hooks/use-document-title';
|
||||
import { OracleDetails } from '../components/oracle';
|
||||
import { useScrollToLocation } from '../../../hooks/scroll-to-location';
|
||||
import filter from 'recursive-key-filter';
|
||||
|
||||
const Oracles = () => {
|
||||
const { data, loading } = useExplorerOracleSpecsQuery();
|
||||
|
||||
useDocumentTitle(['Oracles']);
|
||||
useScrollToLocation();
|
||||
|
||||
return (
|
||||
<section>
|
||||
<RouteTitle data-testid="oracle-specs-heading">{t('Oracles')}</RouteTitle>
|
||||
{loading ? <Loader /> : null}
|
||||
{data?.oracleSpecsConnection?.edges
|
||||
? data.oracleSpecsConnection.edges.map((o) => {
|
||||
const id = o?.node.dataSourceSpec.spec.id;
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div id={id} key={id} className="mb-10">
|
||||
<OracleDetails
|
||||
id={id}
|
||||
dataSource={o?.node}
|
||||
dataConnection={o?.node}
|
||||
/>
|
||||
<details>
|
||||
<summary className="pointer">JSON</summary>
|
||||
<SyntaxHighlighter data={filter(o, ['__typename'])} />
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Oracles;
|
||||
@@ -0,0 +1,49 @@
|
||||
import { RouteTitle } from '../../../components/route-title';
|
||||
import { RenderFetched } from '../../../components/render-fetched';
|
||||
import { t, truncateByChars } from '@vegaprotocol/react-helpers';
|
||||
import { useDocumentTitle } from '../../../hooks/use-document-title';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useExplorerOracleSpecByIdQuery } from '../__generated__/Oracles';
|
||||
import { OracleDetails } from '../components/oracle';
|
||||
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import filter from 'recursive-key-filter';
|
||||
import { TruncateInline } from '../../../components/truncate/truncate';
|
||||
|
||||
export const Oracle = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
||||
useDocumentTitle(['Oracle', `Oracle #${truncateByChars(id || '1', 5, 5)}`]);
|
||||
|
||||
const { data, error, loading } = useExplorerOracleSpecByIdQuery({
|
||||
variables: {
|
||||
id: id || '1',
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<section>
|
||||
<RouteTitle data-testid="block-header">
|
||||
{t(`Oracle `)}
|
||||
<TruncateInline startChars={5} endChars={5} text={id || '1'} />
|
||||
</RouteTitle>
|
||||
<RenderFetched error={error} loading={loading}>
|
||||
{data?.oracleSpec ? (
|
||||
<div id={id} key={id} className="mb-10">
|
||||
<OracleDetails
|
||||
id={id || ''}
|
||||
dataSource={data?.oracleSpec}
|
||||
dataConnection={data?.oracleSpec}
|
||||
showBroadcasts={true}
|
||||
/>
|
||||
<details>
|
||||
<summary className="pointer">JSON</summary>
|
||||
<SyntaxHighlighter data={filter(data, ['__typename'])} />
|
||||
</details>
|
||||
</div>
|
||||
) : (
|
||||
<span></span>
|
||||
)}
|
||||
</RenderFetched>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -1,45 +1,7 @@
|
||||
import { Loader, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import { RouteTitle } from '../../components/route-title';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { useExplorerOracleSpecsQuery } from './__generated__/Oracles';
|
||||
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||
import { OracleDetails } from './components/oracle';
|
||||
import { useScrollToLocation } from '../../hooks/scroll-to-location';
|
||||
import filter from 'recursive-key-filter';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
const Oracles = () => {
|
||||
const { data, loading } = useExplorerOracleSpecsQuery();
|
||||
|
||||
useDocumentTitle(['Oracles']);
|
||||
useScrollToLocation();
|
||||
|
||||
return (
|
||||
<section>
|
||||
<RouteTitle data-testid="oracle-specs-heading">{t('Oracles')}</RouteTitle>
|
||||
{loading ? <Loader /> : null}
|
||||
{data?.oracleSpecsConnection?.edges
|
||||
? data.oracleSpecsConnection.edges.map((o) => {
|
||||
const id = o?.node.dataSourceSpec.spec.id;
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div id={id} key={id} className="mb-10 cursor-pointer">
|
||||
<OracleDetails
|
||||
id={id}
|
||||
dataSource={o?.node}
|
||||
dataConnection={o?.node}
|
||||
/>
|
||||
<details>
|
||||
<summary className="pointer">JSON</summary>
|
||||
<SyntaxHighlighter data={filter(o, ['__typename'])} />
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</section>
|
||||
);
|
||||
const OraclePage = () => {
|
||||
return <Outlet />;
|
||||
};
|
||||
|
||||
export default Oracles;
|
||||
export default OraclePage;
|
||||
|
||||
@@ -3,7 +3,9 @@ import BlockPage from './blocks';
|
||||
import Governance from './governance';
|
||||
import Home from './home';
|
||||
import Markets from './markets';
|
||||
import Oracles from './oracles';
|
||||
import OraclePage from './oracles';
|
||||
import Oracles from './oracles/home';
|
||||
import { Oracle } from './oracles/id';
|
||||
import Party from './parties';
|
||||
import { Parties } from './parties/home';
|
||||
import { Party as PartySingle } from './parties/id';
|
||||
@@ -85,17 +87,6 @@ const marketsRoutes = flags.markets
|
||||
]
|
||||
: [];
|
||||
|
||||
const oraclesRoutes = flags.oracles
|
||||
? [
|
||||
{
|
||||
path: Routes.ORACLES,
|
||||
name: 'Oracles',
|
||||
text: t('Oracles'),
|
||||
element: <Oracles />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const networkParametersRoutes = flags.networkParameters
|
||||
? [
|
||||
{
|
||||
@@ -161,12 +152,27 @@ const routerConfig = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.ORACLES,
|
||||
name: 'Oracles',
|
||||
text: t('Oracles'),
|
||||
element: <OraclePage />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Oracles />,
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
element: <Oracle />,
|
||||
},
|
||||
],
|
||||
},
|
||||
...partiesRoutes,
|
||||
...assetsRoutes,
|
||||
...genesisRoutes,
|
||||
...governanceRoutes,
|
||||
...marketsRoutes,
|
||||
...oraclesRoutes,
|
||||
...networkParametersRoutes,
|
||||
...validators,
|
||||
];
|
||||
|
||||
+13
-10
@@ -10,7 +10,6 @@ import {
|
||||
formatNumberPercentage,
|
||||
t,
|
||||
toBigNum,
|
||||
getDateTimeFormat,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import type { VegaValueFormatterParams } from '@vegaprotocol/ui-toolkit';
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
@@ -31,6 +30,7 @@ import { HealthBar } from '../../health-bar';
|
||||
import { HealthDialog } from '../../health-dialog';
|
||||
import { Status } from '../../status';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { getExpiryDate } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export const MarketList = () => {
|
||||
const { data, error, loading } = useMarketsLiquidity();
|
||||
@@ -299,17 +299,20 @@ export const MarketList = () => {
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Closing Time')}
|
||||
field="proposal.terms.closingDatetime"
|
||||
field="tradableInstrument.instrument.metadata.tags"
|
||||
headerTooltip={t('Closing time of the market')}
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
Market,
|
||||
'proposal.terms.closingDatetime'
|
||||
>) => {
|
||||
return value
|
||||
? getDateTimeFormat().format(new Date(value).getTime())
|
||||
: '-';
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, ''>) => {
|
||||
let expiry;
|
||||
if (data?.tradableInstrument.instrument.metadata.tags) {
|
||||
expiry = getExpiryDate(
|
||||
data?.tradableInstrument.instrument.metadata.tags,
|
||||
data?.marketTimestamps.close,
|
||||
data?.state
|
||||
);
|
||||
}
|
||||
return expiry ? expiry : '-';
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"tranche_end": "2023-12-05T00:00:00.000Z",
|
||||
"total_added": "86666.297",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "74377.3343235149335839749",
|
||||
"locked_amount": "73961.6625805406210086218",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "86666.297",
|
||||
@@ -71,7 +71,7 @@
|
||||
"tranche_end": "2023-06-01T00:00:00.000Z",
|
||||
"total_added": "2500",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "1734.125553266178",
|
||||
"locked_amount": "1710.0785065628815",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "2500",
|
||||
@@ -450,7 +450,7 @@
|
||||
"tranche_end": "2023-12-05T00:00:00.000Z",
|
||||
"total_added": "129999.45",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "74309.47436445978496023",
|
||||
"locked_amount": "73894.181869649098296045",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "129999.45",
|
||||
@@ -516,7 +516,7 @@
|
||||
"tranche_end": "2023-09-03T00:00:00.000Z",
|
||||
"total_added": "62600",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "37773.4128805175029",
|
||||
"locked_amount": "37473.16870243530666",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "10000",
|
||||
@@ -709,7 +709,7 @@
|
||||
"tranche_end": "2023-09-17T00:00:00.000Z",
|
||||
"total_added": "5000",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "3208.826579147641",
|
||||
"locked_amount": "3184.845414764079",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "5000",
|
||||
@@ -920,7 +920,7 @@
|
||||
"tranche_end": "2023-04-05T00:00:00.000Z",
|
||||
"total_added": "97499.58",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "16112.873733795746655318",
|
||||
"locked_amount": "15705.5104826322582056334",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "97499.58",
|
||||
@@ -953,7 +953,7 @@
|
||||
"tranche_end": "2023-04-05T00:00:00.000Z",
|
||||
"total_added": "135173.4239508",
|
||||
"total_removed": "98230.390980249184455396",
|
||||
"locked_amount": "22023.516622497674397517810344",
|
||||
"locked_amount": "21466.721386488566701750705764",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "135173.4239508",
|
||||
@@ -999,7 +999,7 @@
|
||||
"tranche_end": "2023-04-05T00:00:00.000Z",
|
||||
"total_added": "32499.86",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "6778.4077253618634870844",
|
||||
"locked_amount": "6607.0370403844561078578",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "32499.86",
|
||||
@@ -1032,7 +1032,7 @@
|
||||
"tranche_end": "2023-04-05T00:00:00.000Z",
|
||||
"total_added": "10833.29",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "2206.3059384936688557712",
|
||||
"locked_amount": "2150.5264434752857381272",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "10833.29",
|
||||
@@ -1065,7 +1065,7 @@
|
||||
"tranche_end": "2023-04-05T00:00:00.000Z",
|
||||
"total_added": "22749.93",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "8247.664367621075063898",
|
||||
"locked_amount": "8039.148157117145325033",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "6500",
|
||||
@@ -1204,7 +1204,7 @@
|
||||
"tranche_end": "2023-05-01T00:00:00.000Z",
|
||||
"total_added": "22500",
|
||||
"total_removed": "3539.640404325",
|
||||
"locked_amount": "11839.76605662983325",
|
||||
"locked_amount": "11622.146926795578",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "7500",
|
||||
@@ -1369,7 +1369,7 @@
|
||||
"tranche_end": "2023-06-02T00:00:00.000Z",
|
||||
"total_added": "1939928.38",
|
||||
"total_removed": "928642.9598472029154",
|
||||
"locked_amount": "676287.4161622965221101968",
|
||||
"locked_amount": "666983.06788767317384942",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "1852091.69",
|
||||
@@ -1721,7 +1721,7 @@
|
||||
"tranche_end": "2023-02-01T00:00:00.000Z",
|
||||
"total_added": "42500",
|
||||
"total_removed": "24434.0787288",
|
||||
"locked_amount": "1442.306857638887255",
|
||||
"locked_amount": "1037.950539704105075",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "12500",
|
||||
@@ -6711,7 +6711,7 @@
|
||||
"tranche_id": 11,
|
||||
"tranche_start": "2021-09-03T00:00:00.000Z",
|
||||
"tranche_end": "2022-09-03T00:00:00.000Z",
|
||||
"total_added": "53657.000000000000000003",
|
||||
"total_added": "53684.000000000000000003",
|
||||
"total_removed": "42606.21518131551",
|
||||
"locked_amount": "0",
|
||||
"deposits": [
|
||||
@@ -6825,6 +6825,16 @@
|
||||
"user": "0xC6D7208DaDEe4F431bd0f3f11E7d4c91fF51bfb2",
|
||||
"tx": "0xe3fd6d538990df7bf818b8a178ecf7135a5e34f4d07778e9d6dc637cf470e2e0"
|
||||
},
|
||||
{
|
||||
"amount": "12",
|
||||
"user": "0xeD96EAA7D15951ab128abF8918a4D69F0Eef9905",
|
||||
"tx": "0x3775c114790b9a57414975492eec548528a155927399bddc72c40e336f3c9f52"
|
||||
},
|
||||
{
|
||||
"amount": "15",
|
||||
"user": "0xB12Cdb63E38f9a6ff2C0F68aBC3CA9beFCb7148c",
|
||||
"tx": "0xa83f849c425b63fe042be7d606eaaf64f9e89c20c01aa5ec58a3ebf54c573eba"
|
||||
},
|
||||
{
|
||||
"amount": "20",
|
||||
"user": "0xB12Cdb63E38f9a6ff2C0F68aBC3CA9beFCb7148c",
|
||||
@@ -17680,6 +17690,12 @@
|
||||
"tranche_id": 11,
|
||||
"tx": "0x304ceeda11c67174ffc52347ec240ea5aa832675cbb1c828adc098c8886f96fc"
|
||||
},
|
||||
{
|
||||
"amount": "15",
|
||||
"user": "0xB12Cdb63E38f9a6ff2C0F68aBC3CA9beFCb7148c",
|
||||
"tranche_id": 11,
|
||||
"tx": "0xa83f849c425b63fe042be7d606eaaf64f9e89c20c01aa5ec58a3ebf54c573eba"
|
||||
},
|
||||
{
|
||||
"amount": "20",
|
||||
"user": "0xB12Cdb63E38f9a6ff2C0F68aBC3CA9beFCb7148c",
|
||||
@@ -18367,9 +18383,9 @@
|
||||
"tx": "0x6940787f6ceaac0846e69f1bc93ad9efee8c39774b91ea29f84328ae64fc9969"
|
||||
}
|
||||
],
|
||||
"total_tokens": "2585",
|
||||
"total_tokens": "2600",
|
||||
"withdrawn_tokens": "2585",
|
||||
"remaining_tokens": "0"
|
||||
"remaining_tokens": "15"
|
||||
},
|
||||
{
|
||||
"address": "0xE9F41a0090fcc7eaf626037003AAD44B17098E7C",
|
||||
@@ -18591,6 +18607,21 @@
|
||||
"withdrawn_tokens": "124",
|
||||
"remaining_tokens": "0"
|
||||
},
|
||||
{
|
||||
"address": "0xeD96EAA7D15951ab128abF8918a4D69F0Eef9905",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "12",
|
||||
"user": "0xeD96EAA7D15951ab128abF8918a4D69F0Eef9905",
|
||||
"tranche_id": 11,
|
||||
"tx": "0x3775c114790b9a57414975492eec548528a155927399bddc72c40e336f3c9f52"
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"total_tokens": "12",
|
||||
"withdrawn_tokens": "0",
|
||||
"remaining_tokens": "12"
|
||||
},
|
||||
{
|
||||
"address": "0xc7eC0d77a1417C97fb455f404c4B425A69a0004d",
|
||||
"deposits": [
|
||||
@@ -33096,8 +33127,8 @@
|
||||
"tranche_start": "2022-03-05T00:00:00.000Z",
|
||||
"tranche_end": "2023-06-05T00:00:00.000Z",
|
||||
"total_added": "3732368.4671",
|
||||
"total_removed": "441714.6865497406814",
|
||||
"locked_amount": "1063719.625100700820753200245",
|
||||
"total_removed": "442882.3484327902809",
|
||||
"locked_amount": "1049422.081232611329856667873",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "1998.95815",
|
||||
@@ -33276,6 +33307,11 @@
|
||||
"user": "0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0",
|
||||
"tx": "0x63016a4e870bc38285eac8057aab58aa9fe896f1659f4f2e7e4e8de19c1202db"
|
||||
},
|
||||
{
|
||||
"amount": "1167.6618830495995",
|
||||
"user": "0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0",
|
||||
"tx": "0xdd0c8931e8c9ec3205ab9e4cdbb30dd5a6f43253c388f2e05b5bb5fd847ed8fb"
|
||||
},
|
||||
{
|
||||
"amount": "1906.7742941475005",
|
||||
"user": "0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0",
|
||||
@@ -33622,6 +33658,12 @@
|
||||
"tranche_id": 1,
|
||||
"tx": "0x63016a4e870bc38285eac8057aab58aa9fe896f1659f4f2e7e4e8de19c1202db"
|
||||
},
|
||||
{
|
||||
"amount": "1167.6618830495995",
|
||||
"user": "0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0",
|
||||
"tranche_id": 1,
|
||||
"tx": "0xdd0c8931e8c9ec3205ab9e4cdbb30dd5a6f43253c388f2e05b5bb5fd847ed8fb"
|
||||
},
|
||||
{
|
||||
"amount": "1906.7742941475005",
|
||||
"user": "0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0",
|
||||
@@ -33870,8 +33912,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "187637.95",
|
||||
"withdrawn_tokens": "133256.984196977461",
|
||||
"remaining_tokens": "54380.965803022539"
|
||||
"withdrawn_tokens": "134424.6460800270605",
|
||||
"remaining_tokens": "53213.3039199729395"
|
||||
},
|
||||
{
|
||||
"address": "0x1A71e3ED1996CAbB91bB043f880CE963D601707e",
|
||||
@@ -34377,8 +34419,8 @@
|
||||
"tranche_start": "2022-06-05T00:00:00.000Z",
|
||||
"tranche_end": "2023-12-05T00:00:00.000Z",
|
||||
"total_added": "15870102.715470999700000001",
|
||||
"total_removed": "545472.50137003958740452",
|
||||
"locked_amount": "9071569.0789202252235565659217193396309814",
|
||||
"total_removed": "547359.63874938618402952",
|
||||
"locked_amount": "9020870.9063540351013617047345897584630981",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "16249.93",
|
||||
@@ -34982,6 +35024,21 @@
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
"tx": "0x6d913b124af23621f8b97087a51d4cd912c6a8d9cca325cb756b56d4970f3f62"
|
||||
},
|
||||
{
|
||||
"amount": "64.153825929033",
|
||||
"user": "0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0",
|
||||
"tx": "0x33446fce935f9568b7d6e796e6a9a73049585fc7225f935e343acbb5c68a60b7"
|
||||
},
|
||||
{
|
||||
"amount": "869.931062",
|
||||
"user": "0xF4c75FdbAe821C6B5DBB9f001bB23cBA58D1bA37",
|
||||
"tx": "0xc6a7b120dc629017c645e277e57c96fec82c78b518c0b54809dbc10f566e204d"
|
||||
},
|
||||
{
|
||||
"amount": "953.052491417563625",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
"tx": "0x74f9a06c6e8b7807b9c369ec3762e5d4b518f32303faf8c1d4098f9d33f4a997"
|
||||
},
|
||||
{
|
||||
"amount": "434.254023685890375",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
@@ -36516,6 +36573,12 @@
|
||||
"tranche_id": 2,
|
||||
"tx": "0x6d913b124af23621f8b97087a51d4cd912c6a8d9cca325cb756b56d4970f3f62"
|
||||
},
|
||||
{
|
||||
"amount": "953.052491417563625",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
"tranche_id": 2,
|
||||
"tx": "0x74f9a06c6e8b7807b9c369ec3762e5d4b518f32303faf8c1d4098f9d33f4a997"
|
||||
},
|
||||
{
|
||||
"amount": "434.254023685890375",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
@@ -37532,8 +37595,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "259998.8875",
|
||||
"withdrawn_tokens": "111222.20209807200725",
|
||||
"remaining_tokens": "148776.68540192799275"
|
||||
"withdrawn_tokens": "112175.254589489570875",
|
||||
"remaining_tokens": "147823.632910510429125"
|
||||
},
|
||||
{
|
||||
"address": "0x89051CAb67Bc7F8CC44F7e270c6EDaf1EC57676c",
|
||||
@@ -38181,6 +38244,12 @@
|
||||
"tranche_id": 2,
|
||||
"tx": "0x108f2ebc78fb2f656e95e842a65a0c522e7ad6b6b06b158dbd549831e63814c2"
|
||||
},
|
||||
{
|
||||
"amount": "869.931062",
|
||||
"user": "0xF4c75FdbAe821C6B5DBB9f001bB23cBA58D1bA37",
|
||||
"tranche_id": 2,
|
||||
"tx": "0xc6a7b120dc629017c645e277e57c96fec82c78b518c0b54809dbc10f566e204d"
|
||||
},
|
||||
{
|
||||
"amount": "1573.9558",
|
||||
"user": "0xF4c75FdbAe821C6B5DBB9f001bB23cBA58D1bA37",
|
||||
@@ -38333,8 +38402,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "200000",
|
||||
"withdrawn_tokens": "85146.031866",
|
||||
"remaining_tokens": "114853.968134"
|
||||
"withdrawn_tokens": "86015.962928",
|
||||
"remaining_tokens": "113984.037072"
|
||||
},
|
||||
{
|
||||
"address": "0x1b956E6c00E238194B331eddEFF72Cb5f28A8d01",
|
||||
@@ -38806,6 +38875,12 @@
|
||||
"tranche_id": 2,
|
||||
"tx": "0x55802ccf2bf20a0bf6ffffa5add678cacd8d31450517a2ac65ff384ee5f71f22"
|
||||
},
|
||||
{
|
||||
"amount": "64.153825929033",
|
||||
"user": "0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0",
|
||||
"tranche_id": 2,
|
||||
"tx": "0x33446fce935f9568b7d6e796e6a9a73049585fc7225f935e343acbb5c68a60b7"
|
||||
},
|
||||
{
|
||||
"amount": "104.762232778042",
|
||||
"user": "0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0",
|
||||
@@ -39036,8 +39111,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "12362.05",
|
||||
"withdrawn_tokens": "5246.0507139289135",
|
||||
"remaining_tokens": "7115.9992860710865"
|
||||
"withdrawn_tokens": "5310.2045398579465",
|
||||
"remaining_tokens": "7051.8454601420535"
|
||||
},
|
||||
{
|
||||
"address": "0xb091D456d0dFCB94dcba6f355379056C5bb995fC",
|
||||
@@ -39668,8 +39743,8 @@
|
||||
"tranche_start": "2021-11-05T00:00:00.000Z",
|
||||
"tranche_end": "2023-05-05T00:00:00.000Z",
|
||||
"total_added": "14597706.0446472999",
|
||||
"total_removed": "3706973.15022981060693393",
|
||||
"locked_amount": "2653369.424853388202023095424913904",
|
||||
"total_removed": "3708107.46955489707527943",
|
||||
"locked_amount": "2606565.19565117133351441770201319",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "129284.449",
|
||||
@@ -39968,6 +40043,11 @@
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
"tx": "0xc1e8ef457dbeb52bd2d02aefd418a9a67bd17f016c1ca6a1e80716d86ce3c132"
|
||||
},
|
||||
{
|
||||
"amount": "1134.3193250864683455",
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
"tx": "0xfb5cc9685a3c4f5174ff68cc066d8da5121f833d7539f53a78db46c2427eda9b"
|
||||
},
|
||||
{
|
||||
"amount": "12758.73908063512839093",
|
||||
"user": "0x91715128a71c9C734CDC20E5EdEEeA02E72e428E",
|
||||
@@ -42746,6 +42826,12 @@
|
||||
"tranche_id": 3,
|
||||
"tx": "0xc1e8ef457dbeb52bd2d02aefd418a9a67bd17f016c1ca6a1e80716d86ce3c132"
|
||||
},
|
||||
{
|
||||
"amount": "1134.3193250864683455",
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
"tranche_id": 3,
|
||||
"tx": "0xfb5cc9685a3c4f5174ff68cc066d8da5121f833d7539f53a78db46c2427eda9b"
|
||||
},
|
||||
{
|
||||
"amount": "762.7902369187144135",
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
@@ -45058,8 +45144,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "359123.469575",
|
||||
"withdrawn_tokens": "293815.097748589450163",
|
||||
"remaining_tokens": "65308.371826410549837"
|
||||
"withdrawn_tokens": "294949.4170736759185085",
|
||||
"remaining_tokens": "64174.0525013240814915"
|
||||
},
|
||||
{
|
||||
"address": "0xBdd412797c1B78535Afc5F71503b91fAbD0160fB",
|
||||
@@ -46370,7 +46456,7 @@
|
||||
"tranche_end": "2023-04-05T00:00:00.000Z",
|
||||
"total_added": "5778205.3912159303",
|
||||
"total_removed": "2622261.560853924298939789",
|
||||
"locked_amount": "731458.903664069114159753480553424",
|
||||
"locked_amount": "712966.269636651830718004793872006",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "552496.6455",
|
||||
@@ -48264,8 +48350,8 @@
|
||||
"tranche_start": "2022-06-05T00:00:00.000Z",
|
||||
"tranche_end": "2023-06-05T00:00:00.000Z",
|
||||
"total_added": "472355.6199999996",
|
||||
"total_removed": "29683.1054262326685",
|
||||
"locked_amount": "168552.45507780998384909840791476",
|
||||
"total_removed": "30192.1031304386685",
|
||||
"locked_amount": "166286.92752366611650390355859972",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "3000",
|
||||
@@ -54939,6 +55025,31 @@
|
||||
"user": "0xf751033D4e6864a88Be93E36258246F26AEf577c",
|
||||
"tx": "0xb912d9e24a80ec0d9ed4f3b03c20937d4fec08dd99031447ba84a8b6a69ef351"
|
||||
},
|
||||
{
|
||||
"amount": "39.564687976",
|
||||
"user": "0xD27929d68ac0E5fd5C919A5eb5968C1D06D3Fb83",
|
||||
"tx": "0x63c2319241d05630a6d9ea104770cdcb6d93d145554bf2ff9d82cd8ef4e17fc7"
|
||||
},
|
||||
{
|
||||
"amount": "129.409734906",
|
||||
"user": "0x6E624B5788CD721F489074993d5E1Ee2C477eB00",
|
||||
"tx": "0x9b25e6fb021dd73d4022c6879da50a0e2b818a46955153a90e44008f5f4f5a52"
|
||||
},
|
||||
{
|
||||
"amount": "129.527999746",
|
||||
"user": "0x4683B77D114a04f1602dda7B0Ab2A24Fe79295fD",
|
||||
"tx": "0x0a2ab8841a315a8f2bcf534953ff9058607709c1b761f4fded8ba238ea3f3802"
|
||||
},
|
||||
{
|
||||
"amount": "37.881582952",
|
||||
"user": "0xcB555C5602cC0b2434A27d277e14Cb5C7bF4Ead1",
|
||||
"tx": "0x56e6ab9b05da1003e1070f9ffb5cc54038420d69751d49f700bdb008f8600701"
|
||||
},
|
||||
{
|
||||
"amount": "172.613698626",
|
||||
"user": "0x83e600Ae7f4cf265C112314839cDA2341198840B",
|
||||
"tx": "0x871fbcbfe5c488654b8aff67f1c4afff2f5940ea3eb0cdf165b159bb96ee7ffd"
|
||||
},
|
||||
{
|
||||
"amount": "229.648541348",
|
||||
"user": "0x65C13724928ea0AfA68e09c7b70449bB8f6f3Fc8",
|
||||
@@ -56369,6 +56480,12 @@
|
||||
}
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "172.613698626",
|
||||
"user": "0x83e600Ae7f4cf265C112314839cDA2341198840B",
|
||||
"tranche_id": 5,
|
||||
"tx": "0x871fbcbfe5c488654b8aff67f1c4afff2f5940ea3eb0cdf165b159bb96ee7ffd"
|
||||
},
|
||||
{
|
||||
"amount": "87.108637746",
|
||||
"user": "0x83e600Ae7f4cf265C112314839cDA2341198840B",
|
||||
@@ -56383,8 +56500,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "600",
|
||||
"withdrawn_tokens": "216.146099694",
|
||||
"remaining_tokens": "383.853900306"
|
||||
"withdrawn_tokens": "388.75979832",
|
||||
"remaining_tokens": "211.24020168"
|
||||
},
|
||||
{
|
||||
"address": "0x5Ef3F2723f8e8c45aF15Df67dBDfA8a8FF2C3E8F",
|
||||
@@ -72637,6 +72754,12 @@
|
||||
}
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "37.881582952",
|
||||
"user": "0xcB555C5602cC0b2434A27d277e14Cb5C7bF4Ead1",
|
||||
"tranche_id": 5,
|
||||
"tx": "0x56e6ab9b05da1003e1070f9ffb5cc54038420d69751d49f700bdb008f8600701"
|
||||
},
|
||||
{
|
||||
"amount": "40.914155252",
|
||||
"user": "0xcB555C5602cC0b2434A27d277e14Cb5C7bF4Ead1",
|
||||
@@ -72651,8 +72774,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "400",
|
||||
"withdrawn_tokens": "221.208206492",
|
||||
"remaining_tokens": "178.791793508"
|
||||
"withdrawn_tokens": "259.089789444",
|
||||
"remaining_tokens": "140.910210556"
|
||||
},
|
||||
{
|
||||
"address": "0x3738bec36216eA2F11B954891C65AAd1Bc852156",
|
||||
@@ -75109,10 +75232,17 @@
|
||||
"tx": "0xb59405747c8088945a412703637a7b422f3639439ec2ee15e180c0a2a0d71ee4"
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "129.409734906",
|
||||
"user": "0x6E624B5788CD721F489074993d5E1Ee2C477eB00",
|
||||
"tranche_id": 5,
|
||||
"tx": "0x9b25e6fb021dd73d4022c6879da50a0e2b818a46955153a90e44008f5f4f5a52"
|
||||
}
|
||||
],
|
||||
"total_tokens": "200",
|
||||
"withdrawn_tokens": "0",
|
||||
"remaining_tokens": "200"
|
||||
"withdrawn_tokens": "129.409734906",
|
||||
"remaining_tokens": "70.590265094"
|
||||
},
|
||||
{
|
||||
"address": "0xa9a677b0a3Be231C0654CabB0Eaa0A72E91B3E8d",
|
||||
@@ -76026,6 +76156,12 @@
|
||||
}
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "39.564687976",
|
||||
"user": "0xD27929d68ac0E5fd5C919A5eb5968C1D06D3Fb83",
|
||||
"tranche_id": 5,
|
||||
"tx": "0x63c2319241d05630a6d9ea104770cdcb6d93d145554bf2ff9d82cd8ef4e17fc7"
|
||||
},
|
||||
{
|
||||
"amount": "38.141400304",
|
||||
"user": "0xD27929d68ac0E5fd5C919A5eb5968C1D06D3Fb83",
|
||||
@@ -76076,8 +76212,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "400",
|
||||
"withdrawn_tokens": "219.120230844",
|
||||
"remaining_tokens": "180.879769156"
|
||||
"withdrawn_tokens": "258.68491882",
|
||||
"remaining_tokens": "141.31508118"
|
||||
},
|
||||
{
|
||||
"address": "0xF5037DDA4A660d67560200f45380FF8364e35540",
|
||||
@@ -76141,10 +76277,17 @@
|
||||
"tx": "0xe32a466fc780a0fb3fd84a804f622931ebfaf3f428bff0dc6d141270410e75f8"
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "129.527999746",
|
||||
"user": "0x4683B77D114a04f1602dda7B0Ab2A24Fe79295fD",
|
||||
"tranche_id": 5,
|
||||
"tx": "0x0a2ab8841a315a8f2bcf534953ff9058607709c1b761f4fded8ba238ea3f3802"
|
||||
}
|
||||
],
|
||||
"total_tokens": "200",
|
||||
"withdrawn_tokens": "0",
|
||||
"remaining_tokens": "200"
|
||||
"withdrawn_tokens": "129.527999746",
|
||||
"remaining_tokens": "70.472000254"
|
||||
},
|
||||
{
|
||||
"address": "0x0A67488B946C5Ad7e3afc8766316C9Dc17333C6B",
|
||||
@@ -76659,7 +76802,7 @@
|
||||
"tranche_start": "2021-12-05T00:00:00.000Z",
|
||||
"tranche_end": "2022-06-05T00:00:00.000Z",
|
||||
"total_added": "171288.42",
|
||||
"total_removed": "63445.7249006422989",
|
||||
"total_removed": "64196.1049690697989",
|
||||
"locked_amount": "0",
|
||||
"deposits": [
|
||||
{
|
||||
@@ -80904,6 +81047,31 @@
|
||||
"user": "0x4fD63682E3e6803e2F3805D0c40Fcb7a37c5e7c4",
|
||||
"tx": "0x591b81e6f118be1436cede9763019b99a33594f029716341adb45d6710b66ca6"
|
||||
},
|
||||
{
|
||||
"amount": "99.7195512825",
|
||||
"user": "0x01a66CdA5D3212D3Ac9f9ff837eE4e4833E52895",
|
||||
"tx": "0xcaab4c813b9ce156921f1a8cc3193f20d9f2b397e66e774233c13e64475b1853"
|
||||
},
|
||||
{
|
||||
"amount": "100.660517145",
|
||||
"user": "0xC343fD1a1dd3F8D5412b2bae1c3A4a0482029f63",
|
||||
"tx": "0xd6c9e31ca9abc4832ba4d6345ddeefbc3ab947b9c48ad834e2002d790642db22"
|
||||
},
|
||||
{
|
||||
"amount": "250",
|
||||
"user": "0x92Faf283Ff5Bca05673411091D922a054A959900",
|
||||
"tx": "0x5f47c30b18bfd7c1d7997ebd04ea2460b72f6a5880385075e37090428711215f"
|
||||
},
|
||||
{
|
||||
"amount": "250",
|
||||
"user": "0xaD8a77d52Dd1E2dDD364AfE960DF49CAe1124C6D",
|
||||
"tx": "0xd8084414ae0dcff7b19cc85b92bc4898cb656f485e51bc8f8b0d499f97d5e212"
|
||||
},
|
||||
{
|
||||
"amount": "50",
|
||||
"user": "0xa11930Cd107D363aCc44223F03dDF899ac1c864C",
|
||||
"tx": "0x9d260c971e699ed0255fe230c306c70f076064466c0b50c855a234c6e5034156"
|
||||
},
|
||||
{
|
||||
"amount": "50",
|
||||
"user": "0x300A831523b53112F5CF8B802D85EC48a8CB7dcC",
|
||||
@@ -90944,10 +91112,17 @@
|
||||
"tx": "0xadc25e73736cd85e84fee16f2d7ee41f16f7deb83a52ceb82b327974063964f7"
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "250",
|
||||
"user": "0x92Faf283Ff5Bca05673411091D922a054A959900",
|
||||
"tranche_id": 6,
|
||||
"tx": "0x5f47c30b18bfd7c1d7997ebd04ea2460b72f6a5880385075e37090428711215f"
|
||||
}
|
||||
],
|
||||
"total_tokens": "250",
|
||||
"withdrawn_tokens": "0",
|
||||
"remaining_tokens": "250"
|
||||
"withdrawn_tokens": "250",
|
||||
"remaining_tokens": "0"
|
||||
},
|
||||
{
|
||||
"address": "0xB0B91feF5E4Af34F727797079DCDa5CCAc817a4B",
|
||||
@@ -93325,10 +93500,17 @@
|
||||
"tx": "0x9f916cf09e8a3c4ade0ffce5190db464d0a2b1dadba78e1ee7ba5d6e751d6148"
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "250",
|
||||
"user": "0xaD8a77d52Dd1E2dDD364AfE960DF49CAe1124C6D",
|
||||
"tranche_id": 6,
|
||||
"tx": "0xd8084414ae0dcff7b19cc85b92bc4898cb656f485e51bc8f8b0d499f97d5e212"
|
||||
}
|
||||
],
|
||||
"total_tokens": "250",
|
||||
"withdrawn_tokens": "0",
|
||||
"remaining_tokens": "250"
|
||||
"withdrawn_tokens": "250",
|
||||
"remaining_tokens": "0"
|
||||
},
|
||||
{
|
||||
"address": "0xc140931E1A188667395734771f195Ce832c40922",
|
||||
@@ -96556,6 +96738,12 @@
|
||||
}
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "99.7195512825",
|
||||
"user": "0x01a66CdA5D3212D3Ac9f9ff837eE4e4833E52895",
|
||||
"tranche_id": 6,
|
||||
"tx": "0xcaab4c813b9ce156921f1a8cc3193f20d9f2b397e66e774233c13e64475b1853"
|
||||
},
|
||||
{
|
||||
"amount": "150.2804487175",
|
||||
"user": "0x01a66CdA5D3212D3Ac9f9ff837eE4e4833E52895",
|
||||
@@ -96564,8 +96752,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "250",
|
||||
"withdrawn_tokens": "150.2804487175",
|
||||
"remaining_tokens": "99.7195512825"
|
||||
"withdrawn_tokens": "250",
|
||||
"remaining_tokens": "0"
|
||||
},
|
||||
{
|
||||
"address": "0x6b671cfa2619790c861Fd3b4B3cb3aE5A78ee3fC",
|
||||
@@ -96593,6 +96781,12 @@
|
||||
}
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "100.660517145",
|
||||
"user": "0xC343fD1a1dd3F8D5412b2bae1c3A4a0482029f63",
|
||||
"tranche_id": 6,
|
||||
"tx": "0xd6c9e31ca9abc4832ba4d6345ddeefbc3ab947b9c48ad834e2002d790642db22"
|
||||
},
|
||||
{
|
||||
"amount": "149.339482855",
|
||||
"user": "0xC343fD1a1dd3F8D5412b2bae1c3A4a0482029f63",
|
||||
@@ -96601,8 +96795,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "250",
|
||||
"withdrawn_tokens": "149.339482855",
|
||||
"remaining_tokens": "100.660517145"
|
||||
"withdrawn_tokens": "250",
|
||||
"remaining_tokens": "0"
|
||||
},
|
||||
{
|
||||
"address": "0x9a25025F722305b4336CcD7E67995E691A12426E",
|
||||
@@ -97817,10 +98011,17 @@
|
||||
"tx": "0xe32a466fc780a0fb3fd84a804f622931ebfaf3f428bff0dc6d141270410e75f8"
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "50",
|
||||
"user": "0xa11930Cd107D363aCc44223F03dDF899ac1c864C",
|
||||
"tranche_id": 6,
|
||||
"tx": "0x9d260c971e699ed0255fe230c306c70f076064466c0b50c855a234c6e5034156"
|
||||
}
|
||||
],
|
||||
"total_tokens": "50",
|
||||
"withdrawn_tokens": "0",
|
||||
"remaining_tokens": "50"
|
||||
"withdrawn_tokens": "50",
|
||||
"remaining_tokens": "0"
|
||||
},
|
||||
{
|
||||
"address": "0x75AB80d94F9F7C0FE4E7973C8fF505882bE80f97",
|
||||
|
||||
@@ -11,6 +11,7 @@ NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
|
||||
@@ -14,6 +14,7 @@ NX_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit suppl
|
||||
NX_LOCAL_PROVIDER_URL=http://localhost:8545/
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
|
||||
@@ -8,3 +8,4 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
|
||||
@@ -8,4 +8,5 @@ NX_ETHERSCAN_URL=https://etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
|
||||
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40:87edc2605e544f888305d7fc4a9141bd@o286262.ingest.sentry.io/5882996
|
||||
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40:87edc2605e544f888305d7fc4a9141bd@o286262.ingest.sentry.io/5882996
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
|
||||
@@ -8,3 +8,4 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_VEGA_EXPLORER_URL=https://mirror.explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
|
||||
@@ -5,3 +5,4 @@ NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://sta
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/sandbox-network.json
|
||||
NX_VEGA_EXPLORER_URL=https://sandbox.explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
|
||||
@@ -5,3 +5,4 @@ NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://sta
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet1-network.json
|
||||
NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
|
||||
@@ -6,3 +6,4 @@ NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
|
||||
NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
|
||||
@@ -9,3 +9,4 @@ NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
|
||||
@@ -7,13 +7,13 @@ fragment WalletDelegationFields on Delegation {
|
||||
epoch
|
||||
}
|
||||
|
||||
query Delegations($partyId: ID!) {
|
||||
query Delegations($partyId: ID!, $delegationsPagination: Pagination) {
|
||||
epoch {
|
||||
id
|
||||
}
|
||||
party(id: $partyId) {
|
||||
id
|
||||
delegationsConnection {
|
||||
delegationsConnection(pagination: $delegationsPagination) {
|
||||
edges {
|
||||
node {
|
||||
...WalletDelegationFields
|
||||
|
||||
@@ -7,6 +7,7 @@ export type WalletDelegationFieldsFragment = { __typename?: 'Delegation', amount
|
||||
|
||||
export type DelegationsQueryVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
delegationsPagination?: Types.InputMaybe<Types.Pagination>;
|
||||
}>;
|
||||
|
||||
|
||||
@@ -23,13 +24,13 @@ export const WalletDelegationFieldsFragmentDoc = gql`
|
||||
}
|
||||
`;
|
||||
export const DelegationsDocument = gql`
|
||||
query Delegations($partyId: ID!) {
|
||||
query Delegations($partyId: ID!, $delegationsPagination: Pagination) {
|
||||
epoch {
|
||||
id
|
||||
}
|
||||
party(id: $partyId) {
|
||||
id
|
||||
delegationsConnection {
|
||||
delegationsConnection(pagination: $delegationsPagination) {
|
||||
edges {
|
||||
node {
|
||||
...WalletDelegationFields
|
||||
@@ -76,6 +77,7 @@ export const DelegationsDocument = gql`
|
||||
* const { data, loading, error } = useDelegationsQuery({
|
||||
* variables: {
|
||||
* partyId: // value for 'partyId'
|
||||
* delegationsPagination: // value for 'delegationsPagination'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type WalletDelegationFieldsFragment = { __typename?: 'Delegation', amount: string, epoch: number, node: { __typename?: 'Node', id: string, name: string } };
|
||||
|
||||
export type DelegationsQueryVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type DelegationsQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string }, party?: { __typename?: 'Party', id: string, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number, node: { __typename?: 'Node', id: string, name: string } } } | null> | null } | null, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', name: string, id: string, decimals: number, symbol: string, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string } } } } | null> | null } | null } | null };
|
||||
|
||||
export const WalletDelegationFieldsFragmentDoc = gql`
|
||||
fragment WalletDelegationFields on Delegation {
|
||||
amount
|
||||
node {
|
||||
id
|
||||
name
|
||||
}
|
||||
epoch
|
||||
}
|
||||
`;
|
||||
export const DelegationsDocument = gql`
|
||||
query Delegations($partyId: ID!) {
|
||||
epoch {
|
||||
id
|
||||
}
|
||||
party(id: $partyId) {
|
||||
id
|
||||
delegationsConnection {
|
||||
edges {
|
||||
node {
|
||||
...WalletDelegationFields
|
||||
}
|
||||
}
|
||||
}
|
||||
stakingSummary {
|
||||
currentStakeAvailable
|
||||
}
|
||||
accountsConnection {
|
||||
edges {
|
||||
node {
|
||||
asset {
|
||||
name
|
||||
id
|
||||
decimals
|
||||
symbol
|
||||
source {
|
||||
__typename
|
||||
... on ERC20 {
|
||||
contractAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
type
|
||||
balance
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${WalletDelegationFieldsFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useDelegationsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useDelegationsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useDelegationsQuery` 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 } = useDelegationsQuery({
|
||||
* variables: {
|
||||
* partyId: // value for 'partyId'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useDelegationsQuery(baseOptions: Apollo.QueryHookOptions<DelegationsQuery, DelegationsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<DelegationsQuery, DelegationsQueryVariables>(DelegationsDocument, options);
|
||||
}
|
||||
export function useDelegationsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<DelegationsQuery, DelegationsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<DelegationsQuery, DelegationsQueryVariables>(DelegationsDocument, options);
|
||||
}
|
||||
export type DelegationsQueryHookResult = ReturnType<typeof useDelegationsQuery>;
|
||||
export type DelegationsLazyQueryHookResult = ReturnType<typeof useDelegationsLazyQuery>;
|
||||
export type DelegationsQueryResult = Apollo.QueryResult<DelegationsQuery, DelegationsQueryVariables>;
|
||||
@@ -4,6 +4,7 @@ import keyBy from 'lodash/keyBy';
|
||||
import uniq from 'lodash/uniq';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ENV } from '../../config';
|
||||
|
||||
import noIcon from '../../images/token-no-icon.png';
|
||||
import vegaBlack from '../../images/vega_black.png';
|
||||
@@ -23,18 +24,18 @@ import type {
|
||||
DelegationsQuery,
|
||||
DelegationsQueryVariables,
|
||||
WalletDelegationFieldsFragment,
|
||||
} from './__generated___/Delegations';
|
||||
import { DelegationsDocument } from './__generated___/Delegations';
|
||||
} from './__generated__/Delegations';
|
||||
import { DelegationsDocument } from './__generated__/Delegations';
|
||||
|
||||
export const usePollForDelegations = () => {
|
||||
const { token: vegaToken } = useContracts();
|
||||
const {
|
||||
appState: { decimals },
|
||||
} = useAppState();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { pubKey } = useVegaWallet();
|
||||
const client = useApolloClient();
|
||||
const { delegationsPagination } = ENV;
|
||||
const [delegations, setDelegations] = React.useState<
|
||||
WalletDelegationFieldsFragment[]
|
||||
>([]);
|
||||
@@ -62,7 +63,14 @@ export const usePollForDelegations = () => {
|
||||
client
|
||||
.query<DelegationsQuery, DelegationsQueryVariables>({
|
||||
query: DelegationsDocument,
|
||||
variables: { partyId: pubKey },
|
||||
variables: {
|
||||
partyId: pubKey,
|
||||
delegationsPagination: delegationsPagination
|
||||
? {
|
||||
first: Number(delegationsPagination),
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
fetchPolicy: 'network-only',
|
||||
})
|
||||
.then((res) => {
|
||||
@@ -207,7 +215,7 @@ export const usePollForDelegations = () => {
|
||||
clearInterval(interval);
|
||||
mounted = false;
|
||||
};
|
||||
}, [client, decimals, pubKey, t, vegaToken.address]);
|
||||
}, [delegationsPagination, client, decimals, pubKey, t, vegaToken.address]);
|
||||
|
||||
return { delegations, currentStakeAvailable, delegatedNodes, accounts };
|
||||
};
|
||||
|
||||
@@ -64,6 +64,7 @@ export const ENV = {
|
||||
docsUrl: windowOrDefault('NX_VEGA_DOCS_URL'),
|
||||
ethWalletMnemonic: windowOrDefault('NX_ETH_WALLET_MNEMONIC'),
|
||||
localProviderUrl: windowOrDefault('NX_LOCAL_PROVIDER_URL'),
|
||||
delegationsPagination: windowOrDefault('NX_DELEGATIONS_PAGINATION'),
|
||||
flags: {
|
||||
NETWORK_DOWN: TRUTHY.includes(windowOrDefault('NX_NETWORK_DOWN')),
|
||||
MOCK: TRUTHY.includes(windowOrDefault('NX_MOCKED')),
|
||||
|
||||
@@ -20,7 +20,7 @@ fragment DelegationFields on Delegation {
|
||||
epoch
|
||||
}
|
||||
|
||||
query Rewards($partyId: ID!) {
|
||||
query Rewards($partyId: ID!, $delegationsPagination: Pagination) {
|
||||
party(id: $partyId) {
|
||||
id
|
||||
rewardsConnection {
|
||||
@@ -30,7 +30,7 @@ query Rewards($partyId: ID!) {
|
||||
}
|
||||
}
|
||||
}
|
||||
delegationsConnection {
|
||||
delegationsConnection(pagination: $delegationsPagination) {
|
||||
edges {
|
||||
node {
|
||||
...DelegationFields
|
||||
|
||||
+4
-2
@@ -9,6 +9,7 @@ export type DelegationFieldsFragment = { __typename?: 'Delegation', amount: stri
|
||||
|
||||
export type RewardsQueryVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
delegationsPagination?: Types.InputMaybe<Types.Pagination>;
|
||||
}>;
|
||||
|
||||
|
||||
@@ -39,7 +40,7 @@ export const DelegationFieldsFragmentDoc = gql`
|
||||
}
|
||||
`;
|
||||
export const RewardsDocument = gql`
|
||||
query Rewards($partyId: ID!) {
|
||||
query Rewards($partyId: ID!, $delegationsPagination: Pagination) {
|
||||
party(id: $partyId) {
|
||||
id
|
||||
rewardsConnection {
|
||||
@@ -49,7 +50,7 @@ export const RewardsDocument = gql`
|
||||
}
|
||||
}
|
||||
}
|
||||
delegationsConnection {
|
||||
delegationsConnection(pagination: $delegationsPagination) {
|
||||
edges {
|
||||
node {
|
||||
...DelegationFields
|
||||
@@ -82,6 +83,7 @@ ${DelegationFieldsFragmentDoc}`;
|
||||
* const { data, loading, error } = useRewardsQuery({
|
||||
* variables: {
|
||||
* partyId: // value for 'partyId'
|
||||
* delegationsPagination: // value for 'delegationsPagination'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type RewardFieldsFragment = { __typename?: 'Reward', rewardType: Types.AccountType, amount: string, percentageOfTotal: string, receivedAt: any, asset: { __typename?: 'Asset', id: string, symbol: string }, party: { __typename?: 'Party', id: string }, epoch: { __typename?: 'Epoch', id: string } };
|
||||
|
||||
export type DelegationFieldsFragment = { __typename?: 'Delegation', amount: string, epoch: number };
|
||||
|
||||
export type RewardsQueryVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type RewardsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, rewardsConnection?: { __typename?: 'RewardsConnection', edges?: Array<{ __typename?: 'RewardEdge', node: { __typename?: 'Reward', rewardType: Types.AccountType, amount: string, percentageOfTotal: string, receivedAt: any, asset: { __typename?: 'Asset', id: string, symbol: string }, party: { __typename?: 'Party', id: string }, epoch: { __typename?: 'Epoch', id: string } } } | null> | null } | null, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number } } | null> | null } | null } | null, epoch: { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: any | null, end?: any | null, expiry?: any | null } } };
|
||||
|
||||
export const RewardFieldsFragmentDoc = gql`
|
||||
fragment RewardFields on Reward {
|
||||
rewardType
|
||||
asset {
|
||||
id
|
||||
symbol
|
||||
}
|
||||
party {
|
||||
id
|
||||
}
|
||||
epoch {
|
||||
id
|
||||
}
|
||||
amount
|
||||
percentageOfTotal
|
||||
receivedAt
|
||||
}
|
||||
`;
|
||||
export const DelegationFieldsFragmentDoc = gql`
|
||||
fragment DelegationFields on Delegation {
|
||||
amount
|
||||
epoch
|
||||
}
|
||||
`;
|
||||
export const RewardsDocument = gql`
|
||||
query Rewards($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
id
|
||||
rewardsConnection {
|
||||
edges {
|
||||
node {
|
||||
...RewardFields
|
||||
}
|
||||
}
|
||||
}
|
||||
delegationsConnection {
|
||||
edges {
|
||||
node {
|
||||
...DelegationFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
epoch {
|
||||
id
|
||||
timestamps {
|
||||
start
|
||||
end
|
||||
expiry
|
||||
}
|
||||
}
|
||||
}
|
||||
${RewardFieldsFragmentDoc}
|
||||
${DelegationFieldsFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useRewardsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useRewardsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useRewardsQuery` 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 } = useRewardsQuery({
|
||||
* variables: {
|
||||
* partyId: // value for 'partyId'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useRewardsQuery(baseOptions: Apollo.QueryHookOptions<RewardsQuery, RewardsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<RewardsQuery, RewardsQueryVariables>(RewardsDocument, options);
|
||||
}
|
||||
export function useRewardsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<RewardsQuery, RewardsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<RewardsQuery, RewardsQueryVariables>(RewardsDocument, options);
|
||||
}
|
||||
export type RewardsQueryHookResult = ReturnType<typeof useRewardsQuery>;
|
||||
export type RewardsLazyQueryHookResult = ReturnType<typeof useRewardsLazyQuery>;
|
||||
export type RewardsQueryResult = Apollo.QueryResult<RewardsQuery, RewardsQueryVariables>;
|
||||
@@ -9,7 +9,7 @@ import type {
|
||||
RewardsQuery,
|
||||
RewardFieldsFragment,
|
||||
DelegationFieldsFragment,
|
||||
} from './__generated___/Rewards';
|
||||
} from './__generated__/Rewards';
|
||||
import {
|
||||
formatNumber,
|
||||
removePaginationWrapper,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { formatDistance } from 'date-fns';
|
||||
import Duration from 'duration-js';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ENV } from '../../../config';
|
||||
|
||||
import { EpochCountdown } from '../../../components/epoch-countdown';
|
||||
import { Heading } from '../../../components/heading';
|
||||
@@ -15,7 +16,7 @@ import {
|
||||
import { RewardInfo } from './reward-info';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import { useNetworkParams, NetworkParams } from '@vegaprotocol/react-helpers';
|
||||
import { useRewardsQuery } from './__generated___/Rewards';
|
||||
import { useRewardsQuery } from './__generated__/Rewards';
|
||||
|
||||
export const RewardsPage = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -24,8 +25,16 @@ export const RewardsPage = () => {
|
||||
openVegaWalletDialog: store.openVegaWalletDialog,
|
||||
}));
|
||||
const { appDispatch } = useAppState();
|
||||
const { delegationsPagination } = ENV;
|
||||
const { data, loading, error } = useRewardsQuery({
|
||||
variables: { partyId: pubKey || '' },
|
||||
variables: {
|
||||
partyId: pubKey || '',
|
||||
delegationsPagination: delegationsPagination
|
||||
? {
|
||||
first: Number(delegationsPagination),
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
skip: !pubKey,
|
||||
});
|
||||
const { params } = useNetworkParams([
|
||||
|
||||
@@ -6,10 +6,10 @@ fragment StakingDelegationsFields on Delegation {
|
||||
epoch
|
||||
}
|
||||
|
||||
query PartyDelegations($partyId: ID!) {
|
||||
query PartyDelegations($partyId: ID!, $delegationsPagination: Pagination) {
|
||||
party(id: $partyId) {
|
||||
id
|
||||
delegationsConnection {
|
||||
delegationsConnection(pagination: $delegationsPagination) {
|
||||
edges {
|
||||
node {
|
||||
...StakingDelegationsFields
|
||||
|
||||
@@ -23,13 +23,13 @@ fragment StakingNodeFields on Node {
|
||||
}
|
||||
}
|
||||
|
||||
query Staking($partyId: ID!) {
|
||||
query Staking($partyId: ID!, $delegationsPagination: Pagination) {
|
||||
party(id: $partyId) {
|
||||
id
|
||||
stakingSummary {
|
||||
currentStakeAvailable
|
||||
}
|
||||
delegationsConnection {
|
||||
delegationsConnection(pagination: $delegationsPagination) {
|
||||
edges {
|
||||
node {
|
||||
amount
|
||||
|
||||
@@ -7,6 +7,7 @@ export type StakingDelegationsFieldsFragment = { __typename?: 'Delegation', amou
|
||||
|
||||
export type PartyDelegationsQueryVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
delegationsPagination?: Types.InputMaybe<Types.Pagination>;
|
||||
}>;
|
||||
|
||||
|
||||
@@ -22,10 +23,10 @@ export const StakingDelegationsFieldsFragmentDoc = gql`
|
||||
}
|
||||
`;
|
||||
export const PartyDelegationsDocument = gql`
|
||||
query PartyDelegations($partyId: ID!) {
|
||||
query PartyDelegations($partyId: ID!, $delegationsPagination: Pagination) {
|
||||
party(id: $partyId) {
|
||||
id
|
||||
delegationsConnection {
|
||||
delegationsConnection(pagination: $delegationsPagination) {
|
||||
edges {
|
||||
node {
|
||||
...StakingDelegationsFields
|
||||
@@ -52,6 +53,7 @@ export const PartyDelegationsDocument = gql`
|
||||
* const { data, loading, error } = usePartyDelegationsQuery({
|
||||
* variables: {
|
||||
* partyId: // value for 'partyId'
|
||||
* delegationsPagination: // value for 'delegationsPagination'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
|
||||
+4
-2
@@ -7,6 +7,7 @@ export type StakingNodeFieldsFragment = { __typename?: 'Node', id: string, name:
|
||||
|
||||
export type StakingQueryVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
delegationsPagination?: Types.InputMaybe<Types.Pagination>;
|
||||
}>;
|
||||
|
||||
|
||||
@@ -39,13 +40,13 @@ export const StakingNodeFieldsFragmentDoc = gql`
|
||||
}
|
||||
`;
|
||||
export const StakingDocument = gql`
|
||||
query Staking($partyId: ID!) {
|
||||
query Staking($partyId: ID!, $delegationsPagination: Pagination) {
|
||||
party(id: $partyId) {
|
||||
id
|
||||
stakingSummary {
|
||||
currentStakeAvailable
|
||||
}
|
||||
delegationsConnection {
|
||||
delegationsConnection(pagination: $delegationsPagination) {
|
||||
edges {
|
||||
node {
|
||||
amount
|
||||
@@ -94,6 +95,7 @@ export const StakingDocument = gql`
|
||||
* const { data, loading, error } = useStakingQuery({
|
||||
* variables: {
|
||||
* partyId: // value for 'partyId'
|
||||
* delegationsPagination: // value for 'delegationsPagination'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type StakingDelegationsFieldsFragment = { __typename?: 'Delegation', amount: string, epoch: number, node: { __typename?: 'Node', id: string } };
|
||||
|
||||
export type PartyDelegationsQueryVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type PartyDelegationsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number, node: { __typename?: 'Node', id: string } } } | null> | null } | null } | null, epoch: { __typename?: 'Epoch', id: string } };
|
||||
|
||||
export const StakingDelegationsFieldsFragmentDoc = gql`
|
||||
fragment StakingDelegationsFields on Delegation {
|
||||
amount
|
||||
node {
|
||||
id
|
||||
}
|
||||
epoch
|
||||
}
|
||||
`;
|
||||
export const PartyDelegationsDocument = gql`
|
||||
query PartyDelegations($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
id
|
||||
delegationsConnection {
|
||||
edges {
|
||||
node {
|
||||
...StakingDelegationsFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
epoch {
|
||||
id
|
||||
}
|
||||
}
|
||||
${StakingDelegationsFieldsFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __usePartyDelegationsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `usePartyDelegationsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `usePartyDelegationsQuery` 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 } = usePartyDelegationsQuery({
|
||||
* variables: {
|
||||
* partyId: // value for 'partyId'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function usePartyDelegationsQuery(baseOptions: Apollo.QueryHookOptions<PartyDelegationsQuery, PartyDelegationsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<PartyDelegationsQuery, PartyDelegationsQueryVariables>(PartyDelegationsDocument, options);
|
||||
}
|
||||
export function usePartyDelegationsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<PartyDelegationsQuery, PartyDelegationsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<PartyDelegationsQuery, PartyDelegationsQueryVariables>(PartyDelegationsDocument, options);
|
||||
}
|
||||
export type PartyDelegationsQueryHookResult = ReturnType<typeof usePartyDelegationsQuery>;
|
||||
export type PartyDelegationsLazyQueryHookResult = ReturnType<typeof usePartyDelegationsLazyQuery>;
|
||||
export type PartyDelegationsQueryResult = Apollo.QueryResult<PartyDelegationsQuery, PartyDelegationsQueryVariables>;
|
||||
@@ -1,110 +0,0 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type StakingNodeFieldsFragment = { __typename?: 'Node', id: string, name: string, pubkey: string, infoUrl: string, location: string, ethereumAddress: string, stakedByOperator: string, stakedByDelegates: string, stakedTotal: string, pendingStake: string, epochData?: { __typename?: 'EpochData', total: number, offline: number, online: number } | null, rankingScore: { __typename?: 'RankingScore', rankingScore: string, stakeScore: string, performanceScore: string, votingPower: string, status: Types.ValidatorStatus } };
|
||||
|
||||
export type StakingQueryVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type StakingQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string }, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number, node: { __typename?: 'Node', id: string } } } | null> | null } | null } | null, epoch: { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: any | null, end?: any | null, expiry?: any | null } }, nodesConnection: { __typename?: 'NodesConnection', edges?: Array<{ __typename?: 'NodeEdge', node: { __typename?: 'Node', id: string, name: string, pubkey: string, infoUrl: string, location: string, ethereumAddress: string, stakedByOperator: string, stakedByDelegates: string, stakedTotal: string, pendingStake: string, epochData?: { __typename?: 'EpochData', total: number, offline: number, online: number } | null, rankingScore: { __typename?: 'RankingScore', rankingScore: string, stakeScore: string, performanceScore: string, votingPower: string, status: Types.ValidatorStatus } } } | null> | null }, nodeData?: { __typename?: 'NodeData', stakedTotal: string, totalNodes: number, inactiveNodes: number, uptime: number } | null };
|
||||
|
||||
export const StakingNodeFieldsFragmentDoc = gql`
|
||||
fragment StakingNodeFields on Node {
|
||||
id
|
||||
name
|
||||
pubkey
|
||||
infoUrl
|
||||
location
|
||||
ethereumAddress
|
||||
stakedByOperator
|
||||
stakedByDelegates
|
||||
stakedTotal
|
||||
pendingStake
|
||||
epochData {
|
||||
total
|
||||
offline
|
||||
online
|
||||
}
|
||||
rankingScore {
|
||||
rankingScore
|
||||
stakeScore
|
||||
performanceScore
|
||||
votingPower
|
||||
status
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const StakingDocument = gql`
|
||||
query Staking($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
id
|
||||
stakingSummary {
|
||||
currentStakeAvailable
|
||||
}
|
||||
delegationsConnection {
|
||||
edges {
|
||||
node {
|
||||
amount
|
||||
epoch
|
||||
node {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
epoch {
|
||||
id
|
||||
timestamps {
|
||||
start
|
||||
end
|
||||
expiry
|
||||
}
|
||||
}
|
||||
nodesConnection {
|
||||
edges {
|
||||
node {
|
||||
...StakingNodeFields
|
||||
}
|
||||
}
|
||||
}
|
||||
nodeData {
|
||||
stakedTotal
|
||||
totalNodes
|
||||
inactiveNodes
|
||||
uptime
|
||||
}
|
||||
}
|
||||
${StakingNodeFieldsFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useStakingQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useStakingQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useStakingQuery` 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 } = useStakingQuery({
|
||||
* variables: {
|
||||
* partyId: // value for 'partyId'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useStakingQuery(baseOptions: Apollo.QueryHookOptions<StakingQuery, StakingQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<StakingQuery, StakingQueryVariables>(StakingDocument, options);
|
||||
}
|
||||
export function useStakingLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<StakingQuery, StakingQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<StakingQuery, StakingQueryVariables>(StakingDocument, options);
|
||||
}
|
||||
export type StakingQueryHookResult = ReturnType<typeof useStakingQuery>;
|
||||
export type StakingLazyQueryHookResult = ReturnType<typeof useStakingLazyQuery>;
|
||||
export type StakingQueryResult = Apollo.QueryResult<StakingQuery, StakingQueryVariables>;
|
||||
@@ -20,7 +20,7 @@ import NodeContainer from './nodes-container';
|
||||
import { useAppState } from '../../../contexts/app-state/app-state-context';
|
||||
import { Heading, SubHeading } from '../../../components/heading';
|
||||
import Routes from '../../routes';
|
||||
import type { StakingQuery } from './__generated___/Staking';
|
||||
import type { StakingQuery } from './__generated__/Staking';
|
||||
import type { PreviousEpochQuery } from '../__generated___/PreviousEpoch';
|
||||
|
||||
interface StakingNodeProps {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { ENV } from '../../../config';
|
||||
import { Callout, Intent, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useStakingQuery } from './__generated___/Staking';
|
||||
import { SplashLoader } from '../../../components/splash-loader';
|
||||
import { usePreviousEpochQuery } from '../__generated___/PreviousEpoch';
|
||||
import type { StakingQuery } from './__generated___/Staking';
|
||||
import type { PreviousEpochQuery } from '../__generated___/PreviousEpoch';
|
||||
import { useRefreshValidators } from '../../../hooks/use-refresh-validators';
|
||||
import { SplashLoader } from '../../../components/splash-loader';
|
||||
import { useStakingQuery } from './__generated__/Staking';
|
||||
import { usePreviousEpochQuery } from '../__generated___/PreviousEpoch';
|
||||
import type { ReactElement } from 'react';
|
||||
import type { StakingQuery } from './__generated__/Staking';
|
||||
import type { PreviousEpochQuery } from '../__generated___/PreviousEpoch';
|
||||
|
||||
// TODO should only request a single node. When migrating from deprecated APIs we should address this.
|
||||
|
||||
@@ -23,12 +23,20 @@ export const NodeContainer = ({
|
||||
}: {
|
||||
data?: StakingQuery;
|
||||
previousEpochData?: PreviousEpochQuery;
|
||||
}) => React.ReactElement;
|
||||
}) => ReactElement;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { delegationsPagination } = ENV;
|
||||
const { data, loading, error, refetch } = useStakingQuery({
|
||||
variables: { partyId: pubKey || '' },
|
||||
variables: {
|
||||
partyId: pubKey || '',
|
||||
delegationsPagination: delegationsPagination
|
||||
? {
|
||||
first: Number(delegationsPagination),
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
const { data: previousEpochData } = usePreviousEpochQuery({
|
||||
variables: {
|
||||
|
||||
@@ -3,7 +3,8 @@ import * as Sentry from '@sentry/react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { usePartyDelegationsLazyQuery } from './__generated___/PartyDelegations';
|
||||
import { ENV } from '../../../config';
|
||||
import { usePartyDelegationsLazyQuery } from './__generated__/PartyDelegations';
|
||||
import { TokenInput } from '../../../components/token-input';
|
||||
import { useAppState } from '../../../contexts/app-state/app-state-context';
|
||||
import { useSearchParams } from '../../../hooks/use-search-params';
|
||||
@@ -73,6 +74,7 @@ export const StakingForm = ({
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
const [isDialogVisible, setIsDialogVisible] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const { delegationsPagination } = ENV;
|
||||
const [action, setAction] = React.useState<StakeAction>(
|
||||
params.action as StakeAction
|
||||
);
|
||||
@@ -147,6 +149,11 @@ export const StakingForm = ({
|
||||
const [delegationSearch, { data }] = usePartyDelegationsLazyQuery({
|
||||
variables: {
|
||||
partyId: pubKey,
|
||||
delegationsPagination: delegationsPagination
|
||||
? {
|
||||
first: Number(delegationsPagination),
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
fetchPolicy: 'network-only',
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
getUnnormalisedVotingPower,
|
||||
} from '../shared';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { StakingNodeFieldsFragment } from './__generated___/Staking';
|
||||
import type { StakingNodeFieldsFragment } from './__generated__/Staking';
|
||||
import type { PreviousEpochQuery } from '../__generated___/PreviousEpoch';
|
||||
|
||||
const statuses = {
|
||||
|
||||
@@ -14,6 +14,7 @@ NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supp
|
||||
|
||||
# Expose some env vars to cypress environment for market setup
|
||||
CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
|
||||
CYPRESS_ETHEREUM_WALLET_ADDRESS=0xEe7D375bcB50C26d52E1A4a472D8822A2A22d94F
|
||||
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
|
||||
CYPRESS_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint
|
||||
|
||||
@@ -14,6 +14,7 @@ NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supp
|
||||
|
||||
# Expose some env vars to cypress environment for market setup
|
||||
CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
|
||||
CYPRESS_ETHEREUM_WALLET_ADDRESS=0xEe7D375bcB50C26d52E1A4a472D8822A2A22d94F
|
||||
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
|
||||
CYPRESS_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint
|
||||
|
||||
@@ -10,7 +10,9 @@ NX_VEGA_TOKEN_URL=https://stagnet3.token.vega.xyz
|
||||
NX_VEGA_URL=https://api.stagnet3.vega.xyz/graphql
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
|
||||
CYPRESS_ETH_WALLET_MNEMONIC=ugly gallery notice network true range brave clarify flat logic someone chunk
|
||||
CYPRESS_ETH_WALLET_MNEMONIC=
|
||||
CYPRESS_ETHEREUM_WALLET_ADDRESS=0xEe7D375bcB50C26d52E1A4a472D8822A2A22d94F
|
||||
CYPRESS_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
CYPRESS_EXPLORER_URL=https://stagnet3.explorer.vega.xyz
|
||||
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=1a18cd…0cf2e4
|
||||
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=47836c…c7d278
|
||||
|
||||
@@ -24,19 +24,8 @@ module.exports = defineConfig({
|
||||
requestTimeout: 20000,
|
||||
},
|
||||
env: {
|
||||
VEGA_PUBLIC_KEY:
|
||||
'47836c253520d2661bf5bed6339c0de08fd02cf5d4db0efee3b4373f20c7d278',
|
||||
VEGA_PUBLIC_KEY2:
|
||||
'1a18cdcaaa4f44a57b35a4e9b77e0701c17a476f2b407620f8c17371740cf2e4',
|
||||
TRUNCATED_VEGA_PUBLIC_KEY: '47836c…c7d278',
|
||||
TRUNCATED_VEGA_PUBLIC_KEY2: '1a18cd…0cf2e4',
|
||||
ETHEREUM_PROVIDER_URL:
|
||||
'https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8',
|
||||
ETHEREUM_WALLET_ADDRESS: '0x265Cc6d39a1B53d0d92068443009eE7410807158',
|
||||
ETHERSCAN_URL: 'https://sepolia.etherscan.io',
|
||||
ETHEREUM_CHAIN_ID: 11155111,
|
||||
ETH_WALLET_MNEMONIC:
|
||||
'ugly gallery notice network true range brave clarify flat logic someone chunk',
|
||||
TRADING_MODE_LINK:
|
||||
'https://docs.vega.xyz/testnet/concepts/trading-on-vega/trading-modes#auction-type-liquidity-monitoring',
|
||||
grepTags: '@regression @smoke @slow',
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { mockConnectWallet } from '@vegaprotocol/cypress';
|
||||
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/');
|
||||
cy.wait('@Market');
|
||||
cy.getByTestId('dialog-close').click();
|
||||
});
|
||||
|
||||
describe('Desktop view', { tags: '@smoke' }, () => {
|
||||
describe('Navbar', () => {
|
||||
const links = ['Markets', 'Trading', 'Portfolio'];
|
||||
const hashes = ['#/markets/all', '#/markets/market-0', '#/portfolio'];
|
||||
|
||||
links.forEach((link, index) => {
|
||||
it(`${link} should be correctly rendered`, () => {
|
||||
cy.getByTestId('navbar')
|
||||
.find(`[data-testid="navbar-links"] a[data-testid=${link}]`)
|
||||
.then((element) => {
|
||||
cy.wrap(element).click();
|
||||
cy.wrap(element)
|
||||
.get('span.absolute.md\\:h-1.w-full')
|
||||
.should('exist');
|
||||
cy.location('hash').should('equal', hashes[index]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Mobile view', { tags: '@smoke' }, () => {
|
||||
const viewportHeight = Cypress.config('viewportHeight');
|
||||
const viewportWidth = Cypress.config('viewportWidth');
|
||||
before(() => {
|
||||
// a little hack to keep the viewport size between tests (cypress bug)
|
||||
Cypress.config({
|
||||
viewportWidth: 560,
|
||||
viewportHeight: 890,
|
||||
});
|
||||
cy.viewport(560, 890);
|
||||
});
|
||||
|
||||
describe('wallet drawer', () => {
|
||||
it('wallet drawer should be correctly rendered', () => {
|
||||
mockConnectWallet();
|
||||
cy.connectVegaWallet(true);
|
||||
cy.getByTestId('connect-vega-wallet-mobile').click();
|
||||
cy.getByTestId('wallets-drawer').should('be.visible');
|
||||
cy.getByTestId('wallets-drawer').within((el) => {
|
||||
cy.wrap(el).get('button').contains('Disconnect').click();
|
||||
});
|
||||
cy.getByTestId('wallets-drawer').should('not.be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
describe('menu drawer', () => {
|
||||
it('Markets should be correctly rendered', () => {
|
||||
cy.getByTestId('button-menu-drawer').click();
|
||||
cy.getByTestId('menu-drawer').should('be.visible');
|
||||
cy.getByTestId('menu-drawer').within((el) => {
|
||||
cy.wrap(el).getByTestId('Markets').click();
|
||||
cy.location('hash').should('equal', '#/markets/all');
|
||||
});
|
||||
});
|
||||
it('Trading should be correctly rendered', () => {
|
||||
cy.getByTestId('button-menu-drawer').click();
|
||||
cy.getByTestId('menu-drawer').within((el) => {
|
||||
cy.wrap(el).getByTestId('Trading').click();
|
||||
cy.location('hash').should('equal', '#/markets/market-0');
|
||||
});
|
||||
});
|
||||
it('Portfolio should be correctly rendered', () => {
|
||||
cy.getByTestId('button-menu-drawer').click();
|
||||
cy.getByTestId('menu-drawer').within((el) => {
|
||||
cy.wrap(el).getByTestId('Portfolio').click();
|
||||
cy.location('hash').should('equal', '#/portfolio');
|
||||
});
|
||||
});
|
||||
|
||||
it('Menu drawer should not be visible until opened', () => {
|
||||
cy.getByTestId('menu-drawer').should('not.be.visible');
|
||||
cy.getByTestId('button-menu-drawer').click();
|
||||
cy.getByTestId('menu-drawer').should('be.visible');
|
||||
cy.getByTestId('menu-drawer')
|
||||
.find('[data-testid="theme-switcher"]')
|
||||
.should('be.visible');
|
||||
cy.getByTestId('button-menu-drawer').click();
|
||||
cy.getByTestId('menu-drawer').should('not.be.visible');
|
||||
});
|
||||
});
|
||||
after(() => {
|
||||
// a little hack to keep the viewport size between tests (cypress bug)
|
||||
Cypress.config({
|
||||
viewportWidth,
|
||||
viewportHeight,
|
||||
});
|
||||
});
|
||||
});
|
||||
+2
-62
@@ -7,7 +7,7 @@ const manageVegaBtn = 'manage-vega-wallet';
|
||||
const form = 'rest-connector-form';
|
||||
const dialogContent = 'dialog-content';
|
||||
|
||||
describe('vega wallet v1', { tags: '@smoke' }, () => {
|
||||
describe('connect hosted wallet', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
// Using portfolio page as it requires vega wallet connection
|
||||
cy.visit('/#/portfolio');
|
||||
@@ -58,7 +58,7 @@ describe('vega wallet v1', { tags: '@smoke' }, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('vega wallet v2', { tags: '@smoke' }, () => {
|
||||
describe('connect vega wallet', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
// Using portfolio page as it requires vega wallet connection
|
||||
cy.visit('/#/portfolio');
|
||||
@@ -127,63 +127,3 @@ describe('ethereum wallet', { tags: '@smoke' }, () => {
|
||||
cy.getByTestId(connectEthWalletBtn).should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Navbar', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/');
|
||||
cy.wait('@Market');
|
||||
cy.getByTestId('dialog-close').click();
|
||||
});
|
||||
|
||||
it('should be properly rendered', () => {
|
||||
const links = ['Markets', 'Trading', 'Portfolio'];
|
||||
const hashes = ['#/markets/all', '#/markets/market-0', '#/portfolio'];
|
||||
let i = 0;
|
||||
cy.getByTestId('navbar').within(() => {
|
||||
cy.get('[data-testid="navbar-links"] a[data-testid]', { log: true })
|
||||
.should('have.length', 3)
|
||||
.each((item) => {
|
||||
cy.wrap(item).click();
|
||||
cy.wrap(item).get('span.absolute.md\\:h-1.w-full').should('exist');
|
||||
cy.location('hash').should('equal', hashes[i]);
|
||||
cy.wrap(item).should('have.data', 'testid', links[i++]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('wallet drawer should be correctly rendered', () => {
|
||||
cy.viewport(560, 890);
|
||||
mockConnectWallet();
|
||||
cy.connectVegaWallet(true);
|
||||
cy.getByTestId('connect-vega-wallet-mobile').click();
|
||||
cy.getByTestId('wallets-drawer').should('be.visible');
|
||||
cy.getByTestId('wallets-drawer').within((el) => {
|
||||
cy.wrap(el).get('button').contains('Disconnect').click();
|
||||
});
|
||||
cy.getByTestId('wallets-drawer').should('not.be.visible');
|
||||
});
|
||||
|
||||
it('menu drawer should be correctly rendered', () => {
|
||||
cy.viewport(560, 890);
|
||||
cy.getByTestId('button-menu-drawer').click();
|
||||
cy.getByTestId('menu-drawer').should('be.visible');
|
||||
cy.getByTestId('menu-drawer').within((el) => {
|
||||
cy.wrap(el).getByTestId('Markets').click();
|
||||
cy.location('hash').should('equal', '#/markets/all');
|
||||
});
|
||||
cy.getByTestId('button-menu-drawer').click();
|
||||
cy.getByTestId('menu-drawer').within((el) => {
|
||||
cy.wrap(el).getByTestId('Trading').click();
|
||||
cy.location('hash').should('equal', '#/markets/market-0');
|
||||
});
|
||||
cy.getByTestId('button-menu-drawer').click();
|
||||
cy.getByTestId('menu-drawer').within((el) => {
|
||||
cy.wrap(el).getByTestId('Portfolio').click();
|
||||
cy.location('hash').should('equal', '#/portfolio');
|
||||
cy.wrap(el).getByTestId('theme-switcher').should('be.visible');
|
||||
});
|
||||
cy.getByTestId('menu-drawer').should('not.be.visible');
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
useDataProvider,
|
||||
useThrottledDataProvider,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import { AsyncRenderer, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { AsyncRenderer, ExternalLink, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import type {
|
||||
SingleMarketFieldsFragment,
|
||||
MarketData,
|
||||
@@ -113,7 +113,17 @@ export const MarketPage = () => {
|
||||
if (!data && marketId) {
|
||||
return (
|
||||
<Splash>
|
||||
<p>{t('Market not found')}</p>
|
||||
<span className="flex flex-col items-center gap-2">
|
||||
<p className="text-sm justify-center">
|
||||
{t('This market URL is not available anymore.')}
|
||||
</p>
|
||||
<p className="text-sm justify-center">
|
||||
{t(`Please choose another market from the`)}{' '}
|
||||
<ExternalLink onClick={() => navigate(Links[Routes.MARKETS]())}>
|
||||
market list
|
||||
</ExternalLink>
|
||||
</p>
|
||||
</span>
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { ButtonLink, Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { MarketProposalNotification } from '@vegaprotocol/governance';
|
||||
import { getExpiryDate, getMarketExpiryDate } from '@vegaprotocol/market-info';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
getExpiryDate,
|
||||
getMarketExpiryDate,
|
||||
t,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import type { SingleMarketFieldsFragment } from '@vegaprotocol/market-list';
|
||||
import {
|
||||
ColumnKind,
|
||||
@@ -125,7 +128,14 @@ type ExpiryLabelProps = {
|
||||
};
|
||||
|
||||
const ExpiryLabel = ({ market }: ExpiryLabelProps) => {
|
||||
const content = market ? getExpiryDate(market) : '-';
|
||||
const content =
|
||||
market && market.tradableInstrument.instrument.metadata.tags
|
||||
? getExpiryDate(
|
||||
market.tradableInstrument.instrument.metadata.tags,
|
||||
market.marketTimestamps.close,
|
||||
market.state
|
||||
)
|
||||
: '-';
|
||||
return <div data-testid="trading-expiry">{content}</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) > 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}
|
||||
|
||||
@@ -84,7 +84,7 @@ export const SelectMarketLandingTable = ({
|
||||
data-testid="view-market-list-link"
|
||||
onClick={() => onClose()}
|
||||
>
|
||||
<UILink className="uppercase underline">
|
||||
<UILink className="text-sm underline">
|
||||
{'Or view full market list'}
|
||||
</UILink>
|
||||
</Link>
|
||||
|
||||
@@ -27,7 +27,7 @@ const isWithdrawTransaction = (tx: EthStoredTxState) =>
|
||||
tx.methodName === 'withdraw_asset';
|
||||
|
||||
const isDepositTransaction = (tx: EthStoredTxState) =>
|
||||
tx.methodName === 'withdraw_asset';
|
||||
tx.methodName === 'deposit_asset';
|
||||
|
||||
const EthTransactionDetails = ({ tx }: { tx: EthStoredTxState }) => {
|
||||
const { data: assets } = useAssetsDataProvider();
|
||||
@@ -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>
|
||||
@@ -150,11 +150,11 @@ const EthTxCompletedToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||
return (
|
||||
<div>
|
||||
<h3 className="font-bold">
|
||||
{t('Processing')} {isDeposit && 'deposit'}
|
||||
{t('Processing')} {isDeposit && t('deposit')}
|
||||
</h3>
|
||||
<p>
|
||||
{t('Your transaction has been completed.')}
|
||||
{isDeposit && t('Waiting for deposit confirmation')}
|
||||
{t('Your transaction has been completed.')}{' '}
|
||||
{isDeposit && t('Waiting for deposit confirmation.')}
|
||||
</p>
|
||||
<EtherscanLink tx={tx} />
|
||||
<EthTransactionDetails tx={tx} />
|
||||
|
||||
@@ -20,23 +20,43 @@ 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 +69,7 @@ export function createClient(base?: string, cacheConfig?: InMemoryCacheConfig) {
|
||||
url: urlWS.href,
|
||||
})
|
||||
)
|
||||
: new ApolloLink((operation, forward) => forward(operation));
|
||||
: noOpLink;
|
||||
|
||||
const splitLink = isBrowser
|
||||
? split(
|
||||
@@ -87,6 +107,7 @@ export function createClient(base?: string, cacheConfig?: InMemoryCacheConfig) {
|
||||
return new ApolloClient({
|
||||
link: from([errorLink, composedTimeoutLink, retryLink, splitLink]),
|
||||
cache: new InMemoryCache(cacheConfig),
|
||||
connectToDevTools,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ query Candles($marketId: ID!, $interval: Interval!, $since: String!) {
|
||||
market(id: $marketId) {
|
||||
id
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
id
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
query Chart($marketId: ID!) {
|
||||
market(id: $marketId) {
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
data {
|
||||
priceMonitoringBounds {
|
||||
minValidPrice
|
||||
|
||||
+2
-1
@@ -12,7 +12,7 @@ export type CandlesQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type CandlesQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string } }, candlesConnection?: { __typename?: 'CandleDataConnection', edges?: Array<{ __typename?: 'CandleEdge', node: { __typename?: 'Candle', periodStart: any, lastUpdateInPeriod: any, high: string, low: string, open: string, close: string, volume: string } } | null> | null } | null } | null };
|
||||
export type CandlesQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string } }, candlesConnection?: { __typename?: 'CandleDataConnection', edges?: Array<{ __typename?: 'CandleEdge', node: { __typename?: 'Candle', periodStart: any, lastUpdateInPeriod: any, high: string, low: string, open: string, close: string, volume: string } } | null> | null } | null } | null };
|
||||
|
||||
export type CandlesEventsSubscriptionVariables = Types.Exact<{
|
||||
marketId: Types.Scalars['ID'];
|
||||
@@ -38,6 +38,7 @@ export const CandlesDocument = gql`
|
||||
market(id: $marketId) {
|
||||
id
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
id
|
||||
|
||||
+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
|
||||
|
||||
@@ -14,6 +14,7 @@ export const candlesQuery = (
|
||||
market: {
|
||||
id: 'market-0',
|
||||
decimalPlaces: 5,
|
||||
positionDecimalPlaces: 0,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
id: '',
|
||||
|
||||
@@ -20,6 +20,7 @@ export const chartQuery = (override?: PartialDeep<ChartQuery>): ChartQuery => {
|
||||
const defaultResult: ChartQuery = {
|
||||
market: {
|
||||
decimalPlaces: 5,
|
||||
positionDecimalPlaces: 0,
|
||||
data: {
|
||||
priceMonitoringBounds: [priceMonitoringBound],
|
||||
__typename: 'MarketData',
|
||||
|
||||
@@ -49,6 +49,7 @@ export class VegaDataSource implements DataSource {
|
||||
marketId: string;
|
||||
partyId: null | string;
|
||||
_decimalPlaces = 0;
|
||||
_positionDecimalPlaces = 0;
|
||||
|
||||
candlesSub: Subscription | null = null;
|
||||
|
||||
@@ -60,6 +61,14 @@ export class VegaDataSource implements DataSource {
|
||||
return this._decimalPlaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the number of position decimal places that an integer must be shifted by in order to get a correct
|
||||
* number denominated in the unit size of the Market.
|
||||
*/
|
||||
get positionDecimalPlaces(): number {
|
||||
return this._positionDecimalPlaces;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param client - An ApolloClient instance.
|
||||
@@ -93,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,
|
||||
@@ -145,11 +156,14 @@ export class VegaDataSource implements DataSource {
|
||||
|
||||
if (data?.market?.candlesConnection?.edges) {
|
||||
const decimalPlaces = data.market.decimalPlaces;
|
||||
const positionDecimalPlaces = data.market.positionDecimalPlaces;
|
||||
|
||||
const candles = data.market.candlesConnection.edges
|
||||
.map((edge) => edge?.node)
|
||||
.filter((node): node is CandleFieldsFragment => !!node)
|
||||
.map((node) => parseCandle(node, decimalPlaces));
|
||||
.map((node) =>
|
||||
parseCandle(node, decimalPlaces, positionDecimalPlaces)
|
||||
);
|
||||
|
||||
return candles;
|
||||
} else {
|
||||
@@ -180,7 +194,11 @@ export class VegaDataSource implements DataSource {
|
||||
|
||||
this.candlesSub = res.subscribe(({ data }) => {
|
||||
if (data) {
|
||||
const candle = parseCandle(data.candles, this.decimalPlaces);
|
||||
const candle = parseCandle(
|
||||
data.candles,
|
||||
this.decimalPlaces,
|
||||
this.positionDecimalPlaces
|
||||
);
|
||||
|
||||
onSubscriptionData(candle);
|
||||
}
|
||||
@@ -197,7 +215,8 @@ export class VegaDataSource implements DataSource {
|
||||
|
||||
function parseCandle(
|
||||
candle: CandleFieldsFragment,
|
||||
decimalPlaces: number
|
||||
decimalPlaces: number,
|
||||
positionDecimalPlaces: number
|
||||
): Candle {
|
||||
return {
|
||||
date: new Date(candle.periodStart),
|
||||
@@ -205,6 +224,6 @@ function parseCandle(
|
||||
low: Number(addDecimal(candle.low, decimalPlaces)),
|
||||
open: Number(addDecimal(candle.open, decimalPlaces)),
|
||||
close: Number(addDecimal(candle.close, decimalPlaces)),
|
||||
volume: Number(candle.volume),
|
||||
volume: Number(addDecimal(candle.volume, positionDecimalPlaces)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user