Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
429a4ca454 | ||
|
|
4d3cd74847 | ||
|
|
58ef07adaf | ||
|
|
ceeb239c3a | ||
|
|
a5c35b6640 |
@@ -4,5 +4,6 @@ tmp/*
|
||||
.dockerignore
|
||||
dockerfiles
|
||||
node_modules
|
||||
.git
|
||||
.github
|
||||
.vscode
|
||||
|
||||
@@ -196,9 +196,9 @@ jobs:
|
||||
cypress:
|
||||
needs: [build-sources, check-e2e-needed]
|
||||
name: '(CI) cypress'
|
||||
if: ${{ needs.check-e2e-needed.outputs.run-tests == 'true' }}
|
||||
uses: ./.github/workflows/cypress-run.yml
|
||||
secrets: inherit
|
||||
if: needs.check-e2e-needed.outputs.run-tests == 'true' && (contains(needs.build-sources.outputs.projects, 'governance') || contains(needs.build-sources.outputs.projects, 'explorer'))
|
||||
with:
|
||||
projects: ${{ needs.build-sources.outputs.projects-e2e }}
|
||||
tags: '@smoke'
|
||||
@@ -287,7 +287,6 @@ jobs:
|
||||
steps:
|
||||
- run: |
|
||||
result="${{ needs.cypress.result }}"
|
||||
echo "Result: $result"
|
||||
if [[ $result == "success" || $result == "skipped" ]]; then
|
||||
exit 0
|
||||
else
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
inputs:
|
||||
console-test-branch:
|
||||
type: choice
|
||||
description: 'main: v0.74.10, develop: v0.75.5'
|
||||
description: 'main: v0.73.13, develop: v0.74.0'
|
||||
options:
|
||||
- main
|
||||
- develop
|
||||
@@ -57,14 +57,15 @@ jobs:
|
||||
#----------------------------------------------
|
||||
- name: Build trading app
|
||||
run: |
|
||||
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
|
||||
yarn env-cmd -f ./apps/trading/.env.stagnet1 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
|
||||
|
||||
@@ -77,7 +78,7 @@ jobs:
|
||||
load: true
|
||||
build-args: |
|
||||
APP=trading
|
||||
ENV_NAME=${{ needs.console-test-branch.outputs.console-branch == 'main' && 'mainnet' || 'stagnet1' }}
|
||||
ENV_NAME=stagnet1
|
||||
tags: ci/trading:local
|
||||
outputs: type=docker,dest=/tmp/console-image.tar
|
||||
|
||||
@@ -181,22 +182,12 @@ 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 lock && poetry install --no-interaction --no-root
|
||||
run: poetry install --no-interaction --no-root
|
||||
working-directory: apps/trading/e2e
|
||||
#----------------------------------------------
|
||||
# install vega binaries
|
||||
|
||||
@@ -58,6 +58,5 @@ __pycache__/
|
||||
apps/trading/e2e/logs/
|
||||
apps/trading/e2e/.pytest_cache/
|
||||
apps/trading/e2e/traces/
|
||||
apps/trading/e2e/pyproject.toml
|
||||
|
||||
.nx/
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
# path to a directory with all packages
|
||||
storage: ../tmp/local-registry/storage
|
||||
|
||||
# a list of other known repositories we can talk to
|
||||
uplinks:
|
||||
npmjs:
|
||||
url: https://registry.yarnpkg.com
|
||||
maxage: 60m
|
||||
|
||||
packages:
|
||||
'**':
|
||||
# give all users (including non-authenticated users) full access
|
||||
# because it is a local registry
|
||||
access: $all
|
||||
publish: $all
|
||||
unpublish: $all
|
||||
|
||||
# if package is not available locally, proxy requests to npm registry
|
||||
proxy: npmjs
|
||||
|
||||
# log settings
|
||||
logs:
|
||||
type: stdout
|
||||
format: pretty
|
||||
level: warn
|
||||
|
||||
publish:
|
||||
allow_offline: true # set offline to true to allow publish offline
|
||||
@@ -1,5 +1,5 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_ENV=VALIDATORS_TESTNET
|
||||
NX_VEGA_ENV=VALIDATOR_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/
|
||||
|
||||
+2
-5
@@ -1,8 +1,5 @@
|
||||
import type { ChainIdMapping } from '@vegaprotocol/environment';
|
||||
import {
|
||||
SUPPORTED_CHAIN_IDS,
|
||||
SUPPORTED_CHAIN_LABELS,
|
||||
} from '@vegaprotocol/environment';
|
||||
import type { ChainIdMapping } from './external-chain';
|
||||
import { SUPPORTED_CHAIN_IDS, SUPPORTED_CHAIN_LABELS } from './external-chain';
|
||||
|
||||
export const SUPPORTED_CHAIN_ICON_URLS: ChainIdMapping = {
|
||||
'1': '/assets/chain-eth-logo.svg',
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ export const SUPPORTED_CHAIN_LABELS: ChainIdMapping = {
|
||||
'11155111': 'Sepolia',
|
||||
};
|
||||
|
||||
export function getExternalExplorerLink(chainId: string) {
|
||||
export function getExternalExplorerLink(chainId: string, type: string) {
|
||||
if (SUPPORTED_CHAIN_IDS.includes(chainId)) {
|
||||
switch (chainId) {
|
||||
case '1':
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import Hash from '../hash';
|
||||
import { getExternalExplorerLink } from '@vegaprotocol/environment';
|
||||
import { getExternalExplorerLink } from './external-chain';
|
||||
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}/${id}${
|
||||
const link = `${getExternalExplorerLink(chain, type)}/${type}/${id}${
|
||||
code ? '#code' : ''
|
||||
}`;
|
||||
return (
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
LiquiditySLAParametersInfoPanel,
|
||||
MarginScalingFactorsPanel,
|
||||
PriceMonitoringBoundsInfoPanel,
|
||||
PriceMonitoringSettingsInfoPanel,
|
||||
SuccessionLineInfoPanel,
|
||||
getDataSourceSpecForSettlementData,
|
||||
getDataSourceSpecForTradingTermination,
|
||||
@@ -22,6 +21,7 @@ import {
|
||||
RiskModelInfoPanel,
|
||||
SettlementAssetInfoPanel,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { MarketInfoTable } from '@vegaprotocol/markets';
|
||||
import type { DataSourceFragment } from '@vegaprotocol/markets';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
@@ -74,14 +74,27 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
|
||||
<MarginScalingFactorsPanel market={market} />
|
||||
<h2 className={headerClassName}>{t('Risk factors')}</h2>
|
||||
<RiskFactorsInfoPanel market={market} />
|
||||
<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>
|
||||
{(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('Liquidation strategy')}</h2>
|
||||
<LiquidationStrategyInfoPanel market={market} />
|
||||
<h2 className={headerClassName}>{t('Liquidity monitoring')}</h2>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo } from 'react';
|
||||
import { getAsset, type MarketMaybeWithData } from '@vegaprotocol/markets';
|
||||
import { getAsset, type MarketFieldsFragment } 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: MarketMaybeWithData[] | null;
|
||||
data: MarketFieldsFragment[] | 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<MarketMaybeWithData>) => {
|
||||
return data?.data?.marketState
|
||||
? MarketStateMapping[data?.data.marketState]
|
||||
: '-';
|
||||
valueGetter: ({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketFieldsFragment>) => {
|
||||
return data?.state ? MarketStateMapping[data?.state] : '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -70,7 +70,7 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<
|
||||
MarketMaybeWithData,
|
||||
MarketFieldsFragment,
|
||||
'tradableInstrument.instrument.product.settlementAsset.symbol'
|
||||
>) => {
|
||||
const value = data && getAsset(data);
|
||||
@@ -99,7 +99,7 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
|
||||
field: 'id',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaICellRendererParams<MarketMaybeWithData, 'id'>) =>
|
||||
}: VegaICellRendererParams<MarketFieldsFragment, '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: MarketMaybeWithData }) => data.id}
|
||||
getRowId={({ data }: { data: MarketFieldsFragment }) => data.id}
|
||||
overlayNoRowsTemplate={t('This chain has no markets')}
|
||||
domLayout="autoHeight"
|
||||
defaultColDef={{
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
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 (
|
||||
<span>
|
||||
<p>
|
||||
<span>{label}</span>
|
||||
<AssetLink assetId={assetId} showAssetSymbol={true} asDialog={true} />
|
||||
</span>
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
ExternalExplorerLink,
|
||||
EthExplorerLinkTypes,
|
||||
} from '../../../links/external-explorer-link/external-explorer-link';
|
||||
import { getExternalChainLabel } from '@vegaprotocol/environment';
|
||||
import { getExternalChainLabel } from '../../../links/external-explorer-link/external-chain';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import { defaultAbiCoder, base64 } from 'ethers/lib/utils';
|
||||
import { BigNumber } from 'ethers';
|
||||
|
||||
@@ -48,7 +48,7 @@ export const TxDetailsShared = ({
|
||||
}: TxDetailsSharedProps) => {
|
||||
const { data } = useExplorerEpochForBlockQuery({
|
||||
errorPolicy: 'ignore',
|
||||
variables: { block: txData?.block?.toString() || '' },
|
||||
variables: { block: txData?.block.toString() || '' },
|
||||
});
|
||||
|
||||
if (!txData) {
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
query ExplorerTransferStatus($id: ID!) {
|
||||
transfer(id: $id) {
|
||||
fees {
|
||||
amount
|
||||
epoch
|
||||
}
|
||||
transfer {
|
||||
reference
|
||||
timestamp
|
||||
|
||||
+1
-5
@@ -8,16 +8,12 @@ export type ExplorerTransferStatusQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
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 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 const ExplorerTransferStatusDocument = gql`
|
||||
query ExplorerTransferStatus($id: ID!) {
|
||||
transfer(id: $id) {
|
||||
fees {
|
||||
amount
|
||||
epoch
|
||||
}
|
||||
transfer {
|
||||
reference
|
||||
timestamp
|
||||
|
||||
+4
-45
@@ -44,14 +44,8 @@ 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;
|
||||
}
|
||||
|
||||
@@ -66,7 +60,6 @@ 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.
|
||||
@@ -111,9 +104,6 @@ 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 */}
|
||||
@@ -135,6 +125,10 @@ 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>
|
||||
|
||||
@@ -175,38 +169,3 @@ 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,16 +41,9 @@ 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} fees={fees} />
|
||||
<TransferParticipants from={from} transfer={transfer} />
|
||||
{recurring ? <TransferRepeat recurring={transfer.recurring} /> : null}
|
||||
<TransferStatusView status={status} error={error} loading={loading} />
|
||||
{recurring && recurring.dispatchStrategy ? (
|
||||
|
||||
@@ -16,12 +16,12 @@ import { FilterLabel } from './tx-filter-label';
|
||||
|
||||
// All possible transaction types. Should be generated.
|
||||
export type FilterOption =
|
||||
| 'Amend Liquidity Provision Order'
|
||||
| 'Amend LiquidityProvision Order'
|
||||
| 'Amend Order'
|
||||
| 'Apply Referral Code'
|
||||
| 'Batch Market Instructions'
|
||||
| 'Batch Proposal'
|
||||
| 'Cancel Liquidity Provision Order'
|
||||
| 'Cancel LiquidityProvision Order'
|
||||
| 'Cancel Order'
|
||||
| 'Cancel Transfer Funds'
|
||||
| 'Chain Event'
|
||||
@@ -53,10 +53,10 @@ export type FilterOption =
|
||||
|
||||
export const filterOptions: Record<string, FilterOption[]> = {
|
||||
'Market Instructions': [
|
||||
'Amend Liquidity Provision Order',
|
||||
'Amend LiquidityProvision Order',
|
||||
'Amend Order',
|
||||
'Batch Market Instructions',
|
||||
'Cancel Liquidity Provision Order',
|
||||
'Cancel LiquidityProvision Order',
|
||||
'Cancel Order',
|
||||
'Liquidity Provision Order',
|
||||
'Stop Orders Submission',
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('TX: Transfer: getLabelForTransfer', () => {
|
||||
},
|
||||
};
|
||||
|
||||
expect(getTypeLabelForTransfer(mock)).toEqual('Reward transfer');
|
||||
expect(getTypeLabelForTransfer(mock)).toEqual('Reward top up 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 transfer');
|
||||
expect(getTypeLabelForTransfer(mock)).toEqual('Reward top up transfer');
|
||||
});
|
||||
|
||||
it('renders recurring label if the tx has a recurring property', () => {
|
||||
@@ -81,7 +81,6 @@ describe('TxDetailsTransfer', () => {
|
||||
hash: 'test',
|
||||
submitter:
|
||||
'e1943eea46fed576cf2be42972f3c5515ad3d0ac7ac013f56677c12a53a1b3ed',
|
||||
block: '100',
|
||||
command: {
|
||||
nonce: '5188810881378065222',
|
||||
blockHeight: '14951513',
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
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 { marketsWithDataProvider } from '@vegaprotocol/markets';
|
||||
import { marketsProvider } 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: marketsWithDataProvider,
|
||||
dataProvider: marketsProvider,
|
||||
variables: undefined,
|
||||
skipUpdates: true,
|
||||
});
|
||||
|
||||
@@ -89,40 +89,7 @@ fragment ExplorerOracleDataSourceSpec on ExternalDataSourceSpec {
|
||||
}
|
||||
|
||||
query ExplorerOracleFormMarkets {
|
||||
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) {
|
||||
marketsConnection {
|
||||
edges {
|
||||
node {
|
||||
...ExplorerOracleForMarketsMarket
|
||||
|
||||
@@ -16,13 +16,6 @@ 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 {
|
||||
@@ -120,7 +113,7 @@ export const ExplorerOracleDataSourceSpecFragmentDoc = gql`
|
||||
`;
|
||||
export const ExplorerOracleFormMarketsDocument = gql`
|
||||
query ExplorerOracleFormMarkets {
|
||||
marketsConnection(includeSettled: false, pagination: {first: 20}) {
|
||||
marketsConnection {
|
||||
edges {
|
||||
node {
|
||||
...ExplorerOracleForMarketsMarket
|
||||
@@ -133,7 +126,7 @@ export const ExplorerOracleFormMarketsDocument = gql`
|
||||
dataSourceSpec {
|
||||
...ExplorerOracleDataSourceSpec
|
||||
}
|
||||
dataConnection(pagination: {first: 1}) {
|
||||
dataConnection(pagination: {last: 1}) {
|
||||
edges {
|
||||
node {
|
||||
externalData {
|
||||
@@ -179,67 +172,4 @@ 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 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>;
|
||||
export type ExplorerOracleFormMarketsQueryResult = Apollo.QueryResult<ExplorerOracleFormMarketsQuery, ExplorerOracleFormMarketsQueryVariables>;
|
||||
@@ -4,13 +4,8 @@ import {
|
||||
ExternalExplorerLink,
|
||||
EthExplorerLinkTypes,
|
||||
} from '../../../components/links/external-explorer-link/external-explorer-link';
|
||||
import { getExternalChainLabel } from '@vegaprotocol/environment';
|
||||
import { getExternalChainLabel } from '../../../components/links/external-explorer-link/external-chain';
|
||||
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;
|
||||
@@ -39,117 +34,21 @@ 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" className="pt-1 align-text-top">
|
||||
<TableHeader scope="row">
|
||||
{chainLabel} {t('Contract')}
|
||||
</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
<details>
|
||||
<summary className="cursor-pointer">
|
||||
<ExternalExplorerLink
|
||||
chain={chain}
|
||||
id={address}
|
||||
type={EthExplorerLinkTypes.address}
|
||||
code={true}
|
||||
/>
|
||||
<span className="mx-3">⇒</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>
|
||||
<ExternalExplorerLink
|
||||
chain={chain}
|
||||
id={address}
|
||||
type={EthExplorerLinkTypes.address}
|
||||
code={true}
|
||||
/>
|
||||
<span className="mx-3">⇒</span>
|
||||
<code>{sourceType.sourceType.method}</code>
|
||||
</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,9 +1,38 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { OracleFilter } from './oracle-filter';
|
||||
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
|
||||
import { ConditionOperator, DataSourceSpecStatus } from '@vegaprotocol/types';
|
||||
import {
|
||||
ConditionOperator,
|
||||
DataSourceSpecStatus,
|
||||
PropertyKeyType,
|
||||
} 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} />;
|
||||
}
|
||||
@@ -21,6 +50,31 @@ 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({
|
||||
@@ -82,7 +136,7 @@ describe('Oracle Filter view', () => {
|
||||
})
|
||||
);
|
||||
|
||||
// This should never happen, but for coverage we test that it does this
|
||||
// This should never happen, but for coverage sake we test that it does this
|
||||
const ul = res.getByRole('list');
|
||||
expect(ul).toBeInTheDocument();
|
||||
expect(ul).toBeEmptyDOMElement();
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
|
||||
import {
|
||||
OracleSpecInternalTimeTrigger,
|
||||
TimeTrigger,
|
||||
} from './oracle-spec/internal-time-trigger';
|
||||
import { OracleSpecInternalTimeTrigger } from './oracle-spec/internal-time-trigger';
|
||||
import { OracleSpecCondition } from './oracle-spec/condition';
|
||||
import { getCharacterForOperator } from './oracle-spec/operator';
|
||||
|
||||
@@ -14,7 +11,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.
|
||||
*/
|
||||
@@ -24,7 +21,6 @@ export function OracleFilter({ data }: OracleFilterProps) {
|
||||
}
|
||||
|
||||
const s = data.dataSourceSpec.spec.data.sourceType.sourceType;
|
||||
|
||||
if (s.__typename === 'DataSourceSpecConfigurationTime' && s.conditions) {
|
||||
return (
|
||||
<ul>
|
||||
@@ -45,30 +41,30 @@ export function OracleFilter({ data }: OracleFilterProps) {
|
||||
s.triggers
|
||||
) {
|
||||
return <OracleSpecInternalTimeTrigger data={s} />;
|
||||
} else if (s.__typename === 'EthCallSpec') {
|
||||
} else if (
|
||||
s.__typename === 'EthCallSpec' ||
|
||||
s.__typename === 'DataSourceSpecConfiguration'
|
||||
) {
|
||||
if (s.filters !== null && s.filters && 'filters' in s) {
|
||||
return (
|
||||
<div>
|
||||
<ul>
|
||||
{s.filters.map((f) => {
|
||||
const prop = <code title={f.key.type}>{f.key.name}</code>;
|
||||
<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>
|
||||
{s.trigger && <TimeTrigger data={s.trigger.trigger} />}
|
||||
</div>
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+27
-60
@@ -1,10 +1,5 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type {
|
||||
DataSourceSpecConfigurationTimeTrigger,
|
||||
EthTimeTrigger,
|
||||
InternalTimeTrigger,
|
||||
Maybe,
|
||||
} from '@vegaprotocol/types';
|
||||
import type { DataSourceSpecConfigurationTimeTrigger } from '@vegaprotocol/types';
|
||||
import secondsToMinutes from 'date-fns/secondsToMinutes';
|
||||
import fromUnixTime from 'date-fns/fromUnixTime';
|
||||
|
||||
@@ -18,60 +13,32 @@ export function OracleSpecInternalTimeTrigger({
|
||||
return (
|
||||
<div>
|
||||
<span>{t('Time')}</span>,
|
||||
{data.triggers.map((tr) => (
|
||||
<TimeTrigger data={tr} />
|
||||
))}
|
||||
{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>
|
||||
);
|
||||
})}
|
||||
</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,11 +48,6 @@ 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">
|
||||
@@ -69,23 +64,15 @@ 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" className="pt-1 align-text-top">
|
||||
{t('Filter')}
|
||||
</TableHeader>
|
||||
<TableHeader scope="row">{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,10 +1,17 @@
|
||||
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 { OraclesTable } from '../../../components/oracle-table';
|
||||
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';
|
||||
|
||||
const Oracles = () => {
|
||||
const { data, loading, error } = useExplorerOracleFormMarketsQuery({
|
||||
@@ -14,6 +21,8 @@ const Oracles = () => {
|
||||
useDocumentTitle(['Oracles']);
|
||||
useScrollToLocation();
|
||||
|
||||
const [hoveredOracle, setHoveredOracle] = useState('');
|
||||
|
||||
return (
|
||||
<section>
|
||||
<RouteTitle data-testid="oracle-specs-heading">{t('Oracles')}</RouteTitle>
|
||||
@@ -29,7 +38,148 @@ const Oracles = () => {
|
||||
data.oracleSpecsConnection.edges?.length === 0
|
||||
}
|
||||
>
|
||||
<OraclesTable data={data} />
|
||||
<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>
|
||||
</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 { getInitialFilters, useTxsData } from '../../../hooks/use-txs-data';
|
||||
import { 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,18 +15,16 @@ 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 {
|
||||
TxsFilter,
|
||||
type FilterOption,
|
||||
} from '../../../components/txs/tx-filter';
|
||||
import type { FilterOption } from '../../../components/txs/tx-filter';
|
||||
import { AllFilterOptions, TxsFilter } 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 || '-']);
|
||||
|
||||
@@ -5,8 +5,9 @@ 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 } from './parties/id';
|
||||
import { Party as PartySingle } from './parties/id';
|
||||
import { ValidatorsPage } from './validators';
|
||||
import Genesis from './genesis';
|
||||
import { Block } from './blocks/id';
|
||||
@@ -16,7 +17,6 @@ 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,7 +31,6 @@ 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;
|
||||
@@ -68,7 +67,7 @@ export const useRouterConfig = () => {
|
||||
? [
|
||||
{
|
||||
path: Routes.PARTIES,
|
||||
element: <Outlet />,
|
||||
element: <Party />,
|
||||
handle: {
|
||||
name: t('Parties'),
|
||||
text: t('Parties'),
|
||||
@@ -81,12 +80,12 @@ export const useRouterConfig = () => {
|
||||
},
|
||||
{
|
||||
path: ':party',
|
||||
element: <Outlet />,
|
||||
element: <Party />,
|
||||
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Party />,
|
||||
element: <PartySingle />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.PARTIES, params.party)}>
|
||||
@@ -97,7 +96,7 @@ export const useRouterConfig = () => {
|
||||
},
|
||||
{
|
||||
path: 'assets',
|
||||
element: <Outlet />,
|
||||
element: <Party />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<Link to={linkTo(Routes.PARTIES, params.party)}>
|
||||
@@ -200,36 +199,12 @@ export const useRouterConfig = () => {
|
||||
},
|
||||
{
|
||||
path: ':marketId',
|
||||
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'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
element: <MarketPage />,
|
||||
handle: {
|
||||
breadcrumb: (params: Params<string>) => (
|
||||
<MarketLink id={params.marketId as string} />
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
const { join } = require('path');
|
||||
const { createGlobPatternsForDependencies } = require('@nx/react/tailwind');
|
||||
const { theme } = require('../../libs/tailwindcss-config/src/theme');
|
||||
const {
|
||||
vegaCustomClasses,
|
||||
} = require('../../libs/tailwindcss-config/src/vega-custom-classes');
|
||||
const theme = require('../../libs/tailwindcss-config/src/theme');
|
||||
const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom-classes');
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
navigateTo,
|
||||
navigation,
|
||||
turnTelemetryOff,
|
||||
setRiskAccepted,
|
||||
} from '../../support/common.functions';
|
||||
import {
|
||||
clickOnValidatorFromList,
|
||||
@@ -58,7 +57,6 @@ context(
|
||||
// 1002-STKE-002, 1002-STKE-032
|
||||
before('visit staking tab and connect vega wallet', function () {
|
||||
cy.visit('/');
|
||||
setRiskAccepted();
|
||||
ethereumWalletConnect();
|
||||
cy.connectVegaWallet();
|
||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
navigateTo,
|
||||
navigation,
|
||||
turnTelemetryOff,
|
||||
setRiskAccepted,
|
||||
} from '../../support/common.functions';
|
||||
import {
|
||||
stakingPageAssociateTokens,
|
||||
@@ -58,7 +57,6 @@ context(
|
||||
function () {
|
||||
cy.clearLocalStorage();
|
||||
turnTelemetryOff();
|
||||
setRiskAccepted();
|
||||
cy.mockChainId();
|
||||
cy.reload();
|
||||
waitForSpinner();
|
||||
|
||||
@@ -79,23 +79,23 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('should have information on active nodes', function () {
|
||||
it('should have information on active nodes', function () {
|
||||
cy.getByTestId('node-information')
|
||||
.first()
|
||||
.should('contain.text', '2')
|
||||
.should('contain.text', '1')
|
||||
.and('contain.text', 'active nodes');
|
||||
});
|
||||
|
||||
it.skip('should have information on consensus nodes', function () {
|
||||
it('should have information on consensus nodes', function () {
|
||||
cy.getByTestId('node-information')
|
||||
.last()
|
||||
.should('contain.text', '2')
|
||||
.should('contain.text', '1')
|
||||
.and('contain.text', 'consensus nodes');
|
||||
});
|
||||
|
||||
it.skip('should contain link to specific validators', function () {
|
||||
it('should contain link to specific validators', function () {
|
||||
cy.getByTestId('validators')
|
||||
.should('have.length', '2')
|
||||
.should('have.length', '1')
|
||||
.each(($validator) => {
|
||||
cy.wrap($validator).find('a').should('have.attr', 'href');
|
||||
});
|
||||
@@ -153,7 +153,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
.invoke('text')
|
||||
.should('not.eq', currentBlockHeight);
|
||||
});
|
||||
cy.getByTestId('subscription-cell').should('be.be.visible');
|
||||
cy.getByTestId('subscription-cell').should('have.text', 'Yes');
|
||||
});
|
||||
cy.getByTestId('connect').should('be.disabled');
|
||||
cy.getByTestId('node-url-custom').click({ force: true });
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import {
|
||||
navigation,
|
||||
setRiskAccepted,
|
||||
verifyPageHeader,
|
||||
verifyTabHighlighted,
|
||||
} from '../../support/common.functions';
|
||||
@@ -188,7 +187,6 @@ context('Validators Page - verify elements on page', function () {
|
||||
before('connect wallets and click on validator', function () {
|
||||
cy.mockChainId();
|
||||
cy.visit('/validators');
|
||||
setRiskAccepted();
|
||||
cy.connectVegaWallet();
|
||||
clickOnValidatorFromList(0);
|
||||
});
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { truncateByChars } from '@vegaprotocol/utils';
|
||||
import {
|
||||
setRiskAccepted,
|
||||
waitForSpinner,
|
||||
} from '../../support/common.functions';
|
||||
import { waitForSpinner } from '../../support/common.functions';
|
||||
import {
|
||||
vegaWalletFaucetAssetsWithoutCheck,
|
||||
vegaWalletTeardown,
|
||||
@@ -14,6 +11,7 @@ const connectButton = 'connect-vega-wallet';
|
||||
const getVegaLink = 'link';
|
||||
const dialog = '[role="dialog"]:visible';
|
||||
const dialogHeader = 'dialog-title';
|
||||
const walletDialogHeader = 'wallet-dialog-title';
|
||||
const connectorsList = 'connectors-list';
|
||||
const dialogCloseBtn = 'dialog-close';
|
||||
const accountNo = 'vega-account-truncated';
|
||||
@@ -36,7 +34,6 @@ context(
|
||||
() => {
|
||||
before('visit token home page', () => {
|
||||
cy.visit('/');
|
||||
setRiskAccepted();
|
||||
cy.get(walletContainer, { timeout: 60000 }).should('be.visible');
|
||||
});
|
||||
|
||||
@@ -66,12 +63,17 @@ context(
|
||||
|
||||
it('should have Connect Vega header visible', () => {
|
||||
cy.get(dialog).within(() => {
|
||||
cy.getByTestId(connectorsList)
|
||||
cy.getByTestId(walletDialogHeader)
|
||||
.should('be.visible')
|
||||
.and(
|
||||
'have.text',
|
||||
'Get the Vega WalletGet MetaMask>_Command Line WalletView as public key'
|
||||
);
|
||||
.and('have.text', 'Get a Vega wallet');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have jsonRpc and hosted connection options visible on list', function () {
|
||||
cy.getByTestId(connectorsList).within(() => {
|
||||
cy.getByTestId('connector-jsonRpc')
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Use the Desktop App/CLI');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -86,6 +88,7 @@ context(
|
||||
before('connect vega wallet', function () {
|
||||
cy.mockChainId();
|
||||
cy.visit('/');
|
||||
cy.wait('@ChainId');
|
||||
cy.connectVegaWallet();
|
||||
vegaWalletTeardown();
|
||||
});
|
||||
|
||||
@@ -102,12 +102,6 @@ export function turnTelemetryOff() {
|
||||
);
|
||||
}
|
||||
|
||||
export function setRiskAccepted() {
|
||||
cy.window().then((win) =>
|
||||
win.localStorage.setItem('vega_wallet_risk_accepted', 'true')
|
||||
);
|
||||
}
|
||||
|
||||
export function dissociateFromSecondWalletKey() {
|
||||
const secondWalletKey = Cypress.env('vegaWalletPublicKey2Short');
|
||||
cy.getByTestId('vega-in-wallet')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_ENV=VALIDATORS_TESTNET
|
||||
NX_VEGA_ENV=VALIDATOR_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/
|
||||
|
||||
@@ -26,9 +26,9 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||
const { token, staking, vesting } = useContracts();
|
||||
const setAssociatedBalances = useRefreshAssociatedBalances();
|
||||
const [balancesLoaded, setBalancesLoaded] = React.useState(false);
|
||||
const vegaWalletStatus = useEagerConnect();
|
||||
const vegaConnecting = useEagerConnect();
|
||||
|
||||
const loaded = balancesLoaded && vegaWalletStatus !== 'connecting';
|
||||
const loaded = balancesLoaded && !vegaConnecting;
|
||||
|
||||
React.useEffect(() => {
|
||||
const run = async () => {
|
||||
@@ -169,5 +169,3 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||
}
|
||||
return <Suspense fallback={loading}>{children}</Suspense>;
|
||||
};
|
||||
|
||||
AppLoader.displayName = 'AppLoader';
|
||||
|
||||
@@ -47,7 +47,7 @@ export const SubHeading = ({
|
||||
|
||||
return (
|
||||
<h2
|
||||
className={classNames('text-2xl font-alpha calt break-words', {
|
||||
className={classNames('text-2xl font-alpha calt uppercase break-words', {
|
||||
'mx-auto': centerContent,
|
||||
'mb-0': !marginBottom,
|
||||
'mb-4': marginBottom,
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import {
|
||||
useLinks,
|
||||
DApp,
|
||||
CONSOLE_REWARDS_PAGE,
|
||||
} from '@vegaprotocol/environment';
|
||||
import {
|
||||
ExternalLink,
|
||||
Intent,
|
||||
NotificationBanner,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { Trans } from 'react-i18next';
|
||||
import { useMatch } from 'react-router-dom';
|
||||
import Routes from '../../routes/routes';
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
const ConsoleRewardsLink = ({ children }: { children: ReactNode }) => {
|
||||
const consoleLink = useLinks(DApp.Console);
|
||||
return (
|
||||
<ExternalLink
|
||||
href={consoleLink(CONSOLE_REWARDS_PAGE)}
|
||||
className="underline inline-flex gap-1 items-center"
|
||||
title="Rewards in Console"
|
||||
>
|
||||
<span>{children}</span>
|
||||
<VegaIcon size={12} name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
</ExternalLink>
|
||||
);
|
||||
};
|
||||
|
||||
export const RewardsMovedNotification = () => {
|
||||
const onRewardsPage = useMatch(Routes.REWARDS);
|
||||
if (!onRewardsPage) return null;
|
||||
|
||||
return (
|
||||
<NotificationBanner intent={Intent.Warning}>
|
||||
<Trans
|
||||
i18nKey="rewardsMovedNotification"
|
||||
components={[<ConsoleRewardsLink>Console</ConsoleRewardsLink>]}
|
||||
/>
|
||||
</NotificationBanner>
|
||||
);
|
||||
};
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
ProtocolUpgradeProposalNotification,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { ViewingAsBanner } from '@vegaprotocol/ui-toolkit';
|
||||
import { RewardsMovedNotification } from '../notifications/rewards-moved-notification';
|
||||
|
||||
interface AppLayoutProps {
|
||||
children: ReactNode;
|
||||
@@ -46,10 +45,8 @@ export const AppLayout = ({ children }: AppLayoutProps) => {
|
||||
|
||||
const NotificationsContainer = () => {
|
||||
const { isReadOnly, pubKey, disconnect } = useVegaWallet();
|
||||
|
||||
return (
|
||||
<div data-testid="banners">
|
||||
<RewardsMovedNotification />
|
||||
<ProtocolUpgradeProposalNotification
|
||||
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
|
||||
/>
|
||||
|
||||
@@ -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
|
||||
},
|
||||
VALIDATORS_TESTNET: {
|
||||
VALIDATOR_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
|
||||
|
||||
@@ -111,4 +111,3 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
|
||||
</ContractsContext.Provider>
|
||||
);
|
||||
};
|
||||
ContractsProvider.displayName = 'ContractsProvider';
|
||||
|
||||
@@ -6,11 +6,10 @@ import {
|
||||
ViewPartyConnector,
|
||||
createConfig,
|
||||
fairground,
|
||||
validatorsTestnet,
|
||||
stagnet,
|
||||
mainnet,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { CHAIN_IDS, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
|
||||
export const useVegaWalletConfig = () => {
|
||||
const { VEGA_ENV, VEGA_URL, VEGA_WALLET_URL } = useEnvironment();
|
||||
@@ -32,8 +31,8 @@ export const useVegaWalletConfig = () => {
|
||||
const viewParty = new ViewPartyConnector();
|
||||
|
||||
const config = createConfig({
|
||||
chains: [mainnet, fairground, validatorsTestnet, stagnet],
|
||||
defaultChainId: CHAIN_IDS[VEGA_ENV],
|
||||
chains: [mainnet, fairground, stagnet],
|
||||
defaultChainId: fairground.id,
|
||||
connectors: [injected, snap, jsonRpc, viewParty],
|
||||
});
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ i18n
|
||||
load: 'languageOnly',
|
||||
debug: isInDev,
|
||||
// have a common namespace used around the full app
|
||||
ns: ['governance', 'wallet', 'wallet-react', 'assets', 'utils'],
|
||||
ns: ['governance', 'wallet', 'wallet-react'],
|
||||
defaultNS: 'governance',
|
||||
keySeparator: false, // we use content as keys
|
||||
nsSeparator: false,
|
||||
|
||||
+1
-4
@@ -38,7 +38,6 @@ import { differenceInHours, format, formatDistanceToNowStrict } from 'date-fns';
|
||||
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
|
||||
import { MarketName } from '../proposal/market-name';
|
||||
import { Indicator } from '../proposal/indicator';
|
||||
import { type ProposalNode } from '../proposal/proposal-utils';
|
||||
|
||||
const ProposalTypeTags = ({
|
||||
proposal,
|
||||
@@ -541,12 +540,10 @@ const BatchProposalStateText = ({
|
||||
|
||||
export const ProposalHeader = ({
|
||||
proposal,
|
||||
restData,
|
||||
isListItem = true,
|
||||
voteState,
|
||||
}: {
|
||||
proposal: Proposal | BatchProposal;
|
||||
restData?: ProposalNode | null;
|
||||
isListItem?: boolean;
|
||||
voteState?: VoteState | null;
|
||||
}) => {
|
||||
@@ -598,7 +595,7 @@ export const ProposalHeader = ({
|
||||
)}
|
||||
</div>
|
||||
<ProposalDetails proposal={proposal} />
|
||||
<VoteBreakdown proposal={proposal} restData={restData} />
|
||||
<VoteBreakdown proposal={proposal} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+46
-33
@@ -19,7 +19,6 @@ import {
|
||||
getSigners,
|
||||
MarginScalingFactorsPanel,
|
||||
marketInfoProvider,
|
||||
PriceMonitoringSettingsInfoPanel,
|
||||
} from '@vegaprotocol/markets';
|
||||
import {
|
||||
Button,
|
||||
@@ -50,7 +49,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 = ({ marketId }: { marketId: string }) => {
|
||||
export const ProposalMarketData = ({ proposalId }: { proposalId: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, open, close } = useMarketDataDialogStore();
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
@@ -59,7 +58,7 @@ export const ProposalMarketData = ({ marketId }: { marketId: string }) => {
|
||||
dataProvider: marketInfoProvider,
|
||||
skipUpdates: true,
|
||||
variables: {
|
||||
marketId: marketId,
|
||||
marketId: proposalId,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -72,7 +71,7 @@ export const ProposalMarketData = ({ marketId }: { marketId: string }) => {
|
||||
},
|
||||
});
|
||||
|
||||
if (!marketData) {
|
||||
if (!marketData || !parentMarketData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -134,13 +133,13 @@ export const ProposalMarketData = ({ marketId }: { marketId: string }) => {
|
||||
<h2 className={marketDataHeaderStyles}>{t('Key details')}</h2>
|
||||
<KeyDetailsInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData ? parentMarketData : undefined}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Instrument')}</h2>
|
||||
<InstrumentInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData ? parentMarketData : undefined}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
{settlementData &&
|
||||
@@ -156,7 +155,7 @@ export const ProposalMarketData = ({ marketId }: { marketId: string }) => {
|
||||
isParentSettlementDataEqual ||
|
||||
isParentSettlementScheduleDataEqual
|
||||
? undefined
|
||||
: parentMarketData || undefined
|
||||
: parentMarketData
|
||||
}
|
||||
/>
|
||||
</>
|
||||
@@ -169,9 +168,7 @@ export const ProposalMarketData = ({ marketId }: { marketId: string }) => {
|
||||
market={marketData}
|
||||
type="settlementData"
|
||||
parentMarket={
|
||||
isParentSettlementDataEqual
|
||||
? undefined
|
||||
: parentMarketData || undefined
|
||||
isParentSettlementDataEqual ? undefined : parentMarketData
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -187,7 +184,7 @@ export const ProposalMarketData = ({ marketId }: { marketId: string }) => {
|
||||
parentMarket={
|
||||
isParentTerminationDataEqual
|
||||
? undefined
|
||||
: parentMarketData || undefined
|
||||
: parentMarketData
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
@@ -205,7 +202,7 @@ export const ProposalMarketData = ({ marketId }: { marketId: string }) => {
|
||||
parentMarket={
|
||||
isParentSettlementScheduleDataEqual
|
||||
? undefined
|
||||
: parentMarketData || undefined
|
||||
: parentMarketData
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
@@ -219,19 +216,19 @@ export const ProposalMarketData = ({ marketId }: { marketId: string }) => {
|
||||
<h2 className={marketDataHeaderStyles}>{t('Settlement assets')}</h2>
|
||||
<SettlementAssetInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData || undefined}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Metadata')}</h2>
|
||||
<MetadataInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData || undefined}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Risk model')}</h2>
|
||||
<RiskModelInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData || undefined}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
@@ -239,45 +236,61 @@ export const ProposalMarketData = ({ marketId }: { marketId: string }) => {
|
||||
</h2>
|
||||
<MarginScalingFactorsPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData || undefined}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Risk factors')}</h2>
|
||||
<RiskFactorsInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData || undefined}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
{showParentPriceMonitoringBounds && (
|
||||
// shows bounds for parent market
|
||||
{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) => (
|
||||
<>
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t('Parent price monitoring bounds')}
|
||||
{t(`Price monitoring bounds ${triggerIndex + 1}`)}
|
||||
</h2>
|
||||
<div className="text-vega-dark-300 line-through">
|
||||
<PriceMonitoringBoundsInfoPanel market={parentMarketData} />
|
||||
</div>
|
||||
|
||||
<PriceMonitoringBoundsInfoPanel
|
||||
market={marketData}
|
||||
triggerIndex={triggerIndex}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t('Price monitoring settings')}
|
||||
</h2>
|
||||
<PriceMonitoringSettingsInfoPanel market={marketData} />
|
||||
|
||||
))}
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t('Liquidity monitoring parameters')}
|
||||
</h2>
|
||||
<LiquidityMonitoringParametersInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData || undefined}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t('Liquidity price range')}
|
||||
</h2>
|
||||
<LiquidityPriceRangeInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData || undefined}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
@@ -285,7 +298,7 @@ export const ProposalMarketData = ({ marketId }: { marketId: string }) => {
|
||||
</h2>
|
||||
<LiquiditySLAParametersInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData || undefined}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
+2
-16
@@ -17,20 +17,17 @@ 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;
|
||||
@@ -64,18 +61,7 @@ export const ProposalChangeDetails = ({
|
||||
}
|
||||
case 'NewMarket': {
|
||||
if (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} />;
|
||||
details = <ProposalMarketData proposalId={proposal.id} />;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -83,7 +69,7 @@ export const ProposalChangeDetails = ({
|
||||
if (proposal.id) {
|
||||
details = (
|
||||
<div className="flex flex-col gap-4">
|
||||
<ProposalMarketData marketId={proposal.id} />
|
||||
<ProposalMarketData proposalId={proposal.id} />
|
||||
<ProposalMarketChanges
|
||||
indicator={indicator}
|
||||
marketId={terms.change.marketId}
|
||||
|
||||
@@ -91,28 +91,6 @@ export type ProposalNode = {
|
||||
proposal: ProposalData;
|
||||
proposalType: ProposalNodeType;
|
||||
proposals: SubProposalData[];
|
||||
yes?: [
|
||||
{
|
||||
partyId: string;
|
||||
elsPerMarket?: [
|
||||
{
|
||||
marketId: string;
|
||||
els: string;
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
no?: [
|
||||
{
|
||||
partyId: string;
|
||||
elsPerMarket?: [
|
||||
{
|
||||
marketId: string;
|
||||
els: string;
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
type SingleProposalNode = ProposalNode & {
|
||||
|
||||
@@ -48,7 +48,6 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
||||
|
||||
<ProposalHeader
|
||||
proposal={proposal}
|
||||
restData={restData}
|
||||
isListItem={false}
|
||||
voteState={voteState}
|
||||
/>
|
||||
@@ -78,7 +77,6 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
||||
proposal={proposal}
|
||||
terms={p.terms}
|
||||
restData={restData}
|
||||
termsCount={proposal.subProposals?.length}
|
||||
/>
|
||||
);
|
||||
})
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
import { useBatchVoteInformation } from '../../hooks/use-vote-information';
|
||||
import { MarketName } from '../proposal/market-name';
|
||||
import { Indicator } from '../proposal/indicator';
|
||||
import { type ProposalNode } from '../proposal/proposal-utils';
|
||||
|
||||
export const CompactVotes = ({ number }: { number: BigNumber }) => (
|
||||
<CompactNumber
|
||||
@@ -111,64 +110,24 @@ const Status = ({ reached, threshold, text, testId }: StatusProps) => {
|
||||
|
||||
export const VoteBreakdown = ({
|
||||
proposal,
|
||||
restData,
|
||||
}: {
|
||||
proposal: Proposal | BatchProposal;
|
||||
restData?: ProposalNode | null;
|
||||
}) => {
|
||||
if (proposal.__typename === 'Proposal') {
|
||||
return <VoteBreakdownNormal proposal={proposal} />;
|
||||
}
|
||||
|
||||
if (proposal.__typename === 'BatchProposal') {
|
||||
return <VoteBreakdownBatch proposal={proposal} restData={restData} />;
|
||||
return <VoteBreakdownBatch proposal={proposal} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const VoteBreakdownBatch = ({
|
||||
proposal,
|
||||
restData,
|
||||
}: {
|
||||
proposal: BatchProposal;
|
||||
restData?: ProposalNode | null;
|
||||
}) => {
|
||||
const VoteBreakdownBatch = ({ proposal }: { proposal: BatchProposal }) => {
|
||||
const [fullBreakdown, setFullBreakdown] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const yesELS =
|
||||
restData?.yes?.reduce((all, y) => {
|
||||
if (y.elsPerMarket) {
|
||||
y.elsPerMarket.forEach((item) => {
|
||||
const share = Number(item.els);
|
||||
if (all[item.marketId]) {
|
||||
all[item.marketId].push(share);
|
||||
} else {
|
||||
all[item.marketId] = [share];
|
||||
}
|
||||
return all;
|
||||
});
|
||||
}
|
||||
return all;
|
||||
}, {} as Record<string, number[]>) || {};
|
||||
|
||||
const noELS =
|
||||
restData?.no?.reduce((all, y) => {
|
||||
if (y.elsPerMarket) {
|
||||
y.elsPerMarket.forEach((item) => {
|
||||
const share = Number(item.els);
|
||||
if (all[item.marketId]) {
|
||||
all[item.marketId].push(share);
|
||||
} else {
|
||||
all[item.marketId] = [share];
|
||||
}
|
||||
return all;
|
||||
});
|
||||
}
|
||||
return all;
|
||||
}, {} as Record<string, number[]>) || {};
|
||||
|
||||
const voteInfo = useBatchVoteInformation({
|
||||
terms: compact(
|
||||
proposal.subProposals ? proposal.subProposals.map((p) => p?.terms) : []
|
||||
@@ -235,8 +194,6 @@ const VoteBreakdownBatch = ({
|
||||
proposal={proposal}
|
||||
votes={proposal.votes}
|
||||
terms={p.terms}
|
||||
yesELS={yesELS}
|
||||
noELS={noELS}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -297,8 +254,6 @@ const VoteBreakdownBatch = ({
|
||||
proposal={proposal}
|
||||
votes={proposal.votes}
|
||||
terms={p.terms}
|
||||
yesELS={yesELS}
|
||||
noELS={noELS}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -316,17 +271,17 @@ const VoteBreakdownBatchSubProposal = ({
|
||||
votes,
|
||||
terms,
|
||||
indicator,
|
||||
yesELS,
|
||||
noELS,
|
||||
}: {
|
||||
proposal: BatchProposal;
|
||||
votes: VoteFieldsFragment;
|
||||
terms: ProposalTermsFieldsFragment;
|
||||
indicator?: number;
|
||||
yesELS: Record<string, number[]>;
|
||||
noELS: Record<string, number[]>;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const voteInfo = useVoteInformation({
|
||||
votes,
|
||||
terms,
|
||||
});
|
||||
|
||||
const isProposalOpen = proposal?.state === ProposalState.STATE_OPEN;
|
||||
const isUpdateMarket = terms?.change?.__typename === 'UpdateMarket';
|
||||
@@ -339,15 +294,6 @@ const VoteBreakdownBatchSubProposal = ({
|
||||
marketId = terms.change.market.id;
|
||||
}
|
||||
|
||||
const voteInfo = useVoteInformation({
|
||||
votes,
|
||||
terms,
|
||||
// yes votes ELS for this specific proposal (market)
|
||||
yesELS: marketId ? yesELS[marketId] : undefined,
|
||||
// no votes ELS for this specific proposal (market)
|
||||
noELS: marketId ? noELS[marketId] : undefined,
|
||||
});
|
||||
|
||||
const marketName = marketId ? (
|
||||
<>
|
||||
: <MarketName marketId={marketId} />
|
||||
|
||||
@@ -8,18 +8,13 @@ import {
|
||||
type VoteFieldsFragment,
|
||||
} from '../__generated__/Proposals';
|
||||
import { type ProposalChangeType } from '../types';
|
||||
import sum from 'lodash/sum';
|
||||
|
||||
export const useVoteInformation = ({
|
||||
votes,
|
||||
terms,
|
||||
yesELS,
|
||||
noELS,
|
||||
}: {
|
||||
votes: VoteFieldsFragment;
|
||||
terms: ProposalTermsFieldsFragment;
|
||||
yesELS?: number[];
|
||||
noELS?: number[];
|
||||
}) => {
|
||||
const {
|
||||
appState: { totalSupply, decimals },
|
||||
@@ -36,9 +31,7 @@ export const useVoteInformation = ({
|
||||
paramsForChange,
|
||||
votes,
|
||||
totalSupply,
|
||||
decimals,
|
||||
yesELS,
|
||||
noELS
|
||||
decimals
|
||||
);
|
||||
};
|
||||
|
||||
@@ -79,11 +72,7 @@ const getVoteData = (
|
||||
},
|
||||
votes: ProposalFieldsFragment['votes'],
|
||||
totalSupply: BigNumber,
|
||||
decimals: number,
|
||||
/** A list of ELS yes votes */
|
||||
yesELS?: number[],
|
||||
/** A list if ELS no votes */
|
||||
noELS?: number[]
|
||||
decimals: number
|
||||
) => {
|
||||
const requiredMajorityPercentage = params.requiredMajority
|
||||
? new BigNumber(params.requiredMajority).times(100)
|
||||
@@ -97,31 +86,17 @@ const getVoteData = (
|
||||
addDecimal(votes.no.totalTokens ?? 0, decimals)
|
||||
);
|
||||
|
||||
let noEquityLikeShareWeight = !votes.no.totalEquityLikeShareWeight
|
||||
const noEquityLikeShareWeight = !votes.no.totalEquityLikeShareWeight
|
||||
? new BigNumber(0)
|
||||
: new BigNumber(votes.no.totalEquityLikeShareWeight).times(100);
|
||||
// there's no meaningful `totalEquityLikeShareWeight` in batch proposals,
|
||||
// it has to be deduced from `elsPerMarket` of `no` votes of given proposal
|
||||
// data. (by REST DATA)
|
||||
if (noELS != null) {
|
||||
const noTotalELS = sum(noELS);
|
||||
noEquityLikeShareWeight = new BigNumber(noTotalELS).times(100);
|
||||
}
|
||||
|
||||
const yesTokens = new BigNumber(
|
||||
addDecimal(votes.yes.totalTokens ?? 0, decimals)
|
||||
);
|
||||
|
||||
let yesEquityLikeShareWeight = !votes.yes.totalEquityLikeShareWeight
|
||||
const yesEquityLikeShareWeight = !votes.yes.totalEquityLikeShareWeight
|
||||
? new BigNumber(0)
|
||||
: new BigNumber(votes.yes.totalEquityLikeShareWeight).times(100);
|
||||
// there's no meaningful `totalEquityLikeShareWeight` in batch proposals,
|
||||
// it has to be deduced from `elsPerMarket` of `yes` votes of given proposal
|
||||
// data. (by REST DATA)
|
||||
if (noELS != null) {
|
||||
const yesTotalELS = sum(yesELS);
|
||||
yesEquityLikeShareWeight = new BigNumber(yesTotalELS).times(100);
|
||||
}
|
||||
|
||||
const totalTokensVoted = yesTokens.plus(noTokens);
|
||||
|
||||
|
||||
-1
@@ -61,7 +61,6 @@ const mockConsensusValidators: NodesFragmentFragment[] = [
|
||||
];
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
...jest.requireActual('@vegaprotocol/environment'),
|
||||
useVegaRelease: jest.fn(),
|
||||
useVegaReleases: jest.fn(),
|
||||
}));
|
||||
|
||||
+1
-1
@@ -288,7 +288,7 @@ describe('Consensus validators table', () => {
|
||||
|
||||
expect(
|
||||
grid.querySelector('[role="gridcell"][col-id="totalPenalties"]')
|
||||
).toHaveTextContent('10.07%');
|
||||
).toHaveTextContent('13.16%');
|
||||
|
||||
expect(
|
||||
grid.querySelector('[role="gridcell"][col-id="normalisedVotingPower"]')
|
||||
|
||||
+3
-13
@@ -185,19 +185,15 @@ export const ConsensusValidatorsTable = ({
|
||||
const { rawValidatorScore: previousEpochValidatorScore } =
|
||||
getLastEpochScoreAndPerformance(previousEpochData, id);
|
||||
|
||||
const overstakingPenalty = calculateOverstakedPenalty(
|
||||
const overstakingPenalty = calculateOverallPenalty(
|
||||
id,
|
||||
allNodesInPreviousEpoch
|
||||
);
|
||||
const totalPenalty = calculateOverallPenalty(
|
||||
const totalPenalty = calculateOverstakedPenalty(
|
||||
id,
|
||||
allNodesInPreviousEpoch
|
||||
);
|
||||
|
||||
const lastEpochDataForNode = allNodesInPreviousEpoch.find(
|
||||
(node) => node.id === id
|
||||
);
|
||||
|
||||
return {
|
||||
id,
|
||||
[ValidatorFields.RANKING_INDEX]: stakedTotalRanking,
|
||||
@@ -243,12 +239,6 @@ export const ConsensusValidatorsTable = ({
|
||||
: undefined,
|
||||
[ValidatorFields.MULTISIG_ERROR]:
|
||||
multisigStatus?.showMultisigStatusError,
|
||||
[ValidatorFields.MULTISIG_PENALTY]: formatNumberPercentage(
|
||||
new BigNumber(1)
|
||||
.minus(lastEpochDataForNode?.rewardScore?.multisigScore ?? 1)
|
||||
.times(100),
|
||||
2
|
||||
),
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -388,6 +378,7 @@ export const ConsensusValidatorsTable = ({
|
||||
headerTooltip: t('StakeDescription').toString(),
|
||||
cellRenderer: TotalStakeRenderer,
|
||||
width: 120,
|
||||
sort: 'desc',
|
||||
},
|
||||
{
|
||||
field: ValidatorFields.PENDING_STAKE,
|
||||
@@ -409,7 +400,6 @@ export const ConsensusValidatorsTable = ({
|
||||
headerTooltip: t('NormalisedVotingPowerDescription').toString(),
|
||||
cellRenderer: VotingPowerRenderer,
|
||||
width: 120,
|
||||
sort: 'desc',
|
||||
},
|
||||
{
|
||||
field: ValidatorFields.TOTAL_PENALTIES,
|
||||
|
||||
@@ -3,6 +3,8 @@ 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:
|
||||
|
||||
@@ -40,7 +40,6 @@ export enum ValidatorFields {
|
||||
PENDING_USER_STAKE = 'pendingUserStake',
|
||||
USER_STAKE_SHARE = 'userStakeShare',
|
||||
MULTISIG_ERROR = 'multisigError',
|
||||
MULTISIG_PENALTY = 'multisigPenalty',
|
||||
}
|
||||
|
||||
export const addUserDataToValidator = (
|
||||
@@ -328,7 +327,7 @@ interface TotalPenaltiesRendererProps {
|
||||
overstakedAmount: string;
|
||||
overstakingPenalty: string;
|
||||
totalPenalties: string;
|
||||
multisigPenalty: string;
|
||||
multisigError?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -347,9 +346,11 @@ export const TotalPenaltiesRenderer = ({
|
||||
<div data-testid="overstaked-penalty-tooltip">
|
||||
{t('overstakedPenalty')}: {data.overstakingPenalty}
|
||||
</div>
|
||||
<div data-testid="multisig-error-tooltip">
|
||||
{t('multisigPenalty')}: {data.multisigPenalty}
|
||||
</div>
|
||||
{data.multisigError && (
|
||||
<div data-testid="multisig-error-tooltip">
|
||||
{t('multisigPenalty')}: 100%
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
import type { ReactNode } from 'react';
|
||||
import type { StakingNodeFieldsFragment } from '../__generated__/Staking';
|
||||
import type { PreviousEpochQuery } from '../__generated__/PreviousEpoch';
|
||||
import { getMultisigStatusInfo } from '../../../lib/get-multisig-status-info';
|
||||
|
||||
const statuses = {
|
||||
[Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_ERSATZ]: 'status-ersatz',
|
||||
@@ -104,10 +105,9 @@ export const ValidatorTable = ({
|
||||
};
|
||||
}, [node, previousEpochData?.epoch.validatorsConnection?.edges]);
|
||||
|
||||
const previousNodeData =
|
||||
previousEpochData?.epoch.validatorsConnection?.edges?.find(
|
||||
(e) => e?.node.id === node.id
|
||||
);
|
||||
const multisigStatus = previousEpochData
|
||||
? getMultisigStatusInfo(previousEpochData)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -293,15 +293,21 @@ export const ValidatorTable = ({
|
||||
data-testid="multisig-penalty"
|
||||
className="flex gap-2 items-baseline"
|
||||
>
|
||||
{multisigStatus?.zeroScoreNodes.find(
|
||||
(n) => n.id === node.id
|
||||
) ? (
|
||||
<Tooltip
|
||||
description={t('multisigPenaltyThisNodeIndicator')}
|
||||
>
|
||||
<span className="inline-block w-2 h-2 rounded-full bg-vega-red-500"></span>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<Tooltip description={t('multisigPenaltyDescription')}>
|
||||
<span>
|
||||
{formatNumberPercentage(
|
||||
new BigNumber(1)
|
||||
.minus(
|
||||
previousNodeData?.node.rewardScore?.multisigScore ??
|
||||
1
|
||||
)
|
||||
.times(100),
|
||||
BigNumber(
|
||||
multisigStatus?.showMultisigStatusError ? 100 : 0
|
||||
),
|
||||
2
|
||||
)}
|
||||
</span>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply text-2xl text-white mb-4;
|
||||
@apply text-2xl text-white uppercase mb-4;
|
||||
}
|
||||
h2 {
|
||||
@apply text-xl text-white mb-4;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
const { join } = require('path');
|
||||
const { createGlobPatternsForDependencies } = require('@nx/react/tailwind');
|
||||
const { theme } = require('../../libs/tailwindcss-config/src/theme');
|
||||
const {
|
||||
vegaCustomClasses,
|
||||
} = require('../../libs/tailwindcss-config/src/vega-custom-classes');
|
||||
const theme = require('../../libs/tailwindcss-config/src/theme');
|
||||
const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom-classes');
|
||||
|
||||
export default {
|
||||
module.exports = {
|
||||
content: [
|
||||
join(__dirname, 'src/**/*.{js,ts,jsx,tsx}'),
|
||||
'libs/ui-toolkit/src/utils/shared.ts',
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nx/react/babel",
|
||||
{
|
||||
"runtime": "automatic"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# This file is used by:
|
||||
# 1. autoprefixer to adjust CSS to support the below specified browsers
|
||||
# 2. babel preset-env to adjust included polyfills
|
||||
#
|
||||
# For additional information regarding the format and rule options, please see:
|
||||
# https://github.com/browserslist/browserslist#queries
|
||||
#
|
||||
# If you need to support different browsers in production, you may tweak the list below.
|
||||
|
||||
last 1 Chrome version
|
||||
last 1 Firefox version
|
||||
last 2 Edge major versions
|
||||
last 2 Safari major version
|
||||
last 2 iOS major versions
|
||||
Firefox ESR
|
||||
not IE 9-11 # For IE 9-11 support, remove 'not'.
|
||||
@@ -0,0 +1,28 @@
|
||||
# React Environment Variables
|
||||
# https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#expanding-environment-variables-in-env
|
||||
|
||||
# Netlify Environment Variables
|
||||
# https://www.netlify.com/docs/continuous-deployment/#environment-variables
|
||||
NX_VERSION=\$npm_package_version
|
||||
NX_REPOSITORY_URL=\$REPOSITORY_URL
|
||||
NX_BRANCH=\$BRANCH
|
||||
NX_PULL_REQUEST=\$PULL_REQUEST
|
||||
NX_HEAD=\$HEAD
|
||||
NX_COMMIT_REF=\$COMMIT_REF
|
||||
NX_CONTEXT=\$CONTEXT
|
||||
NX_REVIEW_ID=\$REVIEW_ID
|
||||
NX_INCOMING_HOOK_TITLE=\$INCOMING_HOOK_TITLE
|
||||
NX_INCOMING_HOOK_URL=\$INCOMING_HOOK_URL
|
||||
NX_INCOMING_HOOK_BODY=\$INCOMING_HOOK_BODY
|
||||
NX_URL=\$URL
|
||||
NX_DEPLOY_URL=\$DEPLOY_URL
|
||||
NX_DEPLOY_PRIME_URL=\$DEPLOY_PRIME_URL
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml
|
||||
NX_VEGA_ENV = 'TESTNET'
|
||||
NX_VEGA_URL="https://api.n07.testnet.vega.xyz/graphql"
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
|
||||
@@ -0,0 +1,3 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_URL=http://localhost:3008/graphql
|
||||
NX_VEGA_ENV=LOCAL
|
||||
@@ -0,0 +1,8 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
|
||||
NX_VEGA_URL=https://api.n04.d.vega.xyz/graphql
|
||||
NX_VEGA_ENV=DEVNET
|
||||
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_EXPLORER_URL=#
|
||||
@@ -0,0 +1,9 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
|
||||
NX_VEGA_URL=https://api.vega.community/graphql
|
||||
NX_VEGA_ENV=MAINNET
|
||||
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\"}
|
||||
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://etherscan.io
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz
|
||||
NX_VEGA_CONSOLE_URL=https://console.vega.xyz
|
||||
@@ -0,0 +1,9 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
|
||||
NX_VEGA_URL=https://api.n00.stagnet1.vega.xyz/graphql
|
||||
NX_VEGA_ENV=STAGNET1
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
|
||||
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml
|
||||
NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
|
||||
NX_VEGA_ENV=TESTNET
|
||||
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\"}
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": ["plugin:@nx/react", "../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*", "__generated__"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
displayName: 'emblem',
|
||||
displayName: 'liquidity-provision-dashboard',
|
||||
preset: '../../jest.preset.js',
|
||||
globals: {},
|
||||
transform: {
|
||||
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest',
|
||||
'^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }],
|
||||
'^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/next/babel'] }],
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
coverageDirectory: '../../coverage/libs/emblem',
|
||||
setupFilesAfterEnv: ['./src/setup-tests.ts'],
|
||||
coverageDirectory: '../../coverage/apps/liquidity-provision-dashboard',
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
const { join } = require('path');
|
||||
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {
|
||||
config: join(__dirname, 'tailwind.config.js'),
|
||||
},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"name": "liquidity-provision-dashboard",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "apps/liquidity-provision-dashboard/src",
|
||||
"projectType": "application",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/webpack:webpack",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"defaultConfiguration": "production",
|
||||
"options": {
|
||||
"compiler": "babel",
|
||||
"outputPath": "dist/apps/liquidity-provision-dashboard",
|
||||
"index": "apps/liquidity-provision-dashboard/src/index.html",
|
||||
"baseHref": "/",
|
||||
"main": "apps/liquidity-provision-dashboard/src/main.tsx",
|
||||
"polyfills": "apps/liquidity-provision-dashboard/src/polyfills.ts",
|
||||
"tsConfig": "apps/liquidity-provision-dashboard/tsconfig.app.json",
|
||||
"assets": [
|
||||
"apps/liquidity-provision-dashboard/src/favicon.ico",
|
||||
"apps/liquidity-provision-dashboard/src/assets"
|
||||
],
|
||||
"styles": ["apps/liquidity-provision-dashboard/src/styles.scss"],
|
||||
"scripts": [],
|
||||
"webpackConfig": "@nx/react/plugins/webpack"
|
||||
},
|
||||
"configurations": {
|
||||
"development": {
|
||||
"extractLicenses": false,
|
||||
"optimization": false,
|
||||
"sourceMap": true,
|
||||
"vendorChunk": true
|
||||
},
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "apps/liquidity-provision-dashboard/src/environments/environment.ts",
|
||||
"with": "apps/liquidity-provision-dashboard/src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"executor": "@nx/webpack:dev-server",
|
||||
"options": {
|
||||
"buildTarget": "liquidity-provision-dashboard:build",
|
||||
"hmr": true,
|
||||
"port": 4201
|
||||
},
|
||||
"configurations": {
|
||||
"development": {
|
||||
"buildTarget": "liquidity-provision-dashboard:build:development"
|
||||
},
|
||||
"production": {
|
||||
"buildTarget": "liquidity-provision-dashboard:build:production",
|
||||
"hmr": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": [
|
||||
"apps/liquidity-provision-dashboard/**/*.{ts,tsx,js,jsx}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": [
|
||||
"{workspaceRoot}/coverage/apps/liquidity-provision-dashboard"
|
||||
],
|
||||
"options": {
|
||||
"jestConfig": "apps/liquidity-provision-dashboard/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"build-spec": {
|
||||
"executor": "nx:run-commands",
|
||||
"outputs": [],
|
||||
"options": {
|
||||
"command": "yarn tsc --project ./apps/liquidity-provision-dashboard/tsconfig.spec.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||
import { NetworkLoader, useInitializeEnv } from '@vegaprotocol/environment';
|
||||
import { useRoutes } from 'react-router-dom';
|
||||
|
||||
import '../styles.scss';
|
||||
import { Navbar } from './components/navbar';
|
||||
|
||||
import { routerConfig } from './routes/router-config';
|
||||
|
||||
const cache: InMemoryCacheConfig = {
|
||||
typePolicies: {
|
||||
Market: {
|
||||
merge: true,
|
||||
},
|
||||
Party: {
|
||||
merge: true,
|
||||
},
|
||||
Query: {},
|
||||
Account: {
|
||||
keyFields: false,
|
||||
fields: {
|
||||
balanceFormatted: {},
|
||||
},
|
||||
},
|
||||
Node: {
|
||||
keyFields: false,
|
||||
},
|
||||
Instrument: {
|
||||
keyFields: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
const AppRouter = () => useRoutes(routerConfig);
|
||||
|
||||
export function App() {
|
||||
useInitializeEnv();
|
||||
return (
|
||||
<NetworkLoader cache={cache}>
|
||||
<div className="max-h-full min-h-full bg-white">
|
||||
<Navbar />
|
||||
<AppRouter />
|
||||
</div>
|
||||
</NetworkLoader>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,25 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
import { Intro } from './intro';
|
||||
import { MarketList } from './market-list';
|
||||
|
||||
export function Dashboard() {
|
||||
return (
|
||||
<>
|
||||
<div className="px-16 pt-20 pb-12 bg-greys-light-100">
|
||||
<div className="max-w-screen-xl mx-auto">
|
||||
<h1 className="font-alpha calt uppercase text-5xl mb-8">
|
||||
{t('Top liquidity opportunities')}
|
||||
</h1>
|
||||
|
||||
<Intro />
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-16 py-6">
|
||||
<div className="max-w-screen-xl mx-auto">
|
||||
<MarketList />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './dashboard';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './intro';
|
||||
@@ -0,0 +1,53 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
// TODO: add mainnet links once docs have been updated
|
||||
const LINKS = {
|
||||
testnet: [
|
||||
{
|
||||
label: 'Learn about liquidity fees',
|
||||
url: 'https://docs.vega.xyz/testnet/tutorials/providing-liquidity#resources',
|
||||
},
|
||||
{
|
||||
label: 'Provide liquidity',
|
||||
url: 'https://docs.vega.xyz/testnet/tutorials/providing-liquidity#overview',
|
||||
},
|
||||
{
|
||||
label: 'View your liquidity provisions',
|
||||
url: 'https://docs.vega.xyz/testnet/tutorials/providing-liquidity#viewing-existing-liquidity-provisions',
|
||||
},
|
||||
{
|
||||
label: 'Amend or remove liquidity',
|
||||
url: 'https://docs.vega.xyz/testnet/tutorials/providing-liquidity#amending-a-liquidity-commitment',
|
||||
},
|
||||
],
|
||||
mainnet: [],
|
||||
};
|
||||
|
||||
// TODO: update this when network switcher is added
|
||||
type Network = 'testnet' | 'mainnet';
|
||||
|
||||
export const Intro = ({ network = 'testnet' }: { network?: Network }) => {
|
||||
return (
|
||||
<div>
|
||||
<p className="font-alpha calt text-2xl font-medium mb-2">
|
||||
{t(
|
||||
'Become a liquidity provider and earn a cut of the fees paid during trading.'
|
||||
)}
|
||||
</p>
|
||||
<div>
|
||||
<ul className="flex flex-wrap">
|
||||
{LINKS[network].map(
|
||||
({ label, url }: { label: string; url: string }) => (
|
||||
<li key={url} className="mr-6">
|
||||
<ExternalLink href={url} rel="noreferrer">
|
||||
{t(label)}
|
||||
</ExternalLink>
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './market-list';
|
||||
+296
@@ -0,0 +1,296 @@
|
||||
import { DApp, useLinks } from '@vegaprotocol/environment';
|
||||
import { type Market } from '@vegaprotocol/liquidity';
|
||||
import {
|
||||
displayChange,
|
||||
formatWithAsset,
|
||||
useMarketsLiquidity,
|
||||
} from '@vegaprotocol/liquidity';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
formatNumberPercentage,
|
||||
getExpiryDate,
|
||||
toBigNum,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { type VegaValueFormatterParams } from '@vegaprotocol/datagrid';
|
||||
import { PriceChangeCell } from '@vegaprotocol/datagrid';
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
import {
|
||||
AsyncRenderer,
|
||||
Icon,
|
||||
HealthBar,
|
||||
TooltipCellComponent,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
type GetRowIdParams,
|
||||
type RowClickedEvent,
|
||||
type ColDef,
|
||||
} from 'ag-grid-community';
|
||||
import 'ag-grid-community/styles/ag-grid.css';
|
||||
import 'ag-grid-community/styles/ag-theme-alpine.css';
|
||||
import { useCallback, useState, useMemo } from 'react';
|
||||
|
||||
import { Grid } from '../../grid';
|
||||
import { HealthDialog } from '../../health-dialog';
|
||||
import { Status } from '../../status';
|
||||
import { intentForStatus } from '../../../lib/utils';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { getAsset } from '@vegaprotocol/markets';
|
||||
|
||||
export const MarketList = () => {
|
||||
const { data, error, loading } = useMarketsLiquidity();
|
||||
const [isHealthDialogOpen, setIsHealthDialogOpen] = useState(false);
|
||||
const consoleLink = useLinks(DApp.Console);
|
||||
|
||||
const getRowId = useCallback(({ data }: GetRowIdParams) => data.id, []);
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Market (futures)'),
|
||||
field: 'tradableInstrument.instrument.name',
|
||||
cellRenderer: ({ value, data }: { value: string; data: Market }) => {
|
||||
return (
|
||||
<>
|
||||
<span className="leading-3">{value}</span>
|
||||
<span className="leading-3">{getAsset(data).symbol}</span>
|
||||
</>
|
||||
);
|
||||
},
|
||||
minWidth: 100,
|
||||
flex: 1,
|
||||
headerTooltip: t('The market name and settlement asset'),
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Market Code'),
|
||||
headerTooltip: t(
|
||||
'The market code is a unique identifier for this market'
|
||||
),
|
||||
field: 'tradableInstrument.instrument.code',
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Type'),
|
||||
headerTooltip: t('Type'),
|
||||
field: 'tradableInstrument.instrument.product.__typename',
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Last Price'),
|
||||
headerTooltip: t('Latest price for this market'),
|
||||
field: 'data.markPrice',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'data.markPrice'>) =>
|
||||
value && data ? formatWithAsset(value, getAsset(data)) : '-',
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Change (24h)'),
|
||||
headerTooltip: t('Change in price over the last 24h'),
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'data.candles'>) => {
|
||||
if (data && data.candles) {
|
||||
const prices = data.candles.map((candle) => candle.close);
|
||||
return (
|
||||
<PriceChangeCell
|
||||
candles={prices}
|
||||
decimalPlaces={data?.decimalPlaces}
|
||||
/>
|
||||
);
|
||||
} else return <div>{t('-')}</div>;
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Volume (24h)'),
|
||||
field: 'dayVolume',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'dayVolume'>) =>
|
||||
value && data
|
||||
? `${addDecimalsFormatNumber(
|
||||
value,
|
||||
getAsset(data).decimals || 0
|
||||
)} (${displayChange(data.volumeChange)})`
|
||||
: '-',
|
||||
headerTooltip: t('The trade volume over the last 24h'),
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Total staked by LPs'),
|
||||
field: 'liquidityCommitted',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'liquidityCommitted'>) =>
|
||||
data && value
|
||||
? formatWithAsset(value.toString(), getAsset(data))
|
||||
: '-',
|
||||
headerTooltip: t('The amount of funds allocated to provide liquidity'),
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Target stake'),
|
||||
field: 'target',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'target'>) =>
|
||||
data && value ? formatWithAsset(value, getAsset(data)) : '-',
|
||||
headerTooltip: t(
|
||||
'The ideal committed liquidity to operate the market. If total commitment currently below this level then LPs can set the fee level with new commitment.'
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('% Target stake met'),
|
||||
valueFormatter: ({ data }: VegaValueFormatterParams<Market, ''>) => {
|
||||
if (data) {
|
||||
const roundedPercentage =
|
||||
parseInt(
|
||||
(data.liquidityCommitted / parseFloat(data.target)).toFixed(0)
|
||||
) * 100;
|
||||
const display = Number.isNaN(roundedPercentage)
|
||||
? 'N/A'
|
||||
: formatNumberPercentage(toBigNum(roundedPercentage, 0), 0);
|
||||
return display;
|
||||
} else return '-';
|
||||
},
|
||||
headerTooltip: t('% Target stake met'),
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Fee levels'),
|
||||
field: 'fees',
|
||||
valueFormatter: ({ value }: VegaValueFormatterParams<Market, 'fees'>) =>
|
||||
value ? `${value.factors.liquidityFee}%` : '-',
|
||||
headerTooltip: t('Fee level for this market'),
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Status'),
|
||||
field: 'tradingMode',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
data,
|
||||
}: {
|
||||
value: Schema.MarketTradingMode;
|
||||
data: Market;
|
||||
}) => {
|
||||
return <Status trigger={data.data?.trigger} tradingMode={value} />;
|
||||
},
|
||||
headerTooltip: t(
|
||||
'The current market status - those below the target stake mark are most in need of liquidity'
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
headerComponent: () => {
|
||||
return (
|
||||
<div>
|
||||
<span>{t('Health')}</span>{' '}
|
||||
<button
|
||||
onClick={() => setIsHealthDialogOpen(true)}
|
||||
aria-label={t('open tooltip')}
|
||||
>
|
||||
<Icon name="info-sign" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
field: 'tradingMode',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
data,
|
||||
}: {
|
||||
value: Schema.MarketTradingMode;
|
||||
data: Market;
|
||||
}) => (
|
||||
<HealthBar
|
||||
target={data.target}
|
||||
decimals={getAsset(data).decimals || 0}
|
||||
levels={data.feeLevels}
|
||||
intent={intentForStatus(value)}
|
||||
/>
|
||||
),
|
||||
sortable: false,
|
||||
cellStyle: { overflow: 'unset' },
|
||||
},
|
||||
{
|
||||
headerName: t('Age'),
|
||||
field: 'marketTimestamps.open',
|
||||
headerTooltip: t('Age of the market'),
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<Market, 'marketTimestamps.open'>) => {
|
||||
return value ? formatDistanceToNow(new Date(value)) : '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Closing Time'),
|
||||
field: 'tradableInstrument.instrument.metadata.tags',
|
||||
headerTooltip: t('Closing time of the market'),
|
||||
valueFormatter: ({ data }: VegaValueFormatterParams<Market, ''>) => {
|
||||
let expiry;
|
||||
if (data?.tradableInstrument.instrument.metadata.tags) {
|
||||
expiry = getExpiryDate(
|
||||
data?.tradableInstrument.instrument.metadata.tags,
|
||||
data?.marketTimestamps.close,
|
||||
data?.state
|
||||
);
|
||||
}
|
||||
return expiry ? expiry : '-';
|
||||
},
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||
<div
|
||||
className="w-full grow"
|
||||
style={{ minHeight: 500, overflow: 'hidden' }}
|
||||
>
|
||||
<Grid
|
||||
gridOptions={{
|
||||
onRowClicked: ({ data }: RowClickedEvent) => {
|
||||
window.open(
|
||||
liquidityDetailsConsoleLink(data.id, consoleLink),
|
||||
'_blank',
|
||||
'noopener,noreferrer'
|
||||
);
|
||||
},
|
||||
}}
|
||||
rowData={data}
|
||||
defaultColDef={{
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
unSortIcon: true,
|
||||
cellClass: ['flex', 'flex-col', 'justify-center'],
|
||||
tooltipComponent: TooltipCellComponent,
|
||||
}}
|
||||
columnDefs={columnDefs}
|
||||
getRowId={getRowId}
|
||||
isRowClickable
|
||||
tooltipShowDelay={500}
|
||||
/>
|
||||
<HealthDialog
|
||||
isOpen={isHealthDialogOpen}
|
||||
onChange={() => {
|
||||
setIsHealthDialogOpen(!isHealthDialogOpen);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</AsyncRenderer>
|
||||
);
|
||||
};
|
||||
|
||||
const liquidityDetailsConsoleLink = (
|
||||
marketId: string,
|
||||
consoleLink: (url: string | undefined) => string
|
||||
) => consoleLink(`/#/liquidity/${marketId}`);
|
||||
@@ -0,0 +1,103 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { makeDerivedDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import {
|
||||
getFeeLevels,
|
||||
sumLiquidityCommitted,
|
||||
lpAggregatedDataProvider,
|
||||
} from '@vegaprotocol/liquidity';
|
||||
import { getAsset, marketWithDataProvider } from '@vegaprotocol/markets';
|
||||
import type { MarketWithData } from '@vegaprotocol/markets';
|
||||
|
||||
import { Market } from './market';
|
||||
import { Header } from './header';
|
||||
import { LPProvidersGrid } from './providers';
|
||||
|
||||
const formatMarket = (market: MarketWithData) => {
|
||||
return {
|
||||
name: market?.tradableInstrument.instrument.name,
|
||||
symbol: getAsset(market).symbol,
|
||||
settlementAsset: getAsset(market),
|
||||
targetStake: market?.data?.targetStake,
|
||||
tradingMode: market?.data?.marketTradingMode,
|
||||
trigger: market?.data?.trigger,
|
||||
};
|
||||
};
|
||||
|
||||
export const lpDataProvider = makeDerivedDataProvider(
|
||||
[marketWithDataProvider, lpAggregatedDataProvider],
|
||||
([market, lpAggregatedData]) => ({
|
||||
market: { ...formatMarket(market) },
|
||||
liquidityProviders: lpAggregatedData || [],
|
||||
})
|
||||
);
|
||||
|
||||
const useMarketDetails = (marketId: string | undefined) => {
|
||||
const { data, loading, error } = useDataProvider({
|
||||
dataProvider: lpDataProvider,
|
||||
skipUpdates: true,
|
||||
variables: { marketId: marketId || '' },
|
||||
});
|
||||
|
||||
const liquidityProviders = data?.liquidityProviders || [];
|
||||
|
||||
return {
|
||||
data: {
|
||||
name: data?.market?.name,
|
||||
symbol: data?.market?.symbol,
|
||||
liquidityProviders: liquidityProviders,
|
||||
feeLevels: getFeeLevels(liquidityProviders),
|
||||
comittedLiquidity: sumLiquidityCommitted(liquidityProviders) || 0,
|
||||
settlementAsset: data?.market?.settlementAsset || {},
|
||||
targetStake: data?.market?.targetStake || '0',
|
||||
tradingMode: data?.market.tradingMode,
|
||||
},
|
||||
error,
|
||||
loading: loading,
|
||||
};
|
||||
};
|
||||
|
||||
type Params = { marketId: string };
|
||||
|
||||
export const Detail = () => {
|
||||
const { marketId } = useParams<Params>();
|
||||
const { data, loading, error } = useMarketDetails(marketId);
|
||||
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||
<div className="bg-greys-light-100 px-16 pb-12 pt-14">
|
||||
<div className="mx-auto max-w-screen-xl">
|
||||
<Header name={data.name} symbol={data.symbol} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-16">
|
||||
<div className="mx-auto max-w-screen-xl">
|
||||
<div className="py-12">
|
||||
{marketId && (
|
||||
<Market
|
||||
marketId={marketId}
|
||||
feeLevels={data.feeLevels}
|
||||
comittedLiquidity={data.comittedLiquidity}
|
||||
settlementAsset={data.settlementAsset}
|
||||
targetStake={data.targetStake}
|
||||
tradingMode={data.tradingMode}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="font-alpha calt mb-4 text-2xl">
|
||||
{t('Current Liquidity Provision')}
|
||||
</h2>
|
||||
<LPProvidersGrid
|
||||
liquidityProviders={data.liquidityProviders}
|
||||
settlementAsset={data.settlementAsset}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AsyncRenderer>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const Header = ({
|
||||
name,
|
||||
symbol,
|
||||
}: {
|
||||
name?: string;
|
||||
symbol?: string;
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-6">
|
||||
<Link to="/">
|
||||
<Icon name="chevron-left" className="mr-2" />
|
||||
<span className="underline font-alpha calt text-lg font-medium">
|
||||
{t('Liquidity opportunities')}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
<h1 className="font-alpha calt text-5xl mb-6">{name}</h1>
|
||||
<p className="font-alpha calt text-4xl">{symbol}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './header';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './detail';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './last-24h-volume';
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
import { useState, useMemo, useRef, useCallback } from 'react';
|
||||
import throttle from 'lodash/throttle';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import { useYesterday } from '@vegaprotocol/react-helpers';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import {
|
||||
calcDayVolume,
|
||||
getChange,
|
||||
displayChange,
|
||||
} from '@vegaprotocol/liquidity';
|
||||
|
||||
import type { Candle } from '@vegaprotocol/markets';
|
||||
import { marketCandlesProvider } from '@vegaprotocol/markets';
|
||||
|
||||
const THROTTLE_UPDATE_TIME = 500;
|
||||
|
||||
export const Last24hVolume = ({
|
||||
marketId,
|
||||
decimals,
|
||||
}: {
|
||||
marketId: string;
|
||||
decimals: number;
|
||||
}) => {
|
||||
const [candleVolume, setCandleVolume] = useState<string>();
|
||||
const [volumeChange, setVolumeChange] = useState<string>(' - ');
|
||||
|
||||
const yesterday = useYesterday();
|
||||
|
||||
const yTimestamp = useMemo(() => {
|
||||
return new Date(yesterday).toISOString();
|
||||
}, [yesterday]);
|
||||
|
||||
const variables = useMemo(
|
||||
() => ({
|
||||
marketId: marketId,
|
||||
interval: Schema.Interval.INTERVAL_I1H,
|
||||
since: yTimestamp,
|
||||
}),
|
||||
[marketId, yTimestamp]
|
||||
);
|
||||
|
||||
const variables24hAgo = {
|
||||
marketId: marketId,
|
||||
interval: Schema.Interval.INTERVAL_I1D,
|
||||
since: yTimestamp,
|
||||
};
|
||||
|
||||
const throttledSetCandles = useRef(
|
||||
throttle((data: Candle[]) => {
|
||||
setCandleVolume(calcDayVolume(data));
|
||||
}, THROTTLE_UPDATE_TIME)
|
||||
).current;
|
||||
|
||||
const update = useCallback(
|
||||
({ data }: { data: Candle[] | null }) => {
|
||||
if (data) {
|
||||
throttledSetCandles(data);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[throttledSetCandles]
|
||||
);
|
||||
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: marketCandlesProvider,
|
||||
variables: variables,
|
||||
update,
|
||||
skip: !marketId,
|
||||
});
|
||||
|
||||
const throttledSetVolumeChange = useRef(
|
||||
throttle((candles: Candle[]) => {
|
||||
const candle24hAgo = candles?.[0];
|
||||
setVolumeChange(getChange(data || [], candle24hAgo?.close));
|
||||
}, THROTTLE_UPDATE_TIME)
|
||||
).current;
|
||||
|
||||
const updateCandle24hAgo = useCallback(
|
||||
({ data }: { data: Candle[] | null }) => {
|
||||
if (data) {
|
||||
throttledSetVolumeChange(data);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[throttledSetVolumeChange]
|
||||
);
|
||||
|
||||
useDataProvider({
|
||||
dataProvider: marketCandlesProvider,
|
||||
update: updateCandle24hAgo,
|
||||
variables: variables24hAgo,
|
||||
skip: !marketId || !data,
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span className="text-3xl">
|
||||
{!error && candleVolume
|
||||
? addDecimalsFormatNumber(candleVolume, decimals)
|
||||
: '0'}{' '}
|
||||
</span>
|
||||
<span className="text-lg text-greys-light-400">
|
||||
({displayChange(volumeChange)})
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './market';
|
||||
@@ -0,0 +1,118 @@
|
||||
import { useState } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Icon, HealthBar } from '@vegaprotocol/ui-toolkit';
|
||||
import { formatWithAsset } from '@vegaprotocol/liquidity';
|
||||
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
import { HealthDialog } from '../../health-dialog';
|
||||
import { Last24hVolume } from '../last-24h-volume';
|
||||
import { Status } from '../../status';
|
||||
import { intentForStatus } from '../../../lib/utils';
|
||||
|
||||
interface Levels {
|
||||
fee: string;
|
||||
commitmentAmount: number;
|
||||
}
|
||||
|
||||
interface settlementAsset {
|
||||
symbol?: string;
|
||||
decimals?: number;
|
||||
}
|
||||
|
||||
export const Market = ({
|
||||
marketId,
|
||||
feeLevels,
|
||||
comittedLiquidity,
|
||||
settlementAsset,
|
||||
targetStake,
|
||||
tradingMode,
|
||||
trigger,
|
||||
}: {
|
||||
marketId: string;
|
||||
feeLevels: Levels[];
|
||||
comittedLiquidity: number;
|
||||
targetStake: string;
|
||||
settlementAsset?: settlementAsset;
|
||||
tradingMode?: Schema.MarketTradingMode;
|
||||
trigger?: Schema.AuctionTrigger;
|
||||
}) => {
|
||||
const [isHealthDialogOpen, setIsHealthDialogOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="border border-greys-light-200 rounded-2xl px-2 py-6">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr
|
||||
className="text-sm text-greys-light-400 text-left font-alpha calt"
|
||||
style={{ fontFeatureSettings: "'liga' off, 'calt' off" }}
|
||||
>
|
||||
<th className="font-medium px-4">{t('Volume (24h)')}</th>
|
||||
<th className="font-medium px-4">{t('Commited Liquidity')}</th>
|
||||
<th className="font-medium px-4">{t('Status')}</th>
|
||||
<th className="font-medium flex items-center px-4">
|
||||
<span>{t('Health')}</span>{' '}
|
||||
<button
|
||||
onClick={() => setIsHealthDialogOpen(true)}
|
||||
aria-label={t('open tooltip')}
|
||||
className="flex ml-1"
|
||||
>
|
||||
<Icon name="info-sign" />
|
||||
</button>
|
||||
</th>
|
||||
<th className="font-medium">{t('Est. APY')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="px-4">
|
||||
<div>
|
||||
{marketId && settlementAsset?.decimals && (
|
||||
<Last24hVolume
|
||||
marketId={marketId}
|
||||
decimals={settlementAsset.decimals}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4">
|
||||
<span className="text-3xl">
|
||||
{comittedLiquidity && settlementAsset
|
||||
? formatWithAsset(`${comittedLiquidity}`, settlementAsset)
|
||||
: '0'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4">
|
||||
<Status
|
||||
trigger={trigger}
|
||||
tradingMode={tradingMode}
|
||||
size="large"
|
||||
/>
|
||||
</td>
|
||||
<td className="px-4">
|
||||
{tradingMode && settlementAsset?.decimals && feeLevels && (
|
||||
<HealthBar
|
||||
target={targetStake}
|
||||
decimals={settlementAsset.decimals}
|
||||
levels={feeLevels}
|
||||
intent={intentForStatus(tradingMode)}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4">
|
||||
<span className="text-3xl"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<HealthDialog
|
||||
isOpen={isHealthDialogOpen}
|
||||
onChange={() => {
|
||||
setIsHealthDialogOpen(!isHealthDialogOpen);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './providers';
|
||||
@@ -0,0 +1,125 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { type GetRowIdParams, type ColDef } from 'ag-grid-community';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
import {
|
||||
type LiquidityProviderFeeShareFieldsFragment,
|
||||
type LiquidityProvisionFieldsFragment,
|
||||
} from '@vegaprotocol/liquidity';
|
||||
import { formatWithAsset } from '@vegaprotocol/liquidity';
|
||||
|
||||
import { Grid } from '../../grid';
|
||||
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
const formatToHours = ({ value }: { value?: string | null }) => {
|
||||
if (!value) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
const MS_IN_HOUR = 1000 * 60 * 60;
|
||||
const created = new Date(value).getTime();
|
||||
const now = new Date().getTime();
|
||||
return `${Math.round(Math.abs(now - created) / MS_IN_HOUR)}h`;
|
||||
};
|
||||
|
||||
export const LPProvidersGrid = ({
|
||||
liquidityProviders,
|
||||
settlementAsset,
|
||||
}: {
|
||||
liquidityProviders: LiquidityProvisionFieldsFragment &
|
||||
LiquidityProviderFeeShareFieldsFragment[];
|
||||
settlementAsset: {
|
||||
decimals?: number;
|
||||
symbol?: string;
|
||||
};
|
||||
}) => {
|
||||
const getRowId = useCallback(({ data }: GetRowIdParams) => data.party.id, []);
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{
|
||||
headerName: t('LPs'),
|
||||
field: 'party.id',
|
||||
flex: 1,
|
||||
minWidth: 100,
|
||||
headerTooltip: t('Liquidity providers'),
|
||||
},
|
||||
{
|
||||
headerName: t('Duration'),
|
||||
valueFormatter: formatToHours,
|
||||
field: 'createdAt',
|
||||
headerTooltip: t('Time in market'),
|
||||
},
|
||||
{
|
||||
headerName: t('Equity-like share'),
|
||||
field: 'equityLikeShare',
|
||||
valueFormatter: ({ value }: { value?: string | null }) => {
|
||||
return value
|
||||
? `${parseFloat(parseFloat(value).toFixed(2)) * 100}%`
|
||||
: '';
|
||||
},
|
||||
headerTooltip: t(
|
||||
'The share of the markets liquidity held - the earlier you commit liquidity the greater % fees you earn'
|
||||
),
|
||||
minWidth: 140,
|
||||
},
|
||||
{
|
||||
headerName: t('committed bond'),
|
||||
field: 'commitmentAmount',
|
||||
valueFormatter: ({ value }: { value?: string | null }) =>
|
||||
value ? formatWithAsset(value, settlementAsset) : '0',
|
||||
headerTooltip: t('The amount of funds allocated to provide liquidity'),
|
||||
minWidth: 140,
|
||||
},
|
||||
{
|
||||
headerName: t('Margin Req.'),
|
||||
field: 'margin',
|
||||
headerTooltip: t(
|
||||
'Margin required for arising positions based on liquidity commitment'
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t('24h Fees'),
|
||||
field: 'fees',
|
||||
headerTooltip: t(
|
||||
'Total fees earned by the liquidity provider in the last 24 hours'
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t('Fee level'),
|
||||
valueFormatter: ({ value }: { value?: string | null }) => `${value}%`,
|
||||
field: 'fee',
|
||||
headerTooltip: t(
|
||||
"The market's liquidity fee, or the percentage of a trade's value which is collected from the price taker for every trade"
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('APY'),
|
||||
field: 'apy',
|
||||
headerTooltip: t(
|
||||
'An annualised estimate based on the total liquidity provision fees and maker fees collected by liquidity providers, the maximum margin needed and maximum commitment (bond) over the course of 7 epochs'
|
||||
),
|
||||
},
|
||||
],
|
||||
[settlementAsset]
|
||||
);
|
||||
|
||||
return (
|
||||
<Grid
|
||||
rowData={liquidityProviders}
|
||||
tooltipShowDelay={500}
|
||||
defaultColDef={{
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
unSortIcon: true,
|
||||
cellClass: ['flex', 'flex-col', 'justify-center'],
|
||||
tooltipComponent: TooltipCellComponent,
|
||||
minWidth: 100,
|
||||
}}
|
||||
columnDefs={columnDefs}
|
||||
getRowId={getRowId}
|
||||
rowHeight={92}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
.ag-theme-alpine {
|
||||
--ag-line-height: 24px;
|
||||
--ag-row-hover-color: transparent;
|
||||
--ag-header-background-color: transparent;
|
||||
--ag-odd-row-background-color: transparent;
|
||||
--ag-header-foreground-color: #626262;
|
||||
--ag-secondary-foreground-color: #626262;
|
||||
--ag-font-size: 16px;
|
||||
--ag-background-color: transparent;
|
||||
--ag-range-selection-border-color: transparent;
|
||||
|
||||
font-family: AlphaLyrae, Helvetica Neue, -apple-system, BlinkMacSystemFont,
|
||||
Segoe UI, Roboto, Arial, Noto Sans, sans-serif, Apple Color Emoji,
|
||||
Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||
font-feature-settings: 'liga' off, 'calt' off;
|
||||
}
|
||||
|
||||
.ag-theme-alpine .ag-cell {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ag-theme-alpine .ag-header {
|
||||
border-bottom: 1px solid #a7a7a7;
|
||||
font-size: 15px;
|
||||
line-height: 1em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.ag-theme-alpine .ag-root-wrapper {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.ag-theme-alpine .ag-header-row {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ag-theme-alpine .ag-row {
|
||||
border: none;
|
||||
border-bottom: 1px solid #bfccd6;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ag-theme-alpine .ag-root-wrapper-body.ag-layout-normal {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.ag-theme-alpine.row-hover .ag-row:hover {
|
||||
background: #f0f0f0;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { useRef, useCallback, useEffect } from 'react';
|
||||
import { AgGridReact } from 'ag-grid-react';
|
||||
import {
|
||||
type AgGridReactProps,
|
||||
type AgReactUiProps,
|
||||
type AgGridReact as AgGridReactType,
|
||||
} from 'ag-grid-react';
|
||||
import classNames from 'classnames';
|
||||
import 'ag-grid-community/styles/ag-grid.css';
|
||||
import 'ag-grid-community/styles/ag-theme-alpine.css';
|
||||
|
||||
import './grid.scss';
|
||||
|
||||
type Props = (AgGridReactProps | AgReactUiProps) & {
|
||||
isRowClickable?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
|
||||
export const Grid = ({ isRowClickable, ...props }: Props) => {
|
||||
const gridRef = useRef<AgGridReactType | null>(null);
|
||||
|
||||
const resizeGrid = useCallback(() => {
|
||||
gridRef.current?.api?.sizeColumnsToFit();
|
||||
}, [gridRef]);
|
||||
|
||||
const handleOnGridReady = useCallback(() => {
|
||||
resizeGrid();
|
||||
}, [resizeGrid]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', resizeGrid);
|
||||
return () => window.removeEventListener('resize', resizeGrid);
|
||||
}, [resizeGrid]);
|
||||
|
||||
return (
|
||||
<AgGridReact
|
||||
className={classNames('ag-theme-alpine font-alpha calt h-full', {
|
||||
'row-hover': isRowClickable,
|
||||
})}
|
||||
rowHeight={92}
|
||||
ref={gridRef}
|
||||
onGridReady={handleOnGridReady}
|
||||
suppressRowClickSelection
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './grid';
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Dialog, HealthBar } from '@vegaprotocol/ui-toolkit';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import classNames from 'classnames';
|
||||
import { intentForStatus } from '../../lib/utils';
|
||||
|
||||
interface HealthDialogProps {
|
||||
isOpen: boolean;
|
||||
onChange: (isOpen: boolean) => void;
|
||||
}
|
||||
|
||||
const ROWS = [
|
||||
{
|
||||
key: '1',
|
||||
title: 'Continuous',
|
||||
copy: 'Markets that have committed liquidity equal or greater than the target stake are trading continuously.',
|
||||
data: {
|
||||
status: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||
target: '171320',
|
||||
decimals: 5,
|
||||
levels: [
|
||||
{ fee: '0.6', commitmentAmount: 150000 },
|
||||
{ fee: '1', commitmentAmount: 150000 },
|
||||
{ fee: '2', commitmentAmount: 30000 },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
title: 'Monitoring auction (liquidity)',
|
||||
copy: 'Markets below the target stake will see trading suspended and go into liquidity auction.',
|
||||
data: {
|
||||
status: Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
||||
target: '171320',
|
||||
decimals: 5,
|
||||
levels: [
|
||||
{ fee: '0.6', commitmentAmount: 110000 },
|
||||
{ fee: '1', commitmentAmount: 50000 },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
title: 'Opening auction',
|
||||
copy: 'A newly created market looking for a target liquidity amount to start trading.',
|
||||
data: {
|
||||
status: Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION,
|
||||
target: '171320',
|
||||
decimals: 3,
|
||||
levels: [
|
||||
{ fee: '0.6', commitmentAmount: 110000 },
|
||||
{ fee: '1', commitmentAmount: 50000 },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const HealthDialog = ({ onChange, isOpen }: HealthDialogProps) => {
|
||||
return (
|
||||
<Dialog size="large" open={isOpen} onChange={onChange}>
|
||||
<h1 className="text-2xl mb-5 pr-2 font-medium font-alpha uppercase">
|
||||
{t('Health')}
|
||||
</h1>
|
||||
<p className="text-lg font-medium font-alpha mb-8">
|
||||
{t(
|
||||
'Market health is a representation of market and liquidity status and how close that market is to moving from one fee level to another.'
|
||||
)}
|
||||
</p>
|
||||
|
||||
<table className="table-fixed">
|
||||
<thead className="border-b border-greys-light-300">
|
||||
<th className="w-1/2 text-left font-medium font-alpha text-base pb-4 uppercase">
|
||||
{t('Market status')}
|
||||
</th>
|
||||
<th className="w-1/2 text-lef font-medium font-alpha text-base pb-4 uppercase">
|
||||
{t('Liquidity status')}
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ROWS.map((r, index) => {
|
||||
const isFirstRow = index === 0;
|
||||
return (
|
||||
<tr key={r.key}>
|
||||
<td
|
||||
className={classNames('pr-4 pb-10', { 'pt-8': isFirstRow })}
|
||||
>
|
||||
<h2 className="font-medium font-alpha uppercase text-base">
|
||||
{t(r.title)}
|
||||
</h2>
|
||||
<p className="font-medium font-alpha text-lg">{t(r.copy)}</p>
|
||||
</td>
|
||||
<td
|
||||
className={classNames('pl-4 pb-10', { 'pt-8': isFirstRow })}
|
||||
>
|
||||
<HealthBar
|
||||
size="large"
|
||||
levels={r.data.levels}
|
||||
target={r.data.target}
|
||||
decimals={r.data.decimals}
|
||||
intent={intentForStatus(r.data.status)}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './health-dialog';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './indicator';
|
||||
@@ -0,0 +1,24 @@
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
|
||||
import { getColorForStatus } from '../../lib/utils';
|
||||
|
||||
export const Indicator = ({
|
||||
status,
|
||||
opacity,
|
||||
}: {
|
||||
status?: Schema.MarketTradingMode;
|
||||
opacity?: number;
|
||||
}) => {
|
||||
const backgroundColor = status ? getColorForStatus(status) : undefined;
|
||||
return (
|
||||
<div className="inline-block w-2 h-2 mr-1 rounded-full bg-white overflow-hidden shrink-0">
|
||||
<div
|
||||
className="h-full bg-black"
|
||||
style={{
|
||||
opacity,
|
||||
backgroundColor,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user