Compare commits

..
89 Commits
Author SHA1 Message Date
Alex 73049215dd Add path prefix for header links (manifest, theme-setter, etc.) 2024-03-29 21:48:11 -07:00
Alex cc782b5eae Remove '.git' from .dockerignore; the Vega build process uses git commands ('rev-parse') to navigate 2024-03-29 19:59:16 -07:00
Alessio 703792fbda Add a simple path prefix for static assets 2024-03-29 19:59:16 -07:00
daro-maj 029357b5ab fix(trading): fix market sim tests (#6132) 2024-03-29 10:21:23 +00:00
m.ray 954b4b2755 fix(trading): recurring governance transfers after 0.75.1 (#6122) 2024-03-28 15:50:07 +00:00
Matthew Russell d238662c9d fix(trading,governance): align validator testnet name, add chain config (#6133) 2024-03-28 15:00:01 +00:00
Matthew Russell 88251fae4d fix(trading): check for mark price configuration 2024-03-28 14:24:08 +00:00
m.rayandMatthew Russell 17946ae149 feat(trading): show oracle spec in index price stats (#6113)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2024-03-28 13:09:53 +00:00
Matthew Russell ebb0db3b37 fix(governance): fix market info not showing in proposals (#6104) 2024-03-28 11:09:40 +00:00
Bartłomiej GłowniaandDariusz Majcherczyk df21996707 chore(markets): remove trading mode and type from market query (#6117)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-03-28 11:09:18 +00:00
Matthew Russell bb2464ddc2 fix(markets): use position decimals in notional calculation (#6125) 2024-03-28 11:07:06 +00:00
Bartłomiej Głownia a77bb06b2f feat(deal-ticket): improve stop order rejection handling (#6084) 2024-03-27 16:11:53 +00:00
Art 1e15032c06 fix(trading): remove the apploader from bootstrap (#6121) 2024-03-27 15:51:55 +00:00
Art e28cdb2463 fix(trading): prevent whole app rerender on price bounds and withdrawals (#6118) 2024-03-27 15:50:53 +00:00
Matthew Russell 840ca63464 feat(trading): use notional for candle calcs (#6119) 2024-03-27 13:32:58 +00:00
m.ray 64fc075f7a fix(trading): do not show alias edit on read only (#6111) 2024-03-27 10:11:37 +00:00
m.ray bfa6be4c1c feat(trading): show rank table in dispatch strategy tooltip (#6078) 2024-03-27 10:02:46 +00:00
Ben 5f8c725c5b chore(trading): back merge run market sim on main (#6103) 2024-03-26 13:21:05 +00:00
Matthew Russell 6d4d0d548e fix(trading): add bg color to depth chart labels to clarify numbers (#6096) 2024-03-26 13:19:49 +00:00
Matthew RussellandArt cd4b0e508e chore(trading): back merge hotfixes (#6099)
Co-authored-by: Art <artur@vegaprotocol.io>
2024-03-26 09:59:13 +00:00
Matthew Russell ed9cd3e0dd fix(positions): getColId error (#6100) 2024-03-26 09:57:41 +00:00
Art 91d00a0520 fix(trading): game results (#6083) 2024-03-25 16:25:50 +00:00
Matthew Russell 0ada3a2f8d fix(markets): use quote asset for settlement in spot markets (#6090) 2024-03-25 12:09:18 +00:00
m.ray 2efe0a7aa1 fix(trading): align checkboxes tp/sl (#6088) 2024-03-22 17:21:39 +00:00
Matthew Russell 678ba9c4f7 fix(deal-ticket): broken test 2024-03-22 17:09:58 +00:00
Matthew Russell 4d566f5a5f fix(types): duplicate deposit status mapping 2024-03-22 17:07:39 +00:00
m.ray f13307aa77 feat(trading): align deal ticket checkboxes (#6082) 2024-03-22 16:54:45 +00:00
Art aed45e419d chore(trading): date format on team profile page (#6071) 2024-03-22 13:36:25 +00:00
Artandbwallacee 01e87443ee fix(positions): rework of the liquidation tooltip (#6070)
Co-authored-by: bwallacee <ben@vega.xyz>
2024-03-22 13:36:05 +00:00
Matthew Russell 891e0d3d2f fix(trading): wrong team image showing (#6081) 2024-03-22 13:35:29 +00:00
m.ray dc81334aec feat(trading): games container search bar (#6077) 2024-03-22 13:34:40 +00:00
Bartłomiej Głownia 0a70c0ea49 feat(deal-ticket): use order price if markPrice is not available in useMaxSize (#6057) 2024-03-22 12:21:00 +01:00
Ben 05debbd777 chore(trading): tests for market to auction (#6073) 2024-03-21 15:18:48 +00:00
Matthew Russell 8c383d8756 fix(markets): make trading app spot compatible (#6067) 2024-03-21 14:32:22 +00:00
Ben 3e61018d89 chore(trading): update teams tests (#6069) 2024-03-21 13:04:13 +00:00
Ben 77b1306f25 chore(trading): capped rewards e2e test (#6066) 2024-03-21 09:53:27 +00:00
daro-maj ff3cddc21b chore(trading): update the vega version for sim tests 75.1 (#6065) 2024-03-21 10:52:42 +01:00
daro-maj 1d2ca52a25 chore(trading): test filtered future cards (#6029) 2024-03-21 07:42:09 +00:00
ArtandDariusz Majcherczyk 62ecaaa9ce feat(trading): team profile improvements (#6054)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-03-20 18:17:05 +01:00
Bartłomiej Głownia 204871b81c feat(deal-ticket): add useMaxSize unit tests (#6053) 2024-03-20 15:25:30 +01:00
m.ray ecdd917977 fix(trading): do not show tick or cross for rewards that will start in the future (#6052) 2024-03-20 13:10:35 +00:00
Matthew Russell c0396db2b1 fix(governance): withdrawals dissapearing when the withdraw dialog is opened (#6045) 2024-03-19 15:33:08 +00:00
m.ray 1fd1013353 feat(trading): rewards container tweaks (#6037) 2024-03-19 12:53:03 +00:00
Bartłomiej Głownia e389579537 feat(deal-ticket): add size slider (#6006) 2024-03-19 12:49:31 +01:00
m.ray 74f0f7bb3d feat(trading): show inactive rewards (#6031) 2024-03-19 11:29:48 +00:00
Edd 3e78d55c0e feat(emblem): create example emblem component (#6013) 2024-03-19 11:10:21 +00:00
Matthew Russell 1df8ba0972 chore(trading): move meta tag to remove nextjs warning (#6030) 2024-03-19 11:04:56 +00:00
daro-maj e515928d87 chore(trading): update sim-tests v0.75.0-preview.8 (#6025) 2024-03-18 14:25:39 +00:00
Matthew Russell 2928181884 fix(governance): hard coded default chain in vega wallet (#6026) 2024-03-18 11:45:46 +00:00
Edd f6f3fb43eb fix(explorer): fix party filter persistence (#6016) 2024-03-15 16:59:45 +00:00
Edd 1511772849 fix(explorer): fix lp tx filters (#6015) 2024-03-15 16:59:36 +00:00
Edd ab6c67fd37 fix(markets): fix link to explorer oracle (#6020) 2024-03-15 16:59:01 +00:00
Edd f4212724d0 feat(explorer): add full details to oracle page (#6005) 2024-03-15 16:43:57 +00:00
m.rayandDariusz Majcherczyk 7b162104b6 feat(trading): eligibility active rewards (#6010)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-03-15 16:41:48 +00:00
ArtandDariusz Majcherczyk 88f99031d1 feat(markets): new price monitoring bounds panel (#5996)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-03-15 16:22:58 +00:00
Matthew Russell 72df08851c chore(trading): fix ssr loader server mismatch, remove preload (#6018) 2024-03-15 16:00:04 +00:00
Matthew Russell d9292b9be2 fix(markets,explorer): fallback to ethereum mainnet chain Id if sourceChain is not provided (#6014) 2024-03-15 11:10:54 +00:00
m.ray be8de04686 feat(trading): add cap reward fee multiple on the reward cards (#5999) 2024-03-14 16:42:23 +00:00
Matthew Russell bd14c2a39a chore(trading,governance,explorer): remove theme-lite and tailwind warning (#6011) 2024-03-14 16:27:10 +00:00
Matthew Russell 8e2ec8ab67 chore(trading): add missing mock for node health (#6008) 2024-03-14 16:26:37 +00:00
Bartłomiej Głownia 0636d19b0d feat(deal-ticket): change min and max leverage (#5998) 2024-03-14 16:09:32 +00:00
Matthew Russell 0dd58b68cf fix(deal-ticket): dont trigger deal ticket submit when clicking margin (#6004) 2024-03-14 13:43:22 +00:00
Matthew Russell 49fdd0f68a fix(trading,wallet): improve errors from browser wallet (#5995) 2024-03-14 10:36:39 +00:00
Bartłomiej Głownia 3e2e3c3970 feat(trading): Changes to market info for mark price, funding, and settlement (#5967) 2024-03-13 18:52:09 +01:00
m.ray b89523efb2 feat(trading): update validation and rules on take profit and stop loss (#5987) 2024-03-13 17:43:18 +00:00
Edd be9a5a3437 feat(explorer): add market specific oracle page (#5986) 2024-03-13 17:37:58 +00:00
Bartłomiej Głownia 475b52bcaf feat(positions): do not show liquidation price if position openVolume is 0 (#5990) 2024-03-13 16:31:38 +01:00
Ben 7620f0b67d chore(trading): refactor order status test (#5991) 2024-03-13 15:10:33 +01:00
Matthew Russell 0f5b712034 fix(trading): remove invalid candles from price change calc (#5983) (#5984) 2024-03-13 13:34:22 +00:00
Matthew Russell 424061d64c fix(trading): tick size backwards compatibility (#5988) 2024-03-13 11:31:01 +00:00
Edd a49f5f0dd1 feat(explorer): fees on transfer component (#5907) 2024-03-13 11:28:04 +00:00
Edd bf094288fd feat(explorer): add epoch to block pages (#5817) 2024-03-13 11:23:54 +00:00
Ben bc8a427788 chore(trading): fix poetry lock file (#5989) 2024-03-13 09:05:12 +00:00
Ben 447dc254d7 chore(trading): point to main market-sim branch (#5982) 2024-03-12 15:12:16 +00:00
Matthew Russell cebbdb4ee7 Merge branch 'main' into develop 2024-03-12 14:08:50 +00:00
Matthew Russell fd69f6078c fix(trading): incorrect top gaining and top losing order (#5974) 2024-03-12 14:58:13 +01:00
Matthew Russell 917bdde0bb fix(trading): external explorer link (#5973) 2024-03-12 13:56:55 +00:00
Matthew Russell 2153367258 fix(trading): key details panel throwing error on batch created market (#5980) 2024-03-12 13:56:18 +00:00
Art c0942d56a1 fix(trading): crashing tooltip, enactment date from batch proposal (#5979) 2024-03-12 13:48:44 +00:00
Ben e57a6de765 chore(trading): fix volume e2e test (#5978) 2024-03-12 12:52:48 +00:00
Ben 9eee76e5a6 chore(trading): refactor test fixtures (#5969) 2024-03-12 12:52:12 +00:00
Matthew Russell aa71e608f0 fix(trading): proposed markets table (#5958) 2024-03-12 11:59:48 +00:00
Matthew Russell 20ad18c8a0 chore(governance): remove greenfield from logo map (#5960) 2024-03-12 11:12:03 +00:00
Art 457422b5ac chore(governance): lp votes for batch proposal (#5965) 2024-03-12 11:11:51 +00:00
Ben 990c894e2f chore(trading): update vega version to 75 preview 4 (#5968) 2024-03-11 14:15:14 +00:00
Matthew Russell 2e28a175ce chore(wallet): reduce bundle size by removing unnecessary deps (#5955) 2024-03-11 14:07:45 +00:00
Ben a77858c1c0 chore(trading): e2e test for tp and sl (#5962) 2024-03-11 13:52:50 +00:00
Matthew Russell 05b39e2c08 fix(trading): usdt approvals (#5939) 2024-03-07 14:21:31 +00:00
Matthew Russell 654dd1e7b0 fix(trading): stored state causing wrong chart data (#5928) 2024-03-05 18:17:18 +00:00
291 changed files with 8398 additions and 3728 deletions
+16 -7
View File
@@ -10,7 +10,7 @@ on:
inputs:
console-test-branch:
type: choice
description: 'main: v0.73.13, develop: v0.74.0'
description: 'main: v0.74.10, develop: v0.75.5'
options:
- main
- develop
@@ -57,15 +57,14 @@ jobs:
#----------------------------------------------
- name: Build trading app
run: |
yarn env-cmd -f ./apps/trading/.env.stagnet1 yarn nx export trading
ENV_NAME="${{ needs.console-test-branch.outputs.console-branch == 'main' && 'mainnet' || 'stagnet1' }}"
yarn env-cmd -f ./apps/trading/.env.$ENV_NAME yarn nx export trading
DIST_LOCATION=dist/apps/trading/exported
mv $DIST_LOCATION dist-result
tree dist-result
#----------------------------------------------
# export trading app docker image
#----------------------------------------------
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@@ -78,7 +77,7 @@ jobs:
load: true
build-args: |
APP=trading
ENV_NAME=stagnet1
ENV_NAME=${{ needs.console-test-branch.outputs.console-branch == 'main' && 'mainnet' || 'stagnet1' }}
tags: ci/trading:local
outputs: type=docker,dest=/tmp/console-image.tar
@@ -182,12 +181,22 @@ jobs:
virtualenvs-create: true
virtualenvs-in-project: true
virtualenvs-path: .venv
#----------------------------------------------
# Set up pyproject.toml based on branch
#----------------------------------------------
- name: Create pyproject.toml based on branch
run: |
if [ "${{ needs.console-test-branch.outputs.console-branch }}" = "main" ]; then
mv pyproject.main.toml pyproject.toml
elif [ "${{ needs.console-test-branch.outputs.console-branch }}" = "develop" ]; then
mv pyproject.develop.toml pyproject.toml
fi
working-directory: apps/trading/e2e
#----------------------------------------------
# install python dependencies
#----------------------------------------------
- name: Install dependencies
run: poetry install --no-interaction --no-root
run: poetry lock && poetry install --no-interaction --no-root
working-directory: apps/trading/e2e
#----------------------------------------------
# install vega binaries
+1
View File
@@ -58,5 +58,6 @@ __pycache__/
apps/trading/e2e/logs/
apps/trading/e2e/.pytest_cache/
apps/trading/e2e/traces/
apps/trading/e2e/pyproject.toml
.nx/
+1 -1
View File
@@ -1,5 +1,5 @@
# App configuration variables
NX_VEGA_ENV=VALIDATOR_TESTNET
NX_VEGA_ENV=VALIDATORS_TESTNET
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml
NX_VEGA_URL=https://api-validators-testnet.vega.rocks/graphql
NX_VEGA_REST=https://api-validators-testnet.vega.rocks/
@@ -8,12 +8,14 @@ import EpochMissingOverview from './epoch-missing';
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
import type { IconProps } from '@vegaprotocol/ui-toolkit';
import isPast from 'date-fns/isPast';
import { EpochSymbol } from '../links/block-link/block-link';
const borderClass =
'border-solid border-2 border-vega-dark-200 border-collapse';
export type EpochOverviewProps = {
id?: string;
icon?: boolean;
};
/**
@@ -24,7 +26,7 @@ export type EpochOverviewProps = {
*
* The details are hidden in a tooltip, behind the epoch number
*/
const EpochOverview = ({ id }: EpochOverviewProps) => {
const EpochOverview = ({ id, icon = true }: EpochOverviewProps) => {
const { data, error, loading } = useExplorerEpochQuery({
variables: { id: id || '' },
});
@@ -38,7 +40,12 @@ const EpochOverview = ({ id }: EpochOverviewProps) => {
}
if (!ti || loading || error) {
return <span>{id}</span>;
return (
<span>
<EpochSymbol />
{id}
</span>
);
}
const description = (
@@ -90,7 +97,11 @@ const EpochOverview = ({ id }: EpochOverviewProps) => {
return (
<Tooltip description={description}>
<p>
<IconForEpoch start={ti.start} end={ti.end} />
{icon ? (
<IconForEpoch start={ti.start} end={ti.end} />
) : (
<EpochSymbol />
)}
{id}
</p>
</Tooltip>
@@ -0,0 +1,10 @@
query ExplorerEpochForBlock($block: String!) {
epoch(block: $block) {
id
timestamps {
start
end
lastBlock
}
}
}
@@ -0,0 +1,53 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerEpochForBlockQueryVariables = Types.Exact<{
block: Types.Scalars['String'];
}>;
export type ExplorerEpochForBlockQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: any | null, end?: any | null, lastBlock?: string | null } } };
export const ExplorerEpochForBlockDocument = gql`
query ExplorerEpochForBlock($block: String!) {
epoch(block: $block) {
id
timestamps {
start
end
lastBlock
}
}
}
`;
/**
* __useExplorerEpochForBlockQuery__
*
* To run a query within a React component, call `useExplorerEpochForBlockQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerEpochForBlockQuery` 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 } = useExplorerEpochForBlockQuery({
* variables: {
* block: // value for 'block'
* },
* });
*/
export function useExplorerEpochForBlockQuery(baseOptions: Apollo.QueryHookOptions<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>(ExplorerEpochForBlockDocument, options);
}
export function useExplorerEpochForBlockLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>(ExplorerEpochForBlockDocument, options);
}
export type ExplorerEpochForBlockQueryHookResult = ReturnType<typeof useExplorerEpochForBlockQuery>;
export type ExplorerEpochForBlockLazyQueryHookResult = ReturnType<typeof useExplorerEpochForBlockLazyQuery>;
export type ExplorerEpochForBlockQueryResult = Apollo.QueryResult<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>;
@@ -4,17 +4,56 @@ import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
import { useExplorerEpochForBlockQuery } from './__generated__/EpochByBlock';
import { t } from '@vegaprotocol/i18n';
export type BlockLinkProps = Partial<ComponentProps<typeof Link>> & {
height: string;
showEpoch?: boolean;
};
const BlockLink = ({ height, ...props }: BlockLinkProps) => {
const BlockLink = ({ height, showEpoch = false, ...props }: BlockLinkProps) => {
return (
<Link className="underline" {...props} to={`/${Routes.BLOCKS}/${height}`}>
<Hash text={height} />
</Link>
<>
<Link className="underline" {...props} to={`/${Routes.BLOCKS}/${height}`}>
<Hash text={height} />
</Link>
{showEpoch && <EpochForBlock block={height} />}
</>
);
};
export function EpochForBlock(props: { block: string }) {
const { error, data, loading } = useExplorerEpochForBlockQuery({
errorPolicy: 'ignore',
variables: { block: props.block },
});
// NOTE: 0.73.x & <0.74.2 can error showing epoch, so for now we hide loading
// or error states and only display if we get usable data
if (error || loading || !data) {
return null;
}
return (
<span className="ml-2" title={t('Epoch')}>
<EpochSymbol />
{data.epoch.id}
</span>
);
}
export const EPOCH_SYMBOL = 'ⓔ';
export function EpochSymbol() {
return (
<em
title={t('Epoch')}
className="mr-1 cursor-default text-xl leading-none align-text-bottom not-italic"
>
{EPOCH_SYMBOL}
</em>
);
}
export default BlockLink;
@@ -1,5 +1,8 @@
import type { ChainIdMapping } from './external-chain';
import { SUPPORTED_CHAIN_IDS, SUPPORTED_CHAIN_LABELS } from './external-chain';
import type { ChainIdMapping } from '@vegaprotocol/environment';
import {
SUPPORTED_CHAIN_IDS,
SUPPORTED_CHAIN_LABELS,
} from '@vegaprotocol/environment';
export const SUPPORTED_CHAIN_ICON_URLS: ChainIdMapping = {
'1': '/assets/chain-eth-logo.svg',
@@ -1,5 +1,5 @@
import Hash from '../hash';
import { getExternalExplorerLink } from './external-chain';
import { getExternalExplorerLink } from '@vegaprotocol/environment';
import { ExternalChainIcon } from './external-chain-icon';
export enum EthExplorerLinkTypes {
@@ -23,7 +23,7 @@ export const ExternalExplorerLink = ({
code = false,
...props
}: ExternalExplorerLinkProps) => {
const link = `${getExternalExplorerLink(chain, type)}/${type}/${id}${
const link = `${getExternalExplorerLink(chain)}/${type}/${id}${
code ? '#code' : ''
}`;
return (
@@ -0,0 +1,16 @@
import { render, screen } from '@testing-library/react';
import GovernanceLink from './governance-link';
describe('GovernanceLink', () => {
it('renders the link with the correct text', () => {
render(<GovernanceLink text="Governance internet website" />);
const linkElement = screen.getByText('Governance internet website');
expect(linkElement).toBeInTheDocument();
});
it('renders the link with the correct href and sensible default text', () => {
render(<GovernanceLink />);
const linkElement = screen.getByText('Governance');
expect(linkElement).toBeInTheDocument();
});
});
@@ -0,0 +1,18 @@
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { ENV } from '../../../config/env';
import { t } from '@vegaprotocol/i18n';
export type GovernanceLinkProps = {
text?: string;
};
/**
* Just a link to the governance page, with optional text
*/
const GovernanceLink = ({ text = t('Governance') }: GovernanceLinkProps) => {
const base = ENV.dataSources.governanceUrl;
return <ExternalLink href={base}>{text}</ExternalLink>;
};
export default GovernanceLink;
@@ -6,6 +6,7 @@ import {
LiquiditySLAParametersInfoPanel,
MarginScalingFactorsPanel,
PriceMonitoringBoundsInfoPanel,
PriceMonitoringSettingsInfoPanel,
SuccessionLineInfoPanel,
getDataSourceSpecForSettlementData,
getDataSourceSpecForTradingTermination,
@@ -21,7 +22,6 @@ import {
RiskModelInfoPanel,
SettlementAssetInfoPanel,
} from '@vegaprotocol/markets';
import { MarketInfoTable } from '@vegaprotocol/markets';
import type { DataSourceFragment } from '@vegaprotocol/markets';
import isEqual from 'lodash/isEqual';
@@ -74,27 +74,14 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
<MarginScalingFactorsPanel market={market} />
<h2 className={headerClassName}>{t('Risk factors')}</h2>
<RiskFactorsInfoPanel market={market} />
{(market.data?.priceMonitoringBounds || []).map((trigger, i) => (
<>
<h2 className={headerClassName}>
{t('Price monitoring bounds %s', [(i + 1).toString()])}
</h2>
<PriceMonitoringBoundsInfoPanel
market={market}
triggerIndex={i + 1}
/>
</>
))}
{(market.priceMonitoringSettings?.parameters?.triggers || []).map(
(trigger, i) => (
<>
<h2 className={headerClassName}>
{t('Price monitoring settings %s', [(i + 1).toString()])}
</h2>
<MarketInfoTable data={trigger} key={i} />
</>
)
)}
<h2 className={headerClassName}>{t('Price monitoring bounds')}</h2>
<div className="mt-3">
<PriceMonitoringBoundsInfoPanel market={market} />
</div>
<h2 className={headerClassName}>{t('Price monitoring settings')}</h2>
<div className="mt-3">
<PriceMonitoringSettingsInfoPanel market={market} />
</div>
<h2 className={headerClassName}>{t('Liquidation strategy')}</h2>
<LiquidationStrategyInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity monitoring')}</h2>
@@ -1,5 +1,5 @@
import { useMemo } from 'react';
import { getAsset, type MarketFieldsFragment } from '@vegaprotocol/markets';
import { getAsset, type MarketMaybeWithData } from '@vegaprotocol/markets';
import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import { type AgGridReact } from 'ag-grid-react';
@@ -17,7 +17,7 @@ import { type RowClickedEvent } from 'ag-grid-community';
import { Link, useNavigate } from 'react-router-dom';
type MarketsTableProps = {
data: MarketFieldsFragment[] | null;
data: MarketMaybeWithData[] | null;
};
export const MarketsTable = ({ data }: MarketsTableProps) => {
const openAssetDetailsDialog = useAssetDetailsDialogStore(
@@ -56,10 +56,10 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
headerName: t('Status'),
field: 'state',
hide: window.innerWidth <= BREAKPOINT_MD,
valueGetter: ({
data,
}: VegaValueGetterParams<MarketFieldsFragment>) => {
return data?.state ? MarketStateMapping[data?.state] : '-';
valueGetter: ({ data }: VegaValueGetterParams<MarketMaybeWithData>) => {
return data?.data?.marketState
? MarketStateMapping[data?.data.marketState]
: '-';
},
},
{
@@ -70,7 +70,7 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
cellRenderer: ({
data,
}: VegaICellRendererParams<
MarketFieldsFragment,
MarketMaybeWithData,
'tradableInstrument.instrument.product.settlementAsset.symbol'
>) => {
const value = data && getAsset(data);
@@ -99,7 +99,7 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
field: 'id',
cellRenderer: ({
value,
}: VegaICellRendererParams<MarketFieldsFragment, 'id'>) =>
}: VegaICellRendererParams<MarketMaybeWithData, 'id'>) =>
value ? (
<Link className="underline" to={value}>
{t('View details')}
@@ -116,7 +116,7 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
<AgGrid
ref={gridRef}
rowData={data}
getRowId={({ data }: { data: MarketFieldsFragment }) => data.id}
getRowId={({ data }: { data: MarketMaybeWithData }) => data.id}
overlayNoRowsTemplate={t('This chain has no markets')}
domLayout="autoHeight"
defaultColDef={{
@@ -0,0 +1,159 @@
import compact from 'lodash/compact';
import { MarketLink } from '../links';
import { type MarketState, MarketStateMapping } from '@vegaprotocol/types';
import OracleLink from '../links/oracle-link/oracle-link';
import type {
ExplorerOracleForMarketQuery,
ExplorerOracleFormMarketsQuery,
} from '../../routes/oracles/__generated__/OraclesForMarkets';
import { useState } from 'react';
export type OraclesTableProps = {
data?: ExplorerOracleFormMarketsQuery | ExplorerOracleForMarketQuery;
};
const cellSpacing = 'px-3';
export function OraclesTable({ data }: OraclesTableProps) {
const [hoveredOracle, setHoveredOracle] = useState('');
return (
<table className="text-left">
<thead>
<tr>
<th className={cellSpacing}>Market</th>
<th className={cellSpacing}>Type</th>
<th className={cellSpacing}>State</th>
<th className={cellSpacing}>Settlement</th>
<th className={cellSpacing}>Termination</th>
</tr>
</thead>
<tbody>
{data?.marketsConnection?.edges
? data.marketsConnection.edges.map((o) => {
let hasSeenOracleReports = false;
let settlementOracle = '-';
let settlementOracleStatus = '-';
let terminationOracle = '-';
let terminationOracleStatus = '-';
const id = o?.node.id;
if (!id) {
return null;
}
if (
o.node.tradableInstrument.instrument.product.__typename ===
'Future'
) {
settlementOracle =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.id;
terminationOracle =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForTradingTermination.id;
settlementOracleStatus =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.status;
terminationOracleStatus =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForTradingTermination.status;
} else if (
o.node.tradableInstrument.instrument.product.__typename ===
'Perpetual'
) {
settlementOracle =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.id;
terminationOracle =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementSchedule.id;
settlementOracleStatus =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.status;
terminationOracleStatus =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementSchedule.status;
}
const oracleInformationUnfiltered =
data?.oracleSpecsConnection?.edges?.map((e) =>
e && e.node ? e.node : undefined
) || [];
const oracleInformation = compact(oracleInformationUnfiltered)
.filter(
(o) =>
o.dataConnection.edges &&
o.dataConnection.edges.length > 0 &&
(o.dataSourceSpec.spec.id === settlementOracle ||
o.dataSourceSpec.spec.id === terminationOracle)
)
.at(0);
if (oracleInformation) {
hasSeenOracleReports = true;
}
const oracleList = `${settlementOracle} ${terminationOracle}`;
return (
<tr
id={id}
key={id}
className={
hoveredOracle.length > 0 &&
oracleList.indexOf(hoveredOracle) > -1
? 'bg-gray-100 dark:bg-gray-800'
: ''
}
data-testid="oracle-details"
data-oracles={oracleList}
>
<td className={cellSpacing}>
<MarketLink id={id} />
</td>
<td className={cellSpacing}>
{o.node.tradableInstrument.instrument.product.__typename}
</td>
<td className={cellSpacing}>
{MarketStateMapping[o.node.state as MarketState]}
</td>
<td
className={
hoveredOracle.length > 0 &&
hoveredOracle === settlementOracle
? `indent-1 ${cellSpacing}`
: cellSpacing
}
>
<OracleLink
id={settlementOracle}
status={settlementOracleStatus}
hasSeenOracleReports={hasSeenOracleReports}
onMouseOver={() => setHoveredOracle(settlementOracle)}
onMouseOut={() => setHoveredOracle('')}
/>
</td>
<td
className={
hoveredOracle.length > 0 &&
hoveredOracle === terminationOracle
? `indent-1 ${cellSpacing}`
: cellSpacing
}
>
<OracleLink
id={terminationOracle}
status={terminationOracleStatus}
hasSeenOracleReports={hasSeenOracleReports}
onMouseOver={() => setHoveredOracle(terminationOracle)}
onMouseOut={() => setHoveredOracle('')}
/>
</td>
</tr>
);
})
: null}
</tbody>
</table>
);
}
@@ -33,10 +33,10 @@ const SizeInAsset = ({
}
return (
<p>
<span>
<span>{label}</span>&nbsp;
<AssetLink assetId={assetId} showAssetSymbol={true} asDialog={true} />
</p>
</span>
);
};
@@ -4,7 +4,7 @@ import {
ExternalExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/external-explorer-link/external-explorer-link';
import { getExternalChainLabel } from '../../../links/external-explorer-link/external-chain';
import { getExternalChainLabel } from '@vegaprotocol/environment';
import type { components } from '../../../../../types/explorer';
import { defaultAbiCoder, base64 } from 'ethers/lib/utils';
import { BigNumber } from 'ethers';
@@ -10,6 +10,8 @@ import { ChainResponseCode } from '../chain-response-code/chain-reponse.code';
import { TxDataView } from '../../tx-data-view';
import Hash from '../../../links/hash';
import { Signature } from '../../../signature/signature';
import { useExplorerEpochForBlockQuery } from '../../../links/block-link/__generated__/EpochByBlock';
import EpochOverview from '../../../epoch-overview/epoch';
interface TxDetailsSharedProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -44,6 +46,11 @@ export const TxDetailsShared = ({
blockData,
hideTypeRow = false,
}: TxDetailsSharedProps) => {
const { data } = useExplorerEpochForBlockQuery({
errorPolicy: 'ignore',
variables: { block: txData?.block?.toString() || '' },
});
if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
@@ -74,7 +81,7 @@ export const TxDetailsShared = ({
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Block')}</TableCell>
<TableCell>
<BlockLink height={height} />
<BlockLink height={height} showEpoch={false} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@@ -83,6 +90,7 @@ export const TxDetailsShared = ({
<Signature signature={txData.signature} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Time')}</TableCell>
<TableCell>
@@ -100,6 +108,14 @@ export const TxDetailsShared = ({
)}
</TableCell>
</TableRow>
{data && data.epoch && (
<TableRow modifier="bordered">
<TableCell scope="row">{t('Epoch')}</TableCell>
<TableCell modifier="bordered">
<EpochOverview id={data.epoch.id} icon={false} />
</TableCell>
</TableRow>
)}
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Response code')}</TableCell>
<TableCell>
@@ -1,5 +1,9 @@
query ExplorerTransferStatus($id: ID!) {
transfer(id: $id) {
fees {
amount
epoch
}
transfer {
reference
timestamp
@@ -8,12 +8,16 @@ export type ExplorerTransferStatusQueryVariables = Types.Exact<{
}>;
export type ExplorerTransferStatusQuery = { __typename?: 'Query', transfer?: { __typename?: 'TransferNode', transfer: { __typename?: 'Transfer', reference?: string | null, timestamp: any, status: Types.TransferStatus, reason?: string | null, fromAccountType: Types.AccountType, from: string, to: string, toAccountType: Types.AccountType, amount: string, asset?: { __typename?: 'Asset', id: string } | null } } | null };
export type ExplorerTransferStatusQuery = { __typename?: 'Query', transfer?: { __typename?: 'TransferNode', fees?: Array<{ __typename?: 'TransferFee', amount: string, epoch: number } | null> | null, transfer: { __typename?: 'Transfer', reference?: string | null, timestamp: any, status: Types.TransferStatus, reason?: string | null, fromAccountType: Types.AccountType, from: string, to: string, toAccountType: Types.AccountType, amount: string, asset?: { __typename?: 'Asset', id: string } | null } } | null };
export const ExplorerTransferStatusDocument = gql`
query ExplorerTransferStatus($id: ID!) {
transfer(id: $id) {
fees {
amount
epoch
}
transfer {
reference
timestamp
@@ -44,8 +44,14 @@ const AccountType: Record<AccountTypes, string> = {
ACCOUNT_TYPE_ORDER_MARGIN: 'Order Margin',
};
export type TransferFee = {
amount?: string;
epoch?: number;
};
interface TransferParticipantsProps {
transfer: Transfer;
fees?: TransferFee[] | null;
from: string;
}
@@ -60,6 +66,7 @@ interface TransferParticipantsProps {
export function TransferParticipants({
transfer,
from,
fees,
}: TransferParticipantsProps) {
// This mapping is required as the global account types require a type to be set, while
// the underlying protobufs allow for every field to be undefined.
@@ -104,6 +111,9 @@ export function TransferParticipants({
{transfer.asset ? (
<SizeInAsset assetId={transfer.asset} size={transfer.amount} />
) : null}
{transfer.asset && fees && (
<TransferFees assetId={transfer.asset} fees={fees} />
)}
</div>
{/* Empty divs for the top arrow and the bottom arrow of the transfer inset */}
@@ -125,10 +135,6 @@ export function TransferParticipants({
<path d="M0,0L8,9l8,-9Z" />
</svg>
</div>
{/*
<div className="z-10 absolute top-0 left-1/2 transform -translate-x-1/2 -translate-y-1/2 rotate-45 w-4 h-4 dark:border-vega-dark-200 border-vega-light-200 bg-white dark:bg-black border-r border-b"></div>
<div className="z-10 absolute bottom-0 left-1/2 transform -translate-x-1/2 translate-y-1/2 rotate-45 w-4 h-4 border-vega-light-200 dark:border-vega-dark-200 bg-vega-light-200 dark:bg-vega-dark-200 border-r border-b"></div>
*/}
</div>
</div>
@@ -169,3 +175,38 @@ export function TransferRecurringRecipient({
// Fallback should not happen
return null;
}
export function TransferFees({
assetId,
fees,
}: {
assetId: string;
fees: TransferFee[];
}) {
// A recurring transfer that is rejected or cancelled will have an array of fees of 0 length
if (assetId && fees && fees.length > 0) {
if (fees.length === 1) {
return (
<p className="mt-2">
Fee: <SizeInAsset assetId={assetId} size={fees[0].amount} />
</p>
);
} else {
return (
<details className="cursor-pointer mt-2">
<summary>{t('Fees')}</summary>
<ul>
{fees.map((fee) => (
<li className="text-nowrap leading-normal">
<SizeInAsset assetId={assetId} size={fee.amount} />{' '}
{t('in epoch')} {fee.epoch}
</li>
))}
</ul>
</details>
);
}
}
return null;
}
@@ -41,9 +41,16 @@ export function TransferDetails({ transfer, from, id }: TransferDetailsProps) {
? TransferStatus.STATUS_REJECTED
: data?.transfer?.transfer.status;
const fees = data?.transfer?.fees?.map((fee) => {
return {
amount: fee?.amount ? fee.amount : '0',
epoch: fee?.epoch ? fee.epoch : 0,
};
});
return (
<div className="flex flex-wrap">
<TransferParticipants from={from} transfer={transfer} />
<TransferParticipants from={from} transfer={transfer} fees={fees} />
{recurring ? <TransferRepeat recurring={transfer.recurring} /> : null}
<TransferStatusView status={status} error={error} loading={loading} />
{recurring && recurring.dispatchStrategy ? (
@@ -113,13 +113,16 @@ export const TxDetailsTransfer = ({
/**
* Gets a string description of this transfer
* @param txData A full transfer
* @param tx A full transfer
* @returns string Transfer label
*/
export function getTypeLabelForTransfer(tx: Transfer) {
if (tx.to === SPECIAL_CASE_NETWORK || tx.to === SPECIAL_CASE_NETWORK_ID) {
if (tx.toAccountType === 'ACCOUNT_TYPE_NETWORK_TREASURY') {
return 'Treasury transfer';
}
if (tx.recurring && tx.recurring.dispatchStrategy) {
return 'Reward top up transfer';
return 'Reward transfer';
}
// Else: we don't know that it's a reward transfer, so let's not guess
} else if (tx.recurring) {
@@ -16,12 +16,12 @@ import { FilterLabel } from './tx-filter-label';
// All possible transaction types. Should be generated.
export type FilterOption =
| 'Amend LiquidityProvision Order'
| 'Amend Liquidity Provision Order'
| 'Amend Order'
| 'Apply Referral Code'
| 'Batch Market Instructions'
| 'Batch Proposal'
| 'Cancel LiquidityProvision Order'
| 'Cancel Liquidity Provision Order'
| 'Cancel Order'
| 'Cancel Transfer Funds'
| 'Chain Event'
@@ -53,10 +53,10 @@ export type FilterOption =
export const filterOptions: Record<string, FilterOption[]> = {
'Market Instructions': [
'Amend LiquidityProvision Order',
'Amend Liquidity Provision Order',
'Amend Order',
'Batch Market Instructions',
'Cancel LiquidityProvision Order',
'Cancel Liquidity Provision Order',
'Cancel Order',
'Liquidity Provision Order',
'Stop Orders Submission',
@@ -2,6 +2,7 @@ import { t } from '@vegaprotocol/i18n';
import type { components } from '../../../types/explorer';
import { VoteIcon } from '../vote-icon/vote-icon';
import { ExternalChainIcon } from '../links/external-explorer-link/external-chain-icon';
import { getTypeLabelForTransfer } from './details/tx-transfer';
interface TxOrderTypeProps {
orderType: string;
@@ -95,7 +96,7 @@ export function getLabelForOrderType(
/**
* Given a proposal, will return a specific label
* @param chainEvent
* @param proposal
* @returns
*/
export function getLabelForProposal(
@@ -142,6 +143,36 @@ export function getLabelForProposal(
}
}
type label = {
type: string;
colours: string;
};
export function getLabelForTransfer(
transfer: components['schemas']['commandsv1Transfer']
): label {
const type = getTypeLabelForTransfer(transfer);
if (transfer.toAccountType === 'ACCOUNT_TYPE_NETWORK_TREASURY') {
return {
type,
colours:
'text-vega-green dark:text-green bg-vega-dark-150 dark:bg-vega-dark-250',
};
} else if (transfer.recurring) {
return {
type,
colours:
'text-vega-yellow dark:text-yellow bg-vega-dark-150 dark:bg-vega-dark-250',
};
}
return {
type,
colours:
'text-white dark:text-white bg-vega-dark-150 dark:bg-vega-dark-250',
};
}
/**
* Given a chain event, will try to provide a more useful label
* @param chainEvent
@@ -225,9 +256,10 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
if (type === 'Chain Event' && !!command?.chainEvent) {
type = getLabelForChainEvent(command.chainEvent);
colours = 'text-white dark-text-white bg-vega-pink dark:bg-vega-pink';
} else if (type === 'Validator Heartbeat') {
colours =
'text-white dark-text-white bg-vega-light-200 dark:bg-vega-dark-100';
} else if (type === 'Transfer Funds' && command?.transfer) {
const res = getLabelForTransfer(command.transfer);
type = res.type;
colours = res.colours;
} else if (type === 'Proposal' || type === 'Governance Proposal') {
if (command && !!command.proposalSubmission) {
type = getLabelForProposal(command.proposalSubmission);
@@ -21,7 +21,7 @@ describe('TX: Transfer: getLabelForTransfer', () => {
},
};
expect(getTypeLabelForTransfer(mock)).toEqual('Reward top up transfer');
expect(getTypeLabelForTransfer(mock)).toEqual('Reward transfer');
});
it('renders reward top up label if the TO party is network', () => {
@@ -32,7 +32,7 @@ describe('TX: Transfer: getLabelForTransfer', () => {
},
};
expect(getTypeLabelForTransfer(mock)).toEqual('Reward top up transfer');
expect(getTypeLabelForTransfer(mock)).toEqual('Reward transfer');
});
it('renders recurring label if the tx has a recurring property', () => {
@@ -81,6 +81,7 @@ describe('TxDetailsTransfer', () => {
hash: 'test',
submitter:
'e1943eea46fed576cf2be42972f3c5515ad3d0ac7ac013f56677c12a53a1b3ed',
block: '100',
command: {
nonce: '5188810881378065222',
blockHeight: '14951513',
@@ -16,6 +16,8 @@ import { useBlockInfo } from '@vegaprotocol/tendermint';
import { NodeLink } from '../../../components/links';
import { useDocumentTitle } from '../../../hooks/use-document-title';
import EmptyList from '../../../components/empty-list/empty-list';
import { useExplorerEpochForBlockQuery } from '../../../components/links/block-link/__generated__/EpochByBlock';
import EpochOverview from '../../../components/epoch-overview/epoch';
type Params = { block: string };
@@ -26,6 +28,11 @@ const Block = () => {
state: { data: blockData, loading, error },
} = useBlockInfo(Number(block));
const { data } = useExplorerEpochForBlockQuery({
errorPolicy: 'ignore',
variables: { block: block?.toString() || '' },
});
return (
<section>
<RouteTitle data-testid="block-header">{t(`BLOCK ${block}`)}</RouteTitle>
@@ -75,6 +82,7 @@ const Block = () => {
<code>{blockData.result.block.header.consensus_hash}</code>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableHeader scope="row">Mined by</TableHeader>
<TableCell modifier="bordered">
@@ -97,6 +105,14 @@ const Block = () => {
)}
</TableCell>
</TableRow>
{data && data.epoch && (
<TableRow modifier="bordered">
<TableCell scope="row">{t('Epoch')}</TableCell>
<TableCell modifier="bordered">
<EpochOverview id={data.epoch.id} icon={false} />
</TableCell>
</TableRow>
)}
<TableRow modifier="bordered">
<TableHeader scope="row">Transactions</TableHeader>
<TableCell modifier="bordered">
@@ -0,0 +1,42 @@
import { t } from '@vegaprotocol/i18n';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { useParams } from 'react-router-dom';
import { useScrollToLocation } from '../../hooks/scroll-to-location';
import { useDocumentTitle } from '../../hooks/use-document-title';
import { PageTitle } from '../../components/page-helpers/page-title';
import { useExplorerOracleForMarketQuery } from '../oracles/__generated__/OraclesForMarkets';
import { OraclesTable } from '../../components/oracle-table';
type Params = { marketId: string };
export const MarketOraclesPage = () => {
useScrollToLocation();
const { marketId } = useParams<Params>();
const { data, error, loading } = useExplorerOracleForMarketQuery({
errorPolicy: 'ignore',
variables: {
id: marketId || '1',
},
});
useDocumentTitle([marketId ? marketId : 'market', 'Oracles for Market']);
return (
<section className="relative">
<PageTitle
data-testid="markets-heading"
title={t('Oracles for market')}
/>
<AsyncRenderer
noDataMessage={t('This chain has no markets')}
errorMessage={t('Could not fetch market') + ' ' + marketId}
data={data}
loading={loading}
error={error}
>
<OraclesTable data={data} />
</AsyncRenderer>
</section>
);
};
@@ -1,6 +1,6 @@
import { useScrollToLocation } from '../../hooks/scroll-to-location';
import { useDocumentTitle } from '../../hooks/use-document-title';
import { marketsProvider } from '@vegaprotocol/markets';
import { marketsWithDataProvider } from '@vegaprotocol/markets';
import { RouteTitle } from '../../components/route-title';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
@@ -12,7 +12,7 @@ export const MarketsPage = () => {
useScrollToLocation();
const { data, loading, error } = useDataProvider({
dataProvider: marketsProvider,
dataProvider: marketsWithDataProvider,
variables: undefined,
skipUpdates: true,
});
@@ -89,7 +89,40 @@ fragment ExplorerOracleDataSourceSpec on ExternalDataSourceSpec {
}
query ExplorerOracleFormMarkets {
marketsConnection {
marketsConnection(includeSettled: false, pagination: { first: 20 }) {
edges {
node {
...ExplorerOracleForMarketsMarket
}
}
}
oracleSpecsConnection {
edges {
node {
dataSourceSpec {
...ExplorerOracleDataSourceSpec
}
dataConnection(pagination: { first: 1 }) {
edges {
node {
externalData {
data {
data {
name
value
}
}
}
}
}
}
}
}
}
}
query ExplorerOracleForMarket($id: ID!) {
marketsConnection(id: $id) {
edges {
node {
...ExplorerOracleForMarketsMarket
@@ -16,6 +16,13 @@ export type ExplorerOracleFormMarketsQueryVariables = Types.Exact<{ [key: string
export type ExplorerOracleFormMarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Spot' } } } } }> } | null, oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, 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 } | { __typename?: 'EthCallSpec', address: string, sourceChainId: number } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
export type ExplorerOracleForMarketQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerOracleForMarketQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Spot' } } } } }> } | null, oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, 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 } | { __typename?: 'EthCallSpec', address: string, sourceChainId: number } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
export const ExplorerOracleFutureFragmentDoc = gql`
fragment ExplorerOracleFuture on Future {
dataSourceSpecForSettlementData {
@@ -113,7 +120,7 @@ export const ExplorerOracleDataSourceSpecFragmentDoc = gql`
`;
export const ExplorerOracleFormMarketsDocument = gql`
query ExplorerOracleFormMarkets {
marketsConnection {
marketsConnection(includeSettled: false, pagination: {first: 20}) {
edges {
node {
...ExplorerOracleForMarketsMarket
@@ -126,7 +133,7 @@ export const ExplorerOracleFormMarketsDocument = gql`
dataSourceSpec {
...ExplorerOracleDataSourceSpec
}
dataConnection(pagination: {last: 1}) {
dataConnection(pagination: {first: 1}) {
edges {
node {
externalData {
@@ -172,4 +179,67 @@ export function useExplorerOracleFormMarketsLazyQuery(baseOptions?: Apollo.LazyQ
}
export type ExplorerOracleFormMarketsQueryHookResult = ReturnType<typeof useExplorerOracleFormMarketsQuery>;
export type ExplorerOracleFormMarketsLazyQueryHookResult = ReturnType<typeof useExplorerOracleFormMarketsLazyQuery>;
export type ExplorerOracleFormMarketsQueryResult = Apollo.QueryResult<ExplorerOracleFormMarketsQuery, ExplorerOracleFormMarketsQueryVariables>;
export type ExplorerOracleFormMarketsQueryResult = Apollo.QueryResult<ExplorerOracleFormMarketsQuery, ExplorerOracleFormMarketsQueryVariables>;
export const ExplorerOracleForMarketDocument = gql`
query ExplorerOracleForMarket($id: ID!) {
marketsConnection(id: $id) {
edges {
node {
...ExplorerOracleForMarketsMarket
}
}
}
oracleSpecsConnection {
edges {
node {
dataSourceSpec {
...ExplorerOracleDataSourceSpec
}
dataConnection(pagination: {last: 1}) {
edges {
node {
externalData {
data {
data {
name
value
}
}
}
}
}
}
}
}
}
}
${ExplorerOracleForMarketsMarketFragmentDoc}
${ExplorerOracleDataSourceSpecFragmentDoc}`;
/**
* __useExplorerOracleForMarketQuery__
*
* To run a query within a React component, call `useExplorerOracleForMarketQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerOracleForMarketQuery` 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 } = useExplorerOracleForMarketQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useExplorerOracleForMarketQuery(baseOptions: Apollo.QueryHookOptions<ExplorerOracleForMarketQuery, ExplorerOracleForMarketQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerOracleForMarketQuery, ExplorerOracleForMarketQueryVariables>(ExplorerOracleForMarketDocument, options);
}
export function useExplorerOracleForMarketLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerOracleForMarketQuery, ExplorerOracleForMarketQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerOracleForMarketQuery, ExplorerOracleForMarketQueryVariables>(ExplorerOracleForMarketDocument, options);
}
export type ExplorerOracleForMarketQueryHookResult = ReturnType<typeof useExplorerOracleForMarketQuery>;
export type ExplorerOracleForMarketLazyQueryHookResult = ReturnType<typeof useExplorerOracleForMarketLazyQuery>;
export type ExplorerOracleForMarketQueryResult = Apollo.QueryResult<ExplorerOracleForMarketQuery, ExplorerOracleForMarketQueryVariables>;
@@ -4,8 +4,13 @@ import {
ExternalExplorerLink,
EthExplorerLinkTypes,
} from '../../../components/links/external-explorer-link/external-explorer-link';
import { getExternalChainLabel } from '../../../components/links/external-explorer-link/external-chain';
import { getExternalChainLabel } from '@vegaprotocol/environment';
import { t } from 'i18next';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import isArray from 'lodash/isArray';
import type { components } from '../../../../types/explorer';
type Normalisers = components['schemas']['vegaNormaliser'][];
interface OracleDetailsEthSourceProps {
sourceType: SourceType;
@@ -34,21 +39,117 @@ export function OracleEthSource({
const chainLabel = getExternalChainLabel(chain);
const abi = prepareOracleSpecField(sourceType?.sourceType?.abi);
const args = prepareOracleSpecField(sourceType?.sourceType?.args);
const normalisers = serialiseNormalisers(sourceType.sourceType.normalisers);
return (
<TableRow modifier="bordered">
<TableHeader scope="row">
<TableHeader scope="row" className="pt-1 align-text-top">
{chainLabel} {t('Contract')}
</TableHeader>
<TableCell modifier="bordered">
<ExternalExplorerLink
chain={chain}
id={address}
type={EthExplorerLinkTypes.address}
code={true}
/>
<span className="mx-3">&rArr;</span>
<code>{sourceType.sourceType.method}</code>
<details>
<summary className="cursor-pointer">
<ExternalExplorerLink
chain={chain}
id={address}
type={EthExplorerLinkTypes.address}
code={true}
/>
<span className="mx-3">&rArr;</span>
<code>{sourceType.sourceType.method}</code>
</summary>
{args && (
<>
<h2 className={'mt-5 mb-1 text-xl'}>{t('Arguments')}</h2>
<div className="max-w-3">
<SyntaxHighlighter
data={JSON.parse(
sourceType.sourceType.args as unknown as string
)}
/>
</div>
</>
)}
{abi && (
<>
<h2 className={'mt-5 mb-1 text-xl'}>{t('ABI')}</h2>
<div className="max-w-3">
<SyntaxHighlighter data={abi} />
</div>
</>
)}
{normalisers && (
<>
<h2 className={'mt-5 mb-1 text-xl'}>{t('Normalisers')}</h2>
<div className="max-w-3 mb-3">
<SyntaxHighlighter data={normalisers} />
</div>
</>
)}
</details>
</TableCell>
</TableRow>
);
}
// Constant to define the absence of a valid string from the Oracle Spec fields
const NO_DATA = false;
/**
* The ABI and args are stored as either a (JSON escaped, probably) string
* or array of strings. Given that OracleEthSource is simply throwing the
* data in to a SyntaxHighlighter, we don't really care about the format,
* so this function will just try to parse the data and return it as a string.
*
* @param abi
* @returns
*/
export function prepareOracleSpecField(
specField?: string[] | null
): string | false {
if (!specField) {
return NO_DATA;
}
try {
if (isArray(specField)) {
return JSON.parse(specField.join(''));
} else {
return JSON.parse(specField);
}
} catch (e) {
return NO_DATA;
}
}
/**
* Similar to prepareOracleSpecField above, but processes an array of normaliser objects
* removing the __typename and returning a serialised array of normalisers for
* SyntaxHighlighter
*
* @param normalisers
* @returns
*/
export function serialiseNormalisers(
normalisers?: Normalisers | null
): Normalisers | false {
if (!normalisers) {
return NO_DATA;
}
try {
return normalisers.map((normaliser) => {
return {
name: normaliser.name,
expression: normaliser.expression,
};
});
} catch (e) {
return NO_DATA;
}
}
@@ -1,38 +1,9 @@
import { render } from '@testing-library/react';
import { OracleFilter } from './oracle-filter';
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
import {
ConditionOperator,
DataSourceSpecStatus,
PropertyKeyType,
} from '@vegaprotocol/types';
import { ConditionOperator, DataSourceSpecStatus } from '@vegaprotocol/types';
import type { Condition } from '@vegaprotocol/types';
type Spec =
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType'];
const mockExternalSpec: Spec = {
sourceType: {
__typename: 'DataSourceSpecConfiguration',
filters: [
{
__typename: 'Filter',
key: {
type: PropertyKeyType.TYPE_INTEGER,
name: 'testKey',
},
conditions: [
{
__typename: 'Condition',
value: 'testValue',
operator: ConditionOperator.OPERATOR_EQUALS,
},
],
},
],
},
};
function renderComponent(data: ExplorerOracleDataSourceFragment) {
return <OracleFilter data={data} />;
}
@@ -50,31 +21,6 @@ describe('Oracle Filter view', () => {
expect(res.container).toBeEmptyDOMElement();
});
it('Renders filters if type is DataSourceSpecConfiguration', () => {
const res = render(
renderComponent({
dataSourceSpec: {
spec: {
id: 'irrelevant-test-data',
createdAt: 'irrelevant-test-data',
status: DataSourceSpecStatus.STATUS_ACTIVE,
data: {
sourceType: mockExternalSpec,
},
},
},
dataConnection: {
edges: [],
},
})
);
// Renders a comprehensible summary of key = value
expect(res.getByText('testKey')).toBeInTheDocument();
expect(res.getByText('=')).toBeInTheDocument();
expect(res.getByText('testValue')).toBeInTheDocument();
});
it('Renders conditions if type is DataSourceSpecConfigurationTime', () => {
const res = render(
renderComponent({
@@ -136,7 +82,7 @@ describe('Oracle Filter view', () => {
})
);
// This should never happen, but for coverage sake we test that it does this
// This should never happen, but for coverage we test that it does this
const ul = res.getByRole('list');
expect(ul).toBeInTheDocument();
expect(ul).toBeEmptyDOMElement();
@@ -1,5 +1,8 @@
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
import { OracleSpecInternalTimeTrigger } from './oracle-spec/internal-time-trigger';
import {
OracleSpecInternalTimeTrigger,
TimeTrigger,
} from './oracle-spec/internal-time-trigger';
import { OracleSpecCondition } from './oracle-spec/condition';
import { getCharacterForOperator } from './oracle-spec/operator';
@@ -11,7 +14,7 @@ interface OracleFilterProps {
* Shows the conditions that this oracle is using to filter
* data sources, as a list.
*
* Renders nothing if there is no data (which will frequently)
* Renders nothing if there is no data (which will frequently
* be the case) and if there is data, currently renders a simple
* JSON view.
*/
@@ -21,6 +24,7 @@ export function OracleFilter({ data }: OracleFilterProps) {
}
const s = data.dataSourceSpec.spec.data.sourceType.sourceType;
if (s.__typename === 'DataSourceSpecConfigurationTime' && s.conditions) {
return (
<ul>
@@ -41,30 +45,30 @@ export function OracleFilter({ data }: OracleFilterProps) {
s.triggers
) {
return <OracleSpecInternalTimeTrigger data={s} />;
} else if (
s.__typename === 'EthCallSpec' ||
s.__typename === 'DataSourceSpecConfiguration'
) {
} else if (s.__typename === 'EthCallSpec') {
if (s.filters !== null && s.filters && 'filters' in s) {
return (
<ul>
{s.filters.map((f) => {
const prop = <code title={f.key.type}>{f.key.name}</code>;
<div>
<ul>
{s.filters.map((f) => {
const prop = <code title={f.key.type}>{f.key.name}</code>;
if (!f.conditions || f.conditions.length === 0) {
return prop;
} else {
return f.conditions.map((c) => {
return (
<li key={`${prop}${c.value}`}>
{prop} {getCharacterForOperator(c.operator)}{' '}
<code>{c.value ? c.value : '-'}</code>
</li>
);
});
}
})}
</ul>
if (!f.conditions || f.conditions.length === 0) {
return prop;
} else {
return f.conditions.map((c) => {
return (
<li key={`${prop}${c.value}`}>
{prop} {getCharacterForOperator(c.operator)}{' '}
<code>{c.value ? c.value : '-'}</code>
</li>
);
});
}
})}
</ul>
{s.trigger && <TimeTrigger data={s.trigger.trigger} />}
</div>
);
}
}
@@ -1,5 +1,10 @@
import { t } from '@vegaprotocol/i18n';
import type { DataSourceSpecConfigurationTimeTrigger } from '@vegaprotocol/types';
import type {
DataSourceSpecConfigurationTimeTrigger,
EthTimeTrigger,
InternalTimeTrigger,
Maybe,
} from '@vegaprotocol/types';
import secondsToMinutes from 'date-fns/secondsToMinutes';
import fromUnixTime from 'date-fns/fromUnixTime';
@@ -13,32 +18,60 @@ export function OracleSpecInternalTimeTrigger({
return (
<div>
<span>{t('Time')}</span>,&nbsp;
{data.triggers.map((tr) => {
return (
<span>
{tr?.initial ? (
<span title={`${tr.initial}`}>
<strong>{t('starting at')}</strong>{' '}
<em className="not-italic underline decoration-dotted">
{fromUnixTime(tr.initial).toLocaleString()}
</em>
</span>
) : (
''
)}
{tr?.every ? (
<span title={`${tr.every} ${t('seconds')}`}>
, <strong>{t('every')}</strong>{' '}
<em className="not-italic underline decoration-dotted">
{secondsToMinutes(tr.every)} {t('minutes')}
</em>{' '}
</span>
) : (
''
)}
</span>
);
})}
{data.triggers.map((tr) => (
<TimeTrigger data={tr} />
))}
</div>
);
}
export interface TimeTriggerProps {
data: Maybe<InternalTimeTrigger> | Maybe<EthTimeTrigger>;
}
export function TimeTrigger({ data }: TimeTriggerProps) {
const d = parseDate(data?.initial);
return (
<span key={JSON.stringify(data)}>
{data?.initial ? (
<span title={`${data.initial}`}>
<strong>{t('starting at')}</strong>{' '}
<em className="not-italic underline decoration-dotted">{d}</em>
</span>
) : (
''
)}
{data?.every ? (
<span title={`${data.every} ${t('seconds')}`}>
, <strong>{t('every')}</strong>{' '}
<em className="not-italic underline decor</em>ation-dotted">
{secondsToMinutes(data.every)} {t('minutes')}
</em>{' '}
</span>
) : (
''
)}
</span>
);
}
/**
* Dates in oracle triggers can be (or maybe were previously) Unix Time or timestamps
* depending on type. This function handles both cases and returns a nicely formatted date.
*
* @param date
* @returns string Localestring for date
*/
export function parseDate(date?: string | number): string {
if (!date) {
return 'Invalid date';
}
const d = fromUnixTime(+date).toLocaleString();
if (d === 'Invalid Date') {
return new Date(date).toLocaleString();
}
return d;
}
@@ -48,6 +48,11 @@ export const OracleDetails = ({
? dataSource.dataSourceSpec.spec.data.sourceType.sourceType.sourceChainId.toString()
: undefined;
const requiredConfirmations =
(sourceType.sourceType.__typename === 'EthCallSpec' &&
sourceType.sourceType.requiredConfirmations) ||
'';
return (
<div>
<TableWithTbody className="mb-2">
@@ -64,15 +69,23 @@ export const OracleDetails = ({
{getStatusString(dataSource.dataSourceSpec.spec.status)}
</TableCell>
</TableRow>
<OracleMarkets id={id} />
<OracleSigners sourceType={sourceType} />
<OracleEthSource sourceType={sourceType} chain={chain} />
<OracleMarkets id={id} />
<TableRow modifier="bordered">
<TableHeader scope="row">{t('Filter')}</TableHeader>
<TableHeader scope="row" className="pt-1 align-text-top">
{t('Filter')}
</TableHeader>
<TableCell modifier="bordered">
<OracleFilter data={dataSource} />
</TableCell>
</TableRow>
{requiredConfirmations && requiredConfirmations > 0 && (
<TableRow modifier="bordered">
<TableHeader scope="row">{t('Required Confirmations')}</TableHeader>
<TableCell modifier="bordered">{requiredConfirmations}</TableCell>
</TableRow>
)}
</TableWithTbody>
{dataConnection ? <OracleData data={dataConnection} /> : null}
</div>
@@ -1,17 +1,10 @@
import compact from 'lodash/compact';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { RouteTitle } from '../../../components/route-title';
import { t } from '@vegaprotocol/i18n';
import { useDocumentTitle } from '../../../hooks/use-document-title';
import { useScrollToLocation } from '../../../hooks/scroll-to-location';
import { useExplorerOracleFormMarketsQuery } from '../__generated__/OraclesForMarkets';
import { MarketLink } from '../../../components/links';
import { OracleLink } from '../../../components/links/oracle-link/oracle-link';
import { useState } from 'react';
import { MarketStateMapping } from '@vegaprotocol/types';
import type { MarketState } from '@vegaprotocol/types';
const cellSpacing = 'px-3';
import { OraclesTable } from '../../../components/oracle-table';
const Oracles = () => {
const { data, loading, error } = useExplorerOracleFormMarketsQuery({
@@ -21,8 +14,6 @@ const Oracles = () => {
useDocumentTitle(['Oracles']);
useScrollToLocation();
const [hoveredOracle, setHoveredOracle] = useState('');
return (
<section>
<RouteTitle data-testid="oracle-specs-heading">{t('Oracles')}</RouteTitle>
@@ -38,148 +29,7 @@ const Oracles = () => {
data.oracleSpecsConnection.edges?.length === 0
}
>
<table className="text-left">
<thead>
<tr>
<th className={cellSpacing}>Market</th>
<th className={cellSpacing}>Type</th>
<th className={cellSpacing}>State</th>
<th className={cellSpacing}>Settlement</th>
<th className={cellSpacing}>Termination</th>
</tr>
</thead>
<tbody>
{data?.marketsConnection?.edges
? data.marketsConnection.edges.map((o) => {
let hasSeenOracleReports = false;
let settlementOracle = '-';
let settlementOracleStatus = '-';
let terminationOracle = '-';
let terminationOracleStatus = '-';
const id = o?.node.id;
if (!id) {
return null;
}
if (
o.node.tradableInstrument.instrument.product.__typename ===
'Future'
) {
settlementOracle =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.id;
terminationOracle =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForTradingTermination.id;
settlementOracleStatus =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.status;
terminationOracleStatus =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForTradingTermination.status;
} else if (
o.node.tradableInstrument.instrument.product.__typename ===
'Perpetual'
) {
settlementOracle =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.id;
terminationOracle =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementSchedule.id;
settlementOracleStatus =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.status;
terminationOracleStatus =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementSchedule.status;
}
const oracleInformationUnfiltered =
data?.oracleSpecsConnection?.edges?.map((e) =>
e && e.node ? e.node : undefined
) || [];
const oracleInformation = compact(oracleInformationUnfiltered)
.filter(
(o) =>
o.dataConnection.edges &&
o.dataConnection.edges.length > 0 &&
(o.dataSourceSpec.spec.id === settlementOracle ||
o.dataSourceSpec.spec.id === terminationOracle)
)
.at(0);
if (oracleInformation) {
hasSeenOracleReports = true;
}
const oracleList = `${settlementOracle} ${terminationOracle}`;
return (
<tr
id={id}
key={id}
className={
hoveredOracle.length > 0 &&
oracleList.indexOf(hoveredOracle) > -1
? 'bg-gray-100 dark:bg-gray-800'
: ''
}
data-testid="oracle-details"
data-oracles={oracleList}
>
<td className={cellSpacing}>
<MarketLink id={id} />
</td>
<td className={cellSpacing}>
{
o.node.tradableInstrument.instrument.product
.__typename
}
</td>
<td className={cellSpacing}>
{MarketStateMapping[o.node.state as MarketState]}
</td>
<td
className={
hoveredOracle.length > 0 &&
hoveredOracle === settlementOracle
? `indent-1 ${cellSpacing}`
: cellSpacing
}
>
<OracleLink
id={settlementOracle}
status={settlementOracleStatus}
hasSeenOracleReports={hasSeenOracleReports}
onMouseOver={() => setHoveredOracle(settlementOracle)}
onMouseOut={() => setHoveredOracle('')}
/>
</td>
<td
className={
hoveredOracle.length > 0 &&
hoveredOracle === terminationOracle
? `indent-1 ${cellSpacing}`
: cellSpacing
}
>
<OracleLink
id={terminationOracle}
status={terminationOracleStatus}
hasSeenOracleReports={hasSeenOracleReports}
onMouseOver={() =>
setHoveredOracle(terminationOracle)
}
onMouseOut={() => setHoveredOracle('')}
/>
</td>
</tr>
);
})
: null}
</tbody>
</table>
<OraclesTable data={data} />
</AsyncRenderer>
</section>
);
@@ -4,7 +4,7 @@ import { useMemo, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { SubHeading } from '../../../components/sub-heading';
import { toNonHex } from '../../../components/search/detect-search';
import { useTxsData } from '../../../hooks/use-txs-data';
import { getInitialFilters, useTxsData } from '../../../hooks/use-txs-data';
import { TxsInfiniteList } from '../../../components/txs';
import { PageHeader } from '../../../components/page-header';
import { useDocumentTitle } from '../../../hooks/use-document-title';
@@ -15,16 +15,18 @@ import { PartyBlockAccounts } from './components/party-block-accounts';
import { isValidPartyId } from './components/party-id-error';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { TxsListNavigation } from '../../../components/txs/tx-list-navigation';
import type { FilterOption } from '../../../components/txs/tx-filter';
import { AllFilterOptions, TxsFilter } from '../../../components/txs/tx-filter';
import {
TxsFilter,
type FilterOption,
} from '../../../components/txs/tx-filter';
import { useSearchParams } from 'react-router-dom';
type Params = { party: string };
const Party = () => {
const [params] = useSearchParams();
const [filters, setFilters] = useState(getInitialFilters(params));
const [filters, setFilters] = useState(new Set(AllFilterOptions));
const { party } = useParams<Params>();
useDocumentTitle(['Public keys', party || '-']);
+37 -12
View File
@@ -5,9 +5,8 @@ import Home from './home';
import OraclePage from './oracles';
import Oracles from './oracles/home';
import { Oracle } from './oracles/id';
import Party from './parties';
import { Parties } from './parties/home';
import { Party as PartySingle } from './parties/id';
import { Party } from './parties/id';
import { ValidatorsPage } from './validators';
import Genesis from './genesis';
import { Block } from './blocks/id';
@@ -17,6 +16,7 @@ import { TxsList } from './txs/home';
import { t } from '@vegaprotocol/i18n';
import { Routes } from './route-names';
import { NetworkParameters } from './network-parameters';
import { Outlet } from 'react-router-dom';
import type { Params, RouteObject } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { MarketPage, MarketsPage } from './markets';
@@ -31,6 +31,7 @@ import { Disclaimer } from './pages/disclaimer';
import { useFeatureFlags } from '@vegaprotocol/environment';
import RestrictedPage from './restricted';
import { NetworkTreasury } from './treasury';
import { MarketOraclesPage } from './markets/market-oracles-page';
export type Navigable = {
path: string;
@@ -67,7 +68,7 @@ export const useRouterConfig = () => {
? [
{
path: Routes.PARTIES,
element: <Party />,
element: <Outlet />,
handle: {
name: t('Parties'),
text: t('Parties'),
@@ -80,12 +81,12 @@ export const useRouterConfig = () => {
},
{
path: ':party',
element: <Party />,
element: <Outlet />,
children: [
{
index: true,
element: <PartySingle />,
element: <Party />,
handle: {
breadcrumb: (params: Params<string>) => (
<Link to={linkTo(Routes.PARTIES, params.party)}>
@@ -96,7 +97,7 @@ export const useRouterConfig = () => {
},
{
path: 'assets',
element: <Party />,
element: <Outlet />,
handle: {
breadcrumb: (params: Params<string>) => (
<Link to={linkTo(Routes.PARTIES, params.party)}>
@@ -199,12 +200,36 @@ export const useRouterConfig = () => {
},
{
path: ':marketId',
element: <MarketPage />,
handle: {
breadcrumb: (params: Params<string>) => (
<MarketLink id={params.marketId as string} />
),
},
element: <Outlet />,
children: [
{
index: true,
element: <MarketPage />,
handle: {
breadcrumb: (params: Params<string>) => (
<MarketLink id={params.marketId as string} />
),
},
},
{
path: 'oracles',
element: <Outlet />,
handle: {
breadcrumb: (params: Params<string>) => (
<MarketLink id={params.marketId as string} />
),
},
children: [
{
index: true,
element: <MarketOraclesPage />,
handle: {
breadcrumb: (params: Params<string>) => t('Oracles'),
},
},
],
},
],
},
],
},
@@ -33,7 +33,10 @@ export const NetworkAccountsTable = () => {
return (
<section className="md:flex md:flex-row flex-wrap">
{c.map((a) => (
<div className="basis-1/2 md:basis-1/4">
<div
className="basis-1/2 md:basis-1/4"
key={`${a.assetId}-${a.balance}`}
>
<div className="bg-white rounded overflow-hidden shadow-lg dark:bg-black dark:border-slate-500 dark:border">
<div className="text-center p-6 bg-gray-100 dark:bg-slate-900 border-b dark:border-slate-500">
<p className="flex justify-center">
@@ -16,19 +16,21 @@ import type { DeepPartial } from '@apollo/client/utilities';
describe('typeLabel', () => {
it('should return "Transfer" for "OneOffTransfer" kind', () => {
expect(typeLabel('OneOffTransfer')).toBe('Transfer');
expect(typeLabel('OneOffTransfer')).toBe('Transfer - one time');
});
it('should return "Transfer" for "RecurringTransfer" kind', () => {
expect(typeLabel('RecurringTransfer')).toBe('Transfer');
expect(typeLabel('RecurringTransfer')).toBe('Transfer - repeating');
});
it('should return "Governance" for "OneOffGovernanceTransfer" kind', () => {
expect(typeLabel('OneOffGovernanceTransfer')).toBe('Governance');
expect(typeLabel('OneOffGovernanceTransfer')).toBe('Governance - one time');
});
it('should return "Governance" for "RecurringGovernanceTransfer" kind', () => {
expect(typeLabel('RecurringGovernanceTransfer')).toBe('Governance');
expect(typeLabel('RecurringGovernanceTransfer')).toBe(
'Governance - repeating'
);
});
it('should return "Unknown" for unknown kind', () => {
@@ -256,7 +258,7 @@ describe('NetworkTransfersTable', () => {
expect(screen.getByTestId('from-account').textContent).toEqual('Treasury');
expect(screen.getByTestId('to-account').textContent).toEqual('7100…97a0');
expect(screen.getByTestId('transfer-kind').textContent).toEqual(
'Governance'
'Governance - one time'
);
});
});
@@ -12,6 +12,7 @@ import { t } from '@vegaprotocol/i18n';
import { IconNames } from '@blueprintjs/icons';
import { useMemo } from 'react';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
import ProposalLink from '../../../components/links/proposal-link/proposal-link';
export const colours = {
INCOMING: '!fill-vega-green-600 text-vega-green-600 mr-2',
@@ -50,14 +51,24 @@ export function getToAccountTypeLabel(type?: AccountType): string {
}
}
export function isGovernanceTransfer(kind?: string): boolean {
if (kind && kind.includes('Governance')) {
return true;
}
return false;
}
export function typeLabel(kind?: string): string {
switch (kind) {
case 'OneOffTransfer':
return t('Transfer - one time');
case 'RecurringTransfer':
return t('Transfer');
return t('Transfer - repeating');
case 'OneOffGovernanceTransfer':
return t('Governance - one time');
case 'RecurringGovernanceTransfer':
return t('Governance');
return t('Governance - repeating');
default:
return t('Unknown');
}
@@ -239,6 +250,11 @@ export const NetworkTransfersTable = () => {
>
{a && typeLabel(a.kind.__typename)}
</span>
{isGovernanceTransfer(a?.kind.__typename) && a?.id && (
<span className="ml-4">
<ProposalLink id={a?.id} text="View" />
</span>
)}
</td>
</tr>
);
@@ -4,6 +4,7 @@ import { t } from '@vegaprotocol/i18n';
import { RouteTitle } from '../../components/route-title';
import { NetworkAccountsTable } from './components/network-accounts-table';
import { NetworkTransfersTable } from './components/network-transfers-table';
import GovernanceLink from '../../components/links/governance-link/governance-link';
export type NonZeroAccount = {
assetId: string;
@@ -16,7 +17,33 @@ export const NetworkTreasury = () => {
return (
<section>
<RouteTitle data-testid="block-header">{t(`Treasury`)}</RouteTitle>
<div>
<details className="w-full md:w-3/5 cursor-pointer shadow-lg p-5 dark:border-l-2 dark:border-vega-green">
<summary>{t('About the Network Treasury')}</summary>
<section className="mt-4 b-1 border-grey">
<p className="mb-2">
The network treasury can hold funds from any active settlement asset
on the network. It is funded periodically by transfers from Gobalsky
as part of the Community Adoption Fund (CAF), but in future may
receive funds from any sources.
</p>
<p className="mb-2">
Funds in the network treasury can be used by creating governance
initiated transfers via{' '}
<GovernanceLink text={t('community governance')} />. These transfers
can be initiated by anyone and be used to fund reward pools, or can
be used to fund other activities the{' '}
<abbr className="decoration-dotted" title="Community Adoption Fund">
CAF
</abbr>{' '}
is exploring.
</p>
<p>
This page shows details of the balances in the treasury, pending
transfers, and historic transfer movements to and from the treasury.
</p>
</section>
</details>
<div className="mt-6">
<NetworkAccountsTable />
</div>
<div className="mt-5">
+1 -1
View File
@@ -1,5 +1,5 @@
# App configuration variables
NX_VEGA_ENV=VALIDATOR_TESTNET
NX_VEGA_ENV=VALIDATORS_TESTNET
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml
NX_VEGA_URL=https://api-validators-testnet.vega.rocks/graphql
NX_VEGA_REST=https://api-validators-testnet.vega.rocks/
@@ -47,7 +47,7 @@ export const SubHeading = ({
return (
<h2
className={classNames('text-2xl font-alpha calt uppercase break-words', {
className={classNames('text-2xl font-alpha calt break-words', {
'mx-auto': centerContent,
'mb-0': !marginBottom,
'mb-4': marginBottom,
+1 -1
View File
@@ -41,7 +41,7 @@ export const ContractAddresses: {
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
},
VALIDATOR_TESTNET: {
VALIDATORS_TESTNET: {
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
// This is a fallback contract address for the validator testnet network which does not
@@ -6,10 +6,11 @@ import {
ViewPartyConnector,
createConfig,
fairground,
validatorsTestnet,
stagnet,
mainnet,
} from '@vegaprotocol/wallet';
import { useEnvironment } from '@vegaprotocol/environment';
import { CHAIN_IDS, useEnvironment } from '@vegaprotocol/environment';
export const useVegaWalletConfig = () => {
const { VEGA_ENV, VEGA_URL, VEGA_WALLET_URL } = useEnvironment();
@@ -31,8 +32,8 @@ export const useVegaWalletConfig = () => {
const viewParty = new ViewPartyConnector();
const config = createConfig({
chains: [mainnet, fairground, stagnet],
defaultChainId: fairground.id,
chains: [mainnet, fairground, validatorsTestnet, stagnet],
defaultChainId: CHAIN_IDS[VEGA_ENV],
connectors: [injected, snap, jsonRpc, viewParty],
});
+1 -1
View File
@@ -31,7 +31,7 @@ i18n
load: 'languageOnly',
debug: isInDev,
// have a common namespace used around the full app
ns: ['governance', 'wallet', 'wallet-react'],
ns: ['governance', 'wallet', 'wallet-react', 'assets', 'utils'],
defaultNS: 'governance',
keySeparator: false, // we use content as keys
nsSeparator: false,
@@ -19,6 +19,7 @@ import {
getSigners,
MarginScalingFactorsPanel,
marketInfoProvider,
PriceMonitoringSettingsInfoPanel,
} from '@vegaprotocol/markets';
import {
Button,
@@ -49,7 +50,7 @@ export const useMarketDataDialogStore = create<MarketDataDialogState>(
const marketDataHeaderStyles =
'font-alpha calt text-base border-b border-vega-dark-200 mt-2 py-2';
export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
export const ProposalMarketData = ({ marketId }: { marketId: string }) => {
const { t } = useTranslation();
const { isOpen, open, close } = useMarketDataDialogStore();
const [showDetails, setShowDetails] = useState(false);
@@ -58,7 +59,7 @@ export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
dataProvider: marketInfoProvider,
skipUpdates: true,
variables: {
marketId: proposalId,
marketId: marketId,
},
});
@@ -71,7 +72,7 @@ export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
},
});
if (!marketData || !parentMarketData) {
if (!marketData) {
return null;
}
@@ -133,13 +134,13 @@ export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
<h2 className={marketDataHeaderStyles}>{t('Key details')}</h2>
<KeyDetailsInfoPanel
market={marketData}
parentMarket={parentMarketData}
parentMarket={parentMarketData ? parentMarketData : undefined}
/>
<h2 className={marketDataHeaderStyles}>{t('Instrument')}</h2>
<InstrumentInfoPanel
market={marketData}
parentMarket={parentMarketData}
parentMarket={parentMarketData ? parentMarketData : undefined}
/>
{settlementData &&
@@ -155,7 +156,7 @@ export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
isParentSettlementDataEqual ||
isParentSettlementScheduleDataEqual
? undefined
: parentMarketData
: parentMarketData || undefined
}
/>
</>
@@ -168,7 +169,9 @@ export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
market={marketData}
type="settlementData"
parentMarket={
isParentSettlementDataEqual ? undefined : parentMarketData
isParentSettlementDataEqual
? undefined
: parentMarketData || undefined
}
/>
@@ -184,7 +187,7 @@ export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
parentMarket={
isParentTerminationDataEqual
? undefined
: parentMarketData
: parentMarketData || undefined
}
/>
</div>
@@ -202,7 +205,7 @@ export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
parentMarket={
isParentSettlementScheduleDataEqual
? undefined
: parentMarketData
: parentMarketData || undefined
}
/>
</div>
@@ -216,19 +219,19 @@ export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
<h2 className={marketDataHeaderStyles}>{t('Settlement assets')}</h2>
<SettlementAssetInfoPanel
market={marketData}
parentMarket={parentMarketData}
parentMarket={parentMarketData || undefined}
/>
<h2 className={marketDataHeaderStyles}>{t('Metadata')}</h2>
<MetadataInfoPanel
market={marketData}
parentMarket={parentMarketData}
parentMarket={parentMarketData || undefined}
/>
<h2 className={marketDataHeaderStyles}>{t('Risk model')}</h2>
<RiskModelInfoPanel
market={marketData}
parentMarket={parentMarketData}
parentMarket={parentMarketData || undefined}
/>
<h2 className={marketDataHeaderStyles}>
@@ -236,61 +239,45 @@ export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
</h2>
<MarginScalingFactorsPanel
market={marketData}
parentMarket={parentMarketData}
parentMarket={parentMarketData || undefined}
/>
<h2 className={marketDataHeaderStyles}>{t('Risk factors')}</h2>
<RiskFactorsInfoPanel
market={marketData}
parentMarket={parentMarketData}
parentMarket={parentMarketData || undefined}
/>
{showParentPriceMonitoringBounds &&
(
parentMarketData?.priceMonitoringSettings?.parameters
?.triggers || []
).map((_, triggerIndex) => (
<>
<h2 className={marketDataHeaderStyles}>
{t(`Parent price monitoring bounds ${triggerIndex + 1}`)}
</h2>
<div className="text-vega-dark-300 line-through">
<PriceMonitoringBoundsInfoPanel
market={parentMarketData}
triggerIndex={triggerIndex}
/>
</div>
</>
))}
{(
marketData.priceMonitoringSettings?.parameters?.triggers || []
).map((_, triggerIndex) => (
{showParentPriceMonitoringBounds && (
// shows bounds for parent market
<>
<h2 className={marketDataHeaderStyles}>
{t(`Price monitoring bounds ${triggerIndex + 1}`)}
{t('Parent price monitoring bounds')}
</h2>
<PriceMonitoringBoundsInfoPanel
market={marketData}
triggerIndex={triggerIndex}
/>
<div className="text-vega-dark-300 line-through">
<PriceMonitoringBoundsInfoPanel market={parentMarketData} />
</div>
</>
))}
)}
<h2 className={marketDataHeaderStyles}>
{t('Price monitoring settings')}
</h2>
<PriceMonitoringSettingsInfoPanel market={marketData} />
<h2 className={marketDataHeaderStyles}>
{t('Liquidity monitoring parameters')}
</h2>
<LiquidityMonitoringParametersInfoPanel
market={marketData}
parentMarket={parentMarketData}
parentMarket={parentMarketData || undefined}
/>
<h2 className={marketDataHeaderStyles}>
{t('Liquidity price range')}
</h2>
<LiquidityPriceRangeInfoPanel
market={marketData}
parentMarket={parentMarketData}
parentMarket={parentMarketData || undefined}
/>
<h2 className={marketDataHeaderStyles}>
@@ -298,7 +285,7 @@ export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
</h2>
<LiquiditySLAParametersInfoPanel
market={marketData}
parentMarket={parentMarketData}
parentMarket={parentMarketData || undefined}
/>
</div>
</>
@@ -17,17 +17,20 @@ import { type ProposalNode } from './proposal-utils';
import { Lozenge } from '@vegaprotocol/ui-toolkit';
import { Indicator } from './indicator';
import { SubHeading } from '../../../../components/heading';
import { determineId } from '@vegaprotocol/wallet';
export const ProposalChangeDetails = ({
proposal,
terms,
restData,
indicator,
termsCount = 0,
}: {
proposal: Proposal | BatchProposal;
terms: ProposalTermsFieldsFragment;
restData: ProposalNode | null;
indicator?: number;
termsCount?: number;
}) => {
const { t } = useTranslation();
let details = null;
@@ -61,7 +64,18 @@ export const ProposalChangeDetails = ({
}
case 'NewMarket': {
if (proposal.id) {
details = <ProposalMarketData proposalId={proposal.id} />;
let marketId = proposal.id;
// TODO: when https://github.com/vegaprotocol/vega/issues/11005 gets merged
// this will need to be updated to loop forward from 0. Right now subProposals
// are returned (when using GQL) in the reverse order
if (proposal.__typename === 'BatchProposal') {
for (let i = termsCount - 1; i >= 0; i--) {
marketId = determineId(marketId);
}
}
details = <ProposalMarketData marketId={marketId} />;
}
break;
}
@@ -69,7 +83,7 @@ export const ProposalChangeDetails = ({
if (proposal.id) {
details = (
<div className="flex flex-col gap-4">
<ProposalMarketData proposalId={proposal.id} />
<ProposalMarketData marketId={proposal.id} />
<ProposalMarketChanges
indicator={indicator}
marketId={terms.change.marketId}
@@ -78,6 +78,7 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
proposal={proposal}
terms={p.terms}
restData={restData}
termsCount={proposal.subProposals?.length}
/>
);
})
@@ -61,6 +61,7 @@ const mockConsensusValidators: NodesFragmentFragment[] = [
];
jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'),
useVegaRelease: jest.fn(),
useVegaReleases: jest.fn(),
}));
@@ -3,8 +3,6 @@ export const VALIDATOR_LOGO_MAP: { [key: string]: string } = {
'https://pbs.twimg.com/profile_images/1586047492629712897/ZVMWBE94_400x400.jpg',
efbdf943443bd7595e83b0d7e88f37b7932d487d1b94aab3d004997273bb43fc:
'https://pbs.twimg.com/profile_images/1026823609979949057/3e-LCHHm_400x400.jpg',
'126751c5830b50d39eb85412fb2964f46338cce6946ff455b73f1b1be3f5e8cc':
'https://pbs.twimg.com/profile_images/1228627868542029824/9aoaLiIx_400x400.jpg',
'43697a3e911d8b70c0ce672adde17a5c38ca8f6a0486bf85ed0546e1b9a82887':
'https://pbs.twimg.com/profile_images/1352167987478843392/XzX82gIb_400x400.jpg',
ac735acc9ab11cf1d8c59c2df2107e00092b4ac96451cb137a1629af5b66242a:
+1 -1
View File
@@ -13,7 +13,7 @@
}
h1 {
@apply text-2xl text-white uppercase mb-4;
@apply text-2xl text-white mb-4;
}
h2 {
@apply text-xl text-white mb-4;
+1 -1
View File
@@ -2,4 +2,4 @@
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.tom
NX_VEGA_URL=https://api-validators-testnet.vega.rocks/graphql
NX_VEGA_NETWORKS={'{"TESTNET":"https://multisig-signer.fairground.wtf","MAINNET":"https://multisig-signer.vega.xyz"}'
NX_VEGA_ENV=VALIDATOR_TESTNET
NX_VEGA_ENV=VALIDATORS_TESTNET
+1
View File
@@ -21,6 +21,7 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_TAKE_PROFIT_STOP_LOSS=true
NX_ISOLATED_MARGIN=true
NX_ICEBERG_ORDERS=true
NX_METAMASK_SNAPS=true
+2
View File
@@ -21,6 +21,8 @@ NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supp
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=false
NX_STOP_ORDERS=false
NX_TAKE_PROFIT_STOP_LOSS=false
NX_TAKE_PROFIT_STOP_LOSS=true
NX_ISOLATED_MARGIN=true
# NX_ICEBERG_ORDERS
# NX_PRODUCT_PERPETUALS
+1
View File
@@ -21,6 +21,7 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_TAKE_PROFIT_STOP_LOSS=false
NX_ISOLATED_MARGIN=false
NX_ICEBERG_ORDERS=true
NX_METAMASK_SNAPS=true
+1
View File
@@ -21,6 +21,7 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_TAKE_PROFIT_STOP_LOSS=true
NX_ISOLATED_MARGIN=true
NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
+1
View File
@@ -22,6 +22,7 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_TAKE_PROFIT_STOP_LOSS=true
NX_ISOLATED_MARGIN=true
NX_ICEBERG_ORDERS=true
NX_METAMASK_SNAPS=true
+2 -1
View File
@@ -4,7 +4,7 @@ NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml
NX_VEGA_ENV=VALIDATOR_TESTNET
NX_VEGA_ENV=VALIDATORS_TESTNET
NX_VEGA_EXPLORER_URL=https://explorer.validators-testnet.vega.rocks
NX_VEGA_NETWORKS={\"MAINNET\":\"https://console.vega.xyz\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\",\"TESTNET\":\"https://console.fairground.wtf\"}
NX_VEGA_TOKEN_URL=https://governance.validators-testnet.vega.rocks
@@ -22,6 +22,7 @@ NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/m
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_TAKE_PROFIT_STOP_LOSS=true
NX_ISOLATED_MARGIN=true
NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
@@ -9,6 +9,10 @@ import {
Button,
VegaIcon,
VegaIconNames,
Tooltip,
TradingAnchorButton,
Intent,
CopyWithTooltip,
} from '@vegaprotocol/ui-toolkit';
import { TransferStatus, type Asset } from '@vegaprotocol/types';
import classNames from 'classnames';
@@ -17,7 +21,7 @@ import { Table } from '../../components/table';
import {
addDecimalsFormatNumberQuantum,
formatNumber,
getDateTimeFormat,
removePaginationWrapper,
} from '@vegaprotocol/utils';
import {
useTeam,
@@ -41,10 +45,6 @@ import {
} from '../../lib/hooks/use-games';
import { useEpochInfoQuery } from '../../lib/hooks/__generated__/Epoch';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import {
ActiveRewardCard,
DispatchMetricInfo,
} from '../../components/rewards-container/active-rewards';
import { type MarketMap, useMarketsMapProvider } from '@vegaprotocol/markets';
import format from 'date-fns/format';
import {
@@ -52,6 +52,13 @@ import {
isScopedToTeams,
useRewards,
} from '../../lib/hooks/use-rewards';
import {
ActiveRewardCard,
DispatchMetricInfo,
} from '../../components/rewards-container/reward-card';
import { usePartyProfilesQuery } from '../../components/vega-wallet-connect-button/__generated__/PartyProfiles';
const formatDate = (date: Date) => format(date, 'yyyy/MM/dd hh:mm:ss');
export const CompetitionsTeam = () => {
const t = useT();
@@ -140,11 +147,25 @@ const TeamPage = ({
const t = useT();
const [showGames, setShowGames] = useState(true);
const createdAt = new Date(team.createdAt);
const closedIndicator = team.closed ? (
<div className="border rounded border-vega-clight-300 dark:border-vega-cdark-300 px-1 pt-[1px] flex items-baseline gap-1">
<VegaIcon name={VegaIconNames.LOCK} size={10} />
<span>{t('Private')}</span>
</div>
) : (
<div className="border rounded border-vega-clight-300 dark:border-vega-cdark-300 px-1 pt-[1px] flex items-baseline gap-1">
<VegaIcon name={VegaIconNames.GLOBE} size={10} />
<span>{t('Public')}</span>
</div>
);
return (
<LayoutWithGradient>
<header className="flex gap-3 lg:gap-4 pt-5 lg:pt-10">
<TeamAvatar teamId={team.teamId} imgUrl={team.avatarUrl} />
<div className="flex flex-col items-start gap-1 lg:gap-3">
<div className="flex flex-col items-start gap-1 lg:gap-2">
<h1
className="calt text-2xl lg:text-3xl xl:text-5xl"
data-testid="team-name"
@@ -154,6 +175,38 @@ const TeamPage = ({
<div className="flex gap-2">
<JoinTeam team={team} partyTeam={partyTeam} refetch={refetch} />
<UpdateTeamButton team={team} />
{team.teamUrl && team.teamUrl.length > 0 && (
<Tooltip description={t("Visit the team's page.")}>
<span>
<TradingAnchorButton
intent={Intent.Info}
target="_blank"
referrerPolicy="no-referrer"
href={team.teamUrl}
>
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={16} />
</TradingAnchorButton>
</span>
</Tooltip>
)}
<CopyWithTooltip
description={t('Copy this page url.')}
text={globalThis.location.href}
>
<button className="h-10 w-7">
<VegaIcon name={VegaIconNames.COPY} size={16} />
</button>
</CopyWithTooltip>
</div>
<div className="flex gap-2 items-baseline text-xs text-muted font-alpha calt">
{closedIndicator}
<div className="">
{t('Created at')}:{' '}
<span className="text-vega-cdark-600 dark:text-vega-clight-600 ">
{formatDate(createdAt)}
</span>{' '}
({t('epoch')}: {team.createdAtEpoch})
</div>
</div>
</div>
</header>
@@ -230,118 +283,138 @@ const Games = ({
};
return (
<Table
columns={[
{
name: 'epoch',
displayName: t('Epoch'),
},
{
name: 'endtime',
displayName: t('End time'),
},
{ name: 'type', displayName: t('Type') },
{
name: 'asset',
displayName: t('Reward asset'),
},
{ name: 'daily', displayName: t('Daily reward amount') },
{ name: 'rank', displayName: t('Rank') },
{ name: 'amount', displayName: t('Amount earned this epoch') },
{ name: 'total', displayName: t('Cumulative amount earned') },
{
name: 'participatingTeams',
displayName: t('No. of participating teams'),
},
{
name: 'participatingMembers',
displayName: t('No. of participating members'),
},
].map((c) => ({ ...c, headerClassName: 'text-left' }))}
data={games.map((game) => {
let transfer = transfers?.find((t) => {
if (!isScopedToTeams(t)) return false;
<div className="text-sm">
<Table
columns={[
{
name: 'epoch',
displayName: t('Epoch'),
},
{
name: 'endtime',
displayName: t('End time'),
},
{ name: 'type', displayName: t('Type') },
{
name: 'asset',
displayName: t('Reward asset'),
},
{ name: 'daily', displayName: t('Daily reward amount') },
{ name: 'rank', displayName: t('Rank') },
{ name: 'amount', displayName: t('Amount earned this epoch') },
{ name: 'total', displayName: t('Cumulative amount earned') },
{
name: 'participatingTeams',
displayName: t('No. of participating teams'),
},
{
name: 'participatingMembers',
displayName: t('No. of participating members'),
},
].map((c) => ({ ...c, headerClassName: 'text-left' }))}
data={games.map((game) => {
let transfer = transfers?.find((t) => {
if (!isScopedToTeams(t)) return false;
const idMatch = t.transfer.gameId === game.id;
const metricMatch =
t.transfer.kind.dispatchStrategy?.dispatchMetric ===
game.team.rewardMetric;
const idMatch = t.transfer.gameId === game.id;
const metricMatch =
t.transfer.kind.dispatchStrategy?.dispatchMetric ===
game.team.rewardMetric;
const start = t.transfer.kind.startEpoch <= game.epoch;
const end = t.transfer.kind.endEpoch
? t.transfer.kind.endEpoch >= game.epoch
: true;
const start = t.transfer.kind.startEpoch <= game.epoch;
const end = t.transfer.kind.endEpoch
? t.transfer.kind.endEpoch >= game.epoch
: true;
const rejected = t.transfer.status === TransferStatus.STATUS_REJECTED;
const rejected =
t.transfer.status === TransferStatus.STATUS_REJECTED;
return idMatch && metricMatch && start && end && !rejected;
});
if (!transfer || !isScopedToTeams(transfer)) transfer = undefined;
const asset = transfer?.transfer.asset;
return idMatch && metricMatch && start && end && !rejected;
});
if (!transfer || !isScopedToTeams(transfer)) transfer = undefined;
const asset = transfer?.transfer.asset;
const dailyAmount =
asset && transfer
const dailyAmount =
asset && transfer
? addDecimalsFormatNumberQuantum(
transfer.transfer.amount,
asset.decimals,
asset.quantum
)
: '-';
const earnedAmount = asset
? addDecimalsFormatNumberQuantum(
transfer.transfer.amount,
game.team.rewardEarned,
asset.decimals,
asset.quantum
)
: '-';
const earnedAmount = asset
? addDecimalsFormatNumberQuantum(
game.team.rewardEarned,
asset.decimals,
asset.quantum
)
: '-';
const totalAmount = asset
? addDecimalsFormatNumberQuantum(
game.team.totalRewardsEarned,
asset.decimals,
asset.quantum
)
: '-';
const totalAmount = asset
? addDecimalsFormatNumberQuantum(
game.team.totalRewardsEarned,
asset.decimals,
asset.quantum
)
: '-';
const assetSymbol = asset ? <RewardAssetCell asset={asset} /> : '-';
const assetSymbol = asset ? <RewardAssetCell asset={asset} /> : '-';
return {
id: game.id,
amount: dependable(earnedAmount),
asset: dependable(assetSymbol),
daily: dependable(dailyAmount),
endtime: <EndTimeCell epoch={game.epoch} />,
epoch: game.epoch,
participatingMembers: game.numberOfParticipants,
participatingTeams: game.entities.length,
rank: game.team.rank,
total: totalAmount,
// type: DispatchMetricLabels[game.team.rewardMetric as DispatchMetric],
type: dependable(
<GameTypeCell transfer={transfer} allMarkets={allMarkets} />
),
};
})}
noCollapse={false}
/>
return {
id: game.id,
amount: dependable(earnedAmount),
asset: dependable(assetSymbol),
daily: dependable(dailyAmount),
endtime: <EndTimeCell epoch={game.epoch} />,
epoch: game.epoch,
participatingMembers: game.numberOfParticipants,
participatingTeams: game.entities.length,
rank: game.team.rank,
total: totalAmount,
// type: DispatchMetricLabels[game.team.rewardMetric as DispatchMetric],
type: dependable(
<GameTypeCell transfer={transfer} allMarkets={allMarkets} />
),
};
})}
noCollapse={false}
/>
</div>
);
};
const Members = ({ members }: { members?: Member[] }) => {
const t = useT();
const partyIds = members?.map((m) => m.referee) || [];
const { data: profilesData } = usePartyProfilesQuery({
variables: {
partyIds,
},
skip: partyIds.length === 0,
});
const profiles = removePaginationWrapper(
profilesData?.partiesProfilesConnection?.edges
);
if (!members?.length) {
return <p>{t('No members')}</p>;
}
const data = orderBy(
members.map((m) => ({
referee: <RefereeLink pubkey={m.referee} isCreator={m.isCreator} />,
referee: (
<RefereeLink
pubkey={m.referee}
isCreator={m.isCreator}
profiles={profiles}
/>
),
rewards: formatNumber(m.totalQuantumRewards),
volume: formatNumber(m.totalQuantumVolume),
gamesPlayed: formatNumber(m.totalGamesPlayed),
joinedAt: getDateTimeFormat().format(new Date(m.joinedAt)),
joinedAt: formatDate(new Date(m.joinedAt)),
joinedAtEpoch: Number(m.joinedAtEpoch),
})),
'joinedAtEpoch',
@@ -349,45 +422,69 @@ const Members = ({ members }: { members?: Member[] }) => {
);
return (
<Table
columns={[
{ name: 'referee', displayName: t('Member ID') },
{ name: 'rewards', displayName: t('Rewards earned') },
{ name: 'volume', displayName: t('Total volume') },
{ name: 'gamesPlayed', displayName: t('Games played') },
{
name: 'joinedAt',
displayName: t('Joined at'),
},
{
name: 'joinedAtEpoch',
displayName: t('Joined epoch'),
},
]}
data={data}
noCollapse={true}
/>
<div className="text-sm">
<Table
columns={[
{ name: 'referee', displayName: t('Member') },
{ name: 'rewards', displayName: t('Rewards earned') },
{ name: 'volume', displayName: t('Total volume') },
{ name: 'gamesPlayed', displayName: t('Games played') },
{
name: 'joinedAt',
displayName: t('Joined at'),
},
{
name: 'joinedAtEpoch',
displayName: t('Joined epoch'),
},
]}
data={data}
noCollapse={true}
/>
</div>
);
};
const RefereeLink = ({
pubkey,
isCreator,
profiles,
}: {
pubkey: string;
isCreator: boolean;
profiles?: { partyId: string; alias: string }[];
}) => {
const t = useT();
const linkCreator = useLinks(DApp.Explorer);
const link = linkCreator(EXPLORER_PARTIES.replace(':id', pubkey));
const alias = profiles?.find((p) => p.partyId === pubkey)?.alias;
return (
<>
<div className="flex items-baseline gap-2">
<Link to={link} target="_blank" className="underline underline-offset-4">
{truncateMiddle(pubkey)}
</Link>{' '}
<span className="text-muted text-xs">{isCreator ? t('Owner') : ''}</span>
</>
{alias || truncateMiddle(pubkey)}
</Link>
{!alias && (
<Tooltip
description={t(
'You can set your pubkey alias by using the key selector in the top right corner.'
)}
>
<button className="text-muted text-xs">
<VegaIcon name={VegaIconNames.QUESTION_MARK} size={14} />
</button>
</Tooltip>
)}
{alias && (
<span className="text-muted text-xs">{truncateMiddle(pubkey)}</span>
)}
{isCreator && (
<span className="text-muted text-xs border border-vega-clight-300 dark:border-vega-cdark-300 rounded px-1 py-[1px]">
{t('Owner')}
</span>
)}
</div>
);
};
@@ -411,15 +508,12 @@ const EndTimeCell = ({ epoch }: { epoch?: number }) => {
variables: {
epochId: epoch ? epoch.toString() : undefined,
},
fetchPolicy: 'cache-and-network',
fetchPolicy: 'cache-first',
});
if (loading) return <Loader size="small" />;
if (data) {
return format(
new Date(data.epoch.timestamps.expiry),
'yyyy/MM/dd hh:mm:ss'
);
return formatDate(new Date(data.epoch.timestamps.expiry));
}
return null;
@@ -70,6 +70,12 @@ export const JoinButton = ({
}) => {
const t = useT();
/**
* A team cannot be joined (closed) when set as such
* and the currently connected pubkey is not whitelisted.
*/
const isTeamClosed = team.closed && !team.allowList.includes(pubKey || '');
if (!pubKey || isReadOnly) {
return (
<Tooltip description={t('Connect your wallet to join the team')}>
@@ -79,8 +85,9 @@ export const JoinButton = ({
</Tooltip>
);
}
// Party is the creator of a team
else if (partyTeam && partyTeam.referrer === pubKey) {
if (partyTeam && partyTeam.referrer === pubKey) {
// Party is the creator of THIS team
if (partyTeam.teamId === team.teamId) {
return (
@@ -105,8 +112,24 @@ export const JoinButton = ({
);
}
}
// Party is in a team, but not this one
else if (partyTeam && partyTeam.teamId !== team.teamId) {
if (partyTeam && partyTeam.teamId !== team.teamId) {
// This team is closed.
if (isTeamClosed) {
return (
<Tooltip description={t('You cannot join a private team')}>
<Button
intent={Intent.Primary}
data-testid="switch-team-button"
disabled={true}
>
{t('Switch team')}{' '}
</Button>
</Tooltip>
);
}
// This team is open.
return (
<Button
onClick={() => onJoin('switch')}
@@ -117,8 +140,9 @@ export const JoinButton = ({
</Button>
);
}
// Joined. Current party is already in this team
else if (partyTeam && partyTeam.teamId === team.teamId) {
if (partyTeam && partyTeam.teamId === team.teamId) {
return (
<Button intent={Intent.None} disabled={true}>
<span className="flex items-center gap-2">
@@ -131,6 +155,17 @@ export const JoinButton = ({
);
}
// This team is closed.
if (isTeamClosed) {
return (
<Tooltip description={t('You cannot join a closed team')}>
<Button intent={Intent.Primary} disabled={true}>
{t('Join team')}
</Button>
</Tooltip>
);
}
// This team is open.
return (
<Button onClick={() => onJoin('join')} intent={Intent.Primary}>
{t('Join team')}
@@ -5,8 +5,8 @@ import type { Market } from '@vegaprotocol/markets';
import {
addDecimalsFormatNumber,
fromNanoSeconds,
getExpiryDate,
getMarketExpiryDate,
useExpiryDate,
} from '@vegaprotocol/utils';
import {
Last24hPriceChange,
@@ -20,6 +20,7 @@ import {
useMarketTradingMode,
useExternalTwap,
getQuoteName,
useMarketState,
} from '@vegaprotocol/markets';
import { MarketState as State } from '@vegaprotocol/types';
import { HeaderStat } from '../../components/header';
@@ -44,6 +45,12 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => {
const asset = getAsset(market);
const quoteUnit = getQuoteName(market);
const dataSourceSpec = market.markPriceConfiguration?.dataSourcesSpec?.[1];
const sourceType =
dataSourceSpec?.sourceType.__typename === 'DataSourceDefinitionExternal' &&
dataSourceSpec?.sourceType.sourceType.__typename === 'EthCallSpec' &&
dataSourceSpec?.sourceType.sourceType;
return (
<>
<HeaderStat heading={t('Mark Price')} testId="market-price">
@@ -61,16 +68,13 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => {
<HeaderStat heading={t('Volume (24h)')} testId="market-volume">
<Last24hVolume
marketId={market.id}
positionDecimalPlaces={market.positionDecimalPlaces}
marketDecimals={market.decimalPlaces}
positionDecimalPlaces={market.positionDecimalPlaces}
quoteUnit={quoteUnit}
/>
</HeaderStat>
<HeaderStatMarketTradingMode
marketId={market.id}
initialTradingMode={market.tradingMode}
/>
<MarketState market={market} />
<HeaderStatMarketTradingMode marketId={market.id} />
<MarketState marketId={market.id} />
{asset ? (
<HeaderStat
heading={t('Settlement asset')}
@@ -127,14 +131,25 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => {
{t(
'The external time weighted average price (TWAP) received from the data source defined in the data sourcing specification.'
)}
{DocsLinks && (
<ExternalLink
href={DocsLinks.ETH_DATA_SOURCES}
className="mt-2"
>
{t('Find out more')}
</ExternalLink>
)}
<div className="flex flex-col gap-1">
{DocsLinks && (
<ExternalLink
href={DocsLinks.ETH_DATA_SOURCES}
className="mt-2"
>
{t('Find out more')}
</ExternalLink>
)}
{sourceType && (
<ExternalLink
data-testid="oracle-spec-links"
href={`${VEGA_EXPLORER_URL}/markets/${market.id}/oracles#${sourceType.address}`}
className="text-xs my-1"
>
{t('Oracle specification')}
</ExternalLink>
)}
</div>
</div>
}
testId="index-price"
@@ -264,13 +279,13 @@ export const FundingCountdown = ({ marketId }: { marketId: string }) => {
};
const ExpiryLabel = ({ market }: ExpiryLabelProps) => {
const content = market.tradableInstrument.instrument.metadata.tags
? getExpiryDate(
market.tradableInstrument.instrument.metadata.tags,
market.marketTimestamps.close,
market.state
)
: '-';
const { data: marketState } = useMarketState(market.id);
const content =
useExpiryDate(
market.tradableInstrument.instrument.metadata.tags,
market.marketTimestamps.close,
marketState
) || '-';
return <div data-testid="trading-expiry">{content}</div>;
};
@@ -283,6 +298,7 @@ const ExpiryTooltipContent = ({
market,
explorerUrl,
}: ExpiryTooltipContentProps) => {
const { data: state } = useMarketState(market.id);
const t = useT();
if (market.marketTimestamps.close === null) {
const oracleId =
@@ -298,8 +314,8 @@ const ExpiryTooltipContent = ({
const isExpired =
metadataExpiryDate &&
Date.now() - metadataExpiryDate.valueOf() > 0 &&
(market.state === State.STATE_TRADING_TERMINATED ||
market.state === State.STATE_SETTLED);
(state === State.STATE_TRADING_TERMINATED ||
state === State.STATE_SETTLED);
return (
<section data-testid="expiry-tooltip">
+31 -109
View File
@@ -40,7 +40,6 @@ describe('Closed', () => {
const market = createMarketFragment({
id: marketId,
state: MarketState.STATE_SETTLED,
tradableInstrument: {
instrument: {
metadata: {
@@ -96,6 +95,7 @@ describe('Closed', () => {
const marketsData = createMarketsDataFragment({
__typename: 'MarketData',
marketState: MarketState.STATE_SETTLED,
market: {
__typename: 'Market',
id: marketId,
@@ -199,13 +199,16 @@ describe('Closed', () => {
it('renders correctly formatted and filtered rows', async () => {
await renderComponent([marketsMock, marketsDataMock, oracleDataMock]);
await waitFor(() => {
expect(screen.getAllByRole('gridcell').length).toBeGreaterThan(0);
});
const assetSymbol = getAsset(market).symbol;
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
market.tradableInstrument.instrument.code,
MarketStateMapping[market.state],
MarketStateMapping[marketsData.marketState],
'3 days ago',
/* eslint-disable @typescript-eslint/no-non-null-assertion */
addDecimalsFormatNumber(marketsData.bestBidPrice, market.decimalPlaces),
@@ -224,87 +227,6 @@ describe('Closed', () => {
});
});
it('only renders settled and terminated markets', async () => {
const mixedMarkets = [
{
// include as settled
__typename: 'MarketEdge' as const,
node: createMarketFragment({
id: 'include-0',
state: MarketState.STATE_SETTLED,
}),
},
{
// omit this market
__typename: 'MarketEdge' as const,
node: createMarketFragment({
id: 'discard-0',
state: MarketState.STATE_SUSPENDED,
}),
},
{
// include as terminated
__typename: 'MarketEdge' as const,
node: createMarketFragment({
id: 'include-1',
state: MarketState.STATE_TRADING_TERMINATED,
}),
},
{
// omit this market
__typename: 'MarketEdge' as const,
node: createMarketFragment({
id: 'discard-1',
state: MarketState.STATE_ACTIVE,
}),
},
];
const mixedMarketsMock: MockedResponse<MarketsQuery> = {
request: {
query: MarketsDocument,
},
result: {
data: {
marketsConnection: {
__typename: 'MarketConnection',
edges: mixedMarkets,
},
},
},
};
await renderComponent([mixedMarketsMock, marketsDataMock, oracleDataMock]);
// check that the number of rows in datagrid is 2
const container = within(
document.querySelector('.ag-center-cols-container') as HTMLElement
);
const expectedRows = mixedMarkets.filter((m) => {
return [
MarketState.STATE_SETTLED,
MarketState.STATE_TRADING_TERMINATED,
].includes(m.node.state);
});
await waitFor(() => {
// check rows length is correct
const rows = container.getAllByRole('row');
expect(rows).toHaveLength(expectedRows.length);
});
// check that only included ids are shown
const cells = screen
.getAllByRole('gridcell')
.filter((cell) => cell.getAttribute('col-id') === 'code')
.map((cell) => {
const marketCode = within(cell).getByTestId('stack-cell-primary');
return marketCode.textContent;
});
expect(cells).toEqual(
expectedRows.map((m) => m.node.tradableInstrument.instrument.code)
);
});
it('display market actions', async () => {
// Use market with a successor Id as the actions dropdown will optionally
// show a link to the successor market
@@ -312,8 +234,7 @@ describe('Closed', () => {
{
__typename: 'MarketEdge' as const,
node: createMarketFragment({
id: 'include-0',
state: MarketState.STATE_SETTLED,
id: marketId,
successorMarketID: 'successor',
parentMarketID: 'parent',
}),
@@ -338,31 +259,33 @@ describe('Closed', () => {
oracleDataMock,
]);
const actionCell = screen
.getAllByRole('gridcell')
.find((el) => el.getAttribute('col-id') === 'market-actions');
await waitFor(async () => {
const actionCell = screen
.getAllByRole('gridcell')
.find((el) => el.getAttribute('col-id') === 'market-actions');
await userEvent.click(
within(actionCell as HTMLElement).getByTestId('dropdown-menu')
);
await userEvent.click(
within(actionCell as HTMLElement).getByTestId('dropdown-menu')
);
expect(screen.getByRole('menu')).toBeInTheDocument();
expect(screen.getByRole('menu')).toBeInTheDocument();
expect(
screen.getByRole('menuitem', { name: 'Copy Market ID' })
).toBeInTheDocument();
expect(
screen.getByRole('menuitem', { name: 'View on Explorer' })
).toBeInTheDocument();
expect(
screen.getByRole('menuitem', { name: 'View settlement asset details' })
).toBeInTheDocument();
expect(
screen.getByRole('menuitem', { name: 'View parent market' })
).toBeInTheDocument();
expect(
screen.getByRole('menuitem', { name: 'View successor market' })
).toBeInTheDocument();
expect(
screen.getByRole('menuitem', { name: 'Copy Market ID' })
).toBeInTheDocument();
expect(
screen.getByRole('menuitem', { name: 'View on Explorer' })
).toBeInTheDocument();
expect(
screen.getByRole('menuitem', { name: 'View settlement asset details' })
).toBeInTheDocument();
expect(
screen.getByRole('menuitem', { name: 'View parent market' })
).toBeInTheDocument();
expect(
screen.getByRole('menuitem', { name: 'View successor market' })
).toBeInTheDocument();
});
});
it('successor market should be visible', async () => {
@@ -370,8 +293,7 @@ describe('Closed', () => {
{
__typename: 'MarketEdge' as const,
node: createMarketFragment({
id: 'include-0',
state: MarketState.STATE_SETTLED,
id: marketId,
successorMarketID: 'successor',
}),
},
+4 -4
View File
@@ -15,7 +15,7 @@ import {
addDecimalsFormatNumber,
getMarketExpiryDate,
} from '@vegaprotocol/utils';
import { closedMarketsWithDataProvider, getAsset } from '@vegaprotocol/markets';
import { closedMarketsProvider, getAsset } from '@vegaprotocol/markets';
import type { DataSourceFilterFragment } from '@vegaprotocol/markets';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
@@ -35,7 +35,7 @@ interface Row {
code: string;
name: string;
decimalPlaces: number;
state: MarketState;
state?: MarketState;
metadata: string[];
closeTimestamp: string | null;
bestBidPrice: string | undefined;
@@ -53,7 +53,7 @@ interface Row {
export const Closed = () => {
const { data: marketData, error } = useDataProvider({
dataProvider: closedMarketsWithDataProvider,
dataProvider: closedMarketsProvider,
variables: undefined,
});
@@ -87,7 +87,7 @@ export const Closed = () => {
code: instrument.code,
name: instrument.name,
decimalPlaces: market.decimalPlaces,
state: market.state,
state: market.data?.marketState,
metadata: instrument.metadata.tags ?? [],
closeTimestamp: market.marketTimestamps.close,
bestBidPrice: market.data?.bestBidPrice,
@@ -0,0 +1 @@
export { ProposalsList } from './proposals-list';
@@ -6,7 +6,7 @@ import {
ActionsDropdown,
} from '@vegaprotocol/ui-toolkit';
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
import { useT } from '../use-t';
import { useT } from '../../../lib/use-t';
export const ProposalActionsDropdown = ({ id }: { id: string }) => {
const t = useT();
@@ -18,6 +18,7 @@ export const ProposalActionsDropdown = ({ id }: { id: string }) => {
<Link
href={linkCreator(TOKEN_PROPOSAL.replace(':id', id))}
target="_blank"
className="flex items-center gap-2"
>
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={16} />
{t('View proposal')}
@@ -1,77 +1,71 @@
import { render, screen, waitFor, within } from '@testing-library/react';
import merge from 'lodash/merge';
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import { ProposalsList } from './proposals-list';
import * as Types from '@vegaprotocol/types';
import { createProposalListFieldsFragment } from '../../lib/proposals-data-provider/proposals.mock';
import type { ProposalsListQuery } from '../../lib';
import { ProposalsListDocument } from '../../lib';
import type { PartialDeep } from 'type-fest';
import { MarketState } from '@vegaprotocol/types';
import {
createMarketFragment,
createMarketsDataFragment,
} from '@vegaprotocol/mock';
import {
type MarketsQuery,
MarketsDocument,
type MarketsQueryVariables,
type MarketsDataQuery,
type MarketsDataQueryVariables,
MarketsDataDocument,
} from '@vegaprotocol/markets';
const parentMarketName = 'Parent Market Name';
const ParentMarketCell = () => <span>{parentMarketName}</span>;
describe('ProposalsList', () => {
const rowContainerSelector = '.ag-center-cols-container';
const createProposalsMock = (override?: PartialDeep<ProposalsListQuery>) => {
const defaultProposalEdges = [
{
__typename: 'ProposalEdge' as const,
node: createProposalListFieldsFragment({
id: 'id-1',
state: Types.ProposalState.STATE_OPEN,
}),
},
{
__typename: 'ProposalEdge' as const,
node: createProposalListFieldsFragment({
id: 'id-2',
state: Types.ProposalState.STATE_PASSED,
}),
},
{
__typename: 'ProposalEdge' as const,
node: createProposalListFieldsFragment({
id: 'id-3',
state: Types.ProposalState.STATE_WAITING_FOR_NODE_VOTE,
}),
},
];
const data = merge(
{
proposalsConnection: {
__typename: 'ProposalsConnection' as const,
edges: defaultProposalEdges,
},
},
override
);
const mock: MockedResponse<ProposalsListQuery> = {
request: {
query: ProposalsListDocument,
variables: {
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
},
},
result: {
data,
},
};
return mock;
};
beforeEach(() => {
jest.clearAllMocks();
const market = createMarketFragment();
const marketData = createMarketsDataFragment({
marketState: MarketState.STATE_PROPOSED,
});
it('should be properly rendered', async () => {
const mock = createProposalsMock();
const marketMock: MockedResponse<MarketsQuery, MarketsQueryVariables> = {
request: {
query: MarketsDocument,
},
result: {
data: {
marketsConnection: {
edges: [
{
node: market,
},
],
},
},
},
};
const marketDataMock: MockedResponse<
MarketsDataQuery,
MarketsDataQueryVariables
> = {
request: {
query: MarketsDataDocument,
},
result: {
data: {
marketsConnection: {
edges: [
{
node: { data: marketData },
},
],
},
},
},
};
render(
<MockedProvider mocks={[mock]}>
<MockedProvider mocks={[marketMock, marketDataMock]}>
<ProposalsList cellRenderers={{ ParentMarketCell }} />
</MockedProvider>
);
@@ -102,37 +96,47 @@ describe('ProposalsList', () => {
expect(await container.findAllByRole('row')).toHaveLength(
// @ts-ignore data is mocked
mock?.result?.data.proposalsConnection.edges.length
marketMock?.result?.data.marketsConnection.edges.length
);
expect(
container.getAllByRole('gridcell', {
name: (_, element) =>
element.getAttribute('col-id') ===
'terms.change.successorConfiguration.parentMarketId',
element.getAttribute('col-id') === 'parentMarketID',
})[0]
).toHaveTextContent(parentMarketName);
});
it('empty response should causes no data message display', async () => {
const mock: MockedResponse<ProposalsListQuery> = {
const marketMock: MockedResponse<MarketsQuery, MarketsQueryVariables> = {
request: {
query: ProposalsListDocument,
variables: {
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
},
query: MarketsDocument,
},
result: {
data: {
proposalsConnection: {
__typename: 'ProposalsConnection',
marketsConnection: {
edges: [],
},
},
},
};
const marketDataMock: MockedResponse<
MarketsDataQuery,
MarketsDataQueryVariables
> = {
request: {
query: MarketsDataDocument,
},
result: {
data: {
marketsConnection: {
edges: [],
},
},
},
};
render(
<MockedProvider mocks={[mock]}>
<MockedProvider mocks={[marketMock, marketDataMock]}>
<ProposalsList cellRenderers={{ ParentMarketCell }} />
</MockedProvider>
);
@@ -0,0 +1,37 @@
import type { FC } from 'react';
import { AgGrid } from '@vegaprotocol/datagrid';
import { useProposedMarketsList } from '@vegaprotocol/markets';
import { type ProposalListFieldsFragment } from '@vegaprotocol/proposals';
import { useColumnDefs } from './use-column-defs';
import { useT } from '../../../lib/use-t';
const defaultColDef = {
sortable: true,
filter: true,
resizable: true,
filterParams: { buttons: ['reset'] },
};
interface ProposalListProps {
cellRenderers: {
[name: string]: FC<{ value: string; data: ProposalListFieldsFragment }>;
};
}
export const ProposalsList = ({ cellRenderers }: ProposalListProps) => {
const t = useT();
const { data } = useProposedMarketsList();
const columnDefs = useColumnDefs();
return (
<AgGrid
columnDefs={columnDefs}
rowData={data}
defaultColDef={defaultColDef}
getRowId={({ data }) => data.id}
overlayNoRowsTemplate={t('No proposed markets')}
components={cellRenderers}
rowHeight={45}
/>
);
};
@@ -13,12 +13,16 @@ import type {
VegaValueFormatterParams,
} from '@vegaprotocol/datagrid';
import {
ProposalProductTypeShortName,
ProposalStateMapping,
MarketStateMapping,
ProductTypeMapping,
ProductTypeShortName,
} from '@vegaprotocol/types';
import type { ProposalListFieldsFragment } from '../../lib/proposals-data-provider/__generated__/Proposals';
import { ProposalActionsDropdown } from '../proposal-actions-dropdown';
import { useT } from '../../use-t';
import { ProposalActionsDropdown } from './proposal-actions-dropdown';
import {
type MarketMaybeWithData,
getProductType,
} from '@vegaprotocol/markets';
import { useT } from '../../../lib/use-t';
export const useColumnDefs = () => {
const t = useT();
@@ -28,7 +32,7 @@ export const useColumnDefs = () => {
{
colId: 'market',
headerName: t('Market'),
field: 'terms.change.instrument.code',
field: 'tradableInstrument.instrument.code',
pinned: true,
cellStyle: { lineHeight: '14px' },
cellRenderer: ({
@@ -36,20 +40,10 @@ export const useColumnDefs = () => {
data,
}: {
value: string;
data: ProposalListFieldsFragment;
data: MarketMaybeWithData;
}) => {
if (!value || !data) return '-';
const getProductType = (data: ProposalListFieldsFragment) => {
if (
data.terms.__typename === 'ProposalTerms' &&
data.terms.change.__typename === 'NewMarket'
) {
return data.terms.change.instrument.product?.__typename;
}
return undefined;
};
const productType = getProductType(data);
return (
productType && (
@@ -57,10 +51,10 @@ export const useColumnDefs = () => {
primary={value}
secondary={
<span
title={ProposalProductTypeShortName[productType]}
title={ProductTypeMapping[productType]}
className="uppercase"
>
{ProposalProductTypeShortName[productType]}
{ProductTypeShortName[productType]}
</span>
}
/>
@@ -71,47 +65,53 @@ export const useColumnDefs = () => {
{
colId: 'asset',
headerName: t('Settlement asset'),
field: 'terms.change.instrument.product.settlementAsset.symbol',
field: 'tradableInstrument.instrument.product.settlementAsset.symbol',
},
{
colId: 'state',
headerName: t('State'),
field: 'state',
field: 'data.marketState',
valueFormatter: ({
value,
}: VegaValueFormatterParams<ProposalListFieldsFragment, 'state'>) =>
value ? ProposalStateMapping[value] : '-',
}: VegaValueFormatterParams<
MarketMaybeWithData,
'data.marketState'
>) => {
return value ? MarketStateMapping[value] : '-';
},
filter: SetFilter,
filterParams: {
set: ProposalStateMapping,
set: MarketStateMapping,
},
},
{
headerName: t('Parent market'),
field: 'terms.change.successorConfiguration.parentMarketId',
field: 'parentMarketID',
cellRenderer: 'ParentMarketCell',
},
{
colId: 'closing-date',
headerName: t('Closing date'),
field: 'terms.closingDatetime',
field: 'marketTimestamps.pending',
valueFormatter: ({
value,
}: VegaValueFormatterParams<
ProposalListFieldsFragment,
'terms.closingDatetime'
>) => (value ? getDateTimeFormat().format(new Date(value)) : '-'),
MarketMaybeWithData,
'marketTimestamps.pending'
>) => {
return value ? getDateTimeFormat().format(new Date(value)) : '-';
},
filter: DateRangeFilter,
},
{
colId: 'enactment-date',
headerName: t('Enactment date'),
field: 'terms.enactmentDatetime',
field: 'marketTimestamps.open',
valueFormatter: ({
value,
}: VegaValueFormatterParams<
ProposalListFieldsFragment,
'terms.enactmentDatetime'
MarketMaybeWithData,
'marketTimestamps.open'
>) => (value ? getDateTimeFormat().format(new Date(value)) : '-'),
filter: DateRangeFilter,
},
@@ -120,10 +120,10 @@ export const useColumnDefs = () => {
...COL_DEFS.actions,
cellRenderer: ({
data,
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
if (!data?.id) return null;
}: VegaICellRendererParams<MarketMaybeWithData>) => {
if (!data?.marketProposal?.id) return null;
return <ProposalActionsDropdown id={data.id} />;
return <ProposalActionsDropdown id={data.marketProposal.id} />;
},
},
]);
@@ -1,4 +1,4 @@
import { ProposalsList } from '@vegaprotocol/proposals';
import { ProposalsList } from './proposals-list';
import { ParentMarketCell } from './parent-market-cell';
const cellRenderers = {
@@ -9,7 +9,7 @@ export interface SettlementDataCellProps {
oracleSpecId: string;
metaDate: Date | null;
closeTimestamp: string | null;
marketState: MarketState;
marketState?: MarketState;
}
export const SettlementDateCell = ({
@@ -15,7 +15,6 @@ import {
import { ButtonLink, Tooltip } from '@vegaprotocol/ui-toolkit';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import type {
MarketFieldsFragment,
MarketMaybeWithData,
MarketMaybeWithDataAndCandles,
} from '@vegaprotocol/markets';
@@ -126,7 +125,9 @@ export const useMarketsColumnDefs = () => {
valueFormatter: ({
data,
}: VegaValueFormatterParams<MarketMaybeWithData, 'state'>) => {
return data?.state ? Schema.MarketStateMapping[data.state] : '-';
return data?.data?.marketState
? Schema.MarketStateMapping[data?.data?.marketState]
: '-';
},
filter: SetFilter,
filterParams: {
@@ -166,9 +167,10 @@ export const useMarketsColumnDefs = () => {
valueFormatter: ({
data,
}: ValueFormatterParams<MarketMaybeWithDataAndCandles, 'candles'>) => {
const candles = data?.candles;
if (!data) return '-';
const candles = data.candles;
const vol = candles ? calcCandleVolume(candles) : '0';
const quoteName = getQuoteName(data as MarketFieldsFragment);
const quoteName = getQuoteName(data);
const volPrice =
candles &&
calcCandleVolumePrice(
@@ -1,6 +1,5 @@
import type { InMemoryCacheConfig } from '@apollo/client';
import {
AppLoader,
NetworkLoader,
useEnvironment,
useNodeSwitcherStore,
@@ -86,10 +85,6 @@ export const Bootstrapper = ({ children }: { children: ReactNode }) => {
}));
const config = useVegaWalletConfig();
if (!config) {
return <AppLoader />;
}
const ERR_DATA_LOADER = (
<Trans
i18nKey="It appears that the connection to the node <0>{{VEGA_URL}}</0> does not return necessary data, try switching to another node."
@@ -118,7 +113,11 @@ export const Bootstrapper = ({ children }: { children: ReactNode }) => {
skeleton={<Loading />}
failure={<Failure reason={t('Could not configure web3 provider')} />}
>
<WalletProvider config={config}>{children}</WalletProvider>
{config ? (
<WalletProvider config={config}>{children}</WalletProvider>
) : (
<Failure reason={t('Could not configure the wallet provider')} />
)}
</Web3Provider>
</DataLoader>
</NetworkLoader>
@@ -145,6 +144,30 @@ const cacheConfig: InMemoryCacheConfig = {
Product: {
keyFields: ['settlementAsset', ['id']],
},
Market: {
fields: {
/**
* Intercept cache field for tickSize because mainnet specific queries have been
* set up, marking this field as client only. The following can be removed when mainnet
* supports ticksize:
*
* 1. The typePolicy for tickSize below
* 2. The MarketInfoMainnet query in libs/markets/src/lib/components/market-info/MarketInfo.graphql
* 3. The ternary to switch queries in libs/markets/src/lib/components/market-info/market-info-data-provider.ts
* 4. The MarketsMainnet query in libs/markets/src/lib/markets.graphql
* 5. The ternary to switch queries in libs/markets/src/lib/markets-provider.ts
*/
tickSize: {
read(value) {
// value is not present, we have probably marked tickSize as a client only field
if (!value) return '1';
// Use fetch response value
return value;
},
},
},
},
MarketData: {
keyFields: ['market', ['id']],
},
@@ -44,6 +44,7 @@ export const CompetitionsLeaderboard = ({
const avatar = (
<TeamAvatar
key={td.teamId}
teamId={td.teamId}
imgUrl={td.avatarUrl}
alt={td.name}
@@ -67,7 +68,7 @@ export const CompetitionsLeaderboard = ({
),
earned: num(td.totalQuantumRewards),
games: num(td.totalGamesPlayed),
status: td.closed ? t('Closed') : t('Open'),
status: td.closed ? t('Private') : t('Public'),
volume: num(td.totalQuantumVolume),
};
})}
@@ -1,6 +1,81 @@
import { ActiveRewardCard } from '../rewards-container/active-rewards';
import { useT } from '../../lib/use-t';
import { type EnrichedRewardTransfer } from '../../lib/hooks/use-rewards';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useStakeAvailable } from '../../lib/hooks/use-stake-available';
import { useMyTeam } from '../../lib/hooks/use-my-team';
import {
ActiveRewardCard,
areAllMarketsSettled,
} from '../rewards-container/reward-card';
import {
VegaIcon,
VegaIconNames,
TradingInput,
} from '@vegaprotocol/ui-toolkit';
import { useState } from 'react';
import { type AssetFieldsFragment } from '@vegaprotocol/assets';
import { type MarketFieldsFragment } from '@vegaprotocol/markets';
import {
type TransferNode,
DispatchMetricLabels,
EntityScopeLabelMapping,
AccountType,
} from '@vegaprotocol/types';
export type Filter = {
searchTerm: string;
};
export const applyFilter = (
node: TransferNode & {
asset?: AssetFieldsFragment | null;
markets?: (MarketFieldsFragment | null)[];
},
filter: Filter
) => {
const { transfer } = node;
// if the transfer is a staking reward then it should be displayed
if (transfer.toAccountType === AccountType.ACCOUNT_TYPE_GLOBAL_REWARD) {
return true;
}
if (
transfer.kind.__typename !== 'RecurringTransfer' &&
transfer.kind.__typename !== 'RecurringGovernanceTransfer'
) {
return false;
}
if (
(transfer.kind.dispatchStrategy?.dispatchMetric &&
DispatchMetricLabels[transfer.kind.dispatchStrategy.dispatchMetric]
.toLowerCase()
.includes(filter.searchTerm.toLowerCase())) ||
transfer.asset?.symbol
.toLowerCase()
.includes(filter.searchTerm.toLowerCase()) ||
(
(transfer.kind.dispatchStrategy &&
EntityScopeLabelMapping[transfer.kind.dispatchStrategy.entityScope]) ||
'Unspecified'
)
.toLowerCase()
.includes(filter.searchTerm.toLowerCase()) ||
node.asset?.name
.toLocaleLowerCase()
.includes(filter.searchTerm.toLowerCase()) ||
node.markets?.some((m) =>
m?.tradableInstrument?.instrument?.name
.toLocaleLowerCase()
.includes(filter.searchTerm.toLowerCase())
)
) {
return true;
}
return false;
};
export const GamesContainer = ({
data,
@@ -10,6 +85,23 @@ export const GamesContainer = ({
currentEpoch: number;
}) => {
const t = useT();
const { pubKey } = useVegaWallet();
const { team } = useMyTeam();
const { stakeAvailable, isEligible, requiredStake } = useStakeAvailable();
const requirements = pubKey
? {
isEligible,
stakeAvailable,
requiredStake,
team,
pubKey,
}
: undefined;
const [filter, setFilter] = useState<Filter>({
searchTerm: '',
});
if (!data || data.length === 0) {
return (
@@ -20,24 +112,46 @@ export const GamesContainer = ({
}
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{data.map((game, i) => {
// TODO: Remove `kind` prop from ActiveRewardCard
const { transfer } = game;
if (
transfer.kind.__typename !== 'RecurringTransfer' ||
!transfer.kind.dispatchStrategy?.dispatchMetric
) {
return null;
}
return (
<ActiveRewardCard
key={i}
transferNode={game}
currentEpoch={currentEpoch}
/>
);
})}
<div>
{/** CARDS FILTER */}
{data.length > 1 && (
<TradingInput
onChange={(e) =>
setFilter((curr) => ({ ...curr, searchTerm: e.target.value }))
}
value={filter.searchTerm}
type="text"
placeholder={t(
'Search by reward dispatch metric, entity scope or asset name'
)}
data-testid="search-term"
className="mb-4 w-20 mr-2 max-w-xl"
prependElement={<VegaIcon name={VegaIconNames.SEARCH} />}
/>
)}
{/** CARDS */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{data
.filter((n) => applyFilter(n, filter))
// filter out the cards (rewards) for which all of the markets
// are settled
.filter((n) => !areAllMarketsSettled(n))
.map((game, i) => {
// TODO: Remove `kind` prop from ActiveRewardCard
const { transfer } = game;
if (!transfer.kind.dispatchStrategy?.dispatchMetric) {
return null;
}
return (
<ActiveRewardCard
key={i}
transferNode={game}
currentEpoch={currentEpoch}
requirements={requirements}
/>
);
})}
</div>
</div>
);
};
@@ -1,6 +1,4 @@
import { isValidUrl } from '@vegaprotocol/utils';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
const NUM_AVATARS = 20;
const AVATAR_PATHNAME_PATTERN = '/team-avatars/{id}.png';
@@ -13,26 +11,6 @@ export const getFallbackAvatar = (teamId: string) => {
return AVATAR_PATHNAME_PATTERN.replace('{id}', avatarId);
};
const useAvatar = (teamId: string, url: string) => {
const fallback = getFallbackAvatar(teamId);
const [avatar, setAvatar] = useState<string>(fallback);
useEffect(() => {
if (!isValidUrl(url)) return;
fetch(url, { cache: 'force-cache' })
.then((response) => {
if (response.ok) {
setAvatar(url);
}
})
.catch(() => {
/** noop */
});
});
return avatar;
};
export const TeamAvatar = ({
teamId,
imgUrl,
@@ -44,11 +22,11 @@ export const TeamAvatar = ({
alt?: string;
size?: 'large' | 'small';
}) => {
const img = useAvatar(teamId, imgUrl);
// const img = useAvatar(teamId, imgUrl);
return (
// eslint-disable-next-line @next/next/no-img-element
<img
src={img}
src={imgUrl}
alt={alt || 'Team avatar'}
className={classNames(
'rounded-full bg-vega-clight-700 dark:bg-vega-cdark-700 shrink-0',
@@ -58,6 +36,9 @@ export const TeamAvatar = ({
}
)}
referrerPolicy="no-referrer"
onError={(e) => {
e.currentTarget.src = getFallbackAvatar(teamId);
}}
/>
);
};
@@ -115,6 +115,7 @@ describe('MarketSettledBanner', () => {
open: '100',
close: '100',
volume: '100',
notional: '10000',
periodStart: subHours(new Date(now), 1).toISOString(),
},
},
@@ -125,6 +126,7 @@ describe('MarketSettledBanner', () => {
open: '100',
close: '200',
volume: '100',
notional: '10000',
periodStart: subHours(new Date(now), 2).toISOString(),
},
},
@@ -161,6 +163,7 @@ describe('MarketSettledBanner', () => {
open: '100',
close: '100',
volume: '100',
notional: '10000',
periodStart: '2020-01-01T00:00:00',
},
},
@@ -79,6 +79,7 @@ describe('MarketSelectorItem', () => {
high: '5',
low: '5',
volume: '50',
notional: '10000',
periodStart: yesterday.toISOString(),
},
{
@@ -87,6 +88,7 @@ describe('MarketSelectorItem', () => {
high: '10',
low: '10',
volume: '50',
notional: '10000',
periodStart: yesterday.toISOString(),
},
];
@@ -72,17 +72,17 @@ const MarketData = ({
const marketTradingMode = marketData
? marketData.marketTradingMode
: market.data
? market.data.marketTradingMode
: market.tradingMode;
: market.data?.marketTradingMode;
const mode = [
MarketTradingMode.TRADING_MODE_BATCH_AUCTION,
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
MarketTradingMode.TRADING_MODE_OPENING_AUCTION,
].includes(marketTradingMode)
? MarketTradingModeMapping[marketTradingMode]
: '';
const mode =
marketTradingMode &&
[
MarketTradingMode.TRADING_MODE_BATCH_AUCTION,
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
MarketTradingMode.TRADING_MODE_OPENING_AUCTION,
].includes(marketTradingMode)
? MarketTradingModeMapping[marketTradingMode]
: '';
const { oneDayCandles } = useCandles({ marketId: market.id });
@@ -120,7 +120,7 @@ export const MarketSelector = ({
<div data-testid="market-selector-list">
<MarketList
data={markets}
loading={loading}
loading={loading && !data}
error={error}
searchTerm={filter.searchTerm}
currentMarketId={currentMarketId}
@@ -409,7 +409,6 @@ describe('useMarketSelectorList', () => {
}),
createMarketFragment({
id: 'market-1',
state: MarketState.STATE_ACTIVE,
// @ts-ignore data not on fragment
data: createMarketsDataFragment({
marketState: MarketState.STATE_ACTIVE,
@@ -424,7 +423,6 @@ describe('useMarketSelectorList', () => {
}),
createMarketFragment({
id: 'market-2',
state: MarketState.STATE_ACTIVE,
// @ts-ignore data not on fragment
data: createMarketsDataFragment({
marketState: MarketState.STATE_ACTIVE,
@@ -439,7 +437,6 @@ describe('useMarketSelectorList', () => {
}),
createMarketFragment({
id: 'market-3',
state: MarketState.STATE_ACTIVE,
// @ts-ignore data not on fragment
data: createMarketsDataFragment({
marketState: MarketState.STATE_ACTIVE,
@@ -63,7 +63,11 @@ export const useMarketSelectorList = ({
[
(m) => {
if (!m.candles?.length) return 0;
return Number(priceChangePercentage(m.candles.map((c) => c.close)));
return Number(
priceChangePercentage(
m.candles.filter((c) => c.close !== '').map((c) => c.close)
)
);
},
],
[dir]
@@ -1,48 +1,18 @@
import throttle from 'lodash/throttle';
import type { MarketData, Market } from '@vegaprotocol/markets';
import { marketDataProvider } from '@vegaprotocol/markets';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { useMarketState } from '@vegaprotocol/markets';
import * as Schema from '@vegaprotocol/types';
import { HeaderStat } from '../header';
import { useCallback, useRef, useState } from 'react';
import * as constants from '../constants';
import { DocsLinks } from '@vegaprotocol/environment';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { useT } from '../../lib/use-t';
export const MarketState = ({ market }: { market: Market | null }) => {
export const MarketState = ({ marketId }: { marketId?: string }) => {
const t = useT();
const [marketState, setMarketState] = useState<Schema.MarketState | null>(
null
);
const throttledSetMarketState = useRef(
throttle((state: Schema.MarketState) => {
setMarketState(state);
}, constants.THROTTLE_UPDATE_TIME)
).current;
const update = useCallback(
({ data: marketData }: { data: MarketData | null }) => {
if (marketData) {
throttledSetMarketState(marketData.marketState);
}
return true;
},
[throttledSetMarketState]
);
useDataProvider({
dataProvider: marketDataProvider,
update,
variables: { marketId: market?.id || '' },
skip: !market?.id,
});
const { data: marketState } = useMarketState(marketId);
return (
<HeaderStat
heading={t('Status')}
description={useGetMarketStateTooltip(marketState)}
description={useGetMarketStateTooltip(marketState ?? undefined)}
testId="market-state"
>
{marketState ? Schema.MarketStateMapping[marketState] : '-'}
@@ -50,7 +20,7 @@ export const MarketState = ({ market }: { market: Market | null }) => {
);
};
const useGetMarketStateTooltip = (state: Schema.MarketState | null) => {
const useGetMarketStateTooltip = (state?: Schema.MarketState) => {
const t = useT();
if (state === Schema.MarketState.STATE_ACTIVE) {
return t('Enactment date reached and usual auction exit checks pass');
@@ -26,20 +26,15 @@ const getTradingModeLabel = (
interface HeaderStatMarketTradingModeProps {
marketId?: string;
onSelect?: (marketId: string, metaKey?: boolean) => void;
initialTradingMode?: Schema.MarketTradingMode;
initialTrigger?: Schema.AuctionTrigger;
}
export const HeaderStatMarketTradingMode = ({
marketId,
onSelect,
initialTradingMode,
initialTrigger,
}: HeaderStatMarketTradingModeProps) => {
const t = useT();
const { data } = useStaticMarketData(marketId);
const marketTradingMode = data?.marketTradingMode ?? initialTradingMode;
const trigger = data?.trigger ?? initialTrigger;
const { marketTradingMode, trigger } = data || {};
return (
<HeaderStat
@@ -56,8 +51,6 @@ export const HeaderStatMarketTradingMode = ({
export const MarketTradingMode = ({
marketId,
initialTradingMode,
initialTrigger,
inViewRoot,
}: Omit<HeaderStatMarketTradingModeProps, 'onUpdate'> & {
inViewRoot?: RefObject<Element>;
@@ -72,10 +65,7 @@ export const MarketTradingMode = ({
}
>
<span ref={ref}>
{getTradingModeLabel(
data?.marketTradingMode ?? initialTradingMode,
data?.trigger ?? initialTrigger
)}
{getTradingModeLabel(data?.marketTradingMode, data?.trigger)}
</span>
</Tooltip>
);
@@ -1,7 +1,11 @@
import { render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { NodeHealthContainer, NodeUrl } from './node-health';
import { MockedProvider } from '@apollo/client/testing';
import { MockedProvider, type MockedResponse } from '@apollo/client/testing';
import {
NodeCheckDocument,
type NodeCheckQuery,
} from '@vegaprotocol/environment';
const mockSetNodeSwitcher = jest.fn();
@@ -15,20 +19,50 @@ jest.mock('@vegaprotocol/environment', () => ({
}));
describe('NodeHealthContainer', () => {
const blockHeight = '1';
const nodeCheckMock: MockedResponse<NodeCheckQuery, never> = {
request: {
query: NodeCheckDocument,
},
result: {
data: {
statistics: {
chainId: 'chain-id',
blockHeight: blockHeight,
vegaTime: '12345',
},
networkParametersConnection: {
edges: [
{
node: {
key: 'a',
value: '1',
},
},
],
},
},
},
};
const renderComponent = (mocks: MockedResponse[] = []) => {
return render(
<MockedProvider mocks={mocks}>
<NodeHealthContainer />
</MockedProvider>
);
};
it('controls the node switcher dialog', async () => {
render(<NodeHealthContainer />, { wrapper: MockedProvider });
await waitFor(() => {
expect(screen.getByRole('button')).toBeInTheDocument();
});
renderComponent([nodeCheckMock]);
expect(await screen.findByRole('button')).toBeInTheDocument();
await userEvent.click(screen.getByRole('button'));
expect(mockSetNodeSwitcher).toHaveBeenCalled();
});
it('Shows node health data on hover', async () => {
render(<NodeHealthContainer />, { wrapper: MockedProvider });
await waitFor(() => {
expect(screen.getByRole('button')).toBeInTheDocument();
});
renderComponent([nodeCheckMock]);
expect(await screen.findByRole('button')).toBeInTheDocument();
await userEvent.hover(screen.getByRole('button'));
await waitFor(() => {
const portal = within(
@@ -36,12 +70,13 @@ describe('NodeHealthContainer', () => {
'[data-radix-popper-content-wrapper]'
) as HTMLElement
);
// two tooltips get rendered, I believe for animation purposes
const tooltip = within(portal.getAllByTestId('tooltip-content')[0]);
expect(
tooltip.getByRole('link', { name: /^Mainnet status & incidents/ })
).toBeInTheDocument();
expect(tooltip.getByText('Non operational')).toBeInTheDocument();
expect(tooltip.getByText('Operational')).toBeInTheDocument();
expect(tooltip.getByTitle('Connected node')).toHaveTextContent(
'vega-url.wtf'
);
@@ -1,6 +1,6 @@
import { OrderbookManager } from '@vegaprotocol/market-depth';
import { ViewType, useSidebar } from '../sidebar';
import { useDealTicketFormValues } from '@vegaprotocol/deal-ticket';
import { useDealTicketFormValues } from '@vegaprotocol/react-helpers';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
export const OrderbookContainer = ({ marketId }: { marketId: string }) => {
@@ -1,5 +1,5 @@
import { render, screen } from '@testing-library/react';
import { ActiveRewardCard, applyFilter } from './active-rewards';
import { applyFilter } from './active-rewards';
import {
AccountType,
AssetStatus,
@@ -11,6 +11,17 @@ import {
type Transfer,
} from '@vegaprotocol/types';
import { type EnrichedRewardTransfer } from '../../lib/hooks/use-rewards';
import { ActiveRewardCard } from './reward-card';
jest.mock('../../lib/hooks/__generated__/Rewards', () => ({
useTWAPQuery: () => ({
data: {
timeWeightedNotionalPosition: {
timeWeightedNotionalPosition: '1.00',
},
},
}),
}));
describe('ActiveRewards', () => {
const reward: EnrichedRewardTransfer = {
@@ -71,7 +82,7 @@ describe('ActiveRewards', () => {
};
it('renders with valid props', () => {
render(<ActiveRewardCard transferNode={reward} currentEpoch={115432} />);
render(<ActiveRewardCard transferNode={reward} currentEpoch={115500} />);
expect(
screen.getByText(/Liquidity provision fees received/i)
@@ -1,57 +1,24 @@
import { useT } from '../../lib/use-t';
import { addDecimalsFormatNumber, formatNumber } from '@vegaprotocol/utils';
import classNames from 'classnames';
import {
type VegaIconSize,
Tooltip,
VegaIcon,
VegaIconNames,
TradingInput,
TinyScroll,
truncateMiddle,
} from '@vegaprotocol/ui-toolkit';
import {
type TransferNode,
DistributionStrategyDescriptionMapping,
DistributionStrategyMapping,
EntityScope,
EntityScopeMapping,
DispatchMetric,
DispatchMetricDescription,
DispatchMetricLabels,
EntityScopeLabelMapping,
MarketState,
type DispatchStrategy,
IndividualScopeMapping,
IndividualScopeDescriptionMapping,
AccountType,
DistributionStrategy,
IndividualScope,
type Asset,
} from '@vegaprotocol/types';
import { Card } from '../card/card';
import { type ReactNode, useState } from 'react';
import {
type AssetFieldsFragment,
type BasicAssetDetails,
} from '@vegaprotocol/assets';
import { useState } from 'react';
import { type AssetFieldsFragment } from '@vegaprotocol/assets';
import { type MarketFieldsFragment } from '@vegaprotocol/markets';
import {
type EnrichedRewardTransfer,
useRewards,
} from '../../lib/hooks/use-rewards';
import compact from 'lodash/compact';
enum CardColour {
BLUE = 'BLUE',
GREEN = 'GREEN',
GREY = 'GREY',
ORANGE = 'ORANGE',
PINK = 'PINK',
PURPLE = 'PURPLE',
WHITE = 'WHITE',
YELLOW = 'YELLOW',
}
import { useRewards } from '../../lib/hooks/use-rewards';
import { useMyTeam } from '../../lib/hooks/use-my-team';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useStakeAvailable } from '../../lib/hooks/use-stake-available';
import { ActiveRewardCard, areAllMarketsSettled } from './reward-card';
export type Filter = {
searchTerm: string;
@@ -71,7 +38,10 @@ export const applyFilter = (
return true;
}
if (transfer.kind.__typename !== 'RecurringTransfer') {
if (
transfer.kind.__typename !== 'RecurringTransfer' &&
transfer.kind.__typename !== 'RecurringGovernanceTransfer'
) {
return false;
}
@@ -110,6 +80,18 @@ export const ActiveRewards = ({ currentEpoch }: { currentEpoch: number }) => {
const { data } = useRewards({
onlyActive: true,
});
const { pubKey } = useVegaWallet();
const { team } = useMyTeam();
const { stakeAvailable, isEligible, requiredStake } = useStakeAvailable();
const requirements = pubKey
? {
isEligible,
stakeAvailable,
requiredStake,
team,
pubKey,
}
: undefined;
const [filter, setFilter] = useState<Filter>({
searchTerm: '',
@@ -135,713 +117,26 @@ export const ActiveRewards = ({ currentEpoch }: { currentEpoch: number }) => {
'Search by reward dispatch metric, entity scope or asset name'
)}
data-testid="search-term"
className="mb-4 w-20 mr-2"
className="mb-4 w-20 mr-2 max-w-xl"
prependElement={<VegaIcon name={VegaIconNames.SEARCH} />}
/>
)}
{/** CARDS */}
<TinyScroll className="grid gap-x-8 gap-y-10 h-fit grid-cols-[repeat(auto-fill,_minmax(230px,_1fr))] md:grid-cols-[repeat(auto-fill,_minmax(230px,_1fr))] lg:grid-cols-[repeat(auto-fill,_minmax(320px,_1fr))] xl:grid-cols-[repeat(auto-fill,_minmax(335px,_1fr))] max-h-[40rem] overflow-auto pr-2">
<div className="grid gap-x-8 gap-y-10 h-fit grid-cols-[repeat(auto-fill,_minmax(230px,_1fr))] md:grid-cols-[repeat(auto-fill,_minmax(230px,_1fr))] lg:grid-cols-[repeat(auto-fill,_minmax(320px,_1fr))] xl:grid-cols-[repeat(auto-fill,_minmax(335px,_1fr))] pr-2">
{data
.filter((n) => applyFilter(n, filter))
// filter out the cards (rewards) for which all of the markets
// are settled
.filter((n) => !areAllMarketsSettled(n))
.map((node, i) => (
<ActiveRewardCard
key={i}
transferNode={node}
currentEpoch={currentEpoch}
requirements={requirements}
/>
))}
</TinyScroll>
</div>
</Card>
);
};
type ActiveRewardCardProps = {
transferNode: EnrichedRewardTransfer;
currentEpoch: number;
};
export const ActiveRewardCard = ({
transferNode,
currentEpoch,
}: ActiveRewardCardProps) => {
// don't display the cards that are scoped to not trading markets
const marketSettled = transferNode.markets?.filter(
(m) =>
m?.state &&
[
MarketState.STATE_TRADING_TERMINATED,
MarketState.STATE_SETTLED,
MarketState.STATE_CANCELLED,
MarketState.STATE_CLOSED,
].includes(m.state)
);
// hide the card if all of the markets are being marked as e.g. settled
if (
marketSettled?.length === transferNode.markets?.length &&
Boolean(transferNode.markets && transferNode.markets.length > 0)
) {
return null;
}
if (
!transferNode.transfer.kind.dispatchStrategy &&
transferNode.transfer.toAccountType ===
AccountType.ACCOUNT_TYPE_GLOBAL_REWARD
) {
return (
<StakingRewardCard
colour={CardColour.WHITE}
rewardAmount={addDecimalsFormatNumber(
transferNode.transfer.amount,
transferNode.transfer.asset?.decimals || 0,
6
)}
rewardAsset={transferNode.transfer.asset || undefined}
endsIn={
transferNode.transfer.kind.endEpoch != null
? transferNode.transfer.kind.endEpoch - currentEpoch
: undefined
}
/>
);
}
let colour =
DispatchMetricColourMap[
transferNode.transfer.kind.dispatchStrategy.dispatchMetric
];
// grey out of any of the markets is suspended or
// if the asset is not currently traded on any of the active markets
const marketSuspended =
transferNode.markets?.filter(
(m) =>
m?.state === MarketState.STATE_SUSPENDED ||
m?.state === MarketState.STATE_SUSPENDED_VIA_GOVERNANCE
).length === transferNode.markets?.length &&
Boolean(transferNode.markets && transferNode.markets.length > 0);
if (marketSuspended || !transferNode.isAssetTraded) {
colour = CardColour.GREY;
}
return (
<RewardCard
colour={colour}
rewardAmount={addDecimalsFormatNumber(
transferNode.transfer.amount,
transferNode.transfer.asset?.decimals || 0,
6
)}
rewardAsset={transferNode.dispatchAsset}
transferAsset={transferNode.transfer.asset || undefined}
endsIn={
transferNode.transfer.kind.endEpoch != null
? transferNode.transfer.kind.endEpoch - currentEpoch
: undefined
}
dispatchStrategy={transferNode.transfer.kind.dispatchStrategy}
dispatchMetricInfo={<DispatchMetricInfo reward={transferNode} />}
/>
);
};
const RewardCard = ({
colour,
rewardAmount,
rewardAsset,
transferAsset,
vegaAsset,
dispatchStrategy,
endsIn,
dispatchMetricInfo,
}: {
colour: CardColour;
rewardAmount: string;
/** The asset linked to the dispatch strategy via `dispatchMetricAssetId` property. */
rewardAsset?: BasicAssetDetails;
/** The VEGA asset details, required to format the min staking amount. */
transferAsset?: Asset | undefined;
/** The VEGA asset details, required to format the min staking amount. */
vegaAsset?: BasicAssetDetails;
/** The transfer's dispatch strategy. */
dispatchStrategy: DispatchStrategy;
/** The number of epochs until the transfer stops. */
endsIn?: number;
dispatchMetricInfo?: ReactNode;
}) => {
const t = useT();
return (
<div>
<div
className={classNames(
'bg-gradient-to-r col-span-full p-0.5 lg:col-auto h-full',
'rounded-lg',
CardColourStyles[colour].gradientClassName
)}
data-testid="active-rewards-card"
>
<div
className={classNames(
CardColourStyles[colour].mainClassName,
'bg-gradient-to-b bg-vega-clight-800 dark:bg-vega-cdark-800 h-full w-full rounded-md p-4 flex flex-col gap-4'
)}
>
<div className="flex justify-between gap-4">
{/** ENTITY SCOPE */}
<div className="flex flex-col gap-2 items-center text-center">
<EntityIcon entityScope={dispatchStrategy.entityScope} />
{dispatchStrategy.entityScope && (
<span className="text-muted text-xs" data-testid="entity-scope">
{EntityScopeLabelMapping[dispatchStrategy.entityScope] ||
t('Unspecified')}
</span>
)}
</div>
{/** AMOUNT AND DISTRIBUTION STRATEGY */}
<div className="flex flex-col gap-2 items-center text-center">
{/** AMOUNT */}
<h3 className="flex flex-col gap-1 text-2xl shrink-1 text-center">
<span className="font-glitch" data-testid="reward-value">
{rewardAmount}
</span>
<span className="font-alpha" data-testid="reward-asset">
{transferAsset?.symbol || ''}
</span>
</h3>
{/** DISTRIBUTION STRATEGY */}
<Tooltip
description={t(
DistributionStrategyDescriptionMapping[
dispatchStrategy.distributionStrategy
]
)}
underline={true}
>
<span className="text-xs" data-testid="distribution-strategy">
{
DistributionStrategyMapping[
dispatchStrategy.distributionStrategy
]
}
</span>
</Tooltip>
</div>
{/** DISTRIBUTION DELAY */}
<div className="flex flex-col gap-2 items-center text-center">
<CardIcon
iconName={VegaIconNames.LOCK}
tooltip={t(
'Number of epochs after distribution to delay vesting of rewards by'
)}
/>
<span
className="text-muted text-xs whitespace-nowrap"
data-testid="locked-for"
>
{t('numberEpochs', '{{count}} epochs', {
count: dispatchStrategy.lockPeriod,
})}
</span>
</div>
</div>
<span className="border-[0.5px] dark:border-vega-cdark-500 border-vega-clight-500" />
{/** DISPATCH METRIC */}
{dispatchMetricInfo ? (
dispatchMetricInfo
) : (
<span data-testid="dispatch-metric-info">
{DispatchMetricLabels[dispatchStrategy.dispatchMetric]}
</span>
)}
<div className="flex items-center gap-8 flex-wrap">
{/** ENDS IN */}
{endsIn != null && (
<span className="flex flex-col">
<span className="text-muted text-xs">{t('Ends in')} </span>
<span data-testid="ends-in" data-endsin={endsIn}>
{endsIn >= 0
? t('numberEpochs', '{{count}} epochs', {
count: endsIn,
})
: t('Ended')}
</span>
</span>
)}
{/** WINDOW LENGTH */}
<span className="flex flex-col">
<span className="text-muted text-xs">{t('Assessed over')}</span>
<span data-testid="assessed-over">
{t('numberEpochs', '{{count}} epochs', {
count: dispatchStrategy.windowLength,
})}
</span>
</span>
</div>
{/** DISPATCH METRIC DESCRIPTION */}
{dispatchStrategy?.dispatchMetric && (
<p className="text-muted text-sm h-[3rem]">
{t(DispatchMetricDescription[dispatchStrategy?.dispatchMetric])}
</p>
)}
<span className="border-[0.5px] dark:border-vega-cdark-500 border-vega-clight-500" />
{/** REQUIREMENTS */}
{dispatchStrategy && (
<RewardRequirements
dispatchStrategy={dispatchStrategy}
rewardAsset={rewardAsset}
vegaAsset={vegaAsset}
/>
)}
</div>
</div>
</div>
);
};
const StakingRewardCard = ({
colour,
rewardAmount,
rewardAsset,
endsIn,
}: {
colour: CardColour;
rewardAmount: string;
/** The asset linked to the dispatch strategy via `dispatchMetricAssetId` property. */
rewardAsset?: Asset;
/** The number of epochs until the transfer stops. */
endsIn?: number;
/** The VEGA asset details, required to format the min staking amount. */
vegaAsset?: BasicAssetDetails;
}) => {
const t = useT();
return (
<div>
<div
className={classNames(
'bg-gradient-to-r col-span-full p-0.5 lg:col-auto h-full',
'rounded-lg',
CardColourStyles[colour].gradientClassName
)}
data-testid="active-rewards-card"
>
<div
className={classNames(
CardColourStyles[colour].mainClassName,
'bg-gradient-to-b bg-vega-clight-800 dark:bg-vega-cdark-800 h-full w-full rounded-md p-4 flex flex-col gap-4'
)}
>
<div className="flex justify-between gap-4">
{/** ENTITY SCOPE */}
<div className="flex flex-col gap-2 items-center text-center">
<EntityIcon entityScope={EntityScope.ENTITY_SCOPE_INDIVIDUALS} />
{
<span className="text-muted text-xs" data-testid="entity-scope">
{EntityScopeLabelMapping[
EntityScope.ENTITY_SCOPE_INDIVIDUALS
] || t('Unspecified')}
</span>
}
</div>
{/** AMOUNT AND DISTRIBUTION STRATEGY */}
<div className="flex flex-col gap-2 items-center text-center">
{/** AMOUNT */}
<h3 className="flex flex-col gap-1 text-2xl shrink-1 text-center">
<span className="font-glitch" data-testid="reward-value">
{rewardAmount}
</span>
<span className="font-alpha">{rewardAsset?.symbol || ''}</span>
</h3>
{/** DISTRIBUTION STRATEGY */}
<Tooltip
description={t(
DistributionStrategyDescriptionMapping[
DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA
]
)}
underline={true}
>
<span className="text-xs" data-testid="distribution-strategy">
{
DistributionStrategyMapping[
DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA
]
}
</span>
</Tooltip>
</div>
{/** DISTRIBUTION DELAY */}
<div className="flex flex-col gap-2 items-center text-center">
<CardIcon
iconName={VegaIconNames.LOCK}
tooltip={t(
'Number of epochs after distribution to delay vesting of rewards by'
)}
/>
<span
className="text-muted text-xs whitespace-nowrap"
data-testid="locked-for"
>
{t('numberEpochs', '{{count}} epochs', {
count: 0,
})}
</span>
</div>
</div>
<span className="border-[0.5px] dark:border-vega-cdark-500 border-vega-clight-500" />
{/** DISPATCH METRIC */}
{
<span data-testid="dispatch-metric-info">
{t('Staking rewards')}
</span>
}
<div className="flex items-center gap-8 flex-wrap">
{/** ENDS IN */}
{endsIn != null && (
<span className="flex flex-col">
<span className="text-muted text-xs">{t('Ends in')} </span>
<span data-testid="ends-in" data-endsin={endsIn}>
{endsIn >= 0
? t('numberEpochs', '{{count}} epochs', {
count: endsIn,
})
: t('Ended')}
</span>
</span>
)}
{/** WINDOW LENGTH */}
<span className="flex flex-col">
<span className="text-muted text-xs">{t('Assessed over')}</span>
<span data-testid="assessed-over">
{t('numberEpochs', '{{count}} epochs', {
count: 1,
})}
</span>
</span>
</div>
{/** DISPATCH METRIC DESCRIPTION */}
{
<p className="text-muted text-sm h-[3rem]">
{t(
'Global staking reward for staking $VEGA on the network via the Governance app'
)}
</p>
}
<span className="border-[0.5px] dark:border-vega-cdark-500 border-vega-clight-500" />
{/** REQUIREMENTS */}
<dl className="flex justify-between flex-wrap items-center gap-3 text-xs">
<div className="flex flex-col gap-1">
<dt className="flex items-center gap-1 text-muted">
{t('Team scope')}
</dt>
<dd className="flex items-center gap-1" data-testid="scope">
<Tooltip
description={
IndividualScopeDescriptionMapping[
IndividualScope.INDIVIDUAL_SCOPE_ALL
]
}
>
<span>{t('Individual')}</span>
</Tooltip>
</dd>
</div>
<div className="flex flex-col gap-1">
<dt className="flex items-center gap-1 text-muted">
{t('Staked VEGA')}
</dt>
<dd
className="flex items-center gap-1"
data-testid="staking-requirement"
>
{formatNumber(1, 2)}
</dd>
</div>
<div className="flex flex-col gap-1">
<dt className="flex items-center gap-1 text-muted">
{t('Average position')}
</dt>
<dd
className="flex items-center gap-1"
data-testid="average-position"
>
{formatNumber(0, 2)}
</dd>
</div>
</dl>
</div>
</div>
</div>
);
};
export const DispatchMetricInfo = ({
reward,
}: {
reward: EnrichedRewardTransfer;
}) => {
const t = useT();
const dispatchStrategy = reward.transfer.kind.dispatchStrategy;
const marketNames = compact(
reward.markets?.map((m) => m.tradableInstrument.instrument.name)
);
let additionalDispatchMetricInfo = null;
// if asset found then display asset symbol
if (reward.dispatchAsset) {
additionalDispatchMetricInfo = <span>{reward.dispatchAsset.symbol}</span>;
}
// but if scoped to only one market then display market name
if (marketNames.length === 1) {
additionalDispatchMetricInfo = <span>{marketNames[0]}</span>;
}
// or if scoped to many markets then indicate it's scoped to "specific markets"
if (marketNames.length > 1) {
additionalDispatchMetricInfo = (
<Tooltip description={marketNames}>
<span>{t('Specific markets')}</span>
</Tooltip>
);
}
return (
<span data-testid="dispatch-metric-info">
{DispatchMetricLabels[dispatchStrategy.dispatchMetric]}
{additionalDispatchMetricInfo != null && (
<> {additionalDispatchMetricInfo}</>
)}
</span>
);
};
const RewardRequirements = ({
dispatchStrategy,
rewardAsset,
vegaAsset,
}: {
dispatchStrategy: DispatchStrategy;
rewardAsset?: BasicAssetDetails;
vegaAsset?: BasicAssetDetails;
}) => {
const t = useT();
const entityLabel = EntityScopeLabelMapping[dispatchStrategy.entityScope];
return (
<dl className="flex justify-between flex-wrap items-center gap-3 text-xs">
<div className="flex flex-col gap-1">
<dt className="flex items-center gap-1 text-muted">
{entityLabel
? t('{{entity}} scope', {
entity: entityLabel,
})
: t('Scope')}
</dt>
<dd className="flex items-center gap-1" data-testid="scope">
<RewardEntityScope dispatchStrategy={dispatchStrategy} />
</dd>
</div>
<div className="flex flex-col gap-1">
<dt className="flex items-center gap-1 text-muted">
{t('Staked VEGA')}
</dt>
<dd
className="flex items-center gap-1"
data-testid="staking-requirement"
>
{addDecimalsFormatNumber(
dispatchStrategy?.stakingRequirement || 0,
vegaAsset?.decimals || 18
)}
</dd>
</div>
<div className="flex flex-col gap-1">
<dt className="flex items-center gap-1 text-muted">
{t('Average position')}
</dt>
<dd className="flex items-center gap-1" data-testid="average-position">
{addDecimalsFormatNumber(
dispatchStrategy?.notionalTimeWeightedAveragePositionRequirement ||
0,
rewardAsset?.decimals || 0
)}
</dd>
</div>
</dl>
);
};
const RewardEntityScope = ({
dispatchStrategy,
}: {
dispatchStrategy: DispatchStrategy;
}) => {
const t = useT();
if (dispatchStrategy.entityScope === EntityScope.ENTITY_SCOPE_TEAMS) {
return (
<Tooltip
description={
dispatchStrategy.teamScope?.length ? (
<div className="text-xs">
<p className="mb-1">{t('Eligible teams')}</p>
<ul>
{dispatchStrategy.teamScope.map((teamId) => {
if (!teamId) return null;
return <li key={teamId}>{truncateMiddle(teamId)}</li>;
})}
</ul>
</div>
) : (
t('All teams are eligible')
)
}
>
<span>
{dispatchStrategy.teamScope?.length
? t('Some teams')
: t('All teams')}
</span>
</Tooltip>
);
}
if (
dispatchStrategy.entityScope === EntityScope.ENTITY_SCOPE_INDIVIDUALS &&
dispatchStrategy.individualScope
) {
return (
<Tooltip
description={
IndividualScopeDescriptionMapping[dispatchStrategy.individualScope]
}
>
<span>{IndividualScopeMapping[dispatchStrategy.individualScope]}</span>
</Tooltip>
);
}
return t('Unspecified');
};
const CardColourStyles: Record<
CardColour,
{ gradientClassName: string; mainClassName: string }
> = {
[CardColour.BLUE]: {
gradientClassName: 'from-vega-blue-500 to-vega-green-400',
mainClassName: 'from-vega-blue-400 dark:from-vega-blue-600 to-20%',
},
[CardColour.GREEN]: {
gradientClassName: 'from-vega-green-500 to-vega-yellow-500',
mainClassName: 'from-vega-green-400 dark:from-vega-green-600 to-20%',
},
[CardColour.GREY]: {
gradientClassName: 'from-vega-cdark-500 to-vega-clight-200',
mainClassName: 'from-vega-cdark-400 dark:from-vega-cdark-600 to-20%',
},
[CardColour.ORANGE]: {
gradientClassName: 'from-vega-orange-500 to-vega-pink-400',
mainClassName: 'from-vega-orange-400 dark:from-vega-orange-600 to-20%',
},
[CardColour.PINK]: {
gradientClassName: 'from-vega-pink-500 to-vega-purple-400',
mainClassName: 'from-vega-pink-400 dark:from-vega-pink-600 to-20%',
},
[CardColour.PURPLE]: {
gradientClassName: 'from-vega-purple-500 to-vega-blue-400',
mainClassName: 'from-vega-purple-400 dark:from-vega-purple-600 to-20%',
},
[CardColour.WHITE]: {
gradientClassName:
'from-vega-clight-600 dark:from-vega-clight-900 to-vega-yellow-500 dark:to-vega-yellow-400',
mainClassName: 'from-white dark:from-vega-clight-100 to-20%',
},
[CardColour.YELLOW]: {
gradientClassName: 'from-vega-yellow-500 to-vega-orange-400',
mainClassName: 'from-vega-yellow-400 dark:from-vega-yellow-600 to-20%',
},
};
const DispatchMetricColourMap: Record<DispatchMetric, CardColour> = {
// Liquidity provision fees received
[DispatchMetric.DISPATCH_METRIC_LP_FEES_RECEIVED]: CardColour.BLUE,
// Price maker fees paid
[DispatchMetric.DISPATCH_METRIC_MAKER_FEES_PAID]: CardColour.PINK,
// Price maker fees earned
[DispatchMetric.DISPATCH_METRIC_MAKER_FEES_RECEIVED]: CardColour.GREEN,
// Total market value
[DispatchMetric.DISPATCH_METRIC_MARKET_VALUE]: CardColour.WHITE,
// Average position
[DispatchMetric.DISPATCH_METRIC_AVERAGE_POSITION]: CardColour.ORANGE,
// Relative return
[DispatchMetric.DISPATCH_METRIC_RELATIVE_RETURN]: CardColour.PURPLE,
// Return volatility
[DispatchMetric.DISPATCH_METRIC_RETURN_VOLATILITY]: CardColour.YELLOW,
// Validator ranking
[DispatchMetric.DISPATCH_METRIC_VALIDATOR_RANKING]: CardColour.WHITE,
};
const CardIcon = ({
size = 18,
iconName,
tooltip,
}: {
size?: VegaIconSize;
iconName: VegaIconNames;
tooltip: string;
}) => {
return (
<Tooltip description={<span>{tooltip}</span>}>
<span className="flex items-center p-2 rounded-full border border-gray-600">
<VegaIcon name={iconName} size={size} />
</span>
</Tooltip>
);
};
const EntityScopeIconMap: Record<EntityScope, VegaIconNames> = {
[EntityScope.ENTITY_SCOPE_TEAMS]: VegaIconNames.TEAM,
[EntityScope.ENTITY_SCOPE_INDIVIDUALS]: VegaIconNames.MAN,
};
const EntityIcon = ({
entityScope,
size = 18,
}: {
entityScope: EntityScope;
size?: VegaIconSize;
}) => {
return (
<Tooltip
description={
entityScope ? <span>{EntityScopeMapping[entityScope]}</span> : undefined
}
>
<span className="flex items-center p-2 rounded-full border border-gray-600">
<VegaIcon
name={EntityScopeIconMap[entityScope] || VegaIconNames.QUESTION_MARK}
size={size}
/>
</span>
</Tooltip>
);
};
@@ -0,0 +1,41 @@
import { usePayoutPerRank } from './rank-table';
// Test case as per https://docs.google.com/spreadsheets/d/1RpWKnGEf4eYMxjI-feauRa9vWz3pNGdP05ejQrwWatg/
describe('usePayoutPerRank', () => {
it('should return payouts per person', () => {
const rankTable = [
{
startRank: 1,
shareRatio: 10,
},
{
startRank: 2,
shareRatio: 5,
},
{
startRank: 5,
shareRatio: 5,
},
{
startRank: 10,
shareRatio: 3,
},
{
startRank: 20,
shareRatio: 0,
},
];
const result = usePayoutPerRank(rankTable);
expect(result).toEqual({
numPayouts: [1, 3, 5, 10, 0],
ratioShares: [10, 15, 25, 30, 0],
payoutsPerTier: [0.125, 0.1875, 0.3125, 0.375, 0],
payoutsPerWinner: [0.125, 0.0625, 0.0625, 0.0375],
payoutsPerWinnerAsPercentage: [12.5, 6.25, 6.25, 3.75],
endRanks: [2, 5, 10, 20],
startRanks: [1, 2, 5, 10, 20],
});
});
});
@@ -0,0 +1,184 @@
import type { RankTable } from '@vegaprotocol/types';
import { formatNumber } from '@vegaprotocol/utils';
import { useT } from '../../lib/use-t';
export const RankPayoutTable = ({
rankTable,
}: {
rankTable: (RankTable | null)[] | null;
}) => {
const t = useT();
const { payoutsPerWinnerAsPercentage, numPayouts, endRanks } =
usePayoutPerRank(rankTable);
let isOpenFinalTier = false;
return (
rankTable &&
rankTable.length > 0 && (
<>
<table className="min-w-full divide-y divide-gray-200">
<thead className="">
<tr className="text-right text-xs divide-x">
<th scope="col">
<span className="flex p-1 w-full justify-end">
{t('Rank tier')}
</span>
</th>
<th scope="col">
<span className="flex p-1 w-full justify-end">
{t('Start rank')}
</span>
</th>
<th scope="col">
<span className="flex p-1 w-full justify-end">
{t('End rank')}
</span>
</th>
<th scope="col">
<span className="flex p-1 w-full justify-end">
{t('Places paid')}
</span>
</th>
<th scope="col">
<span className="flex p-1 w-full justify-end">
{t('Payout')}
</span>
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-500 text-xs">
{rankTable?.map((rank: RankTable | null, tier: number) => {
const endRank = endRanks?.[tier];
const placesPaid = numPayouts?.[tier];
const isFinalTier = tier === rankTable.length - 1;
isOpenFinalTier =
isFinalTier && rank ? rank.shareRatio > 0 : false;
if (isFinalTier && rank?.shareRatio === 0) {
return null;
}
return (
rank && (
<tr
key={`rank-table-row-${tier}`}
className="whitespace-nowrap text-right divide-x p-4"
>
<td>
<span className="flex p-1 justify-end">{tier + 1}</span>
</td>
<td>
<span className="flex p-1 justify-end">
{rank.startRank}
</span>
</td>
<td>
<span className="flex p-1 justify-end">
{endRank || 'TBC*'}
</span>
</td>
<td>
<span className="flex p-1 justify-end">
{placesPaid || 'TBC*'}
</span>
</td>
<td>
<span className="flex p-1 justify-end">
{payoutsPerWinnerAsPercentage?.[tier]
? `${formatNumber(
payoutsPerWinnerAsPercentage[tier],
2
)} %`
: 'TBC*'}
</span>
</td>
</tr>
)
);
})}
</tbody>
</table>
<p>
{isOpenFinalTier &&
t(
'*Note: Final tier will payout to all game participants, therefore all payout estimates will lower depending on how many participants there are.'
)}
</p>
</>
)
);
};
// Use rank table to calculate payouts per rank
// as per https://docs.google.com/spreadsheets/d/1RpWKnGEf4eYMxjI-feauRa9vWz3pNGdP05ejQrwWatg/
export const usePayoutPerRank = (
rankTable?: (RankTable | null)[] | null
): {
numPayouts?: number[];
startRanks?: number[];
endRanks?: number[];
ratioShares?: number[];
payoutsPerTier?: number[];
payoutsPerWinner?: number[];
payoutsPerWinnerAsPercentage?: number[];
} => {
if (!rankTable) return {};
const numPayouts: number[] = [],
ratioShares: number[] = [],
startRanks: number[] = [],
endRanks: number[] = [],
payoutsPerTier: number[] = [],
payoutsPerWinner: number[] = [],
payoutsPerWinnerAsPercentage: number[] = [];
let totalRatio = 0;
// We have to work out how many ppl in each bucket to get the total for the ratio
rankTable.forEach((rank, tier) => {
const startRank = rank?.startRank;
startRank && startRanks.push(startRank);
const endRank =
tier < rankTable.length - 1 ? rankTable[tier + 1]?.startRank : undefined;
endRank && endRanks.push(endRank);
const numPayout = endRank && startRank ? endRank - startRank : 0;
numPayouts.push(numPayout);
const shareRatio = rank?.shareRatio;
const ratioShare = shareRatio ? shareRatio * numPayout : 0;
ratioShares.push(ratioShare);
totalRatio += ratioShare;
});
rankTable.forEach((_rank, tier) => {
const ratioShare = ratioShares[tier];
// Then we multiply back out to get the total payout per tier
const payoutPerTierValue = totalRatio ? ratioShare / totalRatio : 0;
payoutsPerTier.push(payoutPerTierValue);
const numPayout = numPayouts[tier];
// And then divide by the payouts to get each individual winner payout
const payoutPerWinnerValue =
numPayout && payoutPerTierValue
? payoutPerTierValue / numPayout
: undefined;
payoutPerWinnerValue && payoutsPerWinner.push(payoutPerWinnerValue);
payoutPerWinnerValue &&
payoutsPerWinnerAsPercentage.push(payoutPerWinnerValue * 100);
});
return {
numPayouts,
ratioShares,
startRanks,
endRanks,
payoutsPerTier,
payoutsPerWinner,
payoutsPerWinnerAsPercentage,
};
};
File diff suppressed because it is too large Load Diff
@@ -246,6 +246,19 @@ export const RewardsContainer = () => {
})}
</div>
<div className="grid auto-rows-min grid-cols-6 gap-3">
<Card
title={t('Rewards history')}
className="lg:col-span-full hidden md:block"
loading={rewardsLoading}
noBackgroundOnMobile={true}
highlight={true}
>
<RewardsHistoryContainer
epoch={Number(epochData?.epoch.id)}
pubKey={pubKey}
assets={assetMap}
/>
</Card>
{pubKey && activityStreakBenefitTiers.tiers?.length > 0 && (
<Card
title={t('Activity Streak')}
@@ -287,24 +300,12 @@ export const RewardsContainer = () => {
</Card>
)}
<ActiveRewards currentEpoch={Number(epochData?.epoch.id)} />
<Card
title={t('Rewards history')}
className="lg:col-span-full hidden md:block"
loading={rewardsLoading}
noBackgroundOnMobile={true}
>
<RewardsHistoryContainer
epoch={Number(epochData?.epoch.id)}
pubKey={pubKey}
assets={assetMap}
/>
</Card>
</div>
</div>
);
};
type VestingBalances = NonNullable<
export type VestingBalances = NonNullable<
RewardsPageQuery['party']
>['vestingBalancesSummary'];
@@ -96,6 +96,7 @@ export const VegaWalletConnectButton = ({
pubKeys={pubKeys}
activeKey={activeKey?.publicKey}
onSelect={selectPubKey}
isReadOnly={isReadOnly}
/>
<TradingDropdownSeparator />
{!isReadOnly && (
@@ -141,11 +142,13 @@ const KeypairRadioGroup = ({
pubKeys,
activeKey,
onSelect,
isReadOnly,
}: {
pubKey: string | undefined;
pubKeys: Key[];
activeKey: string | undefined;
onSelect: (pubKey: string) => void;
isReadOnly: boolean;
}) => {
const { data } = usePartyProfilesQuery({
variables: { partyIds: pubKeys.map((pk) => pk.publicKey) },
@@ -165,6 +168,7 @@ const KeypairRadioGroup = ({
pk={pk}
isActive={activeKey === pk.publicKey}
alias={profile?.node.alias}
isReadOnly={isReadOnly}
/>
);
})}
@@ -176,10 +180,12 @@ const KeypairItem = ({
pk,
isActive,
alias,
isReadOnly,
}: {
pk: Key;
alias: string | undefined;
isActive: boolean;
isReadOnly: boolean;
}) => {
const t = useT();
const [copied, setCopied] = useCopyTimeout();
@@ -210,16 +216,18 @@ const KeypairItem = ({
className={classNames('flex-1 mr-2 text-secondary text-sm')}
data-testid={`key-${pk.publicKey}`}
>
<Tooltip description={t('Public facing key alias. Click to edit')}>
<button
data-testid="alias"
onClick={() => setOpen(pk.publicKey)}
className="flex items-center gap-1"
>
{alias ? alias : t('No alias')}
{isActive && <VegaIcon name={VegaIconNames.EDIT} />}
</button>
</Tooltip>
{!isReadOnly && (
<Tooltip description={t('Public facing key alias. Click to edit')}>
<button
data-testid="alias"
onClick={() => setOpen(pk.publicKey)}
className="flex items-center gap-1"
>
{alias ? alias : t('No alias')}
{isActive && <VegaIcon name={VegaIconNames.EDIT} />}
</button>
</Tooltip>
)}
</div>
</div>
<TradingDropdownItemIndicator />
+2 -1
View File
@@ -1,3 +1,4 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest
VEGA_VERSION=v0.75.0-preview.2
VEGA_VERSION=v0.75.5
LOCAL_SERVER=false
VEGA_ENV=STAGNET1
+2 -1
View File
@@ -1,3 +1,4 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:develop
VEGA_VERSION=v0.74.6
VEGA_VERSION=v0.75.5
LOCAL_SERVER=false
VEGA_ENV=STAGNET1
+3 -2
View File
@@ -1,3 +1,4 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:main
VEGA_VERSION=v0.73.13
LOCAL_SERVER=false
VEGA_VERSION=v0.74.10
LOCAL_SERVER=false
VEGA_ENV=MAINNET
+4 -1
View File
@@ -92,7 +92,7 @@ def init_vega(request=None):
"store_transactions": True,
"transactions_per_block": 1000,
"seconds_per_block": seconds_per_block,
"genesis_time": datetime.now() - timedelta(days=1),
"genesis_time": datetime.now() - timedelta(hours=1),
}
if port_config is not None:
@@ -153,10 +153,13 @@ def init_page(vega: VegaServiceNull, browser: Browser, request: pytest.FixtureRe
raise e
# Set window._env_ so built app uses datanode from vega market sim
load_dotenv()
vega_env = os.getenv('VEGA_ENV')
env = json.dumps(
{
"VEGA_URL": f"http://localhost:{vega.data_node_rest_port}/graphql",
"VEGA_WALLET_URL": f"http://localhost:{vega.wallet_port}",
"VEGA_ENV": vega_env,
}
)
window_env = f"window._env_ = Object.assign({{}}, window._env_, {env})"
+143 -143
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
[[package]]
name = "certifi"
@@ -253,13 +253,13 @@ testing = ["hatch", "pre-commit", "pytest", "tox"]
[[package]]
name = "googleapis-common-protos"
version = "1.62.0"
version = "1.63.0"
description = "Common protobufs used in Google APIs"
optional = false
python-versions = ">=3.7"
files = [
{file = "googleapis-common-protos-1.62.0.tar.gz", hash = "sha256:83f0ece9f94e5672cced82f592d2a5edf527a96ed1794f0bab36d5735c996277"},
{file = "googleapis_common_protos-1.62.0-py2.py3-none-any.whl", hash = "sha256:4750113612205514f9f6aa4cb00d523a94f3e8c06c5ad2fee466387dc4875f07"},
{file = "googleapis-common-protos-1.63.0.tar.gz", hash = "sha256:17ad01b11d5f1d0171c06d3ba5c04c54474e883b66b949722b4938ee2694ef4e"},
{file = "googleapis_common_protos-1.63.0-py2.py3-none-any.whl", hash = "sha256:ae45f75702f7c08b541f750854a678bd8f534a1a6bace6afe975f1d0a82d6632"},
]
[package.dependencies]
@@ -341,135 +341,135 @@ test = ["objgraph", "psutil"]
[[package]]
name = "grpcio"
version = "1.62.0"
version = "1.62.1"
description = "HTTP/2-based RPC framework"
optional = false
python-versions = ">=3.7"
files = [
{file = "grpcio-1.62.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:136ffd79791b1eddda8d827b607a6285474ff8a1a5735c4947b58c481e5e4271"},
{file = "grpcio-1.62.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:d6a56ba703be6b6267bf19423d888600c3f574ac7c2cc5e6220af90662a4d6b0"},
{file = "grpcio-1.62.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:4cd356211579043fce9f52acc861e519316fff93980a212c8109cca8f47366b6"},
{file = "grpcio-1.62.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e803e9b58d8f9b4ff0ea991611a8d51b31c68d2e24572cd1fe85e99e8cc1b4f8"},
{file = "grpcio-1.62.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4c04fe33039b35b97c02d2901a164bbbb2f21fb9c4e2a45a959f0b044c3512c"},
{file = "grpcio-1.62.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:95370c71b8c9062f9ea033a0867c4c73d6f0ff35113ebd2618171ec1f1e903e0"},
{file = "grpcio-1.62.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c912688acc05e4ff012c8891803659d6a8a8b5106f0f66e0aed3fb7e77898fa6"},
{file = "grpcio-1.62.0-cp310-cp310-win32.whl", hash = "sha256:821a44bd63d0f04e33cf4ddf33c14cae176346486b0df08b41a6132b976de5fc"},
{file = "grpcio-1.62.0-cp310-cp310-win_amd64.whl", hash = "sha256:81531632f93fece32b2762247c4c169021177e58e725494f9a746ca62c83acaa"},
{file = "grpcio-1.62.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:3fa15850a6aba230eed06b236287c50d65a98f05054a0f01ccedf8e1cc89d57f"},
{file = "grpcio-1.62.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:36df33080cd7897623feff57831eb83c98b84640b016ce443305977fac7566fb"},
{file = "grpcio-1.62.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:7a195531828b46ea9c4623c47e1dc45650fc7206f8a71825898dd4c9004b0928"},
{file = "grpcio-1.62.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab140a3542bbcea37162bdfc12ce0d47a3cda3f2d91b752a124cc9fe6776a9e2"},
{file = "grpcio-1.62.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f9d6c3223914abb51ac564dc9c3782d23ca445d2864321b9059d62d47144021"},
{file = "grpcio-1.62.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fbe0c20ce9a1cff75cfb828b21f08d0a1ca527b67f2443174af6626798a754a4"},
{file = "grpcio-1.62.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38f69de9c28c1e7a8fd24e4af4264726637b72f27c2099eaea6e513e7142b47e"},
{file = "grpcio-1.62.0-cp311-cp311-win32.whl", hash = "sha256:ce1aafdf8d3f58cb67664f42a617af0e34555fe955450d42c19e4a6ad41c84bd"},
{file = "grpcio-1.62.0-cp311-cp311-win_amd64.whl", hash = "sha256:eef1d16ac26c5325e7d39f5452ea98d6988c700c427c52cbc7ce3201e6d93334"},
{file = "grpcio-1.62.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:8aab8f90b2a41208c0a071ec39a6e5dbba16fd827455aaa070fec241624ccef8"},
{file = "grpcio-1.62.0-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:62aa1659d8b6aad7329ede5d5b077e3d71bf488d85795db517118c390358d5f6"},
{file = "grpcio-1.62.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:0d7ae7fc7dbbf2d78d6323641ded767d9ec6d121aaf931ec4a5c50797b886532"},
{file = "grpcio-1.62.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f359d635ee9428f0294bea062bb60c478a8ddc44b0b6f8e1f42997e5dc12e2ee"},
{file = "grpcio-1.62.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d48e5b1f8f4204889f1acf30bb57c30378e17c8d20df5acbe8029e985f735c"},
{file = "grpcio-1.62.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:662d3df5314ecde3184cf87ddd2c3a66095b3acbb2d57a8cada571747af03873"},
{file = "grpcio-1.62.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92cdb616be44c8ac23a57cce0243af0137a10aa82234f23cd46e69e115071388"},
{file = "grpcio-1.62.0-cp312-cp312-win32.whl", hash = "sha256:0b9179478b09ee22f4a36b40ca87ad43376acdccc816ce7c2193a9061bf35701"},
{file = "grpcio-1.62.0-cp312-cp312-win_amd64.whl", hash = "sha256:614c3ed234208e76991992342bab725f379cc81c7dd5035ee1de2f7e3f7a9842"},
{file = "grpcio-1.62.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:7e1f51e2a460b7394670fdb615e26d31d3260015154ea4f1501a45047abe06c9"},
{file = "grpcio-1.62.0-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:bcff647e7fe25495e7719f779cc219bbb90b9e79fbd1ce5bda6aae2567f469f2"},
{file = "grpcio-1.62.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:56ca7ba0b51ed0de1646f1735154143dcbdf9ec2dbe8cc6645def299bb527ca1"},
{file = "grpcio-1.62.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e84bfb2a734e4a234b116be208d6f0214e68dcf7804306f97962f93c22a1839"},
{file = "grpcio-1.62.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c1488b31a521fbba50ae86423f5306668d6f3a46d124f7819c603979fc538c4"},
{file = "grpcio-1.62.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:98d8f4eb91f1ce0735bf0b67c3b2a4fea68b52b2fd13dc4318583181f9219b4b"},
{file = "grpcio-1.62.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b3d3d755cfa331d6090e13aac276d4a3fb828bf935449dc16c3d554bf366136b"},
{file = "grpcio-1.62.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a33f2bfd8a58a02aab93f94f6c61279be0f48f99fcca20ebaee67576cd57307b"},
{file = "grpcio-1.62.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:5e709f7c8028ce0443bddc290fb9c967c1e0e9159ef7a030e8c21cac1feabd35"},
{file = "grpcio-1.62.0-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:2f3d9a4d0abb57e5f49ed5039d3ed375826c2635751ab89dcc25932ff683bbb6"},
{file = "grpcio-1.62.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:62ccb92f594d3d9fcd00064b149a0187c246b11e46ff1b7935191f169227f04c"},
{file = "grpcio-1.62.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:921148f57c2e4b076af59a815467d399b7447f6e0ee10ef6d2601eb1e9c7f402"},
{file = "grpcio-1.62.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f897b16190b46bc4d4aaf0a32a4b819d559a37a756d7c6b571e9562c360eed72"},
{file = "grpcio-1.62.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1bc8449084fe395575ed24809752e1dc4592bb70900a03ca42bf236ed5bf008f"},
{file = "grpcio-1.62.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81d444e5e182be4c7856cd33a610154fe9ea1726bd071d07e7ba13fafd202e38"},
{file = "grpcio-1.62.0-cp38-cp38-win32.whl", hash = "sha256:88f41f33da3840b4a9bbec68079096d4caf629e2c6ed3a72112159d570d98ebe"},
{file = "grpcio-1.62.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc2836cb829895ee190813446dce63df67e6ed7b9bf76060262c55fcd097d270"},
{file = "grpcio-1.62.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:fcc98cff4084467839d0a20d16abc2a76005f3d1b38062464d088c07f500d170"},
{file = "grpcio-1.62.0-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:0d3dee701e48ee76b7d6fbbba18ba8bc142e5b231ef7d3d97065204702224e0e"},
{file = "grpcio-1.62.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:b7a6be562dd18e5d5bec146ae9537f20ae1253beb971c0164f1e8a2f5a27e829"},
{file = "grpcio-1.62.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29cb592c4ce64a023712875368bcae13938c7f03e99f080407e20ffe0a9aa33b"},
{file = "grpcio-1.62.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1eda79574aec8ec4d00768dcb07daba60ed08ef32583b62b90bbf274b3c279f7"},
{file = "grpcio-1.62.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7eea57444a354ee217fda23f4b479a4cdfea35fb918ca0d8a0e73c271e52c09c"},
{file = "grpcio-1.62.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0e97f37a3b7c89f9125b92d22e9c8323f4e76e7993ba7049b9f4ccbe8bae958a"},
{file = "grpcio-1.62.0-cp39-cp39-win32.whl", hash = "sha256:39cd45bd82a2e510e591ca2ddbe22352e8413378852ae814549c162cf3992a93"},
{file = "grpcio-1.62.0-cp39-cp39-win_amd64.whl", hash = "sha256:b71c65427bf0ec6a8b48c68c17356cb9fbfc96b1130d20a07cb462f4e4dcdcd5"},
{file = "grpcio-1.62.0.tar.gz", hash = "sha256:748496af9238ac78dcd98cce65421f1adce28c3979393e3609683fcd7f3880d7"},
{file = "grpcio-1.62.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:179bee6f5ed7b5f618844f760b6acf7e910988de77a4f75b95bbfaa8106f3c1e"},
{file = "grpcio-1.62.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:48611e4fa010e823ba2de8fd3f77c1322dd60cb0d180dc6630a7e157b205f7ea"},
{file = "grpcio-1.62.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:b2a0e71b0a2158aa4bce48be9f8f9eb45cbd17c78c7443616d00abbe2a509f6d"},
{file = "grpcio-1.62.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbe80577c7880911d3ad65e5ecc997416c98f354efeba2f8d0f9112a67ed65a5"},
{file = "grpcio-1.62.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58f6c693d446964e3292425e1d16e21a97a48ba9172f2d0df9d7b640acb99243"},
{file = "grpcio-1.62.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:77c339403db5a20ef4fed02e4d1a9a3d9866bf9c0afc77a42234677313ea22f3"},
{file = "grpcio-1.62.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b5a4ea906db7dec694098435d84bf2854fe158eb3cd51e1107e571246d4d1d70"},
{file = "grpcio-1.62.1-cp310-cp310-win32.whl", hash = "sha256:4187201a53f8561c015bc745b81a1b2d278967b8de35f3399b84b0695e281d5f"},
{file = "grpcio-1.62.1-cp310-cp310-win_amd64.whl", hash = "sha256:844d1f3fb11bd1ed362d3fdc495d0770cfab75761836193af166fee113421d66"},
{file = "grpcio-1.62.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:833379943d1728a005e44103f17ecd73d058d37d95783eb8f0b28ddc1f54d7b2"},
{file = "grpcio-1.62.1-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:c7fcc6a32e7b7b58f5a7d27530669337a5d587d4066060bcb9dee7a8c833dfb7"},
{file = "grpcio-1.62.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:fa7d28eb4d50b7cbe75bb8b45ed0da9a1dc5b219a0af59449676a29c2eed9698"},
{file = "grpcio-1.62.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48f7135c3de2f298b833be8b4ae20cafe37091634e91f61f5a7eb3d61ec6f660"},
{file = "grpcio-1.62.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71f11fd63365ade276c9d4a7b7df5c136f9030e3457107e1791b3737a9b9ed6a"},
{file = "grpcio-1.62.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4b49fd8fe9f9ac23b78437da94c54aa7e9996fbb220bac024a67469ce5d0825f"},
{file = "grpcio-1.62.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:482ae2ae78679ba9ed5752099b32e5fe580443b4f798e1b71df412abf43375db"},
{file = "grpcio-1.62.1-cp311-cp311-win32.whl", hash = "sha256:1faa02530b6c7426404372515fe5ddf66e199c2ee613f88f025c6f3bd816450c"},
{file = "grpcio-1.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bd90b8c395f39bc82a5fb32a0173e220e3f401ff697840f4003e15b96d1befc"},
{file = "grpcio-1.62.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:b134d5d71b4e0837fff574c00e49176051a1c532d26c052a1e43231f252d813b"},
{file = "grpcio-1.62.1-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:d1f6c96573dc09d50dbcbd91dbf71d5cf97640c9427c32584010fbbd4c0e0037"},
{file = "grpcio-1.62.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:359f821d4578f80f41909b9ee9b76fb249a21035a061a327f91c953493782c31"},
{file = "grpcio-1.62.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a485f0c2010c696be269184bdb5ae72781344cb4e60db976c59d84dd6354fac9"},
{file = "grpcio-1.62.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b50b09b4dc01767163d67e1532f948264167cd27f49e9377e3556c3cba1268e1"},
{file = "grpcio-1.62.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3227c667dccbe38f2c4d943238b887bac588d97c104815aecc62d2fd976e014b"},
{file = "grpcio-1.62.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3952b581eb121324853ce2b191dae08badb75cd493cb4e0243368aa9e61cfd41"},
{file = "grpcio-1.62.1-cp312-cp312-win32.whl", hash = "sha256:83a17b303425104d6329c10eb34bba186ffa67161e63fa6cdae7776ff76df73f"},
{file = "grpcio-1.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:6696ffe440333a19d8d128e88d440f91fb92c75a80ce4b44d55800e656a3ef1d"},
{file = "grpcio-1.62.1-cp37-cp37m-linux_armv7l.whl", hash = "sha256:e3393b0823f938253370ebef033c9fd23d27f3eae8eb9a8f6264900c7ea3fb5a"},
{file = "grpcio-1.62.1-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:83e7ccb85a74beaeae2634f10eb858a0ed1a63081172649ff4261f929bacfd22"},
{file = "grpcio-1.62.1-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:882020c87999d54667a284c7ddf065b359bd00251fcd70279ac486776dbf84ec"},
{file = "grpcio-1.62.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a10383035e864f386fe096fed5c47d27a2bf7173c56a6e26cffaaa5a361addb1"},
{file = "grpcio-1.62.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:960edebedc6b9ada1ef58e1c71156f28689978188cd8cff3b646b57288a927d9"},
{file = "grpcio-1.62.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:23e2e04b83f347d0aadde0c9b616f4726c3d76db04b438fd3904b289a725267f"},
{file = "grpcio-1.62.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:978121758711916d34fe57c1f75b79cdfc73952f1481bb9583399331682d36f7"},
{file = "grpcio-1.62.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9084086190cc6d628f282e5615f987288b95457292e969b9205e45b442276407"},
{file = "grpcio-1.62.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:22bccdd7b23c420a27fd28540fb5dcbc97dc6be105f7698cb0e7d7a420d0e362"},
{file = "grpcio-1.62.1-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:8999bf1b57172dbc7c3e4bb3c732658e918f5c333b2942243f10d0d653953ba9"},
{file = "grpcio-1.62.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:d9e52558b8b8c2f4ac05ac86344a7417ccdd2b460a59616de49eb6933b07a0bd"},
{file = "grpcio-1.62.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1714e7bc935780bc3de1b3fcbc7674209adf5208ff825799d579ffd6cd0bd505"},
{file = "grpcio-1.62.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8842ccbd8c0e253c1f189088228f9b433f7a93b7196b9e5b6f87dba393f5d5d"},
{file = "grpcio-1.62.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1f1e7b36bdff50103af95a80923bf1853f6823dd62f2d2a2524b66ed74103e49"},
{file = "grpcio-1.62.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bba97b8e8883a8038606480d6b6772289f4c907f6ba780fa1f7b7da7dfd76f06"},
{file = "grpcio-1.62.1-cp38-cp38-win32.whl", hash = "sha256:a7f615270fe534548112a74e790cd9d4f5509d744dd718cd442bf016626c22e4"},
{file = "grpcio-1.62.1-cp38-cp38-win_amd64.whl", hash = "sha256:e6c8c8693df718c5ecbc7babb12c69a4e3677fd11de8886f05ab22d4e6b1c43b"},
{file = "grpcio-1.62.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:73db2dc1b201d20ab7083e7041946910bb991e7e9761a0394bbc3c2632326483"},
{file = "grpcio-1.62.1-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:407b26b7f7bbd4f4751dbc9767a1f0716f9fe72d3d7e96bb3ccfc4aace07c8de"},
{file = "grpcio-1.62.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:f8de7c8cef9261a2d0a62edf2ccea3d741a523c6b8a6477a340a1f2e417658de"},
{file = "grpcio-1.62.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd5c8a1af40ec305d001c60236308a67e25419003e9bb3ebfab5695a8d0b369"},
{file = "grpcio-1.62.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be0477cb31da67846a33b1a75c611f88bfbcd427fe17701b6317aefceee1b96f"},
{file = "grpcio-1.62.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:60dcd824df166ba266ee0cfaf35a31406cd16ef602b49f5d4dfb21f014b0dedd"},
{file = "grpcio-1.62.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:973c49086cabab773525f6077f95e5a993bfc03ba8fc32e32f2c279497780585"},
{file = "grpcio-1.62.1-cp39-cp39-win32.whl", hash = "sha256:12859468e8918d3bd243d213cd6fd6ab07208195dc140763c00dfe901ce1e1b4"},
{file = "grpcio-1.62.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7209117bbeebdfa5d898205cc55153a51285757902dd73c47de498ad4d11332"},
{file = "grpcio-1.62.1.tar.gz", hash = "sha256:6c455e008fa86d9e9a9d85bb76da4277c0d7d9668a3bfa70dbe86e9f3c759947"},
]
[package.extras]
protobuf = ["grpcio-tools (>=1.62.0)"]
protobuf = ["grpcio-tools (>=1.62.1)"]
[[package]]
name = "grpcio-tools"
version = "1.62.0"
version = "1.62.1"
description = "Protobuf code generator for gRPC"
optional = false
python-versions = ">=3.7"
files = [
{file = "grpcio-tools-1.62.0.tar.gz", hash = "sha256:7fca6ecfbbf0549058bb29dcc6e435d885b878d07701e77ac58e1e1f591736dc"},
{file = "grpcio_tools-1.62.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:465c51ebaa184ee3bb619cd5bfaf562bbdde166f2822a6935461e6a741f5ac19"},
{file = "grpcio_tools-1.62.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:0d9c9a4832f52c4597d6dc12d9ab3109c3bd0ee1686b8bf6d64f9eab4145e3cb"},
{file = "grpcio_tools-1.62.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:5a482d9625209023481e631c29a6df1392bfc49f9accfa880dabbacff642559a"},
{file = "grpcio_tools-1.62.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74196beed18383d53ff3e2412a6c1eefa3ff109e987be240368496bc3dcabc8b"},
{file = "grpcio_tools-1.62.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75aca28cbeb605c59b5689a7e000fbc2bd659d2f322c58461f3912f00069f6da"},
{file = "grpcio_tools-1.62.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:523adf731fa4c5af0bf7ee2edb65e8c7ef4d9df9951461d6a18fe096688efd2d"},
{file = "grpcio_tools-1.62.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:791aa220f8f1936e65bc079e9eb954fa0202a1f16e28b83956e59d17dface127"},
{file = "grpcio_tools-1.62.0-cp310-cp310-win32.whl", hash = "sha256:5dacc691b18d2c294ea971720ff980a1e2d68a3f7ddcd2f0670b3204e81c4b18"},
{file = "grpcio_tools-1.62.0-cp310-cp310-win_amd64.whl", hash = "sha256:6999a4e705b03aacad46e625feb7610e47ec88dbd51220c2282b6334f90721fc"},
{file = "grpcio_tools-1.62.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:19b74e141937c885c9e56b6a7dfa190ca7d583bd48bce9171dd65bbf108b9271"},
{file = "grpcio_tools-1.62.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:17c16e9a89c0b9f4ff2b143f232c5256383453ce7b55fe981598f9517adc8252"},
{file = "grpcio_tools-1.62.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:3730b1cd998a0cffc817602cc55e51f268fa97b3e38fa4bee578e3741474547a"},
{file = "grpcio_tools-1.62.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14201950513636f515dd455a06890e3a21d115b943cf6a8f5af67ad1413cfa1f"},
{file = "grpcio_tools-1.62.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f74e0053360e0eadd75193c0c379b6d7f51d074ebbff856bd41780e1a028b38d"},
{file = "grpcio_tools-1.62.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d5959e3df126931d28cd94dd5f0a708b7dd96019de80ab715fb922fd0c8a838d"},
{file = "grpcio_tools-1.62.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1927934dfba4658a97c2dab267e53ed239264d40fdd5b295fc317693543db85b"},
{file = "grpcio_tools-1.62.0-cp311-cp311-win32.whl", hash = "sha256:2f5bd22203e64e1732e149bfdd3083716d038abca294e4e2852159b3d893f9ec"},
{file = "grpcio_tools-1.62.0-cp311-cp311-win_amd64.whl", hash = "sha256:cd1f4caeca614b04db803566473f7db0971e7a88268f95e4a529b0ace699b949"},
{file = "grpcio_tools-1.62.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f0884eaf6a2bbd7b03fea456e808909ee48dd4f7f455519d67defda791116368"},
{file = "grpcio_tools-1.62.0-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:6b900ae319b6f9ac1be0ca572dfb41c23a7ff6fcbf36e3be6d3054e1e4c60de6"},
{file = "grpcio_tools-1.62.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:3bbe79b134dfb7c98cf60e4962e31039bef824834cc7034bdf1886a2ed1097f9"},
{file = "grpcio_tools-1.62.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:77196c7ac8741d4a2aebb023bcc2964ac65ca44180fd791640889ab2afed3e47"},
{file = "grpcio_tools-1.62.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b65288ebe12e38dd3650fea65d82fcce0d35df1ae4a770b525c10119ee71962f"},
{file = "grpcio_tools-1.62.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:52b216c458458f6c292e12428916e80974c5113abc505a61e7b0b9f8932a785d"},
{file = "grpcio_tools-1.62.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88aa62303278aec45bbb26bf679269c7890346c37140ae30e39da1070c341e11"},
{file = "grpcio_tools-1.62.0-cp312-cp312-win32.whl", hash = "sha256:bb6802d63e42734d2baf02e1343377fe18590ed6a1f5ffbdebbbe0f8331f176b"},
{file = "grpcio_tools-1.62.0-cp312-cp312-win_amd64.whl", hash = "sha256:d5652d3a52a2e8e1d9bdf28fbd15e21b166e31b968cd7c8c604bf31611c0bb5b"},
{file = "grpcio_tools-1.62.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:84e27206bd884be83a7fdcef8be3c90eb1591341c0ba9b0d25ec9db1043ba2f2"},
{file = "grpcio_tools-1.62.0-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:5eb63d9207b02a0fa30216907e1e7705cc2670f933e77236c6e0eb966ad3b4bf"},
{file = "grpcio_tools-1.62.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:95e49839d49e79187c43cd63af5c206dc5743a01d7d3d2f039772fa743cbb30c"},
{file = "grpcio_tools-1.62.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ae5cd2f89e33a529790bf8aa59a459484edb05e4f58d4cf78836b9dfa1fab43"},
{file = "grpcio_tools-1.62.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e1fd7301d762bf5984b7e7fb62fce82cff864d75f0a57e15cfd07ae1bd79133"},
{file = "grpcio_tools-1.62.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e38d5800151e6804d500e329f7ddfb615c50eee0c1607593e3147a4b21037e40"},
{file = "grpcio_tools-1.62.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:563a75924109e75809b2919e68d7e6ae7872e63d20258aae7899b14f6ff9e18b"},
{file = "grpcio_tools-1.62.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5f8934715577c9cc0c792b8a77f7d0dd2bb60e951161b10c5f46b60856673240"},
{file = "grpcio_tools-1.62.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:ed6cf7ff4a10c46f85340f9c68982f9efb29f51ee4b66828310fcdf3c2d7ffd1"},
{file = "grpcio_tools-1.62.0-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:1faa5006fe9e7b9e65c47bc23f7cd333fdcdd4ba35d44080303848266db5ab05"},
{file = "grpcio_tools-1.62.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:3b526dc5566161a3a17599753838b9cfbdd4cb15b6ad419aae8a5d12053fa8ae"},
{file = "grpcio_tools-1.62.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09db3688efd3499ce3c0b02c0bac0656abdab4cb99716f81ad879c08b92c56e"},
{file = "grpcio_tools-1.62.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:006ea0cc16e8bf8f307326e0556e1384f24abb402cc4e6a720aa1dfe8f268647"},
{file = "grpcio_tools-1.62.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b46ba0b6552b4375ede65e0c89491af532635347f78d52a72f8a027529e713ed"},
{file = "grpcio_tools-1.62.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ec6f561c86fe13cff3be16f297cc05e1aa1274294524743a4cf91d971866fbb0"},
{file = "grpcio_tools-1.62.0-cp38-cp38-win32.whl", hash = "sha256:c85391e06620d6e16a56341caae5007d0c6219beba065e1e288f2523fba6a335"},
{file = "grpcio_tools-1.62.0-cp38-cp38-win_amd64.whl", hash = "sha256:679cf2507090e010da73e5001665c76de2a5927b2e2110e459222b1c81cb10c2"},
{file = "grpcio_tools-1.62.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:0e87f105f1d152934759f8975ed002d5ce057b3cdf1cc6cb63fe6008671a27b9"},
{file = "grpcio_tools-1.62.0-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:bf9f281f528e0220558d57e09b4518dec148dcb250d78bd9cbb27e09edabb3f9"},
{file = "grpcio_tools-1.62.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:711314cb4c6c8b3d51bafaee380ffa5012bd0567ed68f1b0b1fc07492b27acab"},
{file = "grpcio_tools-1.62.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54bb570bd963905de3bda596b35e06026552705edebbb2cb737b57aa5252b9e5"},
{file = "grpcio_tools-1.62.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce5f04676cf94e6e2d13d7f91ac2de79097d86675bc4d404a3c24dcc0332c88"},
{file = "grpcio_tools-1.62.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:98ddf871c614cc0ed331c7159ebbbf5040be562279677d3bb97c2e6083539f72"},
{file = "grpcio_tools-1.62.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f3aaf3b20c0f7063856b2432335af8f76cf580f898e04085548cde28332d6833"},
{file = "grpcio_tools-1.62.0-cp39-cp39-win32.whl", hash = "sha256:3dee3be61d9032f777a9b4e2696ea3d0748a582cb99c672b5d41ca66821e8c87"},
{file = "grpcio_tools-1.62.0-cp39-cp39-win_amd64.whl", hash = "sha256:f54b5181784464bd3573ae7dbcf053da18a4b7a75fe19960791f383be3d035ca"},
{file = "grpcio-tools-1.62.1.tar.gz", hash = "sha256:a4991e5ee8a97ab791296d3bf7e8700b1445635cc1828cc98df945ca1802d7f2"},
{file = "grpcio_tools-1.62.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:f2b404bcae7e2ef9b0b9803b2a95119eb7507e6dc80ea4a64a78be052c30cebc"},
{file = "grpcio_tools-1.62.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:fdd987a580b4474769adfd40144486f54bcc73838d5ec5d3647a17883ea78e76"},
{file = "grpcio_tools-1.62.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:07af1a6442e2313cff22af93c2c4dd37ae32b5239b38e0d99e2cbf93de65429f"},
{file = "grpcio_tools-1.62.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:41384c9ee18e61ef20cad2774ef71bd8854b63efce263b5177aa06fccb84df1f"},
{file = "grpcio_tools-1.62.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c38006f7702d2ff52122e4c77a47348709374050c76216e84b30a9f06e45afa"},
{file = "grpcio_tools-1.62.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:08fecc3c5b4e6dd3278f2b9d12837e423c7dcff551ca1e587018b4a0fc5f8019"},
{file = "grpcio_tools-1.62.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a01e8dcd0f041f6fa6d815c54a2017d032950e310c41d514a8bc041e872c4d12"},
{file = "grpcio_tools-1.62.1-cp310-cp310-win32.whl", hash = "sha256:dd933b8e0b3c13fe3543d58f849a6a5e0d7987688cb6801834278378c724f695"},
{file = "grpcio_tools-1.62.1-cp310-cp310-win_amd64.whl", hash = "sha256:2b04844a9382f1bde4b4174e476e654ab3976168d2469cb4b29e352f4f35a5aa"},
{file = "grpcio_tools-1.62.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:024380536ba71a96cdf736f0954f6ad03f5da609c09edbcc2ca02fdd639e0eed"},
{file = "grpcio_tools-1.62.1-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:21f14b99e0cd38ad56754cc0b62b2bf3cf75f9f7fc40647da54669e0da0726fe"},
{file = "grpcio_tools-1.62.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:975ac5fb482c23f3608c16e06a43c8bab4d79c2e2564cdbc25cf753c6e998775"},
{file = "grpcio_tools-1.62.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50739aaab0c8076ad5957204e71f2e0c9876e11fd8338f7f09de12c2d75163c5"},
{file = "grpcio_tools-1.62.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598c54318f0326cf5020aa43fc95a15e933aba4a71943d3bff2677d2d21ddfa1"},
{file = "grpcio_tools-1.62.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f309bdb33a61f8e049480d41498ee2e525cfb5e959958b326abfdf552bf9b9cb"},
{file = "grpcio_tools-1.62.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f358effd3c11d66c150e0227f983d54a5cd30e14038566dadcf25f9f6844e6e8"},
{file = "grpcio_tools-1.62.1-cp311-cp311-win32.whl", hash = "sha256:b76aead9b73f1650a091870fe4e9ed15ac4d8ed136f962042367255199c23594"},
{file = "grpcio_tools-1.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:d66a5d47eaa427039752fa0a83a425ff2a487b6a0ac30556fd3be2f3a27a0130"},
{file = "grpcio_tools-1.62.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:575535d039b97d63e6a9abee626d6c7cd47bd8cb73dd00a5c84a98254a2164a4"},
{file = "grpcio_tools-1.62.1-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:22644c90e43d1a888477899af917979e17364fdd6e9bbb92679cd6a54c4d36c3"},
{file = "grpcio_tools-1.62.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:156d3e1b227c16e903003a56881dbe60e40f2b4bd66f0bc3b27c53e466e6384d"},
{file = "grpcio_tools-1.62.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ad7c5691625a85327e5b683443baf73ae790fd5afc938252041ed5cd665e377"},
{file = "grpcio_tools-1.62.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e140bbc08eea8abf51c0274f45fb1e8350220e64758998d7f3c7f985a0b2496"},
{file = "grpcio_tools-1.62.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7444fcab861911525470d398e5638b70d5cbea3b4674a3de92b5c58c5c515d4d"},
{file = "grpcio_tools-1.62.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e643cd14a5d1e59865cba68a5a6f0175d987f36c5f4cb0db80dee9ed60b4c174"},
{file = "grpcio_tools-1.62.1-cp312-cp312-win32.whl", hash = "sha256:1344a773d2caa9bb7fbea7e879b84f33740c808c34a5bd2a2768e526117a6b44"},
{file = "grpcio_tools-1.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:2eea1db3748b2f37b4dce84d8e0c15d9bc811094807cabafe7b0ea47f424dfd5"},
{file = "grpcio_tools-1.62.1-cp37-cp37m-linux_armv7l.whl", hash = "sha256:45d2e6cf04d27286b6f73e6e20ba3f0a1f6d8f5535e5dcb1356200419bb457f4"},
{file = "grpcio_tools-1.62.1-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:46ae58e6926773e7315e9005f0f17aacedbc0895a8752bec087d24efa2f1fb21"},
{file = "grpcio_tools-1.62.1-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:4c28086df31478023a36f45e50767872ab3aed2419afff09814cb61c88b77db4"},
{file = "grpcio_tools-1.62.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4fba5b339f4797548591036c9481e6895bf920fab7d3dc664d2697f8fb7c0bf"},
{file = "grpcio_tools-1.62.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23eb3d47f78f509fcd201749b1f1e44b76f447913f7fbb3b8bae20f109086295"},
{file = "grpcio_tools-1.62.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fd5d47707bd6bc2b707ece765c362d2a1d2e8f6cd92b04c99fab49a929f3610c"},
{file = "grpcio_tools-1.62.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d1924a6a943df7c73b9ef0048302327c75962b567451479710da729ead241228"},
{file = "grpcio_tools-1.62.1-cp37-cp37m-win_amd64.whl", hash = "sha256:fe71ca30aabe42591e84ecb9694c0297dc699cc20c5b24d2cb267fb0fc01f947"},
{file = "grpcio_tools-1.62.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:1819fd055c1ae672d1d725ec75eefd1f700c18acba0ed9332202be31d69c401d"},
{file = "grpcio_tools-1.62.1-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:5dbe1f7481dd14b6d477b4bace96d275090bc7636b9883975a08b802c94e7b78"},
{file = "grpcio_tools-1.62.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:771c051c5ece27ad03e4f2e33624a925f0ad636c01757ab7dbb04a37964af4ba"},
{file = "grpcio_tools-1.62.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:98209c438b38b6f1276dbc27b1c04e346a75bfaafe72a25a548f2dc5ce71d226"},
{file = "grpcio_tools-1.62.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2152308e5321cb90fb45aaa84d03d6dedb19735a8779aaf36c624f97b831842d"},
{file = "grpcio_tools-1.62.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ed1f27dc2b2262c8b8d9036276619c1bb18791311c16ccbf1f31b660f2aad7cf"},
{file = "grpcio_tools-1.62.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2744947b6c5e907af21133431809ccca535a037356864e32c122efed8cb9de1f"},
{file = "grpcio_tools-1.62.1-cp38-cp38-win32.whl", hash = "sha256:13b20e269d14ad629ff9a2c9a2450f3dbb119d5948de63b27ffe624fa7aea85a"},
{file = "grpcio_tools-1.62.1-cp38-cp38-win_amd64.whl", hash = "sha256:999823758e9eacd0095863d06cd6d388be769f80c9abb65cdb11c4f2cfce3fea"},
{file = "grpcio_tools-1.62.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:941f8a5c31986053e75fa466bcfa743c2bf1b513b7978cf1f4ab4e96a8219d27"},
{file = "grpcio_tools-1.62.1-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:b9c02c88c77ef6057c6cbeea8922d7c2424aabf46bfc40ddf42a32765ba91061"},
{file = "grpcio_tools-1.62.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:6abd4eb3ccb444383a40156139acc3aaa73745d395139cb6bc8e2a3429e1e627"},
{file = "grpcio_tools-1.62.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:449503213d142f8470b331a1c2f346f8457f16c7fe20f531bc2500e271f7c14c"},
{file = "grpcio_tools-1.62.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a11bcf609d00cfc9baed77ab308223cabc1f0b22a05774a26dd4c94c0c80f1f"},
{file = "grpcio_tools-1.62.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5d7bdea33354b55acf40bb4dd3ba7324d6f1ef6b4a1a4da0807591f8c7e87b9a"},
{file = "grpcio_tools-1.62.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d03b645852d605f43003020e78fe6d573cae6ee6b944193e36b8b317e7549a20"},
{file = "grpcio_tools-1.62.1-cp39-cp39-win32.whl", hash = "sha256:52b185dfc3bf32e70929310367dbc66185afba60492a6a75a9b1141d407e160c"},
{file = "grpcio_tools-1.62.1-cp39-cp39-win_amd64.whl", hash = "sha256:63a273b70896d3640b7a883eb4a080c3c263d91662d870a2e9c84b7bbd978e7b"},
]
[package.dependencies]
grpcio = ">=1.62.0"
grpcio = ">=1.62.1"
protobuf = ">=4.21.6,<5.0dev"
setuptools = "*"
@@ -553,13 +553,13 @@ files = [
[[package]]
name = "packaging"
version = "23.2"
version = "24.0"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.7"
files = [
{file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
{file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
{file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
]
[[package]]
@@ -633,18 +633,18 @@ xml = ["lxml (>=4.9.2)"]
[[package]]
name = "playwright"
version = "1.41.2"
version = "1.42.0"
description = "A high-level API to automate web browsers"
optional = false
python-versions = ">=3.8"
files = [
{file = "playwright-1.41.2-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:cf68335a5dfa4038fa797a4ba0105faee0094ebbb372547d7a27feec5b23c672"},
{file = "playwright-1.41.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:431e3a05f8c99147995e2b3e8475d07818745294fd99f1510b61756e73bdcf68"},
{file = "playwright-1.41.2-py3-none-macosx_11_0_universal2.whl", hash = "sha256:0608717cbf291a625ba6f751061af0fc0cc9bdace217e69d87b1eb1383b03406"},
{file = "playwright-1.41.2-py3-none-manylinux1_x86_64.whl", hash = "sha256:4bf214d812092cf5b9b9648ba84611aa35e28685519911342a7da3a3031f9ed6"},
{file = "playwright-1.41.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaa17ab44622c447de26ed8f7d99912719568d8dbc3a9db0e07f0ae1487709d9"},
{file = "playwright-1.41.2-py3-none-win32.whl", hash = "sha256:edb210a015e70bb0d328bf1c9b65fa3a08361f33e4d7c4ddd1ad2adb6d9b4479"},
{file = "playwright-1.41.2-py3-none-win_amd64.whl", hash = "sha256:71ead0f33e00f5a8533c037c647938b99f219436a1b27d4ba4de4e6bf0567278"},
{file = "playwright-1.42.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:e2b293f077efeaa45253fde31cea4bc6b0ae8be6b5e65e8ce8b4aa3b9f0d55b6"},
{file = "playwright-1.42.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:283887f0bdd0039c3d720e32fbc73a045c24fa800599a6ad60fb199c29580534"},
{file = "playwright-1.42.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:4e1fc1c049a0af64626ddd50814d14a01f316bcbb4d1aa83c3416fe420add558"},
{file = "playwright-1.42.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:313f2551a772f57c9ccca017c4dd4661f2277166f9e1d84bbf5a2e316f0f892c"},
{file = "playwright-1.42.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2a46a24641e5d468046cde567c98fdb8d85e32df901630b14dfb288cbd1ed4f"},
{file = "playwright-1.42.0-py3-none-win32.whl", hash = "sha256:dbf473496808d4c2c816902c1dee2aabc029648e56ce8514b643f5a1a6fc8e22"},
{file = "playwright-1.42.0-py3-none-win_amd64.whl", hash = "sha256:e092c6cfbf797bff03fbdfc53c3e6a9e29fbcf6b82f9e43113d37494aee0561b"},
]
[package.dependencies]
@@ -653,13 +653,13 @@ pyee = "11.0.1"
[[package]]
name = "plotly"
version = "5.19.0"
version = "5.20.0"
description = "An open-source, interactive data visualization library for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "plotly-5.19.0-py3-none-any.whl", hash = "sha256:906abcc5f15945765328c5d47edaa884bc99f5985fbc61e8cd4dc361f4ff8f5a"},
{file = "plotly-5.19.0.tar.gz", hash = "sha256:5ea91a56571292ade3e3bc9bf712eba0b95a1fb0a941375d978cc79432e055f4"},
{file = "plotly-5.20.0-py3-none-any.whl", hash = "sha256:837a9c8aa90f2c0a2f0d747b82544d014dc2a2bdde967b5bb1da25b53932d1a9"},
{file = "plotly-5.20.0.tar.gz", hash = "sha256:bf901c805d22032cfa534b2ff7c5aa6b0659e037f19ec1e0cca7f585918b5c89"},
]
[package.dependencies]
@@ -800,13 +800,13 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"]
[[package]]
name = "pytest"
version = "8.0.2"
version = "8.1.1"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pytest-8.0.2-py3-none-any.whl", hash = "sha256:edfaaef32ce5172d5466b5127b42e0d6d35ebbe4453f0e3505d96afd93f6b096"},
{file = "pytest-8.0.2.tar.gz", hash = "sha256:d4051d623a2e0b7e51960ba963193b09ce6daeb9759a451844a21e4ddedfc1bd"},
{file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"},
{file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"},
]
[package.dependencies]
@@ -814,11 +814,11 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=1.3.0,<2.0"
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
pluggy = ">=1.4,<2.0"
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
[package.extras]
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-base-url"
@@ -1019,18 +1019,18 @@ test = ["asv", "gmpy2", "hypothesis", "mpmath", "pooch", "pytest", "pytest-cov",
[[package]]
name = "setuptools"
version = "69.1.1"
version = "69.2.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "setuptools-69.1.1-py3-none-any.whl", hash = "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56"},
{file = "setuptools-69.1.1.tar.gz", hash = "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"},
{file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"},
{file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
@@ -1165,8 +1165,8 @@ profile = ["pytest-profiling", "snakeviz"]
[package.source]
type = "git"
url = "https://github.com/vegaprotocol/vega-market-sim.git/"
reference = "HEAD"
resolved_reference = "53eed8942acb670783105cb1115bab76710a46dc"
reference = "pre-release/vega-v0.75.5"
resolved_reference = "b2a3437fc13ced2b7ecd582ca4778bbc4b0fe3a6"
[[package]]
name = "websocket-client"
@@ -1347,4 +1347,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = ">=3.9,<3.11"
content-hash = "39ce8400de7bf060857447281ef27bd78c9b1d9639da063b051e3ae6e7887a67"
content-hash = "80e87cbde486e7bb150fce0b1c974de38501bc16732952b455d43cb843426623"
+29
View File
@@ -0,0 +1,29 @@
[tool.poetry]
name = "trading market-sim e2e"
version = "0.1.0"
description = ""
authors = ["Matthew Russell <mattrussell36@gmail.com>"]
readme = "README.md"
packages = [{include = "trading market-sim e2e"}]
[tool.poetry.dependencies]
python = ">=3.9,<3.11"
psutil = "^5.9.5"
pytest-playwright = "^0.4.2"
docker = "^6.1.3"
pytest-xdist = "^3.3.1"
python-dotenv = "^1.0.0"
vega-sim = { git = "https://github.com/vegaprotocol/vega-market-sim.git/", branch = "pre-release/vega-v0.75.5"}
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.pytest.ini_options]
log_cli = true
log_cli_format = "%(asctime)s - %(name)s - %(levelname)s: %(message)s"
log_cli_date_format = "%Y-%m-%d %H:%M:%S"
log_cli_level = "INFO"
log_file_format = "%(asctime)s - %(name)s - %(levelname)s: %(message)s"
log_file_date_format = "%Y-%m-%d %H:%M:%S"
log_file_level = "INFO"

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