Compare commits

..
139 changed files with 1239 additions and 2118 deletions
+1
View File
@@ -68,6 +68,7 @@ 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 }}
+1 -2
View File
@@ -6,5 +6,4 @@ 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_VEGA_GOVERNANCE_URL=https://dev.token.vega.xyz
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
-1
View File
@@ -7,4 +7,3 @@ 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
-1
View File
@@ -7,7 +7,6 @@ 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
-1
View File
@@ -9,4 +9,3 @@ 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
-1
View File
@@ -11,4 +11,3 @@ 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
+1 -2
View File
@@ -4,5 +4,4 @@ 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_VEGA_GOVERNANCE_URL=https://stagnet3.token.vega.xyz
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
-1
View File
@@ -10,4 +10,3 @@ 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
+1 -1
View File
@@ -70,7 +70,7 @@
"executor": "@nrwl/workspace:run-commands",
"options": {
"commands": [
"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"
"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"
]
}
},
@@ -8,9 +8,6 @@ query ExplorerMarket($id: ID!) {
product {
... on Future {
quoteName
settlementAsset {
decimals
}
}
}
}
@@ -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, settlementAsset: { __typename?: 'Asset', decimals: number } } } } } | 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 } } } } | null };
export const ExplorerMarketDocument = gql`
@@ -22,9 +22,6 @@ export const ExplorerMarketDocument = gql`
product {
... on Future {
quoteName
settlementAsset {
decimals
}
}
}
}
@@ -1,3 +1,4 @@
import React from 'react';
import { Routes } from '../../../routes/route-names';
import { useExplorerMarketQuery } from './__generated__/Market';
import { Link } from 'react-router-dom';
@@ -7,7 +8,6 @@ import { t } from '@vegaprotocol/react-helpers';
export type MarketLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string;
showMarketName?: boolean;
};
/**
@@ -15,11 +15,7 @@ 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,
showMarketName = true,
...props
}: MarketLinkProps) => {
const MarketLink = ({ id, ...props }: MarketLinkProps) => {
const { data, error, loading } = useExplorerMarketQuery({
variables: { id },
});
@@ -41,24 +37,11 @@ const MarketLink = ({
}
}
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>
);
}
return (
<Link className="underline" {...props} to={`/${Routes.MARKETS}#${id}`}>
{label}
</Link>
);
};
export default MarketLink;
@@ -1,22 +0,0 @@
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;
@@ -1,9 +0,0 @@
query ExplorerProposal($id: ID!) {
proposal(id: $id) {
id
rationale {
title
description
}
}
}
@@ -1,52 +0,0 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type 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>;
@@ -1,85 +0,0 @@
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();
});
});
@@ -1,23 +0,0 @@
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,10 +97,6 @@ describe('Order TX Summary component', () => {
product: {
__typename: 'Future',
quoteName: 'TEST',
settlementAsset: {
__typeName: 'SettlementAsset',
decimals: 18,
},
},
},
},
@@ -3,75 +3,58 @@ 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[],
decimalSource: DecimalSource = 'MARKET'
mocks: MockedResponse[]
) {
return (
<MockedProvider mocks={mocks} addTypename={false}>
<MemoryRouter>
<PriceInMarket
marketId={marketId}
price={price}
decimalSource={decimalSource}
/>
<PriceInMarket marketId={marketId} price={price} />
</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, 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', 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',
},
},
},
},
},
},
};
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();
const res = render(renderComponent('100', '123', [mock]));
expect(await res.findByText('1.00')).toBeInTheDocument();
expect(await res.findByText('dai')).toBeInTheDocument();
});
@@ -3,23 +3,16 @@ 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,
decimalSource = 'MARKET',
}: PriceInMarketProps) => {
const PriceInMarket = ({ marketId, price }: PriceInMarketProps) => {
const { data } = useExplorerMarketQuery({
variables: { id: marketId },
fetchPolicy: 'cache-first',
@@ -27,19 +20,8 @@ const PriceInMarket = ({
let label = price;
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
);
}
if (data && data.market?.decimalPlaces) {
label = addDecimalsFormatNumber(price, data.market.decimalPlaces);
}
const suffix = get(
@@ -20,7 +20,6 @@ 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;
@@ -92,8 +91,6 @@ 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,7 +55,6 @@ export const TxDetailsLiquidityAmendment = ({
<PriceInMarket
price={amendment.commitmentAmount}
marketId={marketId}
decimalSource="SETTLEMENT_ASSET"
/>
</TableCell>
</TableRow>
@@ -54,7 +54,6 @@ export const TxDetailsLiquiditySubmission = ({
<PriceInMarket
price={submission.commitmentAmount}
marketId={marketId}
decimalSource="SETTLEMENT_ASSET"
/>
</TableCell>
</TableRow>
@@ -50,12 +50,6 @@ 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>
@@ -1,57 +0,0 @@
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>
);
};
-1
View File
@@ -16,7 +16,6 @@ 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,9 +51,6 @@ fragment ExplorerOracleDataSource on OracleSpec {
fragment ExplorerOracleDataConnection on OracleSpec {
dataConnection {
pageInfo {
hasNextPage
}
edges {
node {
externalData {
@@ -82,21 +79,12 @@ fragment ExplorerOracleDataConnection on OracleSpec {
}
query ExplorerOracleSpecs {
oracleSpecsConnection(pagination: { first: 50 }) {
pageInfo {
hasNextPage
}
oracleSpecsConnection {
edges {
node {
...ExplorerOracleDataSource
...ExplorerOracleDataConnection
}
}
}
}
query ExplorerOracleSpecById($id: ID!) {
oracleSpec(oracleSpecId: $id) {
...ExplorerOracleDataSource
...ExplorerOracleDataConnection
}
}
+5 -55
View File
@@ -5,19 +5,12 @@ 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', 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 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 ExplorerOracleSpecsQueryVariables = Types.Exact<{ [key: string]: never; }>;
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 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 const ExplorerOracleDataSourceFragmentDoc = gql`
fragment ExplorerOracleDataSource on OracleSpec {
@@ -73,10 +66,7 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
`;
export const ExplorerOracleDataConnectionFragmentDoc = gql`
fragment ExplorerOracleDataConnection on OracleSpec {
dataConnection(pagination: {first: 1}) {
pageInfo {
hasNextPage
}
dataConnection {
edges {
node {
externalData {
@@ -106,10 +96,7 @@ export const ExplorerOracleDataConnectionFragmentDoc = gql`
`;
export const ExplorerOracleSpecsDocument = gql`
query ExplorerOracleSpecs {
oracleSpecsConnection(pagination: {first: 50}) {
pageInfo {
hasNextPage
}
oracleSpecsConnection {
edges {
node {
...ExplorerOracleDataSource
@@ -146,41 +133,4 @@ 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 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>;
export type ExplorerOracleSpecsQueryResult = Apollo.QueryResult<ExplorerOracleSpecsQuery, ExplorerOracleSpecsQueryVariables>;
@@ -37,7 +37,7 @@ describe('Oracle Data view', () => {
dataConnection: {
edges: [],
},
} as unknown as ExplorerOracleDataConnectionFragment)
} as ExplorerOracleDataConnectionFragment)
);
expect(res.container).toBeEmptyDOMElement();
});
@@ -67,7 +67,7 @@ describe('Oracle Signers component', () => {
__typename: 'Signer',
signer: {
__typename: 'PubKey',
key: '1234567891234567789123456789123456778912345678912345677891234567',
key: '123',
},
},
],
@@ -4,7 +4,6 @@ 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';
@@ -15,15 +14,7 @@ export type Signer = {
};
export function getAddressTypeLabel(signer: Signer) {
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;
}
return signer.__typename === 'ETHAddress' ? 'ETH' : 'Vega';
}
export function getAddress(signer: Signer) {
@@ -38,16 +29,6 @@ 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,8 +13,6 @@ 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'];
@@ -23,47 +21,36 @@ 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 className="mb-2">
<TableWithTbody>
<TableRow modifier="bordered">
<TableHeader scope="row">{t('ID')}</TableHeader>
<TableCell modifier="bordered">
<OracleLink id={id} />
</TableCell>
<TableCell modifier="bordered">{id}</TableCell>
</TableRow>
<OracleDetailsType type={sourceType.__typename} />
<OracleSigners sourceType={sourceType} />
{
// Disabled until https://github.com/vegaprotocol/vega/issues/7286 is released
/*<OracleSigners sourceType={sourceType} />*/
}
<OracleMarkets id={id} />
<TableRow modifier="bordered">
<TableHeader scope="row">{t('Matched data')}</TableHeader>
<TableCell modifier="bordered">
{showBroadcasts ? reportsCount : reportsCount > 0 ? '✅' : '❌'}
</TableCell>
<TableHeader scope="row">{t('Broadcasts')}</TableHeader>
<TableCell modifier="bordered">{reportsCount}</TableCell>
</TableRow>
</TableWithTbody>
<OracleFilter data={dataSource} />
{showBroadcasts ? <OracleData data={dataConnection} /> : null}
<OracleData data={dataConnection} />
</div>
);
};
@@ -1,45 +0,0 @@
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;
@@ -1,49 +0,0 @@
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>
);
};
+42 -4
View File
@@ -1,7 +1,45 @@
import { Outlet } from 'react-router-dom';
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 OraclePage = () => {
return <Outlet />;
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>
);
};
export default OraclePage;
export default Oracles;
+13 -19
View File
@@ -3,9 +3,7 @@ import BlockPage from './blocks';
import Governance from './governance';
import Home from './home';
import Markets from './markets';
import OraclePage from './oracles';
import Oracles from './oracles/home';
import { Oracle } from './oracles/id';
import Oracles from './oracles';
import Party from './parties';
import { Parties } from './parties/home';
import { Party as PartySingle } from './parties/id';
@@ -87,6 +85,17 @@ const marketsRoutes = flags.markets
]
: [];
const oraclesRoutes = flags.oracles
? [
{
path: Routes.ORACLES,
name: 'Oracles',
text: t('Oracles'),
element: <Oracles />,
},
]
: [];
const networkParametersRoutes = flags.networkParameters
? [
{
@@ -152,27 +161,12 @@ 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,
];
@@ -10,6 +10,7 @@ import {
formatNumberPercentage,
t,
toBigNum,
getDateTimeFormat,
} from '@vegaprotocol/react-helpers';
import type { VegaValueFormatterParams } from '@vegaprotocol/ui-toolkit';
import type * as Schema from '@vegaprotocol/types';
@@ -30,7 +31,6 @@ 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,20 +299,17 @@ export const MarketList = () => {
/>
<AgGridColumn
headerName={t('Closing Time')}
field="tradableInstrument.instrument.metadata.tags"
field="proposal.terms.closingDatetime"
headerTooltip={t('Closing time of the market')}
valueFormatter={({
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 : '-';
value,
}: VegaValueFormatterParams<
Market,
'proposal.terms.closingDatetime'
>) => {
return value
? getDateTimeFormat().format(new Date(value).getTime())
: '-';
}}
/>
</Grid>
+61 -262
View File
@@ -5,7 +5,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "86666.297",
"total_removed": "0",
"locked_amount": "73961.6625805406210086218",
"locked_amount": "74377.3343235149335839749",
"deposits": [
{
"amount": "86666.297",
@@ -71,7 +71,7 @@
"tranche_end": "2023-06-01T00:00:00.000Z",
"total_added": "2500",
"total_removed": "0",
"locked_amount": "1710.0785065628815",
"locked_amount": "1734.125553266178",
"deposits": [
{
"amount": "2500",
@@ -450,7 +450,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "129999.45",
"total_removed": "0",
"locked_amount": "73894.181869649098296045",
"locked_amount": "74309.47436445978496023",
"deposits": [
{
"amount": "129999.45",
@@ -516,7 +516,7 @@
"tranche_end": "2023-09-03T00:00:00.000Z",
"total_added": "62600",
"total_removed": "0",
"locked_amount": "37473.16870243530666",
"locked_amount": "37773.4128805175029",
"deposits": [
{
"amount": "10000",
@@ -709,7 +709,7 @@
"tranche_end": "2023-09-17T00:00:00.000Z",
"total_added": "5000",
"total_removed": "0",
"locked_amount": "3184.845414764079",
"locked_amount": "3208.826579147641",
"deposits": [
{
"amount": "5000",
@@ -920,7 +920,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "97499.58",
"total_removed": "0",
"locked_amount": "15705.5104826322582056334",
"locked_amount": "16112.873733795746655318",
"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": "21466.721386488566701750705764",
"locked_amount": "22023.516622497674397517810344",
"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": "6607.0370403844561078578",
"locked_amount": "6778.4077253618634870844",
"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": "2150.5264434752857381272",
"locked_amount": "2206.3059384936688557712",
"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": "8039.148157117145325033",
"locked_amount": "8247.664367621075063898",
"deposits": [
{
"amount": "6500",
@@ -1204,7 +1204,7 @@
"tranche_end": "2023-05-01T00:00:00.000Z",
"total_added": "22500",
"total_removed": "3539.640404325",
"locked_amount": "11622.146926795578",
"locked_amount": "11839.76605662983325",
"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": "666983.06788767317384942",
"locked_amount": "676287.4161622965221101968",
"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": "1037.950539704105075",
"locked_amount": "1442.306857638887255",
"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": "53684.000000000000000003",
"total_added": "53657.000000000000000003",
"total_removed": "42606.21518131551",
"locked_amount": "0",
"deposits": [
@@ -6825,16 +6825,6 @@
"user": "0xC6D7208DaDEe4F431bd0f3f11E7d4c91fF51bfb2",
"tx": "0xe3fd6d538990df7bf818b8a178ecf7135a5e34f4d07778e9d6dc637cf470e2e0"
},
{
"amount": "12",
"user": "0xeD96EAA7D15951ab128abF8918a4D69F0Eef9905",
"tx": "0x3775c114790b9a57414975492eec548528a155927399bddc72c40e336f3c9f52"
},
{
"amount": "15",
"user": "0xB12Cdb63E38f9a6ff2C0F68aBC3CA9beFCb7148c",
"tx": "0xa83f849c425b63fe042be7d606eaaf64f9e89c20c01aa5ec58a3ebf54c573eba"
},
{
"amount": "20",
"user": "0xB12Cdb63E38f9a6ff2C0F68aBC3CA9beFCb7148c",
@@ -17690,12 +17680,6 @@
"tranche_id": 11,
"tx": "0x304ceeda11c67174ffc52347ec240ea5aa832675cbb1c828adc098c8886f96fc"
},
{
"amount": "15",
"user": "0xB12Cdb63E38f9a6ff2C0F68aBC3CA9beFCb7148c",
"tranche_id": 11,
"tx": "0xa83f849c425b63fe042be7d606eaaf64f9e89c20c01aa5ec58a3ebf54c573eba"
},
{
"amount": "20",
"user": "0xB12Cdb63E38f9a6ff2C0F68aBC3CA9beFCb7148c",
@@ -18383,9 +18367,9 @@
"tx": "0x6940787f6ceaac0846e69f1bc93ad9efee8c39774b91ea29f84328ae64fc9969"
}
],
"total_tokens": "2600",
"total_tokens": "2585",
"withdrawn_tokens": "2585",
"remaining_tokens": "15"
"remaining_tokens": "0"
},
{
"address": "0xE9F41a0090fcc7eaf626037003AAD44B17098E7C",
@@ -18607,21 +18591,6 @@
"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": [
@@ -33127,8 +33096,8 @@
"tranche_start": "2022-03-05T00:00:00.000Z",
"tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "3732368.4671",
"total_removed": "442882.3484327902809",
"locked_amount": "1049422.081232611329856667873",
"total_removed": "441714.6865497406814",
"locked_amount": "1063719.625100700820753200245",
"deposits": [
{
"amount": "1998.95815",
@@ -33307,11 +33276,6 @@
"user": "0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0",
"tx": "0x63016a4e870bc38285eac8057aab58aa9fe896f1659f4f2e7e4e8de19c1202db"
},
{
"amount": "1167.6618830495995",
"user": "0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0",
"tx": "0xdd0c8931e8c9ec3205ab9e4cdbb30dd5a6f43253c388f2e05b5bb5fd847ed8fb"
},
{
"amount": "1906.7742941475005",
"user": "0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0",
@@ -33658,12 +33622,6 @@
"tranche_id": 1,
"tx": "0x63016a4e870bc38285eac8057aab58aa9fe896f1659f4f2e7e4e8de19c1202db"
},
{
"amount": "1167.6618830495995",
"user": "0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0",
"tranche_id": 1,
"tx": "0xdd0c8931e8c9ec3205ab9e4cdbb30dd5a6f43253c388f2e05b5bb5fd847ed8fb"
},
{
"amount": "1906.7742941475005",
"user": "0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0",
@@ -33912,8 +33870,8 @@
}
],
"total_tokens": "187637.95",
"withdrawn_tokens": "134424.6460800270605",
"remaining_tokens": "53213.3039199729395"
"withdrawn_tokens": "133256.984196977461",
"remaining_tokens": "54380.965803022539"
},
{
"address": "0x1A71e3ED1996CAbB91bB043f880CE963D601707e",
@@ -34419,8 +34377,8 @@
"tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "15870102.715470999700000001",
"total_removed": "547359.63874938618402952",
"locked_amount": "9020870.9063540351013617047345897584630981",
"total_removed": "545472.50137003958740452",
"locked_amount": "9071569.0789202252235565659217193396309814",
"deposits": [
{
"amount": "16249.93",
@@ -35024,21 +34982,6 @@
"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",
@@ -36573,12 +36516,6 @@
"tranche_id": 2,
"tx": "0x6d913b124af23621f8b97087a51d4cd912c6a8d9cca325cb756b56d4970f3f62"
},
{
"amount": "953.052491417563625",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 2,
"tx": "0x74f9a06c6e8b7807b9c369ec3762e5d4b518f32303faf8c1d4098f9d33f4a997"
},
{
"amount": "434.254023685890375",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -37595,8 +37532,8 @@
}
],
"total_tokens": "259998.8875",
"withdrawn_tokens": "112175.254589489570875",
"remaining_tokens": "147823.632910510429125"
"withdrawn_tokens": "111222.20209807200725",
"remaining_tokens": "148776.68540192799275"
},
{
"address": "0x89051CAb67Bc7F8CC44F7e270c6EDaf1EC57676c",
@@ -38244,12 +38181,6 @@
"tranche_id": 2,
"tx": "0x108f2ebc78fb2f656e95e842a65a0c522e7ad6b6b06b158dbd549831e63814c2"
},
{
"amount": "869.931062",
"user": "0xF4c75FdbAe821C6B5DBB9f001bB23cBA58D1bA37",
"tranche_id": 2,
"tx": "0xc6a7b120dc629017c645e277e57c96fec82c78b518c0b54809dbc10f566e204d"
},
{
"amount": "1573.9558",
"user": "0xF4c75FdbAe821C6B5DBB9f001bB23cBA58D1bA37",
@@ -38402,8 +38333,8 @@
}
],
"total_tokens": "200000",
"withdrawn_tokens": "86015.962928",
"remaining_tokens": "113984.037072"
"withdrawn_tokens": "85146.031866",
"remaining_tokens": "114853.968134"
},
{
"address": "0x1b956E6c00E238194B331eddEFF72Cb5f28A8d01",
@@ -38875,12 +38806,6 @@
"tranche_id": 2,
"tx": "0x55802ccf2bf20a0bf6ffffa5add678cacd8d31450517a2ac65ff384ee5f71f22"
},
{
"amount": "64.153825929033",
"user": "0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0",
"tranche_id": 2,
"tx": "0x33446fce935f9568b7d6e796e6a9a73049585fc7225f935e343acbb5c68a60b7"
},
{
"amount": "104.762232778042",
"user": "0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0",
@@ -39111,8 +39036,8 @@
}
],
"total_tokens": "12362.05",
"withdrawn_tokens": "5310.2045398579465",
"remaining_tokens": "7051.8454601420535"
"withdrawn_tokens": "5246.0507139289135",
"remaining_tokens": "7115.9992860710865"
},
{
"address": "0xb091D456d0dFCB94dcba6f355379056C5bb995fC",
@@ -39743,8 +39668,8 @@
"tranche_start": "2021-11-05T00:00:00.000Z",
"tranche_end": "2023-05-05T00:00:00.000Z",
"total_added": "14597706.0446472999",
"total_removed": "3708107.46955489707527943",
"locked_amount": "2606565.19565117133351441770201319",
"total_removed": "3706973.15022981060693393",
"locked_amount": "2653369.424853388202023095424913904",
"deposits": [
{
"amount": "129284.449",
@@ -40043,11 +39968,6 @@
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tx": "0xc1e8ef457dbeb52bd2d02aefd418a9a67bd17f016c1ca6a1e80716d86ce3c132"
},
{
"amount": "1134.3193250864683455",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tx": "0xfb5cc9685a3c4f5174ff68cc066d8da5121f833d7539f53a78db46c2427eda9b"
},
{
"amount": "12758.73908063512839093",
"user": "0x91715128a71c9C734CDC20E5EdEEeA02E72e428E",
@@ -42826,12 +42746,6 @@
"tranche_id": 3,
"tx": "0xc1e8ef457dbeb52bd2d02aefd418a9a67bd17f016c1ca6a1e80716d86ce3c132"
},
{
"amount": "1134.3193250864683455",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tranche_id": 3,
"tx": "0xfb5cc9685a3c4f5174ff68cc066d8da5121f833d7539f53a78db46c2427eda9b"
},
{
"amount": "762.7902369187144135",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
@@ -45144,8 +45058,8 @@
}
],
"total_tokens": "359123.469575",
"withdrawn_tokens": "294949.4170736759185085",
"remaining_tokens": "64174.0525013240814915"
"withdrawn_tokens": "293815.097748589450163",
"remaining_tokens": "65308.371826410549837"
},
{
"address": "0xBdd412797c1B78535Afc5F71503b91fAbD0160fB",
@@ -46456,7 +46370,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "5778205.3912159303",
"total_removed": "2622261.560853924298939789",
"locked_amount": "712966.269636651830718004793872006",
"locked_amount": "731458.903664069114159753480553424",
"deposits": [
{
"amount": "552496.6455",
@@ -48350,8 +48264,8 @@
"tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "472355.6199999996",
"total_removed": "30192.1031304386685",
"locked_amount": "166286.92752366611650390355859972",
"total_removed": "29683.1054262326685",
"locked_amount": "168552.45507780998384909840791476",
"deposits": [
{
"amount": "3000",
@@ -55025,31 +54939,6 @@
"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",
@@ -56480,12 +56369,6 @@
}
],
"withdrawals": [
{
"amount": "172.613698626",
"user": "0x83e600Ae7f4cf265C112314839cDA2341198840B",
"tranche_id": 5,
"tx": "0x871fbcbfe5c488654b8aff67f1c4afff2f5940ea3eb0cdf165b159bb96ee7ffd"
},
{
"amount": "87.108637746",
"user": "0x83e600Ae7f4cf265C112314839cDA2341198840B",
@@ -56500,8 +56383,8 @@
}
],
"total_tokens": "600",
"withdrawn_tokens": "388.75979832",
"remaining_tokens": "211.24020168"
"withdrawn_tokens": "216.146099694",
"remaining_tokens": "383.853900306"
},
{
"address": "0x5Ef3F2723f8e8c45aF15Df67dBDfA8a8FF2C3E8F",
@@ -72754,12 +72637,6 @@
}
],
"withdrawals": [
{
"amount": "37.881582952",
"user": "0xcB555C5602cC0b2434A27d277e14Cb5C7bF4Ead1",
"tranche_id": 5,
"tx": "0x56e6ab9b05da1003e1070f9ffb5cc54038420d69751d49f700bdb008f8600701"
},
{
"amount": "40.914155252",
"user": "0xcB555C5602cC0b2434A27d277e14Cb5C7bF4Ead1",
@@ -72774,8 +72651,8 @@
}
],
"total_tokens": "400",
"withdrawn_tokens": "259.089789444",
"remaining_tokens": "140.910210556"
"withdrawn_tokens": "221.208206492",
"remaining_tokens": "178.791793508"
},
{
"address": "0x3738bec36216eA2F11B954891C65AAd1Bc852156",
@@ -75232,17 +75109,10 @@
"tx": "0xb59405747c8088945a412703637a7b422f3639439ec2ee15e180c0a2a0d71ee4"
}
],
"withdrawals": [
{
"amount": "129.409734906",
"user": "0x6E624B5788CD721F489074993d5E1Ee2C477eB00",
"tranche_id": 5,
"tx": "0x9b25e6fb021dd73d4022c6879da50a0e2b818a46955153a90e44008f5f4f5a52"
}
],
"withdrawals": [],
"total_tokens": "200",
"withdrawn_tokens": "129.409734906",
"remaining_tokens": "70.590265094"
"withdrawn_tokens": "0",
"remaining_tokens": "200"
},
{
"address": "0xa9a677b0a3Be231C0654CabB0Eaa0A72E91B3E8d",
@@ -76156,12 +76026,6 @@
}
],
"withdrawals": [
{
"amount": "39.564687976",
"user": "0xD27929d68ac0E5fd5C919A5eb5968C1D06D3Fb83",
"tranche_id": 5,
"tx": "0x63c2319241d05630a6d9ea104770cdcb6d93d145554bf2ff9d82cd8ef4e17fc7"
},
{
"amount": "38.141400304",
"user": "0xD27929d68ac0E5fd5C919A5eb5968C1D06D3Fb83",
@@ -76212,8 +76076,8 @@
}
],
"total_tokens": "400",
"withdrawn_tokens": "258.68491882",
"remaining_tokens": "141.31508118"
"withdrawn_tokens": "219.120230844",
"remaining_tokens": "180.879769156"
},
{
"address": "0xF5037DDA4A660d67560200f45380FF8364e35540",
@@ -76277,17 +76141,10 @@
"tx": "0xe32a466fc780a0fb3fd84a804f622931ebfaf3f428bff0dc6d141270410e75f8"
}
],
"withdrawals": [
{
"amount": "129.527999746",
"user": "0x4683B77D114a04f1602dda7B0Ab2A24Fe79295fD",
"tranche_id": 5,
"tx": "0x0a2ab8841a315a8f2bcf534953ff9058607709c1b761f4fded8ba238ea3f3802"
}
],
"withdrawals": [],
"total_tokens": "200",
"withdrawn_tokens": "129.527999746",
"remaining_tokens": "70.472000254"
"withdrawn_tokens": "0",
"remaining_tokens": "200"
},
{
"address": "0x0A67488B946C5Ad7e3afc8766316C9Dc17333C6B",
@@ -76802,7 +76659,7 @@
"tranche_start": "2021-12-05T00:00:00.000Z",
"tranche_end": "2022-06-05T00:00:00.000Z",
"total_added": "171288.42",
"total_removed": "64196.1049690697989",
"total_removed": "63445.7249006422989",
"locked_amount": "0",
"deposits": [
{
@@ -81047,31 +80904,6 @@
"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",
@@ -91112,17 +90944,10 @@
"tx": "0xadc25e73736cd85e84fee16f2d7ee41f16f7deb83a52ceb82b327974063964f7"
}
],
"withdrawals": [
{
"amount": "250",
"user": "0x92Faf283Ff5Bca05673411091D922a054A959900",
"tranche_id": 6,
"tx": "0x5f47c30b18bfd7c1d7997ebd04ea2460b72f6a5880385075e37090428711215f"
}
],
"withdrawals": [],
"total_tokens": "250",
"withdrawn_tokens": "250",
"remaining_tokens": "0"
"withdrawn_tokens": "0",
"remaining_tokens": "250"
},
{
"address": "0xB0B91feF5E4Af34F727797079DCDa5CCAc817a4B",
@@ -93500,17 +93325,10 @@
"tx": "0x9f916cf09e8a3c4ade0ffce5190db464d0a2b1dadba78e1ee7ba5d6e751d6148"
}
],
"withdrawals": [
{
"amount": "250",
"user": "0xaD8a77d52Dd1E2dDD364AfE960DF49CAe1124C6D",
"tranche_id": 6,
"tx": "0xd8084414ae0dcff7b19cc85b92bc4898cb656f485e51bc8f8b0d499f97d5e212"
}
],
"withdrawals": [],
"total_tokens": "250",
"withdrawn_tokens": "250",
"remaining_tokens": "0"
"withdrawn_tokens": "0",
"remaining_tokens": "250"
},
{
"address": "0xc140931E1A188667395734771f195Ce832c40922",
@@ -96738,12 +96556,6 @@
}
],
"withdrawals": [
{
"amount": "99.7195512825",
"user": "0x01a66CdA5D3212D3Ac9f9ff837eE4e4833E52895",
"tranche_id": 6,
"tx": "0xcaab4c813b9ce156921f1a8cc3193f20d9f2b397e66e774233c13e64475b1853"
},
{
"amount": "150.2804487175",
"user": "0x01a66CdA5D3212D3Ac9f9ff837eE4e4833E52895",
@@ -96752,8 +96564,8 @@
}
],
"total_tokens": "250",
"withdrawn_tokens": "250",
"remaining_tokens": "0"
"withdrawn_tokens": "150.2804487175",
"remaining_tokens": "99.7195512825"
},
{
"address": "0x6b671cfa2619790c861Fd3b4B3cb3aE5A78ee3fC",
@@ -96781,12 +96593,6 @@
}
],
"withdrawals": [
{
"amount": "100.660517145",
"user": "0xC343fD1a1dd3F8D5412b2bae1c3A4a0482029f63",
"tranche_id": 6,
"tx": "0xd6c9e31ca9abc4832ba4d6345ddeefbc3ab947b9c48ad834e2002d790642db22"
},
{
"amount": "149.339482855",
"user": "0xC343fD1a1dd3F8D5412b2bae1c3A4a0482029f63",
@@ -96795,8 +96601,8 @@
}
],
"total_tokens": "250",
"withdrawn_tokens": "250",
"remaining_tokens": "0"
"withdrawn_tokens": "149.339482855",
"remaining_tokens": "100.660517145"
},
{
"address": "0x9a25025F722305b4336CcD7E67995E691A12426E",
@@ -98011,17 +97817,10 @@
"tx": "0xe32a466fc780a0fb3fd84a804f622931ebfaf3f428bff0dc6d141270410e75f8"
}
],
"withdrawals": [
{
"amount": "50",
"user": "0xa11930Cd107D363aCc44223F03dDF899ac1c864C",
"tranche_id": 6,
"tx": "0x9d260c971e699ed0255fe230c306c70f076064466c0b50c855a234c6e5034156"
}
],
"withdrawals": [],
"total_tokens": "50",
"withdrawn_tokens": "50",
"remaining_tokens": "0"
"withdrawn_tokens": "0",
"remaining_tokens": "50"
},
{
"address": "0x75AB80d94F9F7C0FE4E7973C8fF505882bE80f97",
-1
View File
@@ -11,7 +11,6 @@ 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
-1
View File
@@ -14,7 +14,6 @@ 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
-1
View File
@@ -8,4 +8,3 @@ 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
+1 -2
View File
@@ -8,5 +8,4 @@ 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_DELEGATIONS_PAGINATION=50
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40:87edc2605e544f888305d7fc4a9141bd@o286262.ingest.sentry.io/5882996
-1
View File
@@ -8,4 +8,3 @@ 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
-1
View File
@@ -5,4 +5,3 @@ 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
-1
View File
@@ -5,4 +5,3 @@ 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
-1
View File
@@ -6,4 +6,3 @@ 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
-1
View File
@@ -9,4 +9,3 @@ 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!, $delegationsPagination: Pagination) {
query Delegations($partyId: ID!) {
epoch {
id
}
party(id: $partyId) {
id
delegationsConnection(pagination: $delegationsPagination) {
delegationsConnection {
edges {
node {
...WalletDelegationFields
@@ -7,7 +7,6 @@ export type WalletDelegationFieldsFragment = { __typename?: 'Delegation', amount
export type DelegationsQueryVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
delegationsPagination?: Types.InputMaybe<Types.Pagination>;
}>;
@@ -24,13 +23,13 @@ export const WalletDelegationFieldsFragmentDoc = gql`
}
`;
export const DelegationsDocument = gql`
query Delegations($partyId: ID!, $delegationsPagination: Pagination) {
query Delegations($partyId: ID!) {
epoch {
id
}
party(id: $partyId) {
id
delegationsConnection(pagination: $delegationsPagination) {
delegationsConnection {
edges {
node {
...WalletDelegationFields
@@ -77,7 +76,6 @@ export const DelegationsDocument = gql`
* const { data, loading, error } = useDelegationsQuery({
* variables: {
* partyId: // value for 'partyId'
* delegationsPagination: // value for 'delegationsPagination'
* },
* });
*/
@@ -0,0 +1,92 @@
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>;
+5 -13
View File
@@ -4,7 +4,6 @@ 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';
@@ -24,18 +23,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[]
>([]);
@@ -63,14 +62,7 @@ export const usePollForDelegations = () => {
client
.query<DelegationsQuery, DelegationsQueryVariables>({
query: DelegationsDocument,
variables: {
partyId: pubKey,
delegationsPagination: delegationsPagination
? {
first: Number(delegationsPagination),
}
: undefined,
},
variables: { partyId: pubKey },
fetchPolicy: 'network-only',
})
.then((res) => {
@@ -215,7 +207,7 @@ export const usePollForDelegations = () => {
clearInterval(interval);
mounted = false;
};
}, [delegationsPagination, client, decimals, pubKey, t, vegaToken.address]);
}, [client, decimals, pubKey, t, vegaToken.address]);
return { delegations, currentStakeAvailable, delegatedNodes, accounts };
};
-1
View File
@@ -64,7 +64,6 @@ 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!, $delegationsPagination: Pagination) {
query Rewards($partyId: ID!) {
party(id: $partyId) {
id
rewardsConnection {
@@ -30,7 +30,7 @@ query Rewards($partyId: ID!, $delegationsPagination: Pagination) {
}
}
}
delegationsConnection(pagination: $delegationsPagination) {
delegationsConnection {
edges {
node {
...DelegationFields
+2 -4
View File
@@ -9,7 +9,6 @@ export type DelegationFieldsFragment = { __typename?: 'Delegation', amount: stri
export type RewardsQueryVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
delegationsPagination?: Types.InputMaybe<Types.Pagination>;
}>;
@@ -40,7 +39,7 @@ export const DelegationFieldsFragmentDoc = gql`
}
`;
export const RewardsDocument = gql`
query Rewards($partyId: ID!, $delegationsPagination: Pagination) {
query Rewards($partyId: ID!) {
party(id: $partyId) {
id
rewardsConnection {
@@ -50,7 +49,7 @@ export const RewardsDocument = gql`
}
}
}
delegationsConnection(pagination: $delegationsPagination) {
delegationsConnection {
edges {
node {
...DelegationFields
@@ -83,7 +82,6 @@ ${DelegationFieldsFragmentDoc}`;
* const { data, loading, error } = useRewardsQuery({
* variables: {
* partyId: // value for 'partyId'
* delegationsPagination: // value for 'delegationsPagination'
* },
* });
*/
@@ -0,0 +1,98 @@
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,7 +4,6 @@ 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';
@@ -16,7 +15,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();
@@ -25,16 +24,8 @@ export const RewardsPage = () => {
openVegaWalletDialog: store.openVegaWalletDialog,
}));
const { appDispatch } = useAppState();
const { delegationsPagination } = ENV;
const { data, loading, error } = useRewardsQuery({
variables: {
partyId: pubKey || '',
delegationsPagination: delegationsPagination
? {
first: Number(delegationsPagination),
}
: undefined,
},
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
const { params } = useNetworkParams([
@@ -6,10 +6,10 @@ fragment StakingDelegationsFields on Delegation {
epoch
}
query PartyDelegations($partyId: ID!, $delegationsPagination: Pagination) {
query PartyDelegations($partyId: ID!) {
party(id: $partyId) {
id
delegationsConnection(pagination: $delegationsPagination) {
delegationsConnection {
edges {
node {
...StakingDelegationsFields
@@ -23,13 +23,13 @@ fragment StakingNodeFields on Node {
}
}
query Staking($partyId: ID!, $delegationsPagination: Pagination) {
query Staking($partyId: ID!) {
party(id: $partyId) {
id
stakingSummary {
currentStakeAvailable
}
delegationsConnection(pagination: $delegationsPagination) {
delegationsConnection {
edges {
node {
amount
@@ -7,7 +7,6 @@ export type StakingDelegationsFieldsFragment = { __typename?: 'Delegation', amou
export type PartyDelegationsQueryVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
delegationsPagination?: Types.InputMaybe<Types.Pagination>;
}>;
@@ -23,10 +22,10 @@ export const StakingDelegationsFieldsFragmentDoc = gql`
}
`;
export const PartyDelegationsDocument = gql`
query PartyDelegations($partyId: ID!, $delegationsPagination: Pagination) {
query PartyDelegations($partyId: ID!) {
party(id: $partyId) {
id
delegationsConnection(pagination: $delegationsPagination) {
delegationsConnection {
edges {
node {
...StakingDelegationsFields
@@ -53,7 +52,6 @@ export const PartyDelegationsDocument = gql`
* const { data, loading, error } = usePartyDelegationsQuery({
* variables: {
* partyId: // value for 'partyId'
* delegationsPagination: // value for 'delegationsPagination'
* },
* });
*/
+2 -4
View File
@@ -7,7 +7,6 @@ export type StakingNodeFieldsFragment = { __typename?: 'Node', id: string, name:
export type StakingQueryVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
delegationsPagination?: Types.InputMaybe<Types.Pagination>;
}>;
@@ -40,13 +39,13 @@ export const StakingNodeFieldsFragmentDoc = gql`
}
`;
export const StakingDocument = gql`
query Staking($partyId: ID!, $delegationsPagination: Pagination) {
query Staking($partyId: ID!) {
party(id: $partyId) {
id
stakingSummary {
currentStakeAvailable
}
delegationsConnection(pagination: $delegationsPagination) {
delegationsConnection {
edges {
node {
amount
@@ -95,7 +94,6 @@ export const StakingDocument = gql`
* const { data, loading, error } = useStakingQuery({
* variables: {
* partyId: // value for 'partyId'
* delegationsPagination: // value for 'delegationsPagination'
* },
* });
*/
@@ -0,0 +1,68 @@
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>;
@@ -0,0 +1,110 @@
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>;
+1 -1
View File
@@ -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 { useRefreshValidators } from '../../../hooks/use-refresh-validators';
import { useStakingQuery } from './__generated___/Staking';
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 { StakingQuery } from './__generated___/Staking';
import type { PreviousEpochQuery } from '../__generated___/PreviousEpoch';
import { useRefreshValidators } from '../../../hooks/use-refresh-validators';
// TODO should only request a single node. When migrating from deprecated APIs we should address this.
@@ -23,20 +23,12 @@ export const NodeContainer = ({
}: {
data?: StakingQuery;
previousEpochData?: PreviousEpochQuery;
}) => ReactElement;
}) => React.ReactElement;
}) => {
const { t } = useTranslation();
const { pubKey } = useVegaWallet();
const { delegationsPagination } = ENV;
const { data, loading, error, refetch } = useStakingQuery({
variables: {
partyId: pubKey || '',
delegationsPagination: delegationsPagination
? {
first: Number(delegationsPagination),
}
: undefined,
},
variables: { partyId: pubKey || '' },
});
const { data: previousEpochData } = usePreviousEpochQuery({
variables: {
@@ -3,8 +3,7 @@ import * as Sentry from '@sentry/react';
import React, { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { ENV } from '../../../config';
import { usePartyDelegationsLazyQuery } from './__generated__/PartyDelegations';
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';
@@ -74,7 +73,6 @@ 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
);
@@ -149,11 +147,6 @@ 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 = {
-1
View File
@@ -14,7 +14,6 @@ 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
-1
View File
@@ -14,7 +14,6 @@ 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
+1 -3
View File
@@ -10,9 +10,7 @@ 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=
CYPRESS_ETHEREUM_WALLET_ADDRESS=0xEe7D375bcB50C26d52E1A4a472D8822A2A22d94F
CYPRESS_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
CYPRESS_ETH_WALLET_MNEMONIC=ugly gallery notice network true range brave clarify flat logic someone chunk
CYPRESS_EXPLORER_URL=https://stagnet3.explorer.vega.xyz
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=1a18cd…0cf2e4
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=47836c…c7d278
+11
View File
@@ -24,8 +24,19 @@ 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',
@@ -7,7 +7,7 @@ const manageVegaBtn = 'manage-vega-wallet';
const form = 'rest-connector-form';
const dialogContent = 'dialog-content';
describe('connect hosted wallet', { tags: '@smoke' }, () => {
describe('vega wallet v1', { tags: '@smoke' }, () => {
beforeEach(() => {
// Using portfolio page as it requires vega wallet connection
cy.visit('/#/portfolio');
@@ -58,7 +58,7 @@ describe('connect hosted wallet', { tags: '@smoke' }, () => {
});
});
describe('connect vega wallet', { tags: '@smoke' }, () => {
describe('vega wallet v2', { tags: '@smoke' }, () => {
beforeEach(() => {
// Using portfolio page as it requires vega wallet connection
cy.visit('/#/portfolio');
@@ -127,3 +127,63 @@ 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');
});
});
@@ -1,99 +0,0 @@
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 -12
View File
@@ -7,7 +7,7 @@ import {
useDataProvider,
useThrottledDataProvider,
} from '@vegaprotocol/react-helpers';
import { AsyncRenderer, ExternalLink, Splash } from '@vegaprotocol/ui-toolkit';
import { AsyncRenderer, Splash } from '@vegaprotocol/ui-toolkit';
import type {
SingleMarketFieldsFragment,
MarketData,
@@ -113,17 +113,7 @@ export const MarketPage = () => {
if (!data && marketId) {
return (
<Splash>
<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>
<p>{t('Market not found')}</p>
</Splash>
);
}
@@ -2,11 +2,8 @@ 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,
t,
} from '@vegaprotocol/react-helpers';
import { getExpiryDate, getMarketExpiryDate } from '@vegaprotocol/market-info';
import { t } from '@vegaprotocol/react-helpers';
import type { SingleMarketFieldsFragment } from '@vegaprotocol/market-list';
import {
ColumnKind,
@@ -128,14 +125,7 @@ type ExpiryLabelProps = {
};
const ExpiryLabel = ({ market }: ExpiryLabelProps) => {
const content =
market && market.tradableInstrument.instrument.metadata.tags
? getExpiryDate(
market.tradableInstrument.instrument.metadata.tags,
market.marketTimestamps.close,
market.state
)
: '-';
const content = market ? getExpiryDate(market) : '-';
return <div data-testid="trading-expiry">{content}</div>;
};
+6 -38
View File
@@ -1,58 +1,26 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { Footer, NodeHealth } from './footer';
import { Footer } from './footer';
import { useEnvironment } from '@vegaprotocol/environment';
jest.mock('@vegaprotocol/environment');
describe('Footer', () => {
it('can open node switcher by clicking the node url', () => {
it('renders a button to open node switcher', () => {
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.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'));
fireEvent.click(screen.getByRole('button'));
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();
}
);
});
+9 -56
View File
@@ -1,75 +1,28 @@
import { useEnvironment } from '@vegaprotocol/environment';
import { t, useNavigatorOnline } from '@vegaprotocol/react-helpers';
import { ButtonLink, Indicator, Intent } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/react-helpers';
import { ButtonLink, Link } from '@vegaprotocol/ui-toolkit';
export const Footer = () => {
const { VEGA_URL, blockDifference, setNodeSwitcherOpen } = useEnvironment();
const { VEGA_URL, 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 && (
<>
<NodeHealth
blockDiff={blockDifference}
openNodeSwitcher={setNodeSwitcherOpen}
/>
{' | '}
<NodeUrl url={VEGA_URL} openNodeSwitcher={setNodeSwitcherOpen} />
</>
)}
{VEGA_URL && <NodeUrl url={VEGA_URL} />}
<ButtonLink onClick={setNodeSwitcherOpen}>{t('Change')}</ButtonLink>
</div>
</div>
</footer>
);
};
interface NodeUrlProps {
url: string;
openNodeSwitcher: () => void;
}
const NodeUrl = ({ url, openNodeSwitcher }: NodeUrlProps) => {
const NodeUrl = ({ url }: { url: string }) => {
// 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 (
<span>
<Indicator variant={intent} />
<ButtonLink onClick={openNodeSwitcher}>{text}</ButtonLink>
</span>
<Link href={'https://' + nodeUrl} target="_blank">
{nodeUrl}
</Link>
);
};
@@ -112,7 +112,7 @@ export const SelectMarketPopover = ({
} = useMarketList();
const variables = useMemo(() => ({ partyId: pubKey }), [pubKey]);
const {
data: positions,
data: party,
loading: positionsLoading,
reload,
} = useDataProvider({
@@ -132,9 +132,11 @@ export const SelectMarketPopover = ({
const markets = useMemo(
() =>
data?.filter((market) =>
positions?.find((node) => node.market.id === market.id)
party?.positionsConnection?.edges?.find(
(edge) => edge.node.market.id === market.id
)
),
[data, positions]
[data, party]
);
useEffect(() => {
@@ -170,13 +172,15 @@ export const SelectMarketPopover = ({
</div>
) : (
<table className="relative text-sm w-full whitespace-nowrap">
{pubKey && (positions?.length ?? 0) > 0 ? (
{pubKey && (party?.positionsConnection?.edges?.length ?? 0) > 0 ? (
<>
<TableTitle>{t('My markets')}</TableTitle>
<SelectAllMarketsTableBody
inViewRoot={inViewRoot}
markets={markets}
positions={positions || undefined}
positions={party?.positionsConnection?.edges
?.filter((edge) => edge.node)
.map((edge) => edge.node)}
onSelect={onSelectMarket}
onCellClick={onCellClick}
headers={columnHeadersPositionMarkets}
@@ -84,7 +84,7 @@ export const SelectMarketLandingTable = ({
data-testid="view-market-list-link"
onClick={() => onClose()}
>
<UILink className="text-sm underline">
<UILink className="uppercase 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 === 'deposit_asset';
tx.methodName === 'withdraw_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 && t('deposit')}
{t('Processing')} {isDeposit && '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} />
+13 -34
View File
@@ -20,43 +20,23 @@ const isBrowser = typeof window !== 'undefined';
const NOT_FOUND = 'NotFound';
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!');
export function createClient(base?: string, cacheConfig?: InMemoryCacheConfig) {
if (!base) {
throw new Error('Base must be passed into createClient!');
}
const urlHTTP = new URL(url);
const urlWS = new URL(url);
const urlHTTP = new URL(base);
const urlWS = new URL(base);
// 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 = retry
? new RetryLink({
delay: {
initial: 300,
max: 10000,
jitter: true,
},
})
: noOpLink;
const retryLink = new RetryLink({
delay: {
initial: 300,
max: 10000,
jitter: true,
},
});
const httpLink = new HttpLink({
uri: urlHTTP.href,
@@ -69,7 +49,7 @@ export function createClient({
url: urlWS.href,
})
)
: noOpLink;
: new ApolloLink((operation, forward) => forward(operation));
const splitLink = isBrowser
? split(
@@ -107,7 +87,6 @@ export function createClient({
return new ApolloClient({
link: from([errorLink, composedTimeoutLink, retryLink, splitLink]),
cache: new InMemoryCache(cacheConfig),
connectToDevTools,
});
}
@@ -12,7 +12,6 @@ query Candles($marketId: ID!, $interval: Interval!, $since: String!) {
market(id: $marketId) {
id
decimalPlaces
positionDecimalPlaces
tradableInstrument {
instrument {
id
-1
View File
@@ -1,7 +1,6 @@
query Chart($marketId: ID!) {
market(id: $marketId) {
decimalPlaces
positionDecimalPlaces
data {
priceMonitoringBounds {
minValidPrice
+1 -2
View File
@@ -12,7 +12,7 @@ export type CandlesQueryVariables = Types.Exact<{
}>;
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 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 CandlesEventsSubscriptionVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
@@ -38,7 +38,6 @@ export const CandlesDocument = gql`
market(id: $marketId) {
id
decimalPlaces
positionDecimalPlaces
tradableInstrument {
instrument {
id
+1 -2
View File
@@ -8,14 +8,13 @@ export type ChartQueryVariables = Types.Exact<{
}>;
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 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 const ChartDocument = gql`
query Chart($marketId: ID!) {
market(id: $marketId) {
decimalPlaces
positionDecimalPlaces
data {
priceMonitoringBounds {
minValidPrice
@@ -14,7 +14,6 @@ export const candlesQuery = (
market: {
id: 'market-0',
decimalPlaces: 5,
positionDecimalPlaces: 0,
tradableInstrument: {
instrument: {
id: '',
-1
View File
@@ -20,7 +20,6 @@ export const chartQuery = (override?: PartialDeep<ChartQuery>): ChartQuery => {
const defaultResult: ChartQuery = {
market: {
decimalPlaces: 5,
positionDecimalPlaces: 0,
data: {
priceMonitoringBounds: [priceMonitoringBound],
__typename: 'MarketData',
+4 -23
View File
@@ -49,7 +49,6 @@ export class VegaDataSource implements DataSource {
marketId: string;
partyId: null | string;
_decimalPlaces = 0;
_positionDecimalPlaces = 0;
candlesSub: Subscription | null = null;
@@ -61,14 +60,6 @@ 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.
@@ -102,11 +93,9 @@ 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,
@@ -156,14 +145,11 @@ 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, positionDecimalPlaces)
);
.map((node) => parseCandle(node, decimalPlaces));
return candles;
} else {
@@ -194,11 +180,7 @@ export class VegaDataSource implements DataSource {
this.candlesSub = res.subscribe(({ data }) => {
if (data) {
const candle = parseCandle(
data.candles,
this.decimalPlaces,
this.positionDecimalPlaces
);
const candle = parseCandle(data.candles, this.decimalPlaces);
onSubscriptionData(candle);
}
@@ -215,8 +197,7 @@ export class VegaDataSource implements DataSource {
function parseCandle(
candle: CandleFieldsFragment,
decimalPlaces: number,
positionDecimalPlaces: number
decimalPlaces: number
): Candle {
return {
date: new Date(candle.periodStart),
@@ -224,6 +205,6 @@ function parseCandle(
low: Number(addDecimal(candle.low, decimalPlaces)),
open: Number(addDecimal(candle.open, decimalPlaces)),
close: Number(addDecimal(candle.close, decimalPlaces)),
volume: Number(addDecimal(candle.volume, positionDecimalPlaces)),
volume: Number(candle.volume),
};
}
@@ -46,10 +46,7 @@ describe('Network loader', () => {
render(
<NetworkLoader skeleton={SKELETON_TEXT}>{SUCCESS_TEXT}</NetworkLoader>
);
expect(createClient).toHaveBeenCalledWith({
url: 'http://vega.node',
cacheConfig: undefined,
});
expect(createClient).toHaveBeenCalledWith('http://vega.node', undefined);
expect(await screen.findByText(SUCCESS_TEXT)).toBeInTheDocument();
});
});
@@ -20,10 +20,7 @@ export function NetworkLoader({
const client = useMemo(() => {
if (VEGA_URL) {
return createClient({
url: VEGA_URL,
cacheConfig: cache,
});
return createClient(VEGA_URL, cache);
}
return undefined;
}, [VEGA_URL, cache]);
-1
View File
@@ -1,3 +1,2 @@
export * from './use-environment';
export * from './use-links';
export * from './use-node-health';
@@ -18,9 +18,6 @@ type UseConfigOptions = {
defaultConfig?: Configuration;
};
/**
* Fetch list of hosts from the VEGA_CONFIG_URL
*/
export const useConfig = (
{ environment, defaultConfig }: UseConfigOptions,
onError: (errorType: ErrorType) => void
@@ -101,16 +101,6 @@ 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());
@@ -137,7 +127,6 @@ 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,8 +1,7 @@
// 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 } from '@testing-library/react';
import type { ClientOptions } from '@vegaprotocol/apollo-client';
import { renderHook, waitFor, act } from '@testing-library/react';
import { createClient } from '@vegaprotocol/apollo-client';
import { useEnvironment, EnvironmentProvider } from './use-environment';
import { Networks, ErrorType } from '../types';
@@ -33,7 +32,6 @@ 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',
@@ -44,10 +42,6 @@ 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,
};
@@ -102,7 +96,7 @@ const getQuickestNode = (mockNodes: Record<string, MockRequestConfig>) => {
};
beforeEach(() => {
// @ts-ignore: typscript doesn't recognise the mock implementation
// @ts-ignore: typescript doesn't recognize the mock implementation
global.fetch.mockImplementation(setupFetch());
window.localStorage.clear();
@@ -135,7 +129,6 @@ describe('useEnvironment hook', () => {
const { result } = renderHook(() => useEnvironment(), {
wrapper: MockWrapper,
});
await waitFor(() => {
expect(result.current).toEqual({
...mockEnvironmentState,
@@ -143,67 +136,39 @@ 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,
});
await waitFor(() => {
expect(result.current).toEqual({
...mockEnvironmentState,
VEGA_CONFIG_URL: undefined,
setNodeSwitcherOpen: result.current.setNodeSwitcherOpen,
});
});
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,
});
it('allows for the VEGA_NETWORKS to be missing from the environment', async () => {
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'];
const { result } = renderHook(() => useEnvironment(), {
wrapper: MockWrapper,
});
await waitFor(() => {
expect(result.current).toEqual({
...mockEnvironmentState,
VEGA_NETWORKS: {
TESTNET: window.location.origin,
},
VEGA_NETWORKS: {},
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 () => {
it('when VEGA_NETWORKS is not a valid json, prints a warning and continues without using the value from it', async () => {
act(async () => {
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(noop);
process.env['NX_VEGA_NETWORKS'] = '{not:{valid:json';
const { result } = renderHook(() => useEnvironment(), {
@@ -212,56 +177,26 @@ describe('useEnvironment hook', () => {
await waitFor(() => {
expect(result.current).toEqual({
...mockEnvironmentState,
VEGA_NETWORKS: {
TESTNET: window.location.origin,
},
VEGA_NETWORKS: {},
setNodeSwitcherOpen: result.current.setNodeSwitcherOpen,
});
});
expect(consoleWarnSpy).toHaveBeenCalled();
consoleWarnSpy.mockRestore();
});
});
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 }) => {
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 () => {
// @ts-ignore allow adding a mock return value to mocked module
createClient.mockImplementation(() => createMockClient({ network: env }));
@@ -280,39 +215,13 @@ describe('useEnvironment hook', () => {
setNodeSwitcherOpen: result.current.setNodeSwitcherOpen,
});
});
}
);
});
}
);
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 () => {
describe('node selection', () => {
it('updates the VEGA_URL from the config when it is missing from the environment', async () => {
act(async () => {
delete process.env['NX_VEGA_URL'];
const { result } = renderHook(() => useEnvironment(), {
wrapper: MockWrapper,
@@ -326,9 +235,10 @@ describe('useEnvironment hook', () => {
});
});
});
});
// 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 () => {
it('updates the VEGA_URL with the quickest node to respond from the config urls', async () => {
act(async () => {
delete process.env['NX_VEGA_URL'];
const mockNodes: Record<string, MockRequestConfig> = {
@@ -338,14 +248,13 @@ describe('useEnvironment hook', () => {
'https://mock-node-4.com': { hasError: false, delay: 0 },
};
// @ts-ignore: typscript doesn't recognise the mock implementation
// @ts-ignore: typescript doesn't recognize 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((cfg: ClientOptions) => {
// eslint-disable-next-line
return createMockClient({ statistics: mockNodes[cfg.url!] });
createClient.mockImplementation((url: keyof typeof mockNodes) => {
return createMockClient({ statistics: mockNodes[url] });
});
const nodeUrl = getQuickestNode(mockNodes);
@@ -362,8 +271,10 @@ describe('useEnvironment hook', () => {
});
});
});
});
it('ignores failing nodes and selects the first successful one to use', async () => {
it('ignores failing nodes and selects the first successful one to use', async () => {
act(async () => {
delete process.env['NX_VEGA_URL'];
const mockNodes: Record<string, MockRequestConfig> = {
@@ -373,14 +284,13 @@ describe('useEnvironment hook', () => {
'https://mock-node-4.com': { hasError: true, delay: 0 },
};
// @ts-ignore: typscript doesn't recognise the mock implementation
// @ts-ignore: typescript doesn't recognize 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((cfg: ClientOptions) => {
// eslint-disable-next-line
return createMockClient({ statistics: mockNodes[cfg.url!] });
createClient.mockImplementation((url: keyof typeof mockNodes) => {
return createMockClient({ statistics: mockNodes[url] });
});
const nodeUrl = getQuickestNode(mockNodes);
@@ -397,8 +307,10 @@ describe('useEnvironment hook', () => {
});
});
});
});
it('has a network error when cannot connect to any nodes', async () => {
it('has a network error when cannot connect to any nodes', async () => {
act(async () => {
delete process.env['NX_VEGA_URL'];
const mockNodes: Record<string, MockRequestConfig> = {
@@ -408,14 +320,13 @@ describe('useEnvironment hook', () => {
'https://mock-node-4.com': { hasError: true, delay: 0 },
};
// @ts-ignore: typscript doesn't recognise the mock implementation
// @ts-ignore: typescript doesn't recognize 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((cfg: ClientOptions) => {
// eslint-disable-next-line
return createMockClient({ statistics: mockNodes[cfg.url!] });
createClient.mockImplementation((url: keyof typeof mockNodes) => {
return createMockClient({ statistics: mockNodes[url] });
});
const { result } = renderHook(() => useEnvironment(), {
@@ -427,16 +338,17 @@ describe('useEnvironment hook', () => {
...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 () => {
it('has a network error when it cannot fetch the network config and there is no VEGA_URL in the environment', async () => {
act(async () => {
delete process.env['NX_VEGA_URL'];
// @ts-ignore: typscript doesn't recognise the mock implementation
// @ts-ignore: typescript doesn't recognize the mock implementation
global.fetch.mockImplementation(() => {
throw new Error('Cannot fetch');
});
@@ -450,18 +362,19 @@ describe('useEnvironment hook', () => {
...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 () => {
it('logs an error when it cannot fetch the network config and there is a VEGA_URL in the environment', async () => {
act(async () => {
const consoleWarnSpy = jest
.spyOn(console, 'warn')
.mockImplementation(noop);
// @ts-ignore: typscript doesn't recognise the mock implementation
// @ts-ignore: typescript doesn't recognize the mock implementation
global.fetch.mockImplementation(() => {
throw new Error('Cannot fetch');
});
@@ -473,7 +386,6 @@ describe('useEnvironment hook', () => {
await waitFor(() => {
expect(result.current).toEqual({
...mockEnvironmentState,
nodeSwitcherOpen: false,
setNodeSwitcherOpen: result.current.setNodeSwitcherOpen,
});
expect(consoleWarnSpy).toHaveBeenCalledWith(
@@ -484,13 +396,15 @@ describe('useEnvironment hook', () => {
);
});
});
});
// 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 () => {
// 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 () => {
delete process.env['NX_VEGA_URL'];
// @ts-ignore: typscript doesn't recognise the mock implementation
// @ts-ignore: typescript doesn't recognize the mock implementation
global.fetch.mockImplementation(() =>
Promise.resolve({
ok: true,
@@ -507,20 +421,21 @@ describe('useEnvironment hook', () => {
...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 is invalid and there is a VEGA_URL in the environment', 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 in invalid and there is a VEGA_URL in the environment', async () => {
act(async () => {
const consoleWarnSpy = jest
.spyOn(console, 'warn')
.mockImplementation(noop);
// @ts-ignore: typscript doesn't recognise the mock implementation
// @ts-ignore: typescript doesn't recognize the mock implementation
global.fetch.mockImplementation(() =>
Promise.resolve({
ok: true,
@@ -532,8 +447,6 @@ describe('useEnvironment hook', () => {
wrapper: MockWrapper,
});
expect(result.current.configLoading).toBe(true);
await waitFor(() => {
expect(result.current).toEqual({
...mockEnvironmentState,
@@ -547,8 +460,12 @@ describe('useEnvironment hook', () => {
);
});
});
});
it('has a network error when the selected node is not a valid url', 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 selected node is not a valid url', async () => {
act(async () => {
process.env['NX_VEGA_URL'] = 'not-url';
const { result } = renderHook(() => useEnvironment(), {
@@ -558,15 +475,15 @@ describe('useEnvironment hook', () => {
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 () => {
it('has a network error when cannot connect to the selected node', async () => {
act(async () => {
// @ts-ignore allow adding a mock return value to mocked module
createClient.mockImplementation(() => {
return createMockClient({ statistics: { hasError: true } });
@@ -579,14 +496,15 @@ describe('useEnvironment hook', () => {
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 no subscription available', async () => {
it('has a network error when the selected node has not subscription available', async () => {
act(async () => {
// @ts-ignore allow adding a mock return value to mocked module
createClient.mockImplementation(() => {
return createMockClient({ busEvents: { hasError: true } });
@@ -601,7 +519,6 @@ describe('useEnvironment hook', () => {
...mockEnvironmentState,
networkError: ErrorType.SUBSCRIPTION_ERROR,
setNodeSwitcherOpen: result.current.setNodeSwitcherOpen,
nodeSwitcherOpen: true,
});
});
});
+4 -16
View File
@@ -25,7 +25,6 @@ import type {
NodeData,
Configuration,
} from '../types';
import { useNodeHealth } from './use-node-health';
type EnvironmentProviderProps = {
config?: Configuration;
@@ -34,10 +33,7 @@ type EnvironmentProviderProps = {
};
export type EnvironmentState = Environment & {
configLoading: boolean;
networkError?: ErrorType;
blockDifference: number;
nodeSwitcherOpen: boolean;
setNodeSwitcherOpen: () => void;
};
@@ -80,14 +76,10 @@ 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(() => {
@@ -97,10 +89,9 @@ 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: url,
VEGA_URL: nodes[successfulNodeKey].url,
}));
}
}
@@ -148,10 +139,7 @@ export const EnvironmentProvider = ({
<EnvironmentContext.Provider
value={{
...environment,
configLoading: loading,
networkError,
blockDifference,
nodeSwitcherOpen: isNodeSwitcherOpen,
setNodeSwitcherOpen: () => setNodeSwitcherOpen(true),
}}
>
@@ -161,9 +149,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>
@@ -1,117 +0,0 @@
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);
});
});
@@ -1,88 +0,0 @@
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);
};
+1 -5
View File
@@ -74,7 +74,7 @@ const getInitialState = (config?: Configuration) =>
{}
);
export type ClientCollection = Record<
type ClientCollection = Record<
string,
undefined | ReturnType<typeof createClient>
>;
@@ -178,10 +178,6 @@ 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,7 +83,9 @@ const getBundledEnvironmentValue = (key: EnvKey) => {
case 'ETH_WALLET_MNEMONIC':
return process.env['NX_ETH_WALLET_MNEMONIC'];
case 'MAINTENANCE_PAGE':
return process.env['NX_MAINTENANCE_PAGE'];
return (
process.env['MAINTENANCE_PAGE'] || process.env['NX_MAINTENANCE_PAGE']
);
}
};
@@ -108,7 +110,7 @@ export const compileEnvironment = (
const environment = ENV_KEYS.reduce((acc, key) => {
const value = getValue(key, definitions);
if (value !== undefined && value !== null) {
if (value) {
return {
...acc,
[key]: value,
+1 -5
View File
@@ -38,11 +38,7 @@ export const requestNode = (
let subscriptionSucceeded = false;
const client = createClient({
url,
retry: false,
connectToDevTools: false,
});
const client = createClient(url);
// make a query for block height
client

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