Compare commits
38
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e2c51f7c0 | ||
|
|
67afff2baf | ||
|
|
ca63a671c8 | ||
|
|
4165f3905f | ||
|
|
a359ccb755 | ||
|
|
e584737005 | ||
|
|
b12c5ed39f | ||
|
|
d3dbd64cf1 | ||
|
|
2df62bca61 | ||
|
|
fd2a434b46 | ||
|
|
4e7a78f988 | ||
|
|
327619652a | ||
|
|
d2339205cc | ||
|
|
63df4cf465 | ||
|
|
5e34178291 | ||
|
|
0c44664aa5 | ||
|
|
7ef1d75529 | ||
|
|
84795b2d6a | ||
|
|
300019f108 | ||
|
|
2d0aebd3fe | ||
|
|
9e86d2aff5 | ||
|
|
0f7e76ae01 | ||
|
|
56732d1503 | ||
|
|
5c19263653 | ||
|
|
0d911b3029 | ||
|
|
9f059e7862 | ||
|
|
92ea93dadb | ||
|
|
6165aefad7 | ||
|
|
c95369d914 | ||
|
|
5dbc5d7997 | ||
|
|
47b3d5b55f | ||
|
|
da7c0b84f7 | ||
|
|
67e602ebb1 | ||
|
|
f407110e95 | ||
|
|
66a85cce2a | ||
|
|
93f8450ecb | ||
|
|
0447a6cf5d | ||
|
|
498be838c7 |
@@ -2,11 +2,11 @@
|
|||||||
NX_VEGA_ENV=VALIDATOR_TESTNET
|
NX_VEGA_ENV=VALIDATOR_TESTNET
|
||||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml
|
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_URL=https://api-validators-testnet.vega.rocks/graphql
|
||||||
NX_VEGA_REST=https://api.validators-testnet.vega.xyz/
|
NX_VEGA_REST=https://api-validators-testnet.vega.rocks/
|
||||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||||
|
|
||||||
NX_VEGA_GOVERNANCE_URL=https://validator-testnet.governance.vega.xyz
|
NX_VEGA_GOVERNANCE_URL=https://validator-testnet.governance.vega.xyz
|
||||||
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.xyz
|
NX_TENDERMINT_URL=https://tm-be.validators-testnet.vega.rocks
|
||||||
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.xyz/rest
|
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/
|
||||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket
|
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const Footer = () => {
|
|||||||
const [nodeSwitcherOpen, setNodeSwitcherOpen] = useState(false);
|
const [nodeSwitcherOpen, setNodeSwitcherOpen] = useState(false);
|
||||||
const { screenSize } = useScreenDimensions();
|
const { screenSize } = useScreenDimensions();
|
||||||
const showFullFeedbackLabel = useMemo(
|
const showFullFeedbackLabel = useMemo(
|
||||||
() => ['lg', 'xl'].includes(screenSize),
|
() => ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize),
|
||||||
[screenSize]
|
[screenSize]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,143 +1,52 @@
|
|||||||
import {
|
|
||||||
addDecimalsFormatNumber,
|
|
||||||
formatNumberPercentage,
|
|
||||||
getMarketExpiryDateFormatted,
|
|
||||||
} from '@vegaprotocol/utils';
|
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import type { MarketInfoWithData } from '@vegaprotocol/market-info';
|
import type { MarketInfoWithData } from '@vegaprotocol/market-info';
|
||||||
import { MarketInfoTable } from '@vegaprotocol/market-info';
|
import { LiquidityInfoPanel } from '@vegaprotocol/market-info';
|
||||||
|
import { LiquidityMonitoringParametersInfoPanel } from '@vegaprotocol/market-info';
|
||||||
import {
|
import {
|
||||||
MarketStateMapping,
|
InstrumentInfoPanel,
|
||||||
MarketTradingModeMapping,
|
KeyDetailsInfoPanel,
|
||||||
} from '@vegaprotocol/types';
|
LiquidityPriceRangeInfoPanel,
|
||||||
import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets';
|
MetadataInfoPanel,
|
||||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
OracleInfoPanel,
|
||||||
import BigNumber from 'bignumber.js';
|
RiskFactorsInfoPanel,
|
||||||
import { useMemo } from 'react';
|
RiskModelInfoPanel,
|
||||||
|
RiskParametersInfoPanel,
|
||||||
|
SettlementAssetInfoPanel,
|
||||||
|
} from '@vegaprotocol/market-info';
|
||||||
|
import { MarketInfoTable } from '@vegaprotocol/market-info';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
|
export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
|
||||||
const quoteUnit = market?.tradableInstrument.instrument.product.quoteName;
|
|
||||||
const assetId = useMemo(
|
|
||||||
() => market?.tradableInstrument.instrument.product?.settlementAsset.id,
|
|
||||||
[market]
|
|
||||||
);
|
|
||||||
const { data: asset } = useAssetDataProvider(assetId ?? '');
|
|
||||||
|
|
||||||
if (!market) return null;
|
if (!market) return null;
|
||||||
|
|
||||||
const keyDetails = {
|
|
||||||
decimalPlaces: market.decimalPlaces,
|
|
||||||
positionDecimalPlaces: market.positionDecimalPlaces,
|
|
||||||
tradingMode: market.tradingMode,
|
|
||||||
state: MarketStateMapping[market.state],
|
|
||||||
};
|
|
||||||
const assetDecimals =
|
|
||||||
market.tradableInstrument.instrument.product.settlementAsset.decimals;
|
|
||||||
|
|
||||||
const liquidityPriceRange = formatNumberPercentage(
|
|
||||||
new BigNumber(market.lpPriceRange).times(100)
|
|
||||||
);
|
|
||||||
|
|
||||||
const panels = [
|
const panels = [
|
||||||
{
|
{
|
||||||
title: t('Key details'),
|
title: t('Key details'),
|
||||||
content: (
|
content: <KeyDetailsInfoPanel noBorder={false} market={market} />,
|
||||||
<MarketInfoTable
|
|
||||||
noBorder={false}
|
|
||||||
data={{
|
|
||||||
name: market.tradableInstrument.instrument.name,
|
|
||||||
marketID: market.id,
|
|
||||||
tradingMode:
|
|
||||||
keyDetails.tradingMode &&
|
|
||||||
MarketTradingModeMapping[keyDetails.tradingMode],
|
|
||||||
marketDecimalPlaces: market.decimalPlaces,
|
|
||||||
positionDecimalPlaces: market.positionDecimalPlaces,
|
|
||||||
settlementAssetDecimalPlaces: assetDecimals,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Instrument'),
|
title: t('Instrument'),
|
||||||
content: (
|
content: <InstrumentInfoPanel noBorder={false} market={market} />,
|
||||||
<MarketInfoTable
|
|
||||||
noBorder={false}
|
|
||||||
data={{
|
|
||||||
marketName: market.tradableInstrument.instrument.name,
|
|
||||||
code: market.tradableInstrument.instrument.code,
|
|
||||||
productType:
|
|
||||||
market.tradableInstrument.instrument.product.__typename,
|
|
||||||
...market.tradableInstrument.instrument.product,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Settlement asset'),
|
title: t('Settlement asset'),
|
||||||
content: asset ? (
|
content: <SettlementAssetInfoPanel market={market} noBorder={false} />,
|
||||||
<AssetDetailsTable
|
|
||||||
asset={asset}
|
|
||||||
inline={true}
|
|
||||||
noBorder={false}
|
|
||||||
dtClassName="text-black dark:text-white text-ui !px-0 !font-normal"
|
|
||||||
ddClassName="text-black dark:text-white text-ui !px-0 !font-normal max-w-full"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Splash>{t('No data')}</Splash>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Metadata'),
|
title: t('Metadata'),
|
||||||
content: (
|
content: <MetadataInfoPanel noBorder={false} market={market} />,
|
||||||
<MarketInfoTable
|
|
||||||
noBorder={false}
|
|
||||||
data={{
|
|
||||||
expiryDate: getMarketExpiryDateFormatted(
|
|
||||||
market.tradableInstrument.instrument.metadata.tags
|
|
||||||
),
|
|
||||||
...market.tradableInstrument.instrument.metadata.tags
|
|
||||||
?.map((tag) => {
|
|
||||||
const [key, value] = tag.split(':');
|
|
||||||
return { [key]: value };
|
|
||||||
})
|
|
||||||
.reduce((acc, curr) => ({ ...acc, ...curr }), {}),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Risk model'),
|
title: t('Risk model'),
|
||||||
content: (
|
content: <RiskModelInfoPanel noBorder={false} market={market} />,
|
||||||
<MarketInfoTable
|
|
||||||
noBorder={false}
|
|
||||||
data={market.tradableInstrument.riskModel}
|
|
||||||
unformatted={true}
|
|
||||||
omits={[]}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Risk parameters'),
|
title: t('Risk parameters'),
|
||||||
content: (
|
content: <RiskParametersInfoPanel noBorder={false} market={market} />,
|
||||||
<MarketInfoTable
|
|
||||||
noBorder={false}
|
|
||||||
data={market.tradableInstrument.riskModel.params}
|
|
||||||
unformatted={true}
|
|
||||||
omits={[]}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Risk factors'),
|
title: t('Risk factors'),
|
||||||
content: (
|
content: <RiskFactorsInfoPanel noBorder={false} market={market} />,
|
||||||
<MarketInfoTable
|
|
||||||
noBorder={false}
|
|
||||||
data={market.riskFactors}
|
|
||||||
unformatted={true}
|
|
||||||
omits={['market', '__typename']}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
...(market.priceMonitoringSettings?.parameters?.triggers || []).map(
|
...(market.priceMonitoringSettings?.parameters?.triggers || []).map(
|
||||||
(trigger, i) => ({
|
(trigger, i) => ({
|
||||||
@@ -158,7 +67,10 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
|
|||||||
<MarketInfoTable
|
<MarketInfoTable
|
||||||
noBorder={false}
|
noBorder={false}
|
||||||
data={{ referencePrice: trigger.referencePrice }}
|
data={{ referencePrice: trigger.referencePrice }}
|
||||||
decimalPlaces={assetDecimals}
|
decimalPlaces={
|
||||||
|
market.tradableInstrument.instrument.product.settlementAsset
|
||||||
|
.decimals
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
@@ -166,64 +78,26 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
|
|||||||
{
|
{
|
||||||
title: t('Liquidity monitoring parameters'),
|
title: t('Liquidity monitoring parameters'),
|
||||||
content: (
|
content: (
|
||||||
<MarketInfoTable
|
<LiquidityMonitoringParametersInfoPanel
|
||||||
noBorder={false}
|
noBorder={false}
|
||||||
data={{
|
market={market}
|
||||||
triggeringRatio:
|
|
||||||
market.liquidityMonitoringParameters.triggeringRatio,
|
|
||||||
...market.liquidityMonitoringParameters.targetStakeParameters,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t('Liquidity'),
|
||||||
|
content: <LiquidityInfoPanel market={market} noBorder={false} />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t('Liquidity price range'),
|
title: t('Liquidity price range'),
|
||||||
content: (
|
content: (
|
||||||
<>
|
<LiquidityPriceRangeInfoPanel market={market} noBorder={false} />
|
||||||
<p className="text-xs mb-4">
|
|
||||||
{`For liquidity orders to count towards a commitment, they must be
|
|
||||||
within the liquidity monitoring bounds.`}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs mb-4">
|
|
||||||
{`The liquidity price range is a ${liquidityPriceRange} difference from the mid
|
|
||||||
price.`}
|
|
||||||
</p>
|
|
||||||
<MarketInfoTable
|
|
||||||
noBorder={false}
|
|
||||||
data={{
|
|
||||||
liquidityPriceRange: `${liquidityPriceRange} of mid price`,
|
|
||||||
lowestPrice:
|
|
||||||
market.data?.midPrice &&
|
|
||||||
`${addDecimalsFormatNumber(
|
|
||||||
new BigNumber(1)
|
|
||||||
.minus(market.lpPriceRange)
|
|
||||||
.times(market.data.midPrice)
|
|
||||||
.toString(),
|
|
||||||
market.decimalPlaces
|
|
||||||
)} ${quoteUnit}`,
|
|
||||||
highestPrice:
|
|
||||||
market.data?.midPrice &&
|
|
||||||
`${addDecimalsFormatNumber(
|
|
||||||
new BigNumber(1)
|
|
||||||
.plus(market.lpPriceRange)
|
|
||||||
.times(market.data.midPrice)
|
|
||||||
.toString(),
|
|
||||||
market.decimalPlaces
|
|
||||||
)} ${quoteUnit}`,
|
|
||||||
}}
|
|
||||||
></MarketInfoTable>
|
|
||||||
</>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Oracle'),
|
title: t('Oracle'),
|
||||||
content: (
|
content: (
|
||||||
<MarketInfoTable
|
<OracleInfoPanel noBorder={false} market={market}>
|
||||||
noBorder={false}
|
|
||||||
data={
|
|
||||||
market.tradableInstrument.instrument.product.dataSourceSpecBinding
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Link
|
<Link
|
||||||
className="text-xs hover:underline"
|
className="text-xs hover:underline"
|
||||||
to={`/oracles#${market.tradableInstrument.instrument.product.dataSourceSpecForSettlementData.id}`}
|
to={`/oracles#${market.tradableInstrument.instrument.product.dataSourceSpecForSettlementData.id}`}
|
||||||
@@ -236,7 +110,7 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
|
|||||||
>
|
>
|
||||||
{t('View termination oracle specification')}
|
{t('View termination oracle specification')}
|
||||||
</Link>
|
</Link>
|
||||||
</MarketInfoTable>
|
</OracleInfoPanel>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -244,7 +118,7 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{panels.map((p) => (
|
{panels.map((p) => (
|
||||||
<div className="mb-3">
|
<div key={p.title} className="mb-3">
|
||||||
<h2 className="font-alpha calt text-xl">{p.title}</h2>
|
<h2 className="font-alpha calt text-xl">{p.title}</h2>
|
||||||
{p.content}
|
{p.content}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||||
import { StatusMessage } from '../status-message';
|
import { StatusMessage } from '../status-message';
|
||||||
|
|
||||||
interface RenderFetchedProps {
|
interface RenderFetchedProps {
|
||||||
@@ -7,6 +8,7 @@ interface RenderFetchedProps {
|
|||||||
loading: boolean | undefined;
|
loading: boolean | undefined;
|
||||||
className?: string;
|
className?: string;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
|
refetch?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RenderFetched = ({
|
export const RenderFetched = ({
|
||||||
@@ -15,6 +17,7 @@ export const RenderFetched = ({
|
|||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
errorMessage = t('Error retrieving data'),
|
errorMessage = t('Error retrieving data'),
|
||||||
|
refetch,
|
||||||
}: RenderFetchedProps) => {
|
}: RenderFetchedProps) => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
@@ -23,7 +26,20 @@ export const RenderFetched = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <StatusMessage className={className}>{errorMessage}</StatusMessage>;
|
return (
|
||||||
|
<>
|
||||||
|
<StatusMessage className={className}>{errorMessage}</StatusMessage>
|
||||||
|
{refetch && (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
refetch();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Try again')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return children;
|
return children;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useScrollToLocation } from '../../hooks/scroll-to-location';
|
|||||||
import { useDocumentTitle } from '../../hooks/use-document-title';
|
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||||
import compact from 'lodash/compact';
|
import compact from 'lodash/compact';
|
||||||
import { JsonViewerDialog } from '../../components/dialogs/json-viewer-dialog';
|
import { JsonViewerDialog } from '../../components/dialogs/json-viewer-dialog';
|
||||||
import { marketInfoProvider } from '@vegaprotocol/market-info';
|
import { marketInfoWithDataProvider } from '@vegaprotocol/market-info';
|
||||||
import { PageTitle } from '../../components/page-helpers/page-title';
|
import { PageTitle } from '../../components/page-helpers/page-title';
|
||||||
|
|
||||||
export const MarketPage = () => {
|
export const MarketPage = () => {
|
||||||
@@ -17,7 +17,7 @@ export const MarketPage = () => {
|
|||||||
const { marketId } = useParams<{ marketId: string }>();
|
const { marketId } = useParams<{ marketId: string }>();
|
||||||
|
|
||||||
const { data, loading, error } = useDataProvider({
|
const { data, loading, error } = useDataProvider({
|
||||||
dataProvider: marketInfoProvider,
|
dataProvider: marketInfoWithDataProvider,
|
||||||
skipUpdates: true,
|
skipUpdates: true,
|
||||||
variables: {
|
variables: {
|
||||||
marketId: marketId || '',
|
marketId: marketId || '',
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const Tx = () => {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
state: { data, loading, error },
|
state: { data, loading, error },
|
||||||
|
refetch,
|
||||||
} = useFetch<BlockExplorerTransaction>(
|
} = useFetch<BlockExplorerTransaction>(
|
||||||
`${DATA_SOURCES.blockExplorerUrl}/transactions/${toNonHex(hash)}`
|
`${DATA_SOURCES.blockExplorerUrl}/transactions/${toNonHex(hash)}`
|
||||||
);
|
);
|
||||||
@@ -56,6 +57,7 @@ const Tx = () => {
|
|||||||
error={error}
|
error={error}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
errorMessage={errorMessage}
|
errorMessage={errorMessage}
|
||||||
|
refetch={refetch}
|
||||||
>
|
>
|
||||||
<TxDetails
|
<TxDetails
|
||||||
className="mb-28"
|
className="mb-28"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { JsonViewerDialog } from '../../components/dialogs/json-viewer-dialog';
|
|||||||
import { PageTitle } from '../../components/page-helpers/page-title';
|
import { PageTitle } from '../../components/page-helpers/page-title';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import {
|
import {
|
||||||
ContractAddressLink,
|
EtherscanLink,
|
||||||
DApp,
|
DApp,
|
||||||
TOKEN_VALIDATOR,
|
TOKEN_VALIDATOR,
|
||||||
useLinks,
|
useLinks,
|
||||||
@@ -224,7 +224,7 @@ export const ValidatorsPage = () => {
|
|||||||
<KeyValueTableRow>
|
<KeyValueTableRow>
|
||||||
<div>{t('Ethereum address')}</div>
|
<div>{t('Ethereum address')}</div>
|
||||||
<div className="break-all text-xs">
|
<div className="break-all text-xs">
|
||||||
<ContractAddressLink address={v.ethereumAddress} />{' '}
|
<EtherscanLink address={v.ethereumAddress} />{' '}
|
||||||
<CopyWithTooltip text={v.ethereumAddress}>
|
<CopyWithTooltip text={v.ethereumAddress}>
|
||||||
<button title={t('Copy address to clipboard')}>
|
<button title={t('Copy address to clipboard')}>
|
||||||
<Icon size={3} name="duplicate" />
|
<Icon size={3} name="duplicate" />
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
"changes": {
|
"changes": {
|
||||||
"decimalPlaces": "5",
|
"decimalPlaces": "5",
|
||||||
"positionDecimalPlaces": "5",
|
"positionDecimalPlaces": "5",
|
||||||
|
"linearSlippageFactor": "0.001",
|
||||||
|
"quadraticSlippageFactor": "0",
|
||||||
"lpPriceRange": "10",
|
"lpPriceRange": "10",
|
||||||
"instrument": {
|
"instrument": {
|
||||||
"name": "Token test market",
|
"name": "Token test market",
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"lpPriceRange": "10",
|
"lpPriceRange": "10",
|
||||||
|
"linearSlippageFactor": "0.001",
|
||||||
|
"quadraticSlippageFactor": "0",
|
||||||
"instrument": {
|
"instrument": {
|
||||||
"code": "TEST.24h",
|
"code": "TEST.24h",
|
||||||
"future": {
|
"future": {
|
||||||
|
|||||||
@@ -110,10 +110,9 @@ Cypress.Commands.add('vega_wallet_teardown', function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
cy.get(vegaWalletContainer).within(() => {
|
cy.get(vegaWalletContainer).within(() => {
|
||||||
cy.get('[data-testid="associated-amount"]', { timeout: 30000 }).should(
|
cy.get('[data-testid="vega-wallet-balance-unstaked"]', {
|
||||||
'contain.text',
|
timeout: 30000,
|
||||||
'0.00'
|
}).should('contain.text', '0.00');
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
NX_VEGA_ENV=VALIDATOR_TESTNET
|
NX_VEGA_ENV=VALIDATOR_TESTNET
|
||||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml
|
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_URL=https://api-validators-testnet.vega.rocks/graphql
|
||||||
NX_VEGA_REST=https://api.validators-testnet.vega.xyz/
|
NX_VEGA_REST=https://api-validators-testnet.vega.rocks/
|
||||||
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
|
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
|
||||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||||
|
|||||||
+7
-2
@@ -2,7 +2,7 @@ import type { Dispatch, SetStateAction } from 'react';
|
|||||||
import { forwardRef, useMemo, useRef, useState } from 'react';
|
import { forwardRef, useMemo, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
import { Button, Icon } from '@vegaprotocol/ui-toolkit';
|
||||||
import { AgGridDynamic as AgGrid } from '@vegaprotocol/datagrid';
|
import { AgGridDynamic as AgGrid } from '@vegaprotocol/datagrid';
|
||||||
import { useAppState } from '../../../../contexts/app-state/app-state-context';
|
import { useAppState } from '../../../../contexts/app-state/app-state-context';
|
||||||
import { BigNumber } from '../../../../lib/bignumber';
|
import { BigNumber } from '../../../../lib/bignumber';
|
||||||
@@ -87,7 +87,12 @@ const TopThirdCellRenderer = (
|
|||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<Button
|
<Button
|
||||||
data-testid="show-all-validators"
|
data-testid="show-all-validators"
|
||||||
rightIcon="arrow-right"
|
rightIcon={
|
||||||
|
<Icon
|
||||||
|
name="arrow-right"
|
||||||
|
className="fill-current mr-2 align-text-top"
|
||||||
|
/>
|
||||||
|
}
|
||||||
className="inline-flex items-center"
|
className="inline-flex items-center"
|
||||||
>
|
>
|
||||||
{t('Reveal top validators')}
|
{t('Reveal top validators')}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
KeyValueTable,
|
KeyValueTable,
|
||||||
KeyValueTableRow,
|
KeyValueTableRow,
|
||||||
Link,
|
|
||||||
RoundedWrapper,
|
RoundedWrapper,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
@@ -11,7 +10,7 @@ import { useParams } from 'react-router';
|
|||||||
import { Navigate } from 'react-router-dom';
|
import { Navigate } from 'react-router-dom';
|
||||||
import { formatNumber } from '@vegaprotocol/utils';
|
import { formatNumber } from '@vegaprotocol/utils';
|
||||||
|
|
||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
import { EtherscanLink } from '@vegaprotocol/environment';
|
||||||
import { TrancheItem } from '../redemption/tranche-item';
|
import { TrancheItem } from '../redemption/tranche-item';
|
||||||
import Routes from '../routes';
|
import Routes from '../routes';
|
||||||
import { TrancheLabel } from './tranche-label';
|
import { TrancheLabel } from './tranche-label';
|
||||||
@@ -19,7 +18,6 @@ import { useTranches } from '../../lib/tranches/tranches-store';
|
|||||||
|
|
||||||
export const Tranche = () => {
|
export const Tranche = () => {
|
||||||
const tranches = useTranches((state) => state.tranches);
|
const tranches = useTranches((state) => state.tranches);
|
||||||
const { ETHERSCAN_URL } = useEnvironment();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { trancheId } = useParams<{ trancheId: string; address: string }>();
|
const { trancheId } = useParams<{ trancheId: string; address: string }>();
|
||||||
const { chainId } = useWeb3React();
|
const { chainId } = useWeb3React();
|
||||||
@@ -59,25 +57,15 @@ export const Tranche = () => {
|
|||||||
</KeyValueTableRow>
|
</KeyValueTableRow>
|
||||||
{tranche.users.map((user) => (
|
{tranche.users.map((user) => (
|
||||||
<KeyValueTableRow key={user}>
|
<KeyValueTableRow key={user}>
|
||||||
{
|
<EtherscanLink address={user} data-testid="link" />
|
||||||
<Link
|
<RouterLink
|
||||||
title={t('View on Etherscan (opens in a new tab)')}
|
className="underline"
|
||||||
href={`${ETHERSCAN_URL}/address/${user}`}
|
title={t('View vesting information')}
|
||||||
target="_blank"
|
to={`${Routes.REDEEM}/${user}`}
|
||||||
>
|
data-testid="redeem-link"
|
||||||
{user}
|
>
|
||||||
</Link>
|
{t('View vesting information')}
|
||||||
}
|
</RouterLink>
|
||||||
{
|
|
||||||
<RouterLink
|
|
||||||
className="underline"
|
|
||||||
title={t('View vesting information')}
|
|
||||||
to={`${Routes.REDEEM}/${user}`}
|
|
||||||
data-testid="redeem-link"
|
|
||||||
>
|
|
||||||
{t('View vesting information')}
|
|
||||||
</RouterLink>
|
|
||||||
}
|
|
||||||
</KeyValueTableRow>
|
</KeyValueTableRow>
|
||||||
))}
|
))}
|
||||||
</KeyValueTable>
|
</KeyValueTable>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"hosts": ["https://api.n04.d.vega.xyz/graphql"]
|
"hosts": ["https://api-n04.d.vega.rocks/graphql"]
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"hosts": ["https://api.n00.mainnet-mirror.vega.xyz/graphql"]
|
"hosts": ["https://api-n00.mainnet-mirror.vega.rocks/graphql"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"hosts": ["https://api.n01.sandbox.vega.xyz/graphql"]
|
"hosts": ["https://api-n01.sandbox.vega.rocks/graphql"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"hosts": ["https://api.n00.stagnet1.vega.xyz/graphql"]
|
"hosts": ["https://api-n00.stagnet1.vega.rocks/graphql"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"hosts": ["https://api.stagnet3.vega.xyz/graphql"]
|
"hosts": ["https://api-stagnet3.vega.rocks/graphql"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"hosts": [
|
"hosts": [
|
||||||
"https://api.n06.testnet.vega.xyz/graphql",
|
"https://api-n06.testnet.vega.rocks/graphql",
|
||||||
"https://api.n07.testnet.vega.xyz/graphql",
|
"https://api-n07.testnet.vega.rocks/graphql",
|
||||||
"https://api.n08.testnet.vega.xyz/graphql",
|
"https://api-n08.testnet.vega.rocks/graphql",
|
||||||
"https://api.n09.testnet.vega.xyz/graphql",
|
"https://api-n09.testnet.vega.rocks/graphql",
|
||||||
"https://api.n10.testnet.vega.xyz/graphql",
|
"https://api-n10.testnet.vega.rocks/graphql",
|
||||||
"https://api.n11.testnet.vega.xyz/graphql",
|
"https://api-n11.testnet.vega.rocks/graphql",
|
||||||
"https://api.n12.testnet.vega.xyz/graphql"
|
"https://api-n12.testnet.vega.rocks/graphql"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ const externalLink = 'external-link';
|
|||||||
const accordionContent = 'accordion-content';
|
const accordionContent = 'accordion-content';
|
||||||
|
|
||||||
describe('market info is displayed', { tags: '@smoke' }, () => {
|
describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.mockTradingPage();
|
||||||
|
});
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.mockTradingPage();
|
cy.mockTradingPage();
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
|
|||||||
@@ -216,6 +216,60 @@ describe('Market trading page', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('market bottom panel', { tags: '@smoke' }, () => {
|
||||||
|
it('on xxl screen should be splitted out into two tables', () => {
|
||||||
|
cy.getByTestId('tab-positions').should(
|
||||||
|
'have.attr',
|
||||||
|
'data-state',
|
||||||
|
'active'
|
||||||
|
);
|
||||||
|
cy.getByTestId('tab-orders').should(
|
||||||
|
'have.attr',
|
||||||
|
'data-state',
|
||||||
|
'inactive'
|
||||||
|
);
|
||||||
|
cy.getByTestId('tab-fills').should('have.attr', 'data-state', 'inactive');
|
||||||
|
cy.getByTestId('tab-accounts').should(
|
||||||
|
'have.attr',
|
||||||
|
'data-state',
|
||||||
|
'inactive'
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.viewport(1801, 1000);
|
||||||
|
cy.getByTestId('tab-positions').should(
|
||||||
|
'have.attr',
|
||||||
|
'data-state',
|
||||||
|
'active'
|
||||||
|
);
|
||||||
|
cy.getByTestId('tab-orders').should('have.attr', 'data-state', 'active');
|
||||||
|
cy.getByTestId('tab-fills').should('have.attr', 'data-state', 'inactive');
|
||||||
|
cy.getByTestId('tab-accounts').should(
|
||||||
|
'have.attr',
|
||||||
|
'data-state',
|
||||||
|
'inactive'
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.getByTestId('Fills').click();
|
||||||
|
cy.getByTestId('Collateral').click();
|
||||||
|
cy.getByTestId('tab-positions').should(
|
||||||
|
'have.attr',
|
||||||
|
'data-state',
|
||||||
|
'inactive'
|
||||||
|
);
|
||||||
|
cy.getByTestId('tab-orders').should(
|
||||||
|
'have.attr',
|
||||||
|
'data-state',
|
||||||
|
'inactive'
|
||||||
|
);
|
||||||
|
cy.getByTestId('tab-fills').should('have.attr', 'data-state', 'active');
|
||||||
|
cy.getByTestId('tab-accounts').should(
|
||||||
|
'have.attr',
|
||||||
|
'data-state',
|
||||||
|
'active'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('market states not accepting orders', { tags: '@smoke' }, function () {
|
describe('market states not accepting orders', { tags: '@smoke' }, function () {
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { connectEthereumWallet } from '../support/ethereum-wallet';
|
||||||
|
|
||||||
|
const connectEthWalletBtn = 'connect-eth-wallet-btn';
|
||||||
|
|
||||||
|
describe('ethereum wallet', { tags: '@smoke' }, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.mockWeb3Provider();
|
||||||
|
// Using portfolio withdrawals tab is it requires Ethereum wallet connection
|
||||||
|
cy.mockTradingPage();
|
||||||
|
cy.mockSubscription();
|
||||||
|
cy.setVegaWallet();
|
||||||
|
cy.visit('/#/portfolio');
|
||||||
|
cy.get('main[data-testid="/portfolio"]').should('exist');
|
||||||
|
cy.getByTestId('Withdrawals').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can connect', () => {
|
||||||
|
// 0004-EWAL-001
|
||||||
|
|
||||||
|
cy.wait('@NetworkParams');
|
||||||
|
cy.getByTestId('Deposits').click();
|
||||||
|
cy.getByTestId('deposit-button').click();
|
||||||
|
cy.getByTestId('connect-eth-wallet-btn').click();
|
||||||
|
cy.getByTestId('web3-connector-list').should('exist');
|
||||||
|
cy.getByTestId('web3-connector-MetaMask').click();
|
||||||
|
cy.getByTestId('web3-connector-list').should('not.exist');
|
||||||
|
cy.getByTestId('tab-deposits').should('not.be.empty');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should see an option to cancel the attempted connection', () => {
|
||||||
|
// 0004-EWAL-003
|
||||||
|
|
||||||
|
cy.wait('@NetworkParams');
|
||||||
|
cy.getByTestId('Deposits').click();
|
||||||
|
cy.getByTestId('deposit-button').click();
|
||||||
|
cy.getByTestId('connect-eth-wallet-btn').click();
|
||||||
|
cy.getByTestId('web3-connector-list').should('exist');
|
||||||
|
cy.getByTestId('web3-connector-WalletConnect').click();
|
||||||
|
cy.get('#walletconnect-qrcode-text').should('exist');
|
||||||
|
cy.get('#walletconnect-qrcode-close').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('able to disconnect eth wallet', () => {
|
||||||
|
// 0004-EWAL-004
|
||||||
|
// 0004-EWAL-005
|
||||||
|
// 0004-EWAL-006
|
||||||
|
|
||||||
|
const ethWalletAddress = Cypress.env('ETHEREUM_WALLET_ADDRESS');
|
||||||
|
cy.getByTestId('Deposits').click();
|
||||||
|
cy.getByTestId('deposit-button').click();
|
||||||
|
connectEthereumWallet('MetaMask');
|
||||||
|
cy.getByTestId('ethereum-address').should('have.text', ethWalletAddress);
|
||||||
|
cy.getByTestId('disconnect-ethereum-wallet')
|
||||||
|
.should('have.text', 'Disconnect')
|
||||||
|
.click();
|
||||||
|
cy.getByTestId(connectEthWalletBtn).should('exist');
|
||||||
|
});
|
||||||
|
});
|
||||||
+19
-39
@@ -1,7 +1,8 @@
|
|||||||
import { mockConnectWallet } from '@vegaprotocol/cypress';
|
import {
|
||||||
import { connectEthereumWallet } from '../support/ethereum-wallet';
|
mockConnectWallet,
|
||||||
|
mockConnectWalletWithUserError,
|
||||||
|
} from '@vegaprotocol/cypress';
|
||||||
|
|
||||||
const connectEthWalletBtn = 'connect-eth-wallet-btn';
|
|
||||||
const connectVegaBtn = 'connect-vega-wallet';
|
const connectVegaBtn = 'connect-vega-wallet';
|
||||||
const manageVegaBtn = 'manage-vega-wallet';
|
const manageVegaBtn = 'manage-vega-wallet';
|
||||||
const form = 'rest-connector-form';
|
const form = 'rest-connector-form';
|
||||||
@@ -127,6 +128,21 @@ describe('connect vega wallet', { tags: '@smoke' }, () => {
|
|||||||
cy.getByTestId(manageVegaBtn).should('exist');
|
cy.getByTestId(manageVegaBtn).should('exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can not connect', () => {
|
||||||
|
// 0002-WCON-002
|
||||||
|
// 0002-WCON-005
|
||||||
|
// 0002-WCON-007
|
||||||
|
|
||||||
|
mockConnectWalletWithUserError();
|
||||||
|
cy.getByTestId(connectVegaBtn).click();
|
||||||
|
cy.getByTestId('connectors-list')
|
||||||
|
.find('[data-testid="connector-jsonRpc"]')
|
||||||
|
.click();
|
||||||
|
cy.getByTestId('dialog-content')
|
||||||
|
.should('contain.text', 'User error')
|
||||||
|
.and('contain.text', 'the user rejected the wallet connection');
|
||||||
|
});
|
||||||
|
|
||||||
it('can change selected public key and disconnect', () => {
|
it('can change selected public key and disconnect', () => {
|
||||||
// 0002-WCON-022
|
// 0002-WCON-022
|
||||||
// 0002-WCON-023
|
// 0002-WCON-023
|
||||||
@@ -164,39 +180,3 @@ describe('connect vega wallet', { tags: '@smoke' }, () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ethereum wallet', { tags: '@smoke' }, () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.mockWeb3Provider();
|
|
||||||
// Using portfolio withdrawals tab is it requires Ethereum wallet connection
|
|
||||||
cy.mockTradingPage();
|
|
||||||
cy.mockSubscription();
|
|
||||||
cy.setVegaWallet();
|
|
||||||
cy.visit('/#/portfolio');
|
|
||||||
cy.get('main[data-testid="/portfolio"]').should('exist');
|
|
||||||
cy.getByTestId('Withdrawals').click();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can connect', () => {
|
|
||||||
cy.wait('@NetworkParams');
|
|
||||||
cy.getByTestId('Deposits').click();
|
|
||||||
cy.getByTestId('deposit-button').click();
|
|
||||||
cy.getByTestId('connect-eth-wallet-btn').click();
|
|
||||||
cy.getByTestId('web3-connector-list').should('exist');
|
|
||||||
cy.getByTestId('web3-connector-MetaMask').click();
|
|
||||||
cy.getByTestId('web3-connector-list').should('not.exist');
|
|
||||||
cy.getByTestId('tab-deposits').should('not.be.empty');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('able to disconnect eth wallet', () => {
|
|
||||||
const ethWalletAddress = Cypress.env('ETHEREUM_WALLET_ADDRESS');
|
|
||||||
cy.getByTestId('Deposits').click();
|
|
||||||
cy.getByTestId('deposit-button').click();
|
|
||||||
connectEthereumWallet('MetaMask');
|
|
||||||
cy.getByTestId('ethereum-address').should('have.text', ethWalletAddress);
|
|
||||||
cy.getByTestId('disconnect-ethereum-wallet')
|
|
||||||
.should('have.text', 'Disconnect')
|
|
||||||
.click();
|
|
||||||
cy.getByTestId(connectEthWalletBtn).should('exist');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||||
import debounce from 'lodash/debounce';
|
|
||||||
import { addDecimalsFormatNumber, titlefy } from '@vegaprotocol/utils';
|
import { addDecimalsFormatNumber, titlefy } from '@vegaprotocol/utils';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import {
|
import {
|
||||||
useDataProvider,
|
useDataProvider,
|
||||||
|
useScreenDimensions,
|
||||||
useThrottledDataProvider,
|
useThrottledDataProvider,
|
||||||
} from '@vegaprotocol/react-helpers';
|
} from '@vegaprotocol/react-helpers';
|
||||||
import { AsyncRenderer, ExternalLink, Splash } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer, ExternalLink, Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
@@ -61,7 +61,8 @@ export const MarketPage = () => {
|
|||||||
const { marketId } = useParams();
|
const { marketId } = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { w } = useWindowSize();
|
const { screenSize } = useScreenDimensions();
|
||||||
|
const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize);
|
||||||
const update = useGlobalStore((store) => store.update);
|
const update = useGlobalStore((store) => store.update);
|
||||||
const lastMarketId = useGlobalStore((store) => store.marketId);
|
const lastMarketId = useGlobalStore((store) => store.marketId);
|
||||||
|
|
||||||
@@ -87,7 +88,7 @@ export const MarketPage = () => {
|
|||||||
}, [update, lastMarketId, data?.id]);
|
}, [update, lastMarketId, data?.id]);
|
||||||
|
|
||||||
const tradeView = useMemo(() => {
|
const tradeView = useMemo(() => {
|
||||||
if (w > 960) {
|
if (largeScreen) {
|
||||||
return (
|
return (
|
||||||
<TradeGrid
|
<TradeGrid
|
||||||
market={data}
|
market={data}
|
||||||
@@ -105,7 +106,7 @@ export const MarketPage = () => {
|
|||||||
onClickCollateral={() => navigate('/portfolio')}
|
onClickCollateral={() => navigate('/portfolio')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}, [w, data, onSelect, navigate]);
|
}, [largeScreen, data, onSelect, navigate]);
|
||||||
if (!data && marketId) {
|
if (!data && marketId) {
|
||||||
return (
|
return (
|
||||||
<Splash>
|
<Splash>
|
||||||
@@ -140,37 +141,3 @@ export const MarketPage = () => {
|
|||||||
</AsyncRenderer>
|
</AsyncRenderer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useWindowSize = () => {
|
|
||||||
const [windowSize, setWindowSize] = useState(() => {
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
return {
|
|
||||||
w: window.innerWidth,
|
|
||||||
h: window.innerHeight,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Something sensible for server rendered page
|
|
||||||
return {
|
|
||||||
w: 1200,
|
|
||||||
h: 900,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleResize = debounce(({ target }) => {
|
|
||||||
setWindowSize({
|
|
||||||
w: target.innerWidth,
|
|
||||||
h: target.innerHeight,
|
|
||||||
});
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
window.addEventListener('resize', handleResize);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('resize', handleResize);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return windowSize;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { TradesContainer } from '@vegaprotocol/trades';
|
|||||||
import { LayoutPriority } from 'allotment';
|
import { LayoutPriority } from 'allotment';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
import { memo, useState } from 'react';
|
import { memo, useCallback, useState } from 'react';
|
||||||
import type { ReactNode, ComponentProps } from 'react';
|
import type { ReactNode, ComponentProps } from 'react';
|
||||||
import { DepthChartContainer } from '@vegaprotocol/market-depth';
|
import { DepthChartContainer } from '@vegaprotocol/market-depth';
|
||||||
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
|
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
|
||||||
@@ -29,6 +29,7 @@ import { LiquidityContainer } from '../liquidity/liquidity';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Links, Routes } from '../../pages/client-router';
|
import { Links, Routes } from '../../pages/client-router';
|
||||||
import type { PinnedAsset } from '@vegaprotocol/accounts';
|
import type { PinnedAsset } from '@vegaprotocol/accounts';
|
||||||
|
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
type MarketDependantView =
|
type MarketDependantView =
|
||||||
| typeof CandlesChartContainer
|
| typeof CandlesChartContainer
|
||||||
@@ -69,129 +70,204 @@ interface TradeGridProps {
|
|||||||
pinnedAsset?: PinnedAsset;
|
pinnedAsset?: PinnedAsset;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MainGrid = ({
|
interface BottomPanelProps {
|
||||||
marketId,
|
|
||||||
onSelect,
|
|
||||||
pinnedAsset,
|
|
||||||
}: {
|
|
||||||
marketId: string;
|
marketId: string;
|
||||||
onSelect?: (marketId: string) => void;
|
|
||||||
pinnedAsset?: PinnedAsset;
|
pinnedAsset?: PinnedAsset;
|
||||||
}) => {
|
}
|
||||||
const navigate = useNavigate();
|
|
||||||
const onMarketClick = (marketId: string) => {
|
const MarketBottomPanel = memo(
|
||||||
navigate(Links[Routes.MARKET](marketId), {
|
({ marketId, pinnedAsset }: BottomPanelProps) => {
|
||||||
replace: true,
|
const { screenSize } = useScreenDimensions();
|
||||||
});
|
const navigate = useNavigate();
|
||||||
};
|
const onMarketClick = useCallback(
|
||||||
return (
|
(marketId: string) => {
|
||||||
<ResizableGrid vertical>
|
navigate(Links[Routes.MARKET](marketId), {
|
||||||
<ResizableGridPanel minSize={75} priority={LayoutPriority.High}>
|
replace: true,
|
||||||
<ResizableGrid proportionalLayout={false} minSize={200}>
|
});
|
||||||
<ResizableGridPanel
|
},
|
||||||
priority={LayoutPriority.High}
|
[navigate]
|
||||||
minSize={200}
|
);
|
||||||
preferredSize="50%"
|
|
||||||
>
|
return 'xxxl' === screenSize ? (
|
||||||
<TradeGridChild>
|
<ResizableGrid proportionalLayout minSize={200}>
|
||||||
<Tabs>
|
<ResizableGridPanel
|
||||||
<Tab id="chart" name={t('Chart')}>
|
priority={LayoutPriority.Low}
|
||||||
<TradingViews.Candles marketId={marketId} />
|
preferredSize="50%"
|
||||||
</Tab>
|
minSize={50}
|
||||||
<Tab id="depth" name={t('Depth')}>
|
>
|
||||||
<TradingViews.Depth marketId={marketId} />
|
<TradeGridChild>
|
||||||
</Tab>
|
<Tabs>
|
||||||
<Tab id="liquidity" name={t('Liquidity')}>
|
<Tab id="orders" name={t('Orders')}>
|
||||||
<TradingViews.Liquidity marketId={marketId} />
|
<VegaWalletContainer>
|
||||||
</Tab>
|
<TradingViews.Orders
|
||||||
</Tabs>
|
|
||||||
</TradeGridChild>
|
|
||||||
</ResizableGridPanel>
|
|
||||||
<ResizableGridPanel
|
|
||||||
priority={LayoutPriority.Low}
|
|
||||||
preferredSize={330}
|
|
||||||
minSize={300}
|
|
||||||
>
|
|
||||||
<TradeGridChild>
|
|
||||||
<Tabs>
|
|
||||||
<Tab id="ticket" name={t('Ticket')}>
|
|
||||||
<TradingViews.Ticket
|
|
||||||
marketId={marketId}
|
marketId={marketId}
|
||||||
onClickCollateral={() => navigate('/portfolio')}
|
onMarketClick={onMarketClick}
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</VegaWalletContainer>
|
||||||
<Tab id="info" name={t('Info')}>
|
</Tab>
|
||||||
<TradingViews.Info
|
<Tab id="fills" name={t('Fills')}>
|
||||||
|
<VegaWalletContainer>
|
||||||
|
<TradingViews.Fills
|
||||||
marketId={marketId}
|
marketId={marketId}
|
||||||
onSelect={(id: string) => {
|
onMarketClick={onMarketClick}
|
||||||
onSelect?.(id);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</VegaWalletContainer>
|
||||||
</Tabs>
|
</Tab>
|
||||||
</TradeGridChild>
|
</Tabs>
|
||||||
</ResizableGridPanel>
|
</TradeGridChild>
|
||||||
<ResizableGridPanel
|
</ResizableGridPanel>
|
||||||
priority={LayoutPriority.Low}
|
<ResizableGridPanel
|
||||||
preferredSize={430}
|
priority={LayoutPriority.Low}
|
||||||
minSize={200}
|
preferredSize="50%"
|
||||||
>
|
minSize={50}
|
||||||
<TradeGridChild>
|
>
|
||||||
<Tabs>
|
<TradeGridChild>
|
||||||
<Tab id="orderbook" name={t('Orderbook')}>
|
<Tabs>
|
||||||
<TradingViews.Orderbook marketId={marketId} />
|
<Tab id="positions" name={t('Positions')}>
|
||||||
</Tab>
|
<VegaWalletContainer>
|
||||||
<Tab id="trades" name={t('Trades')}>
|
<TradingViews.Positions
|
||||||
<TradingViews.Trades marketId={marketId} />
|
onMarketClick={onMarketClick}
|
||||||
</Tab>
|
noBottomPlaceholder
|
||||||
</Tabs>
|
/>
|
||||||
</TradeGridChild>
|
</VegaWalletContainer>
|
||||||
</ResizableGridPanel>
|
</Tab>
|
||||||
</ResizableGrid>
|
<Tab id="accounts" name={t('Collateral')}>
|
||||||
</ResizableGridPanel>
|
<VegaWalletContainer>
|
||||||
<ResizableGridPanel
|
<TradingViews.Collateral
|
||||||
priority={LayoutPriority.Low}
|
pinnedAsset={pinnedAsset}
|
||||||
preferredSize="25%"
|
noBottomPlaceholder
|
||||||
minSize={50}
|
hideButtons
|
||||||
>
|
/>
|
||||||
<TradeGridChild>
|
</VegaWalletContainer>
|
||||||
<Tabs>
|
</Tab>
|
||||||
<Tab id="positions" name={t('Positions')}>
|
</Tabs>
|
||||||
<VegaWalletContainer>
|
</TradeGridChild>
|
||||||
<TradingViews.Positions onMarketClick={onMarketClick} />
|
</ResizableGridPanel>
|
||||||
</VegaWalletContainer>
|
</ResizableGrid>
|
||||||
</Tab>
|
) : (
|
||||||
<Tab id="orders" name={t('Orders')}>
|
<TradeGridChild>
|
||||||
<VegaWalletContainer>
|
<Tabs>
|
||||||
<TradingViews.Orders
|
<Tab id="positions" name={t('Positions')}>
|
||||||
marketId={marketId}
|
<VegaWalletContainer>
|
||||||
onMarketClick={onMarketClick}
|
<TradingViews.Positions onMarketClick={onMarketClick} />
|
||||||
/>
|
</VegaWalletContainer>
|
||||||
</VegaWalletContainer>
|
</Tab>
|
||||||
</Tab>
|
<Tab id="orders" name={t('Orders')}>
|
||||||
<Tab id="fills" name={t('Fills')}>
|
<VegaWalletContainer>
|
||||||
<VegaWalletContainer>
|
<TradingViews.Orders
|
||||||
<TradingViews.Fills
|
marketId={marketId}
|
||||||
marketId={marketId}
|
onMarketClick={onMarketClick}
|
||||||
onMarketClick={onMarketClick}
|
/>
|
||||||
/>
|
</VegaWalletContainer>
|
||||||
</VegaWalletContainer>
|
</Tab>
|
||||||
</Tab>
|
<Tab id="fills" name={t('Fills')}>
|
||||||
<Tab id="accounts" name={t('Collateral')}>
|
<VegaWalletContainer>
|
||||||
<VegaWalletContainer>
|
<TradingViews.Fills
|
||||||
<TradingViews.Collateral
|
marketId={marketId}
|
||||||
pinnedAsset={pinnedAsset}
|
onMarketClick={onMarketClick}
|
||||||
hideButtons
|
/>
|
||||||
/>
|
</VegaWalletContainer>
|
||||||
</VegaWalletContainer>
|
</Tab>
|
||||||
</Tab>
|
<Tab id="accounts" name={t('Collateral')}>
|
||||||
</Tabs>
|
<VegaWalletContainer>
|
||||||
</TradeGridChild>
|
<TradingViews.Collateral pinnedAsset={pinnedAsset} hideButtons />
|
||||||
</ResizableGridPanel>
|
</VegaWalletContainer>
|
||||||
</ResizableGrid>
|
</Tab>
|
||||||
);
|
</Tabs>
|
||||||
};
|
</TradeGridChild>
|
||||||
const MainGridWrapped = memo(MainGrid);
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
MarketBottomPanel.displayName = 'MarketBottomPanel';
|
||||||
|
|
||||||
|
const MainGrid = memo(
|
||||||
|
({
|
||||||
|
marketId,
|
||||||
|
onSelect,
|
||||||
|
pinnedAsset,
|
||||||
|
}: {
|
||||||
|
marketId: string;
|
||||||
|
onSelect?: (marketId: string) => void;
|
||||||
|
pinnedAsset?: PinnedAsset;
|
||||||
|
}) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return (
|
||||||
|
<ResizableGrid vertical>
|
||||||
|
<ResizableGridPanel minSize={75} priority={LayoutPriority.High}>
|
||||||
|
<ResizableGrid proportionalLayout={false} minSize={200}>
|
||||||
|
<ResizableGridPanel
|
||||||
|
priority={LayoutPriority.High}
|
||||||
|
minSize={200}
|
||||||
|
preferredSize="50%"
|
||||||
|
>
|
||||||
|
<TradeGridChild>
|
||||||
|
<Tabs>
|
||||||
|
<Tab id="chart" name={t('Chart')}>
|
||||||
|
<TradingViews.Candles marketId={marketId} />
|
||||||
|
</Tab>
|
||||||
|
<Tab id="depth" name={t('Depth')}>
|
||||||
|
<TradingViews.Depth marketId={marketId} />
|
||||||
|
</Tab>
|
||||||
|
<Tab id="liquidity" name={t('Liquidity')}>
|
||||||
|
<TradingViews.Liquidity marketId={marketId} />
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
</TradeGridChild>
|
||||||
|
</ResizableGridPanel>
|
||||||
|
<ResizableGridPanel
|
||||||
|
priority={LayoutPriority.Low}
|
||||||
|
preferredSize={330}
|
||||||
|
minSize={300}
|
||||||
|
>
|
||||||
|
<TradeGridChild>
|
||||||
|
<Tabs>
|
||||||
|
<Tab id="ticket" name={t('Ticket')}>
|
||||||
|
<TradingViews.Ticket
|
||||||
|
marketId={marketId}
|
||||||
|
onClickCollateral={() => navigate('/portfolio')}
|
||||||
|
/>
|
||||||
|
</Tab>
|
||||||
|
<Tab id="info" name={t('Info')}>
|
||||||
|
<TradingViews.Info
|
||||||
|
marketId={marketId}
|
||||||
|
onSelect={(id: string) => {
|
||||||
|
onSelect?.(id);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
</TradeGridChild>
|
||||||
|
</ResizableGridPanel>
|
||||||
|
<ResizableGridPanel
|
||||||
|
priority={LayoutPriority.Low}
|
||||||
|
preferredSize={430}
|
||||||
|
minSize={200}
|
||||||
|
>
|
||||||
|
<TradeGridChild>
|
||||||
|
<Tabs>
|
||||||
|
<Tab id="orderbook" name={t('Orderbook')}>
|
||||||
|
<TradingViews.Orderbook marketId={marketId} />
|
||||||
|
</Tab>
|
||||||
|
<Tab id="trades" name={t('Trades')}>
|
||||||
|
<TradingViews.Trades marketId={marketId} />
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
</TradeGridChild>
|
||||||
|
</ResizableGridPanel>
|
||||||
|
</ResizableGrid>
|
||||||
|
</ResizableGridPanel>
|
||||||
|
<ResizableGridPanel
|
||||||
|
priority={LayoutPriority.Low}
|
||||||
|
preferredSize="25%"
|
||||||
|
minSize={50}
|
||||||
|
>
|
||||||
|
<MarketBottomPanel marketId={marketId} pinnedAsset={pinnedAsset} />
|
||||||
|
</ResizableGridPanel>
|
||||||
|
</ResizableGrid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
MainGrid.displayName = 'MainGrid';
|
||||||
|
|
||||||
export const TradeGrid = ({
|
export const TradeGrid = ({
|
||||||
market,
|
market,
|
||||||
@@ -201,7 +277,7 @@ export const TradeGrid = ({
|
|||||||
return (
|
return (
|
||||||
<div className="h-full grid grid-rows-[min-content_1fr]">
|
<div className="h-full grid grid-rows-[min-content_1fr]">
|
||||||
<TradeMarketHeader market={market} onSelect={onSelect} />
|
<TradeMarketHeader market={market} onSelect={onSelect} />
|
||||||
<MainGridWrapped
|
<MainGrid
|
||||||
marketId={market?.id || ''}
|
marketId={market?.id || ''}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
pinnedAsset={pinnedAsset}
|
pinnedAsset={pinnedAsset}
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ export const DepositsContainer = () => {
|
|||||||
<div className="h-full relative">
|
<div className="h-full relative">
|
||||||
<DepositsTable
|
<DepositsTable
|
||||||
rowData={data || []}
|
rowData={data || []}
|
||||||
noRowsOverlayComponent={() => null}
|
suppressLoadingOverlay
|
||||||
|
suppressNoRowsOverlay
|
||||||
ref={gridRef}
|
ref={gridRef}
|
||||||
{...bottomPlaceholderProps}
|
{...bottomPlaceholderProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ export const WithdrawalsContainer = () => {
|
|||||||
<WithdrawalsTable
|
<WithdrawalsTable
|
||||||
data-testid="withdrawals-history"
|
data-testid="withdrawals-history"
|
||||||
rowData={data}
|
rowData={data}
|
||||||
noRowsOverlayComponent={() => null}
|
suppressLoadingOverlay
|
||||||
|
suppressNoRowsOverlay
|
||||||
/>
|
/>
|
||||||
<div className="pointer-events-none absolute inset-0">
|
<div className="pointer-events-none absolute inset-0">
|
||||||
<AsyncRenderer
|
<AsyncRenderer
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ import { useDepositDialog } from '@vegaprotocol/deposits';
|
|||||||
export const AccountsContainer = ({
|
export const AccountsContainer = ({
|
||||||
pinnedAsset,
|
pinnedAsset,
|
||||||
hideButtons,
|
hideButtons,
|
||||||
|
noBottomPlaceholder,
|
||||||
}: {
|
}: {
|
||||||
pinnedAsset?: PinnedAsset;
|
pinnedAsset?: PinnedAsset;
|
||||||
hideButtons?: boolean;
|
hideButtons?: boolean;
|
||||||
|
noBottomPlaceholder?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const { pubKey, isReadOnly } = useVegaWallet();
|
const { pubKey, isReadOnly } = useVegaWallet();
|
||||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||||
@@ -46,6 +48,7 @@ export const AccountsContainer = ({
|
|||||||
onClickDeposit={openDepositDialog}
|
onClickDeposit={openDepositDialog}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
pinnedAsset={pinnedAsset}
|
pinnedAsset={pinnedAsset}
|
||||||
|
noBottomPlaceholder={noBottomPlaceholder}
|
||||||
/>
|
/>
|
||||||
{!isReadOnly && !hideButtons && (
|
{!isReadOnly && !hideButtons && (
|
||||||
<div className="flex gap-2 justify-end p-2 px-[11px] absolute lg:fixed bottom-0 right-3 dark:bg-black/75 bg-white/75 rounded">
|
<div className="flex gap-2 justify-end p-2 px-[11px] absolute lg:fixed bottom-0 right-3 dark:bg-black/75 bg-white/75 rounded">
|
||||||
|
|||||||
@@ -1,20 +1,30 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { NodeHealth, NodeUrl, HealthIndicator } from './footer';
|
import { NodeHealth, NodeUrl, HealthIndicator } from './footer';
|
||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
import { Intent } from '@vegaprotocol/ui-toolkit';
|
||||||
|
|
||||||
|
jest.mock('@vegaprotocol/environment', () => ({
|
||||||
|
...jest.requireActual('@vegaprotocol/environment'),
|
||||||
|
useEnvironment: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(() => ({ VEGA_URL: 'https://vega-url.wtf' })),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mockSetNodeSwitcher = jest.fn();
|
||||||
|
jest.mock('../../stores', () => ({
|
||||||
|
...jest.requireActual('../../stores'),
|
||||||
|
useGlobalStore: () => mockSetNodeSwitcher,
|
||||||
|
}));
|
||||||
|
|
||||||
describe('NodeHealth', () => {
|
describe('NodeHealth', () => {
|
||||||
it('controls the node switcher dialog', async () => {
|
it('controls the node switcher dialog', async () => {
|
||||||
const mockOnClick = jest.fn();
|
render(<NodeHealth />, { wrapper: MockedProvider });
|
||||||
render(
|
await waitFor(() => {
|
||||||
<NodeHealth
|
expect(screen.getByRole('button')).toBeInTheDocument();
|
||||||
onClick={mockOnClick}
|
});
|
||||||
url={'https://api.n99.somenetwork.vega.xyz'}
|
|
||||||
blockHeight={100}
|
|
||||||
blockDiff={0}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
await userEvent.click(screen.getByRole('button'));
|
await userEvent.click(screen.getByRole('button'));
|
||||||
expect(mockOnClick).toHaveBeenCalled();
|
expect(mockSetNodeSwitcher).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -31,14 +41,22 @@ describe('NodeUrl', () => {
|
|||||||
|
|
||||||
describe('HealthIndicator', () => {
|
describe('HealthIndicator', () => {
|
||||||
const cases = [
|
const cases = [
|
||||||
{ diff: 0, classname: 'bg-vega-green-550', text: 'Operational' },
|
{
|
||||||
{ diff: 5, classname: 'bg-warning', text: '5 Blocks behind' },
|
intent: Intent.Success,
|
||||||
{ diff: null, classname: 'bg-danger', text: 'Non operational' },
|
text: 'Operational',
|
||||||
|
classname: 'bg-vega-green-550',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
intent: Intent.Warning,
|
||||||
|
text: '5 Blocks behind',
|
||||||
|
classname: 'bg-warning',
|
||||||
|
},
|
||||||
|
{ intent: Intent.Danger, text: 'Non operational', classname: 'bg-danger' },
|
||||||
];
|
];
|
||||||
it.each(cases)(
|
it.each(cases)(
|
||||||
'renders correct text and indicator color for $diff block difference',
|
'renders correct text and indicator color for $diff block difference',
|
||||||
(elem) => {
|
(elem) => {
|
||||||
render(<HealthIndicator blockDiff={elem.diff} />);
|
render(<HealthIndicator text={elem.text} intent={elem.intent} />);
|
||||||
expect(screen.getByTestId('indicator')).toHaveClass(elem.classname);
|
expect(screen.getByTestId('indicator')).toHaveClass(elem.classname);
|
||||||
expect(screen.getByText(elem.text)).toBeInTheDocument();
|
expect(screen.getByText(elem.text)).toBeInTheDocument();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +1,45 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
import { useEnvironment, useNodeHealth } from '@vegaprotocol/environment';
|
import { useEnvironment, useNodeHealth } from '@vegaprotocol/environment';
|
||||||
import { useNavigatorOnline } from '@vegaprotocol/react-helpers';
|
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { Indicator, Intent } from '@vegaprotocol/ui-toolkit';
|
import type { Intent } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { Indicator } from '@vegaprotocol/ui-toolkit';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
||||||
import { useGlobalStore } from '../../stores';
|
import { useGlobalStore } from '../../stores';
|
||||||
|
|
||||||
export const Footer = () => {
|
export const Footer = () => {
|
||||||
const { VEGA_URL } = useEnvironment();
|
|
||||||
const setNodeSwitcher = useGlobalStore(
|
|
||||||
(store) => (open: boolean) => store.update({ nodeSwitcherDialog: open })
|
|
||||||
);
|
|
||||||
const { blockDiff, datanodeBlockHeight } = useNodeHealth();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="px-4 py-1 text-xs border-t border-default text-vega-light-300 dark:text-vega-dark-300 lg:fixed bottom-0 left-0 border-r bg-white dark:bg-black">
|
<footer className="px-4 py-1 text-xs border-t border-default text-vega-light-300 dark:text-vega-dark-300 lg:fixed bottom-0 left-0 border-r bg-white dark:bg-black">
|
||||||
{/* Pull left to align with top nav, due to button padding */}
|
{/* Pull left to align with top nav, due to button padding */}
|
||||||
<div className="-ml-2">
|
<div className="-ml-2">
|
||||||
{VEGA_URL && (
|
<NodeHealth />
|
||||||
<NodeHealth
|
|
||||||
url={VEGA_URL}
|
|
||||||
blockHeight={datanodeBlockHeight}
|
|
||||||
blockDiff={blockDiff}
|
|
||||||
onClick={() => setNodeSwitcher(true)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
interface NodeHealthProps {
|
|
||||||
url: string;
|
|
||||||
blockHeight: number | undefined;
|
|
||||||
blockDiff: number | null;
|
|
||||||
onClick: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const NodeHealth = ({
|
export const NodeHealth = () => {
|
||||||
url,
|
const { VEGA_URL } = useEnvironment();
|
||||||
blockHeight,
|
const setNodeSwitcher = useGlobalStore(
|
||||||
blockDiff,
|
(store) => (open: boolean) => store.update({ nodeSwitcherDialog: open })
|
||||||
onClick,
|
);
|
||||||
}: NodeHealthProps) => {
|
const { datanodeBlockHeight, text, intent } = useNodeHealth();
|
||||||
return (
|
const onClick = useCallback(() => {
|
||||||
|
setNodeSwitcher(true);
|
||||||
|
}, [setNodeSwitcher]);
|
||||||
|
return VEGA_URL ? (
|
||||||
<FooterButton onClick={onClick} data-testid="node-health">
|
<FooterButton onClick={onClick} data-testid="node-health">
|
||||||
<FooterButtonPart>
|
<FooterButtonPart>
|
||||||
<HealthIndicator blockDiff={blockDiff} />
|
<HealthIndicator text={text} intent={intent} />
|
||||||
</FooterButtonPart>
|
</FooterButtonPart>
|
||||||
<FooterButtonPart>
|
<FooterButtonPart>
|
||||||
<NodeUrl url={url} />
|
<NodeUrl url={VEGA_URL} />
|
||||||
</FooterButtonPart>
|
</FooterButtonPart>
|
||||||
<FooterButtonPart>
|
<FooterButtonPart>
|
||||||
<span title={t('Block height')}>{blockHeight}</span>
|
<span title={t('Block height')}>{datanodeBlockHeight}</span>
|
||||||
</FooterButtonPart>
|
</FooterButtonPart>
|
||||||
</FooterButton>
|
</FooterButton>
|
||||||
);
|
) : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface NodeUrlProps {
|
interface NodeUrlProps {
|
||||||
@@ -69,31 +54,11 @@ export const NodeUrl = ({ url }: NodeUrlProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface HealthIndicatorProps {
|
interface HealthIndicatorProps {
|
||||||
blockDiff: number | null;
|
text: string;
|
||||||
|
intent: Intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// How many blocks behind the most advanced block that is
|
export const HealthIndicator = ({ text, intent }: HealthIndicatorProps) => {
|
||||||
// deemed acceptable for "Good" status
|
|
||||||
const BLOCK_THRESHOLD = 3;
|
|
||||||
|
|
||||||
export const HealthIndicator = ({ blockDiff }: HealthIndicatorProps) => {
|
|
||||||
const online = useNavigatorOnline();
|
|
||||||
|
|
||||||
let intent = Intent.Success;
|
|
||||||
let text = 'Operational';
|
|
||||||
|
|
||||||
if (!online) {
|
|
||||||
text = t('Offline');
|
|
||||||
intent = Intent.Danger;
|
|
||||||
} else if (blockDiff === null) {
|
|
||||||
// Block height query failed and null was returned
|
|
||||||
text = t('Non operational');
|
|
||||||
intent = Intent.Danger;
|
|
||||||
} else if (blockDiff >= BLOCK_THRESHOLD) {
|
|
||||||
text = t(`${blockDiff} Blocks behind`);
|
|
||||||
intent = Intent.Warning;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span title={t('Node health')}>
|
<span title={t('Node health')}>
|
||||||
<Indicator variant={intent} />
|
<Indicator variant={intent} />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { useAssetsDataProvider } from '@vegaprotocol/assets';
|
import { useAssetsDataProvider } from '@vegaprotocol/assets';
|
||||||
import { ETHERSCAN_TX, useEtherscanLink } from '@vegaprotocol/environment';
|
import { EtherscanLink } from '@vegaprotocol/environment';
|
||||||
import { formatNumber, toBigNum } from '@vegaprotocol/utils';
|
import { formatNumber, toBigNum } from '@vegaprotocol/utils';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import type { Toast, ToastContent } from '@vegaprotocol/ui-toolkit';
|
import type { Toast, ToastContent } from '@vegaprotocol/ui-toolkit';
|
||||||
@@ -8,7 +8,7 @@ import { ToastHeading } from '@vegaprotocol/ui-toolkit';
|
|||||||
import { Panel } from '@vegaprotocol/ui-toolkit';
|
import { Panel } from '@vegaprotocol/ui-toolkit';
|
||||||
import { CLOSE_AFTER } from '@vegaprotocol/ui-toolkit';
|
import { CLOSE_AFTER } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useToasts } from '@vegaprotocol/ui-toolkit';
|
import { useToasts } from '@vegaprotocol/ui-toolkit';
|
||||||
import { ExternalLink, Intent, ProgressBar } from '@vegaprotocol/ui-toolkit';
|
import { Intent, ProgressBar } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import compact from 'lodash/compact';
|
import compact from 'lodash/compact';
|
||||||
import type { EthStoredTxState } from '@vegaprotocol/web3';
|
import type { EthStoredTxState } from '@vegaprotocol/web3';
|
||||||
@@ -103,7 +103,7 @@ const EthTxPendingToastContent = ({ tx }: EthTxToastContentProps) => {
|
|||||||
<>
|
<>
|
||||||
<ToastHeading>{t('Awaiting confirmation')}</ToastHeading>
|
<ToastHeading>{t('Awaiting confirmation')}</ToastHeading>
|
||||||
<p>{t('Please wait for your transaction to be confirmed.')}</p>
|
<p>{t('Please wait for your transaction to be confirmed.')}</p>
|
||||||
<EtherscanLink tx={tx} />
|
{tx.txHash && <EtherscanLink tx={tx.txHash} />}
|
||||||
<EthTransactionDetails tx={tx} />
|
<EthTransactionDetails tx={tx} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -126,26 +126,12 @@ const EthTxErrorToastContent = ({ tx }: EthTxToastContentProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const EtherscanLink = ({ tx }: EthTxToastContentProps) => {
|
|
||||||
const etherscanLink = useEtherscanLink();
|
|
||||||
return tx.txHash ? (
|
|
||||||
<p className="break-all">
|
|
||||||
<ExternalLink
|
|
||||||
href={etherscanLink(ETHERSCAN_TX.replace(':hash', tx.txHash))}
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
{t('View on Etherscan')}
|
|
||||||
</ExternalLink>
|
|
||||||
</p>
|
|
||||||
) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const EthTxConfirmedToastContent = ({ tx }: EthTxToastContentProps) => {
|
const EthTxConfirmedToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ToastHeading>{t('Transaction confirmed')}</ToastHeading>
|
<ToastHeading>{t('Transaction confirmed')}</ToastHeading>
|
||||||
<p>{t('Your transaction has been confirmed.')}</p>
|
<p>{t('Your transaction has been confirmed.')}</p>
|
||||||
<EtherscanLink tx={tx} />
|
{tx.txHash && <EtherscanLink tx={tx.txHash} />}
|
||||||
<EthTransactionDetails tx={tx} />
|
<EthTransactionDetails tx={tx} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -162,7 +148,7 @@ const EthTxCompletedToastContent = ({ tx }: EthTxToastContentProps) => {
|
|||||||
{t('Your transaction has been completed.')}{' '}
|
{t('Your transaction has been completed.')}{' '}
|
||||||
{isDeposit && t('Waiting for deposit confirmation.')}
|
{isDeposit && t('Waiting for deposit confirmation.')}
|
||||||
</p>
|
</p>
|
||||||
<EtherscanLink tx={tx} />
|
{tx.txHash && <EtherscanLink tx={tx.txHash} />}
|
||||||
<EthTransactionDetails tx={tx} />
|
<EthTransactionDetails tx={tx} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,51 +15,29 @@ jest.mock('@vegaprotocol/react-helpers', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
describe('AccountManager', () => {
|
describe('AccountManager', () => {
|
||||||
beforeEach(() => {
|
describe('when rerender', () => {
|
||||||
mockedUseDataProvider
|
beforeEach(() => {
|
||||||
.mockImplementationOnce((args) => {
|
mockedUseDataProvider
|
||||||
return {
|
.mockImplementationOnce((args) => {
|
||||||
data: [],
|
return {
|
||||||
};
|
data: [],
|
||||||
})
|
};
|
||||||
.mockImplementationOnce((args) => {
|
})
|
||||||
return {
|
.mockImplementationOnce((args) => {
|
||||||
data: [
|
return {
|
||||||
{ asset: { id: 'a1' }, party: { id: 't1' } },
|
data: [
|
||||||
{ asset: { id: 'a2' }, party: { id: 't2' } },
|
{ asset: { id: 'a1' }, party: { id: 't1' } },
|
||||||
],
|
{ asset: { id: 'a2' }, party: { id: 't2' } },
|
||||||
};
|
],
|
||||||
});
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
it('change partyId should reload data provider', async () => {
|
|
||||||
const { rerender } = render(
|
|
||||||
<AccountManager
|
|
||||||
partyId="partyOne"
|
|
||||||
onClickAsset={jest.fn}
|
|
||||||
isReadOnly={false}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
(helpers.useDataProvider as jest.Mock).mock.calls[0][0].variables.partyId
|
|
||||||
).toEqual('partyOne');
|
|
||||||
await act(() => {
|
|
||||||
rerender(
|
|
||||||
<AccountManager
|
|
||||||
partyId="partyTwo"
|
|
||||||
onClickAsset={jest.fn}
|
|
||||||
isReadOnly={false}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
expect(
|
|
||||||
(helpers.useDataProvider as jest.Mock).mock.calls[1][0].variables.partyId
|
|
||||||
).toEqual('partyTwo');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('update method should return proper result', async () => {
|
afterEach(() => {
|
||||||
let rerenderer: (ui: React.ReactElement) => void;
|
jest.clearAllMocks();
|
||||||
await act(() => {
|
});
|
||||||
|
|
||||||
|
it('change partyId should reload data provider', async () => {
|
||||||
const { rerender } = render(
|
const { rerender } = render(
|
||||||
<AccountManager
|
<AccountManager
|
||||||
partyId="partyOne"
|
partyId="partyOne"
|
||||||
@@ -67,13 +45,67 @@ describe('AccountManager', () => {
|
|||||||
isReadOnly={false}
|
isReadOnly={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
rerenderer = rerender;
|
expect(
|
||||||
|
(helpers.useDataProvider as jest.Mock).mock.calls[0][0].variables
|
||||||
|
.partyId
|
||||||
|
).toEqual('partyOne');
|
||||||
|
await act(() => {
|
||||||
|
rerender(
|
||||||
|
<AccountManager
|
||||||
|
partyId="partyTwo"
|
||||||
|
onClickAsset={jest.fn}
|
||||||
|
isReadOnly={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
(helpers.useDataProvider as jest.Mock).mock.calls[1][0].variables
|
||||||
|
.partyId
|
||||||
|
).toEqual('partyTwo');
|
||||||
});
|
});
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByText('No accounts')).toBeInTheDocument();
|
it('update method should return proper result', async () => {
|
||||||
|
let rerenderer: (ui: React.ReactElement) => void;
|
||||||
|
await act(() => {
|
||||||
|
const { rerender } = render(
|
||||||
|
<AccountManager
|
||||||
|
partyId="partyOne"
|
||||||
|
onClickAsset={jest.fn}
|
||||||
|
isReadOnly={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
rerenderer = rerender;
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('No accounts')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
await act(() => {
|
||||||
|
rerenderer(
|
||||||
|
<AccountManager
|
||||||
|
partyId="partyOne"
|
||||||
|
onClickAsset={jest.fn}
|
||||||
|
isReadOnly={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const container = document.querySelector('.ag-center-cols-container');
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(container).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
expect(getAllByRole(container as HTMLDivElement, 'row')).toHaveLength(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('splash loading should be displayed', async () => {
|
||||||
|
mockedUseDataProvider.mockImplementation((args) => {
|
||||||
|
return {
|
||||||
|
loading: true,
|
||||||
|
data: null,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
await act(() => {
|
await act(() => {
|
||||||
rerenderer(
|
render(
|
||||||
<AccountManager
|
<AccountManager
|
||||||
partyId="partyOne"
|
partyId="partyOne"
|
||||||
onClickAsset={jest.fn}
|
onClickAsset={jest.fn}
|
||||||
@@ -81,11 +113,15 @@ describe('AccountManager', () => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const container = document.querySelector('.ag-center-cols-container');
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(container).toBeInTheDocument();
|
expect(
|
||||||
|
screen.getByText(
|
||||||
|
(content, element) =>
|
||||||
|
Boolean(
|
||||||
|
element?.className.endsWith('flex items-center justify-center')
|
||||||
|
) && content.startsWith('Loading')
|
||||||
|
)
|
||||||
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
expect(getAllByRole(container as HTMLDivElement, 'row')).toHaveLength(2);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ interface AccountManagerProps {
|
|||||||
onClickDeposit?: (assetId?: string) => void;
|
onClickDeposit?: (assetId?: string) => void;
|
||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
pinnedAsset?: PinnedAsset;
|
pinnedAsset?: PinnedAsset;
|
||||||
|
noBottomPlaceholder?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AccountManager = ({
|
export const AccountManager = ({
|
||||||
@@ -28,6 +29,7 @@ export const AccountManager = ({
|
|||||||
partyId,
|
partyId,
|
||||||
isReadOnly,
|
isReadOnly,
|
||||||
pinnedAsset,
|
pinnedAsset,
|
||||||
|
noBottomPlaceholder,
|
||||||
}: AccountManagerProps) => {
|
}: AccountManagerProps) => {
|
||||||
const gridRef = useRef<AgGridReact | null>(null);
|
const gridRef = useRef<AgGridReact | null>(null);
|
||||||
const variables = useMemo(() => ({ partyId }), [partyId]);
|
const variables = useMemo(() => ({ partyId }), [partyId]);
|
||||||
@@ -45,6 +47,7 @@ export const AccountManager = ({
|
|||||||
const bottomPlaceholderProps = useBottomPlaceholder<AccountFields>({
|
const bottomPlaceholderProps = useBottomPlaceholder<AccountFields>({
|
||||||
gridRef,
|
gridRef,
|
||||||
setId,
|
setId,
|
||||||
|
disabled: noBottomPlaceholder,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getRowHeight = useCallback(
|
const getRowHeight = useCallback(
|
||||||
@@ -60,7 +63,8 @@ export const AccountManager = ({
|
|||||||
onClickDeposit={onClickDeposit}
|
onClickDeposit={onClickDeposit}
|
||||||
onClickWithdraw={onClickWithdraw}
|
onClickWithdraw={onClickWithdraw}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
noRowsOverlayComponent={() => null}
|
suppressLoadingOverlay
|
||||||
|
suppressNoRowsOverlay
|
||||||
pinnedAsset={pinnedAsset}
|
pinnedAsset={pinnedAsset}
|
||||||
getRowHeight={getRowHeight}
|
getRowHeight={getRowHeight}
|
||||||
{...bottomPlaceholderProps}
|
{...bottomPlaceholderProps}
|
||||||
|
|||||||
@@ -59,13 +59,19 @@ export function createClient({
|
|||||||
const timestamp = r?.headers.get('x-block-timestamp');
|
const timestamp = r?.headers.get('x-block-timestamp');
|
||||||
if (blockHeight && timestamp) {
|
if (blockHeight && timestamp) {
|
||||||
const state = useHeaderStore.getState();
|
const state = useHeaderStore.getState();
|
||||||
useHeaderStore.setState({
|
const urlState = state[r.url];
|
||||||
...state,
|
if (
|
||||||
[r.url]: {
|
!urlState?.blockHeight ||
|
||||||
blockHeight: Number(blockHeight),
|
urlState.blockHeight !== blockHeight
|
||||||
timestamp: new Date(Number(timestamp.slice(0, -6))),
|
) {
|
||||||
},
|
useHeaderStore.setState({
|
||||||
});
|
...state,
|
||||||
|
[r.url]: {
|
||||||
|
blockHeight: Number(blockHeight),
|
||||||
|
timestamp: new Date(Number(timestamp.slice(0, -6))),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ContractAddressLink } from '@vegaprotocol/environment';
|
import { EtherscanLink } from '@vegaprotocol/environment';
|
||||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import type * as Schema from '@vegaprotocol/types';
|
import type * as Schema from '@vegaprotocol/types';
|
||||||
@@ -108,7 +108,7 @@ export const rows: Rows = [
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ContractAddressLink address={asset.source.contractAddress} />{' '}
|
<EtherscanLink address={asset.source.contractAddress} />{' '}
|
||||||
<CopyWithTooltip text={asset.source.contractAddress}>
|
<CopyWithTooltip text={asset.source.contractAddress}>
|
||||||
<button title={t('Copy address to clipboard')}>
|
<button title={t('Copy address to clipboard')}>
|
||||||
<Icon size={3} name="duplicate" />
|
<Icon size={3} name="duplicate" />
|
||||||
|
|||||||
@@ -46,7 +46,10 @@ addVegaWalletSubmitProposal();
|
|||||||
addVegaWalletSubmitLiquidityProvision();
|
addVegaWalletSubmitLiquidityProvision();
|
||||||
addImportNodeWallets();
|
addImportNodeWallets();
|
||||||
|
|
||||||
export { mockConnectWallet } from './lib/commands/vega-wallet-connect';
|
export {
|
||||||
|
mockConnectWallet,
|
||||||
|
mockConnectWalletWithUserError,
|
||||||
|
} from './lib/commands/vega-wallet-connect';
|
||||||
export type { onMessage } from './lib/mock-ws';
|
export type { onMessage } from './lib/mock-ws';
|
||||||
export { aliasGQLQuery } from './lib/mock-gql';
|
export { aliasGQLQuery } from './lib/mock-gql';
|
||||||
export { aliasWalletQuery } from './lib/mock-rest';
|
export { aliasWalletQuery } from './lib/mock-rest';
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ function createNewMarketProposal(): ProposalSubmissionBody {
|
|||||||
changes: {
|
changes: {
|
||||||
decimalPlaces: '5',
|
decimalPlaces: '5',
|
||||||
positionDecimalPlaces: '5',
|
positionDecimalPlaces: '5',
|
||||||
|
linearSlippageFactor: '0.001',
|
||||||
|
quadraticSlippageFactor: '0',
|
||||||
lpPriceRange: '10',
|
lpPriceRange: '10',
|
||||||
instrument: {
|
instrument: {
|
||||||
name: 'Test market 1',
|
name: 'Test market 1',
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { aliasWalletConnectQuery } from '../mock-rest';
|
import {
|
||||||
|
aliasWalletConnectQuery,
|
||||||
|
aliasWalletConnectWithUserError,
|
||||||
|
} from '../mock-rest';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||||
@@ -20,6 +23,12 @@ export const mockConnectWallet = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const mockConnectWalletWithUserError = () => {
|
||||||
|
cy.mockWallet((req) => {
|
||||||
|
aliasWalletConnectWithUserError(req);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export function addVegaWalletConnect() {
|
export function addVegaWalletConnect() {
|
||||||
Cypress.Commands.add('connectVegaWallet', (isMobile) => {
|
Cypress.Commands.add('connectVegaWallet', (isMobile) => {
|
||||||
mockConnectWallet();
|
mockConnectWallet();
|
||||||
|
|||||||
@@ -63,3 +63,23 @@ export const aliasWalletConnectQuery = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const aliasWalletConnectWithUserError = (
|
||||||
|
req: CyHttpMessages.IncomingHttpRequest
|
||||||
|
) => {
|
||||||
|
if (hasMethod(req, 'client.connect_wallet')) {
|
||||||
|
req.alias = 'client.connect_wallet';
|
||||||
|
req.reply({
|
||||||
|
statusCode: 400,
|
||||||
|
body: {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
error: {
|
||||||
|
code: 3001,
|
||||||
|
data: 'the user rejected the wallet connection',
|
||||||
|
message: 'User error',
|
||||||
|
},
|
||||||
|
id: '0',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { Asset } from '@vegaprotocol/assets';
|
import type { Asset } from '@vegaprotocol/assets';
|
||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
import { EtherscanLink } from '@vegaprotocol/environment';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { ExternalLink, Intent, Notification } from '@vegaprotocol/ui-toolkit';
|
import { Intent, Notification } from '@vegaprotocol/ui-toolkit';
|
||||||
import { formatNumber } from '@vegaprotocol/utils';
|
import { formatNumber } from '@vegaprotocol/utils';
|
||||||
import type { EthStoredTxState } from '@vegaprotocol/web3';
|
import type { EthStoredTxState } from '@vegaprotocol/web3';
|
||||||
import { EthTxStatus, useEthTransactionStore } from '@vegaprotocol/web3';
|
import { EthTxStatus, useEthTransactionStore } from '@vegaprotocol/web3';
|
||||||
@@ -126,14 +126,10 @@ const ApprovalTxFeedback = ({
|
|||||||
selectedAsset: Asset;
|
selectedAsset: Asset;
|
||||||
allowance?: BigNumber;
|
allowance?: BigNumber;
|
||||||
}) => {
|
}) => {
|
||||||
const { ETHERSCAN_URL } = useEnvironment();
|
|
||||||
|
|
||||||
if (!tx) return null;
|
if (!tx) return null;
|
||||||
|
|
||||||
const txLink = tx.txHash && (
|
const txLink = tx.txHash && (
|
||||||
<ExternalLink href={`${ETHERSCAN_URL}/tx/${tx.txHash}`}>
|
<EtherscanLink tx={tx.txHash}>{t('View on Etherscan')}</EtherscanLink>
|
||||||
{t('View on Etherscan')}
|
|
||||||
</ExternalLink>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (tx.status === EthTxStatus.Error) {
|
if (tx.status === EthTxStatus.Error) {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
} from '@vegaprotocol/utils';
|
} from '@vegaprotocol/utils';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { AgGridDynamic as AgGrid } from '@vegaprotocol/datagrid';
|
import { AgGridDynamic as AgGrid } from '@vegaprotocol/datagrid';
|
||||||
import type {
|
import type {
|
||||||
VegaICellRendererParams,
|
VegaICellRendererParams,
|
||||||
@@ -16,14 +15,13 @@ import type {
|
|||||||
TypedDataAgGrid,
|
TypedDataAgGrid,
|
||||||
} from '@vegaprotocol/datagrid';
|
} from '@vegaprotocol/datagrid';
|
||||||
import type { DepositFieldsFragment } from './__generated__/Deposit';
|
import type { DepositFieldsFragment } from './__generated__/Deposit';
|
||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
import { EtherscanLink } from '@vegaprotocol/environment';
|
||||||
import { DepositStatusMapping } from '@vegaprotocol/types';
|
import { DepositStatusMapping } from '@vegaprotocol/types';
|
||||||
|
|
||||||
export const DepositsTable = forwardRef<
|
export const DepositsTable = forwardRef<
|
||||||
AgGridReact,
|
AgGridReact,
|
||||||
TypedDataAgGrid<DepositFieldsFragment>
|
TypedDataAgGrid<DepositFieldsFragment>
|
||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
const { ETHERSCAN_URL } = useEnvironment();
|
|
||||||
return (
|
return (
|
||||||
<AgGrid
|
<AgGrid
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -77,14 +75,9 @@ export const DepositsTable = forwardRef<
|
|||||||
if (!data) return null;
|
if (!data) return null;
|
||||||
if (!value) return '-';
|
if (!value) return '-';
|
||||||
return (
|
return (
|
||||||
<Link
|
<EtherscanLink tx={value} data-testid="etherscan-link">
|
||||||
title={t('View transaction on Etherscan')}
|
|
||||||
href={`${ETHERSCAN_URL}/tx/${value}`}
|
|
||||||
data-testid="etherscan-link"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{truncateByChars(value)}
|
{truncateByChars(value)}
|
||||||
</Link>
|
</EtherscanLink>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { Asset } from '@vegaprotocol/assets';
|
import type { Asset } from '@vegaprotocol/assets';
|
||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
import { EtherscanLink } from '@vegaprotocol/environment';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { ExternalLink, Intent, Notification } from '@vegaprotocol/ui-toolkit';
|
import { Intent, Notification } from '@vegaprotocol/ui-toolkit';
|
||||||
import { EthTxStatus, useEthTransactionStore } from '@vegaprotocol/web3';
|
import { EthTxStatus, useEthTransactionStore } from '@vegaprotocol/web3';
|
||||||
import { getFaucetError } from './get-faucet-error';
|
import { getFaucetError } from './get-faucet-error';
|
||||||
|
|
||||||
@@ -19,7 +19,6 @@ export const FaucetNotification = ({
|
|||||||
selectedAsset,
|
selectedAsset,
|
||||||
faucetTxId,
|
faucetTxId,
|
||||||
}: FaucetNotificationProps) => {
|
}: FaucetNotificationProps) => {
|
||||||
const { ETHERSCAN_URL } = useEnvironment();
|
|
||||||
const tx = useEthTransactionStore((state) => {
|
const tx = useEthTransactionStore((state) => {
|
||||||
return state.transactions.find((t) => t?.id === faucetTxId);
|
return state.transactions.find((t) => t?.id === faucetTxId);
|
||||||
});
|
});
|
||||||
@@ -79,9 +78,9 @@ export const FaucetNotification = ({
|
|||||||
</p>
|
</p>
|
||||||
{tx.txHash && (
|
{tx.txHash && (
|
||||||
<p>
|
<p>
|
||||||
<ExternalLink href={`${ETHERSCAN_URL}/tx/${tx.txHash}`}>
|
<EtherscanLink tx={tx.txHash}>
|
||||||
{t('View on Etherscan')}
|
{t('View on Etherscan')}
|
||||||
</ExternalLink>
|
</EtherscanLink>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -107,9 +106,9 @@ export const FaucetNotification = ({
|
|||||||
</p>
|
</p>
|
||||||
{tx.txHash && (
|
{tx.txHash && (
|
||||||
<p>
|
<p>
|
||||||
<ExternalLink href={`${ETHERSCAN_URL}/tx/${tx.txHash}`}>
|
<EtherscanLink tx={tx.txHash}>
|
||||||
{t('View on Etherscan')}
|
{t('View on Etherscan')}
|
||||||
</ExternalLink>
|
</EtherscanLink>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
|
||||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { useEtherscanLink } from '../hooks';
|
|
||||||
|
|
||||||
export const ContractAddressLink = ({ address }: { address: string }) => {
|
|
||||||
const etherscanLink = useEtherscanLink();
|
|
||||||
const href = etherscanLink(`/address/${address}`);
|
|
||||||
return (
|
|
||||||
<Link href={href} target="_blank" title={t('View on etherscan')}>
|
|
||||||
{address}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import type { ComponentProps } from 'react';
|
||||||
|
import { ETHERSCAN_ADDRESS, ETHERSCAN_TX, useEtherscanLink } from '../hooks';
|
||||||
|
|
||||||
|
export const EtherscanLink = ({
|
||||||
|
address,
|
||||||
|
tx,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
address?: string;
|
||||||
|
tx?: string;
|
||||||
|
} & ComponentProps<typeof ExternalLink>) => {
|
||||||
|
const etherscanLink = useEtherscanLink();
|
||||||
|
let href = '';
|
||||||
|
|
||||||
|
if ((!address && !tx) || (address && tx)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (address) {
|
||||||
|
href = etherscanLink(ETHERSCAN_ADDRESS.replace(':hash', address));
|
||||||
|
}
|
||||||
|
if (tx) {
|
||||||
|
href = etherscanLink(ETHERSCAN_TX.replace(':hash', tx));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ExternalLink
|
||||||
|
href={href}
|
||||||
|
title={t('View on Etherscan (opens in a new tab)')}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children || address || tx}
|
||||||
|
</ExternalLink>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -2,4 +2,4 @@ export * from './network-loader';
|
|||||||
export * from './network-switcher';
|
export * from './network-switcher';
|
||||||
export * from './node-guard';
|
export * from './node-guard';
|
||||||
export * from './node-switcher';
|
export * from './node-switcher';
|
||||||
export * from './contract-address-link';
|
export * from './etherscan-link';
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ export const TOKEN_VALIDATOR = '/validators/:id';
|
|||||||
export const EXPLORER_TX = '/txs/:hash';
|
export const EXPLORER_TX = '/txs/:hash';
|
||||||
|
|
||||||
// Etherscan pages
|
// Etherscan pages
|
||||||
|
export const ETHERSCAN_ADDRESS = '/address/:hash';
|
||||||
export const ETHERSCAN_TX = '/tx/:hash';
|
export const ETHERSCAN_TX = '/tx/:hash';
|
||||||
|
|
||||||
// Console pages
|
// Console pages
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { MockedProvider } from '@apollo/react-testing';
|
|||||||
import type { StatisticsQuery } from '../utils/__generated__/Node';
|
import type { StatisticsQuery } from '../utils/__generated__/Node';
|
||||||
import { StatisticsDocument } from '../utils/__generated__/Node';
|
import { StatisticsDocument } from '../utils/__generated__/Node';
|
||||||
import { useHeaderStore } from '@vegaprotocol/apollo-client';
|
import { useHeaderStore } from '@vegaprotocol/apollo-client';
|
||||||
|
import { Intent } from '@vegaprotocol/ui-toolkit';
|
||||||
|
|
||||||
const vegaUrl = 'https://foo.bar.com';
|
const vegaUrl = 'https://foo.bar.com';
|
||||||
|
|
||||||
@@ -55,9 +56,24 @@ function setup(
|
|||||||
|
|
||||||
describe('useNodeHealth', () => {
|
describe('useNodeHealth', () => {
|
||||||
it.each([
|
it.each([
|
||||||
{ core: 1, node: 1, expected: 0 },
|
{
|
||||||
{ core: 1, node: 5, expected: -4 },
|
core: 1,
|
||||||
{ core: 10, node: 5, expected: 5 },
|
node: 1,
|
||||||
|
expectedText: 'Operational',
|
||||||
|
expectedIntent: Intent.Success,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
core: 1,
|
||||||
|
node: 5,
|
||||||
|
expectedText: 'Operational',
|
||||||
|
expectedIntent: Intent.Success,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
core: 10,
|
||||||
|
node: 5,
|
||||||
|
expectedText: '5 Blocks behind',
|
||||||
|
expectedIntent: Intent.Warning,
|
||||||
|
},
|
||||||
])(
|
])(
|
||||||
'provides difference core block $core and node block $node',
|
'provides difference core block $core and node block $node',
|
||||||
async (cases) => {
|
async (cases) => {
|
||||||
@@ -65,12 +81,12 @@ describe('useNodeHealth', () => {
|
|||||||
blockHeight: cases.node,
|
blockHeight: cases.node,
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
});
|
});
|
||||||
expect(result.current.blockDiff).toEqual(null);
|
expect(result.current.text).toEqual('Non operational');
|
||||||
expect(result.current.coreBlockHeight).toEqual(undefined);
|
expect(result.current.intent).toEqual(Intent.Danger);
|
||||||
expect(result.current.datanodeBlockHeight).toEqual(cases.node);
|
expect(result.current.datanodeBlockHeight).toEqual(cases.node);
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(result.current.blockDiff).toEqual(cases.expected);
|
expect(result.current.text).toEqual(cases.expectedText);
|
||||||
expect(result.current.coreBlockHeight).toEqual(cases.core);
|
expect(result.current.intent).toEqual(cases.expectedIntent);
|
||||||
expect(result.current.datanodeBlockHeight).toEqual(cases.node);
|
expect(result.current.datanodeBlockHeight).toEqual(cases.node);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -90,25 +106,64 @@ describe('useNodeHealth', () => {
|
|||||||
blockHeight: 1,
|
blockHeight: 1,
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
});
|
});
|
||||||
expect(result.current.blockDiff).toEqual(null);
|
expect(result.current.text).toEqual('Non operational');
|
||||||
expect(result.current.coreBlockHeight).toEqual(undefined);
|
expect(result.current.intent).toEqual(Intent.Danger);
|
||||||
expect(result.current.datanodeBlockHeight).toEqual(1);
|
expect(result.current.datanodeBlockHeight).toEqual(1);
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(result.current.blockDiff).toEqual(null);
|
expect(result.current.text).toEqual('Non operational');
|
||||||
expect(result.current.coreBlockHeight).toEqual(undefined);
|
expect(result.current.intent).toEqual(Intent.Danger);
|
||||||
expect(result.current.datanodeBlockHeight).toEqual(1);
|
expect(result.current.datanodeBlockHeight).toEqual(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns 0 if no headers are found (waits until stats query resolves)', async () => {
|
it('returns 0 if no headers are found (waits until stats query resolves)', async () => {
|
||||||
const { result } = setup(createStatsMock(1), undefined);
|
const { result } = setup(createStatsMock(1), undefined);
|
||||||
expect(result.current.blockDiff).toEqual(null);
|
expect(result.current.text).toEqual('Non operational');
|
||||||
expect(result.current.coreBlockHeight).toEqual(undefined);
|
expect(result.current.intent).toEqual(Intent.Danger);
|
||||||
expect(result.current.datanodeBlockHeight).toEqual(undefined);
|
expect(result.current.datanodeBlockHeight).toEqual(undefined);
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(result.current.blockDiff).toEqual(0);
|
expect(result.current.text).toEqual('Operational');
|
||||||
expect(result.current.coreBlockHeight).toEqual(1);
|
expect(result.current.intent).toEqual(Intent.Success);
|
||||||
expect(result.current.datanodeBlockHeight).toEqual(undefined);
|
expect(result.current.datanodeBlockHeight).toEqual(undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Warning latency', async () => {
|
||||||
|
const now = 1678800900087;
|
||||||
|
const headerTimestamp = now - 4000;
|
||||||
|
const dateNow = new Date(now);
|
||||||
|
const dateHeaderTimestamp = new Date(headerTimestamp);
|
||||||
|
jest.useFakeTimers().setSystemTime(dateNow);
|
||||||
|
|
||||||
|
const { result } = setup(createStatsMock(2), {
|
||||||
|
blockHeight: 2,
|
||||||
|
timestamp: dateHeaderTimestamp,
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.text).toEqual('Warning delay ( >3 sec): 4.05 sec');
|
||||||
|
expect(result.current.intent).toEqual(Intent.Warning);
|
||||||
|
expect(result.current.datanodeBlockHeight).toEqual(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Erroneous latency', async () => {
|
||||||
|
const now = 1678800900087;
|
||||||
|
const headerTimestamp = now - 11000;
|
||||||
|
const dateNow = new Date(now);
|
||||||
|
const dateHeaderTimestamp = new Date(headerTimestamp);
|
||||||
|
jest.useFakeTimers().setSystemTime(dateNow);
|
||||||
|
|
||||||
|
const { result } = setup(createStatsMock(2), {
|
||||||
|
blockHeight: 2,
|
||||||
|
timestamp: dateHeaderTimestamp,
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.text).toEqual(
|
||||||
|
'Erroneous latency ( >10 sec): 11.05 sec'
|
||||||
|
);
|
||||||
|
expect(result.current.intent).toEqual(Intent.Danger);
|
||||||
|
expect(result.current.datanodeBlockHeight).toEqual(2);
|
||||||
|
});
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,30 +2,35 @@ import { useEffect, useMemo } from 'react';
|
|||||||
import { useStatisticsQuery } from '../utils/__generated__/Node';
|
import { useStatisticsQuery } from '../utils/__generated__/Node';
|
||||||
import { useHeaderStore } from '@vegaprotocol/apollo-client';
|
import { useHeaderStore } from '@vegaprotocol/apollo-client';
|
||||||
import { useEnvironment } from './use-environment';
|
import { useEnvironment } from './use-environment';
|
||||||
import { fromNanoSeconds } from '@vegaprotocol/utils';
|
import { useNavigatorOnline } from '@vegaprotocol/react-helpers';
|
||||||
|
import { Intent } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
|
||||||
const POLL_INTERVAL = 1000;
|
const POLL_INTERVAL = 1000;
|
||||||
|
const BLOCK_THRESHOLD = 3;
|
||||||
|
const ERROR_LATENCY = 10000;
|
||||||
|
const WARNING_LATENCY = 3000;
|
||||||
|
|
||||||
export const useNodeHealth = () => {
|
export const useNodeHealth = () => {
|
||||||
|
const online = useNavigatorOnline();
|
||||||
const url = useEnvironment((store) => store.VEGA_URL);
|
const url = useEnvironment((store) => store.VEGA_URL);
|
||||||
const headerStore = useHeaderStore();
|
const headerStore = useHeaderStore();
|
||||||
const headers = url ? headerStore[url] : undefined;
|
const headers = url ? headerStore[url] : undefined;
|
||||||
const { data, error, loading, startPolling, stopPolling } =
|
const { data, error, startPolling, stopPolling } = useStatisticsQuery({
|
||||||
useStatisticsQuery({
|
fetchPolicy: 'no-cache',
|
||||||
fetchPolicy: 'no-cache',
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const blockDiff = useMemo(() => {
|
const blockDiff = useMemo(() => {
|
||||||
if (!data?.statistics.blockHeight) {
|
if (!data?.statistics.blockHeight) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!headers) {
|
if (!headers?.blockHeight) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Number(data.statistics.blockHeight) - headers.blockHeight;
|
return Number(data.statistics.blockHeight) - headers.blockHeight;
|
||||||
}, [data, headers]);
|
}, [data?.statistics.blockHeight, headers?.blockHeight]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -38,17 +43,43 @@ export const useNodeHealth = () => {
|
|||||||
}
|
}
|
||||||
}, [error, startPolling, stopPolling]);
|
}, [error, startPolling, stopPolling]);
|
||||||
|
|
||||||
|
const blockUpdateMsLatency = headers?.timestamp
|
||||||
|
? Date.now() - headers.timestamp.getTime()
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
const [text, intent] = useMemo(() => {
|
||||||
|
let intent = Intent.Success;
|
||||||
|
let text = 'Operational';
|
||||||
|
|
||||||
|
if (!online) {
|
||||||
|
text = t('Offline');
|
||||||
|
intent = Intent.Danger;
|
||||||
|
} else if (blockDiff === null) {
|
||||||
|
// Block height query failed and null was returned
|
||||||
|
text = t('Non operational');
|
||||||
|
intent = Intent.Danger;
|
||||||
|
} else if (blockUpdateMsLatency > ERROR_LATENCY) {
|
||||||
|
text = t('Erroneous latency ( >%s sec): %s sec', [
|
||||||
|
(ERROR_LATENCY / 1000).toString(),
|
||||||
|
(blockUpdateMsLatency / 1000).toFixed(2),
|
||||||
|
]);
|
||||||
|
intent = Intent.Danger;
|
||||||
|
} else if (blockDiff >= BLOCK_THRESHOLD) {
|
||||||
|
text = t(`%s Blocks behind`, String(blockDiff));
|
||||||
|
intent = Intent.Warning;
|
||||||
|
} else if (blockUpdateMsLatency > WARNING_LATENCY) {
|
||||||
|
text = t('Warning delay ( >%s sec): %s sec', [
|
||||||
|
(WARNING_LATENCY / 1000).toString(),
|
||||||
|
(blockUpdateMsLatency / 1000).toFixed(2),
|
||||||
|
]);
|
||||||
|
intent = Intent.Warning;
|
||||||
|
}
|
||||||
|
return [text, intent];
|
||||||
|
}, [online, blockDiff, blockUpdateMsLatency]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
error,
|
|
||||||
loading,
|
|
||||||
coreBlockHeight: data?.statistics
|
|
||||||
? Number(data.statistics.blockHeight)
|
|
||||||
: undefined,
|
|
||||||
coreVegaTime: data?.statistics
|
|
||||||
? fromNanoSeconds(data?.statistics.vegaTime)
|
|
||||||
: undefined,
|
|
||||||
datanodeBlockHeight: headers?.blockHeight,
|
datanodeBlockHeight: headers?.blockHeight,
|
||||||
datanodeVegaTime: headers?.timestamp,
|
text,
|
||||||
blockDiff,
|
intent,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ export * from './info-market';
|
|||||||
export * from './tooltip-mapping';
|
export * from './tooltip-mapping';
|
||||||
export * from './__generated__/MarketInfo';
|
export * from './__generated__/MarketInfo';
|
||||||
export * from './market-info-data-provider';
|
export * from './market-info-data-provider';
|
||||||
|
export * from './market-info-panels';
|
||||||
|
|||||||
@@ -1,17 +1,5 @@
|
|||||||
import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets';
|
|
||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
import { useEnvironment } from '@vegaprotocol/environment';
|
||||||
import {
|
import { removePaginationWrapper, TokenLinks } from '@vegaprotocol/utils';
|
||||||
totalFeesPercentage,
|
|
||||||
calcCandleVolume,
|
|
||||||
} from '@vegaprotocol/market-list';
|
|
||||||
import {
|
|
||||||
addDecimalsFormatNumber,
|
|
||||||
formatNumber,
|
|
||||||
formatNumberPercentage,
|
|
||||||
removePaginationWrapper,
|
|
||||||
TokenLinks,
|
|
||||||
getMarketExpiryDateFormatted,
|
|
||||||
} from '@vegaprotocol/utils';
|
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { useDataProvider, useYesterday } from '@vegaprotocol/react-helpers';
|
import { useDataProvider, useYesterday } from '@vegaprotocol/react-helpers';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
@@ -22,15 +10,31 @@ import {
|
|||||||
Link as UILink,
|
Link as UILink,
|
||||||
Splash,
|
Splash,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import BigNumber from 'bignumber.js';
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { generatePath, Link } from 'react-router-dom';
|
import { generatePath, Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { MarketInfoTable } from './info-key-value-table';
|
|
||||||
import { marketInfoWithDataAndCandlesProvider } from './market-info-data-provider';
|
import { marketInfoWithDataAndCandlesProvider } from './market-info-data-provider';
|
||||||
|
|
||||||
import type { MarketInfoWithDataAndCandles } from './market-info-data-provider';
|
import type { MarketInfoWithDataAndCandles } from './market-info-data-provider';
|
||||||
import { MarketProposalNotification } from '@vegaprotocol/proposals';
|
import { MarketProposalNotification } from '@vegaprotocol/proposals';
|
||||||
|
import {
|
||||||
|
CurrentFeesInfoPanel,
|
||||||
|
InstrumentInfoPanel,
|
||||||
|
InsurancePoolInfoPanel,
|
||||||
|
KeyDetailsInfoPanel,
|
||||||
|
LiquidityInfoPanel,
|
||||||
|
LiquidityMonitoringParametersInfoPanel,
|
||||||
|
LiquidityPriceRangeInfoPanel,
|
||||||
|
MarketPriceInfoPanel,
|
||||||
|
MarketVolumeInfoPanel,
|
||||||
|
MetadataInfoPanel,
|
||||||
|
OracleInfoPanel,
|
||||||
|
PriceMonitoringBoundsInfoPanel,
|
||||||
|
RiskFactorsInfoPanel,
|
||||||
|
RiskModelInfoPanel,
|
||||||
|
RiskParametersInfoPanel,
|
||||||
|
SettlementAssetInfoPanel,
|
||||||
|
} from './market-info-panels';
|
||||||
|
|
||||||
export interface InfoProps {
|
export interface InfoProps {
|
||||||
market: MarketInfoWithDataAndCandles;
|
market: MarketInfoWithDataAndCandles;
|
||||||
@@ -80,15 +84,6 @@ export const MarketInfoContainer = ({
|
|||||||
export const Info = ({ market, onSelect }: InfoProps) => {
|
export const Info = ({ market, onSelect }: InfoProps) => {
|
||||||
const { VEGA_TOKEN_URL, VEGA_EXPLORER_URL } = useEnvironment();
|
const { VEGA_TOKEN_URL, VEGA_EXPLORER_URL } = useEnvironment();
|
||||||
const headerClassName = 'uppercase text-lg';
|
const headerClassName = 'uppercase text-lg';
|
||||||
const assetSymbol =
|
|
||||||
market?.tradableInstrument.instrument.product?.settlementAsset.symbol || '';
|
|
||||||
const quoteUnit =
|
|
||||||
market?.tradableInstrument.instrument.product?.quoteName || '';
|
|
||||||
const assetId = useMemo(
|
|
||||||
() => market?.tradableInstrument.instrument.product?.settlementAsset.id,
|
|
||||||
[market]
|
|
||||||
);
|
|
||||||
const { data: asset } = useAssetDataProvider(assetId ?? '');
|
|
||||||
|
|
||||||
if (!market) return null;
|
if (!market) return null;
|
||||||
|
|
||||||
@@ -96,272 +91,75 @@ export const Info = ({ market, onSelect }: InfoProps) => {
|
|||||||
market.accountsConnection?.edges
|
market.accountsConnection?.edges
|
||||||
);
|
);
|
||||||
|
|
||||||
const last24hourVolume = market.candles && calcCandleVolume(market.candles);
|
|
||||||
|
|
||||||
const marketDataPanels = [
|
const marketDataPanels = [
|
||||||
{
|
{
|
||||||
title: t('Current fees'),
|
title: t('Current fees'),
|
||||||
content: (
|
content: <CurrentFeesInfoPanel market={market} />,
|
||||||
<>
|
|
||||||
<MarketInfoTable
|
|
||||||
data={{
|
|
||||||
...market.fees.factors,
|
|
||||||
totalFees: totalFeesPercentage(market.fees.factors),
|
|
||||||
}}
|
|
||||||
asPercentage={true}
|
|
||||||
/>
|
|
||||||
<p className="text-xs">
|
|
||||||
{t(
|
|
||||||
'All fees are paid by price takers and are a % of the trade notional value. Fees are not paid during auction uncrossing.'
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Market price'),
|
title: t('Market price'),
|
||||||
content: (
|
content: <MarketPriceInfoPanel market={market} />,
|
||||||
<>
|
|
||||||
<MarketInfoTable
|
|
||||||
data={{
|
|
||||||
markPrice: market.data?.markPrice,
|
|
||||||
bestBidPrice: market.data?.bestBidPrice,
|
|
||||||
bestOfferPrice: market.data?.bestOfferPrice,
|
|
||||||
quoteUnit: market.tradableInstrument.instrument.product.quoteName,
|
|
||||||
}}
|
|
||||||
decimalPlaces={market.decimalPlaces}
|
|
||||||
/>
|
|
||||||
<p className="text-xs mt-4">
|
|
||||||
{t(
|
|
||||||
'There is 1 unit of the settlement asset (%s) to every 1 quote unit (%s).',
|
|
||||||
[assetSymbol, quoteUnit]
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Market volume'),
|
title: t('Market volume'),
|
||||||
content: (
|
content: <MarketVolumeInfoPanel market={market} />,
|
||||||
<MarketInfoTable
|
|
||||||
data={{
|
|
||||||
'24hourVolume':
|
|
||||||
last24hourVolume && last24hourVolume !== '0'
|
|
||||||
? addDecimalsFormatNumber(
|
|
||||||
last24hourVolume,
|
|
||||||
market.positionDecimalPlaces
|
|
||||||
)
|
|
||||||
: '-',
|
|
||||||
openInterest: market.data?.openInterest,
|
|
||||||
bestBidVolume: market.data?.bestBidVolume,
|
|
||||||
bestOfferVolume: market.data?.bestOfferVolume,
|
|
||||||
bestStaticBidVolume: market.data?.bestStaticBidVolume,
|
|
||||||
bestStaticOfferVolume: market.data?.bestStaticOfferVolume,
|
|
||||||
}}
|
|
||||||
decimalPlaces={market.positionDecimalPlaces}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
...marketAccounts
|
...marketAccounts
|
||||||
.filter((a) => a.type === Schema.AccountType.ACCOUNT_TYPE_INSURANCE)
|
.filter((a) => a.type === Schema.AccountType.ACCOUNT_TYPE_INSURANCE)
|
||||||
.map((a) => ({
|
.map((a) => ({
|
||||||
title: t(`Insurance pool`),
|
title: t(`Insurance pool`),
|
||||||
content: (
|
content: <InsurancePoolInfoPanel market={market} account={a} />,
|
||||||
<MarketInfoTable
|
|
||||||
data={{
|
|
||||||
balance: a.balance,
|
|
||||||
}}
|
|
||||||
assetSymbol={assetSymbol}
|
|
||||||
decimalPlaces={
|
|
||||||
market.tradableInstrument.instrument.product.settlementAsset
|
|
||||||
.decimals
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
})),
|
})),
|
||||||
];
|
];
|
||||||
|
|
||||||
const keyDetails = {
|
|
||||||
decimalPlaces: market.decimalPlaces,
|
|
||||||
positionDecimalPlaces: market.positionDecimalPlaces,
|
|
||||||
tradingMode: market.tradingMode,
|
|
||||||
state: Schema.MarketStateMapping[market.state],
|
|
||||||
};
|
|
||||||
|
|
||||||
const assetDecimals =
|
|
||||||
market.tradableInstrument.instrument.product.settlementAsset.decimals;
|
|
||||||
|
|
||||||
const liquidityPriceRange = formatNumberPercentage(
|
|
||||||
new BigNumber(market.lpPriceRange).times(100)
|
|
||||||
);
|
|
||||||
|
|
||||||
const marketSpecPanels = [
|
const marketSpecPanels = [
|
||||||
{
|
{
|
||||||
title: t('Key details'),
|
title: t('Key details'),
|
||||||
content: (
|
content: <KeyDetailsInfoPanel market={market} />,
|
||||||
<MarketInfoTable
|
|
||||||
data={{
|
|
||||||
name: market.tradableInstrument.instrument.name,
|
|
||||||
marketID: market.id,
|
|
||||||
tradingMode:
|
|
||||||
keyDetails.tradingMode &&
|
|
||||||
Schema.MarketTradingModeMapping[keyDetails.tradingMode],
|
|
||||||
marketDecimalPlaces: market.decimalPlaces,
|
|
||||||
positionDecimalPlaces: market.positionDecimalPlaces,
|
|
||||||
settlementAssetDecimalPlaces: assetDecimals,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Instrument'),
|
title: t('Instrument'),
|
||||||
content: (
|
content: <InstrumentInfoPanel market={market} />,
|
||||||
<MarketInfoTable
|
|
||||||
data={{
|
|
||||||
marketName: market.tradableInstrument.instrument.name,
|
|
||||||
code: market.tradableInstrument.instrument.code,
|
|
||||||
productType:
|
|
||||||
market.tradableInstrument.instrument.product.__typename,
|
|
||||||
...market.tradableInstrument.instrument.product,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Settlement asset'),
|
title: t('Settlement asset'),
|
||||||
content: asset ? (
|
content: <SettlementAssetInfoPanel market={market} />,
|
||||||
<>
|
|
||||||
<AssetDetailsTable
|
|
||||||
asset={asset}
|
|
||||||
inline={true}
|
|
||||||
noBorder={true}
|
|
||||||
dtClassName="text-black dark:text-white text-ui !px-0 !font-normal"
|
|
||||||
ddClassName="text-black dark:text-white text-ui !px-0 !font-normal max-w-full"
|
|
||||||
/>
|
|
||||||
<p className="text-xs mt-4">
|
|
||||||
{t(
|
|
||||||
'There is 1 unit of the settlement asset (%s) to every 1 quote unit (%s).',
|
|
||||||
[assetSymbol, quoteUnit]
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Splash>{t('No data')}</Splash>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Metadata'),
|
title: t('Metadata'),
|
||||||
content: (
|
content: <MetadataInfoPanel market={market} />,
|
||||||
<MarketInfoTable
|
|
||||||
data={{
|
|
||||||
expiryDate: getMarketExpiryDateFormatted(
|
|
||||||
market.tradableInstrument.instrument.metadata.tags
|
|
||||||
),
|
|
||||||
...market.tradableInstrument.instrument.metadata.tags
|
|
||||||
?.map((tag) => {
|
|
||||||
const [key, value] = tag.split(':');
|
|
||||||
return { [key]: value };
|
|
||||||
})
|
|
||||||
.reduce((acc, curr) => ({ ...acc, ...curr }), {}),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Risk model'),
|
title: t('Risk model'),
|
||||||
content: (
|
content: <RiskModelInfoPanel market={market} />,
|
||||||
<MarketInfoTable
|
|
||||||
data={market.tradableInstrument.riskModel}
|
|
||||||
unformatted={true}
|
|
||||||
omits={[]}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Risk parameters'),
|
title: t('Risk parameters'),
|
||||||
content: (
|
content: <RiskParametersInfoPanel market={market} />,
|
||||||
<MarketInfoTable
|
|
||||||
data={market.tradableInstrument.riskModel.params}
|
|
||||||
unformatted={true}
|
|
||||||
omits={[]}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Risk factors'),
|
title: t('Risk factors'),
|
||||||
content: (
|
content: <RiskFactorsInfoPanel market={market} />,
|
||||||
<MarketInfoTable
|
|
||||||
data={market.riskFactors}
|
|
||||||
unformatted={true}
|
|
||||||
omits={['market', '__typename']}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
...(market.priceMonitoringSettings?.parameters?.triggers || []).map(
|
...(market.priceMonitoringSettings?.parameters?.triggers || []).map(
|
||||||
(trigger, i) => {
|
(_, triggerIndex) => ({
|
||||||
const bounds = market.data?.priceMonitoringBounds?.[i];
|
title: t(`Price monitoring bounds ${triggerIndex + 1}`),
|
||||||
return {
|
content: (
|
||||||
title: t(`Price monitoring bounds ${i + 1}`),
|
<PriceMonitoringBoundsInfoPanel
|
||||||
content: (
|
market={market}
|
||||||
<div className="text-xs">
|
triggerIndex={triggerIndex}
|
||||||
<div className="grid grid-cols-2 text-xs mb-4">
|
/>
|
||||||
<p className="col-span-1">
|
),
|
||||||
{t('%s probability price bounds', [
|
})
|
||||||
formatNumberPercentage(
|
|
||||||
new BigNumber(trigger.probability).times(100)
|
|
||||||
),
|
|
||||||
])}
|
|
||||||
</p>
|
|
||||||
<p className="col-span-1 text-right">
|
|
||||||
{t('Within %s seconds', [formatNumber(trigger.horizonSecs)])}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="pl-2 pb-0 text-xs border-l-2">
|
|
||||||
{bounds && (
|
|
||||||
<MarketInfoTable
|
|
||||||
data={{
|
|
||||||
highestPrice: bounds.maxValidPrice,
|
|
||||||
lowestPrice: bounds.minValidPrice,
|
|
||||||
}}
|
|
||||||
decimalPlaces={market.decimalPlaces}
|
|
||||||
assetSymbol={quoteUnit}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<p className="mt-4">
|
|
||||||
{t('Results in %s seconds auction if breached', [
|
|
||||||
trigger.auctionExtensionSecs.toString(),
|
|
||||||
])}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
title: t('Liquidity monitoring parameters'),
|
title: t('Liquidity monitoring parameters'),
|
||||||
content: (
|
content: <LiquidityMonitoringParametersInfoPanel market={market} />,
|
||||||
<MarketInfoTable
|
|
||||||
data={{
|
|
||||||
triggeringRatio:
|
|
||||||
market.liquidityMonitoringParameters.triggeringRatio,
|
|
||||||
...market.liquidityMonitoringParameters.targetStakeParameters,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Liquidity'),
|
title: t('Liquidity'),
|
||||||
content: (
|
content: (
|
||||||
<MarketInfoTable
|
<LiquidityInfoPanel market={market}>
|
||||||
data={{
|
|
||||||
targetStake: market.data && market.data.targetStake,
|
|
||||||
suppliedStake: market.data && market.data?.suppliedStake,
|
|
||||||
marketValueProxy: market.data && market.data.marketValueProxy,
|
|
||||||
}}
|
|
||||||
decimalPlaces={assetDecimals}
|
|
||||||
assetSymbol={assetSymbol}
|
|
||||||
>
|
|
||||||
<Link
|
<Link
|
||||||
to={`/liquidity/${market.id}`}
|
to={`/liquidity/${market.id}`}
|
||||||
onClick={() => onSelect(market.id)}
|
onClick={() => onSelect(market.id)}
|
||||||
@@ -369,57 +167,17 @@ export const Info = ({ market, onSelect }: InfoProps) => {
|
|||||||
>
|
>
|
||||||
<UILink>{t('View liquidity provision table')}</UILink>
|
<UILink>{t('View liquidity provision table')}</UILink>
|
||||||
</Link>
|
</Link>
|
||||||
</MarketInfoTable>
|
</LiquidityInfoPanel>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Liquidity price range'),
|
title: t('Liquidity price range'),
|
||||||
content: (
|
content: <LiquidityPriceRangeInfoPanel market={market} />,
|
||||||
<>
|
|
||||||
<p className="text-xs mb-4">
|
|
||||||
{`For liquidity orders to count towards a commitment, they must be
|
|
||||||
within the liquidity monitoring bounds.`}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs mb-4">
|
|
||||||
{`The liquidity price range is a ${liquidityPriceRange} difference from the mid
|
|
||||||
price.`}
|
|
||||||
</p>
|
|
||||||
<div className="pl-2 pb-0 text-xs border-l-2">
|
|
||||||
<MarketInfoTable
|
|
||||||
data={{
|
|
||||||
liquidityPriceRange: `${liquidityPriceRange} of mid price`,
|
|
||||||
lowestPrice:
|
|
||||||
market.data?.midPrice &&
|
|
||||||
`${addDecimalsFormatNumber(
|
|
||||||
new BigNumber(1)
|
|
||||||
.minus(market.lpPriceRange)
|
|
||||||
.times(market.data.midPrice)
|
|
||||||
.toString(),
|
|
||||||
market.decimalPlaces
|
|
||||||
)} ${quoteUnit}`,
|
|
||||||
highestPrice:
|
|
||||||
market.data?.midPrice &&
|
|
||||||
`${addDecimalsFormatNumber(
|
|
||||||
new BigNumber(1)
|
|
||||||
.plus(market.lpPriceRange)
|
|
||||||
.times(market.data.midPrice)
|
|
||||||
.toString(),
|
|
||||||
market.decimalPlaces
|
|
||||||
)} ${quoteUnit}`,
|
|
||||||
}}
|
|
||||||
></MarketInfoTable>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Oracle'),
|
title: t('Oracle'),
|
||||||
content: (
|
content: (
|
||||||
<MarketInfoTable
|
<OracleInfoPanel market={market}>
|
||||||
data={
|
|
||||||
market.tradableInstrument.instrument.product.dataSourceSpecBinding
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<ExternalLink
|
<ExternalLink
|
||||||
href={`${VEGA_EXPLORER_URL}/oracles#${market.tradableInstrument.instrument.product.dataSourceSpecForSettlementData.id}`}
|
href={`${VEGA_EXPLORER_URL}/oracles#${market.tradableInstrument.instrument.product.dataSourceSpecForSettlementData.id}`}
|
||||||
>
|
>
|
||||||
@@ -430,7 +188,7 @@ export const Info = ({ market, onSelect }: InfoProps) => {
|
|||||||
>
|
>
|
||||||
{t('View termination oracle specification')}
|
{t('View termination oracle specification')}
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
</MarketInfoTable>
|
</OracleInfoPanel>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,418 @@
|
|||||||
|
import type { ComponentProps } from 'react';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import {
|
||||||
|
calcCandleVolume,
|
||||||
|
totalFeesPercentage,
|
||||||
|
} from '@vegaprotocol/market-list';
|
||||||
|
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import {
|
||||||
|
addDecimalsFormatNumber,
|
||||||
|
formatNumber,
|
||||||
|
formatNumberPercentage,
|
||||||
|
getMarketExpiryDateFormatted,
|
||||||
|
} from '@vegaprotocol/utils';
|
||||||
|
import type { Get } from 'type-fest';
|
||||||
|
import { MarketInfoTable } from './info-key-value-table';
|
||||||
|
import type {
|
||||||
|
MarketInfo,
|
||||||
|
MarketInfoWithData,
|
||||||
|
MarketInfoWithDataAndCandles,
|
||||||
|
} from './market-info-data-provider';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
import { MarketTradingModeMapping } from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
type PanelProps = Pick<
|
||||||
|
ComponentProps<typeof MarketInfoTable>,
|
||||||
|
'children' | 'noBorder'
|
||||||
|
>;
|
||||||
|
|
||||||
|
type MarketInfoProps = {
|
||||||
|
market: MarketInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MarketInfoWithDataProps = {
|
||||||
|
market: MarketInfoWithData;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MarketInfoWithDataAndCandlesProps = {
|
||||||
|
market: MarketInfoWithDataAndCandles;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CurrentFeesInfoPanel = ({
|
||||||
|
market,
|
||||||
|
...props
|
||||||
|
}: MarketInfoProps & PanelProps) => (
|
||||||
|
<>
|
||||||
|
<MarketInfoTable
|
||||||
|
data={{
|
||||||
|
...market.fees.factors,
|
||||||
|
totalFees: totalFeesPercentage(market.fees.factors),
|
||||||
|
}}
|
||||||
|
asPercentage={true}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
<p className="text-xs">
|
||||||
|
{t(
|
||||||
|
'All fees are paid by price takers and are a % of the trade notional value. Fees are not paid during auction uncrossing.'
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const MarketPriceInfoPanel = ({
|
||||||
|
market,
|
||||||
|
...props
|
||||||
|
}: MarketInfoWithDataProps & PanelProps) => {
|
||||||
|
const assetSymbol =
|
||||||
|
market?.tradableInstrument.instrument.product?.settlementAsset.symbol || '';
|
||||||
|
const quoteUnit =
|
||||||
|
market?.tradableInstrument.instrument.product?.quoteName || '';
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MarketInfoTable
|
||||||
|
data={{
|
||||||
|
markPrice: market.data?.markPrice,
|
||||||
|
bestBidPrice: market.data?.bestBidPrice,
|
||||||
|
bestOfferPrice: market.data?.bestOfferPrice,
|
||||||
|
quoteUnit: market.tradableInstrument.instrument.product.quoteName,
|
||||||
|
}}
|
||||||
|
decimalPlaces={market.decimalPlaces}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
<p className="text-xs mt-4">
|
||||||
|
{t(
|
||||||
|
'There is 1 unit of the settlement asset (%s) to every 1 quote unit (%s).',
|
||||||
|
[assetSymbol, quoteUnit]
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MarketVolumeInfoPanel = ({
|
||||||
|
market,
|
||||||
|
...props
|
||||||
|
}: MarketInfoWithDataAndCandlesProps & PanelProps) => {
|
||||||
|
const last24hourVolume = market.candles && calcCandleVolume(market.candles);
|
||||||
|
return (
|
||||||
|
<MarketInfoTable
|
||||||
|
data={{
|
||||||
|
'24hourVolume':
|
||||||
|
last24hourVolume && last24hourVolume !== '0'
|
||||||
|
? addDecimalsFormatNumber(
|
||||||
|
last24hourVolume,
|
||||||
|
market.positionDecimalPlaces
|
||||||
|
)
|
||||||
|
: '-',
|
||||||
|
openInterest: market.data?.openInterest,
|
||||||
|
bestBidVolume: market.data?.bestBidVolume,
|
||||||
|
bestOfferVolume: market.data?.bestOfferVolume,
|
||||||
|
bestStaticBidVolume: market.data?.bestStaticBidVolume,
|
||||||
|
bestStaticOfferVolume: market.data?.bestStaticOfferVolume,
|
||||||
|
}}
|
||||||
|
decimalPlaces={market.positionDecimalPlaces}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InsurancePoolInfoPanel = ({
|
||||||
|
market,
|
||||||
|
account,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
account: NonNullable<
|
||||||
|
Get<MarketInfoWithData, 'accountsConnection.edges[0].node'>
|
||||||
|
>;
|
||||||
|
} & MarketInfoProps &
|
||||||
|
PanelProps) => {
|
||||||
|
const assetSymbol =
|
||||||
|
market?.tradableInstrument.instrument.product?.settlementAsset.symbol || '';
|
||||||
|
return (
|
||||||
|
<MarketInfoTable
|
||||||
|
data={{
|
||||||
|
balance: account.balance,
|
||||||
|
}}
|
||||||
|
assetSymbol={assetSymbol}
|
||||||
|
decimalPlaces={
|
||||||
|
market.tradableInstrument.instrument.product.settlementAsset.decimals
|
||||||
|
}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const KeyDetailsInfoPanel = ({
|
||||||
|
market,
|
||||||
|
}: MarketInfoProps & PanelProps) => {
|
||||||
|
const assetDecimals =
|
||||||
|
market.tradableInstrument.instrument.product.settlementAsset.decimals;
|
||||||
|
return (
|
||||||
|
<MarketInfoTable
|
||||||
|
data={{
|
||||||
|
name: market.tradableInstrument.instrument.name,
|
||||||
|
marketID: market.id,
|
||||||
|
tradingMode:
|
||||||
|
market.tradingMode && MarketTradingModeMapping[market.tradingMode],
|
||||||
|
marketDecimalPlaces: market.decimalPlaces,
|
||||||
|
positionDecimalPlaces: market.positionDecimalPlaces,
|
||||||
|
settlementAssetDecimalPlaces: assetDecimals,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InstrumentInfoPanel = ({
|
||||||
|
market,
|
||||||
|
...props
|
||||||
|
}: MarketInfoProps & PanelProps) => (
|
||||||
|
<MarketInfoTable
|
||||||
|
data={{
|
||||||
|
marketName: market.tradableInstrument.instrument.name,
|
||||||
|
code: market.tradableInstrument.instrument.code,
|
||||||
|
productType: market.tradableInstrument.instrument.product.__typename,
|
||||||
|
...market.tradableInstrument.instrument.product,
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const SettlementAssetInfoPanel = ({
|
||||||
|
market,
|
||||||
|
noBorder = true,
|
||||||
|
}: MarketInfoProps & PanelProps) => {
|
||||||
|
const assetSymbol =
|
||||||
|
market?.tradableInstrument.instrument.product?.settlementAsset.symbol || '';
|
||||||
|
const quoteUnit =
|
||||||
|
market?.tradableInstrument.instrument.product?.quoteName || '';
|
||||||
|
const assetId = useMemo(
|
||||||
|
() => market?.tradableInstrument.instrument.product?.settlementAsset.id,
|
||||||
|
[market]
|
||||||
|
);
|
||||||
|
const { data: asset } = useAssetDataProvider(assetId ?? '');
|
||||||
|
return asset ? (
|
||||||
|
<>
|
||||||
|
<AssetDetailsTable
|
||||||
|
asset={asset}
|
||||||
|
inline={true}
|
||||||
|
noBorder={noBorder}
|
||||||
|
dtClassName="text-black dark:text-white text-ui !px-0 !font-normal"
|
||||||
|
ddClassName="text-black dark:text-white text-ui !px-0 !font-normal max-w-full"
|
||||||
|
/>
|
||||||
|
<p className="text-xs mt-4">
|
||||||
|
{t(
|
||||||
|
'There is 1 unit of the settlement asset (%s) to every 1 quote unit (%s).',
|
||||||
|
[assetSymbol, quoteUnit]
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Splash>{t('No data')}</Splash>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MetadataInfoPanel = ({
|
||||||
|
market,
|
||||||
|
...props
|
||||||
|
}: MarketInfoProps & PanelProps) => (
|
||||||
|
<MarketInfoTable
|
||||||
|
data={{
|
||||||
|
expiryDate: getMarketExpiryDateFormatted(
|
||||||
|
market.tradableInstrument.instrument.metadata.tags
|
||||||
|
),
|
||||||
|
...market.tradableInstrument.instrument.metadata.tags
|
||||||
|
?.map((tag) => {
|
||||||
|
const [key, value] = tag.split(':');
|
||||||
|
return { [key]: value };
|
||||||
|
})
|
||||||
|
.reduce((acc, curr) => ({ ...acc, ...curr }), {}),
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const RiskModelInfoPanel = ({
|
||||||
|
market,
|
||||||
|
...props
|
||||||
|
}: MarketInfoProps & PanelProps) => (
|
||||||
|
<MarketInfoTable
|
||||||
|
data={market.tradableInstrument.riskModel}
|
||||||
|
unformatted={true}
|
||||||
|
omits={[]}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const RiskParametersInfoPanel = ({
|
||||||
|
market,
|
||||||
|
...props
|
||||||
|
}: MarketInfoProps & PanelProps) => (
|
||||||
|
<MarketInfoTable
|
||||||
|
data={market.tradableInstrument.riskModel.params}
|
||||||
|
unformatted={true}
|
||||||
|
omits={[]}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const RiskFactorsInfoPanel = ({
|
||||||
|
market,
|
||||||
|
...props
|
||||||
|
}: MarketInfoProps & PanelProps) => (
|
||||||
|
<MarketInfoTable
|
||||||
|
data={market.riskFactors}
|
||||||
|
unformatted={true}
|
||||||
|
omits={['market', '__typename']}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const PriceMonitoringBoundsInfoPanel = ({
|
||||||
|
market,
|
||||||
|
triggerIndex,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
triggerIndex: number;
|
||||||
|
} & MarketInfoWithDataProps &
|
||||||
|
PanelProps) => {
|
||||||
|
const quoteUnit =
|
||||||
|
market?.tradableInstrument.instrument.product?.quoteName || '';
|
||||||
|
const trigger =
|
||||||
|
market.priceMonitoringSettings?.parameters?.triggers?.[triggerIndex];
|
||||||
|
const bounds = market.data?.priceMonitoringBounds?.[triggerIndex];
|
||||||
|
if (!trigger) {
|
||||||
|
console.error(
|
||||||
|
`Could not find data for trigger ${triggerIndex} (market id: ${market.id})`
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="text-xs">
|
||||||
|
<div className="grid grid-cols-2 text-xs mb-4">
|
||||||
|
<p className="col-span-1">
|
||||||
|
{t('%s probability price bounds', [
|
||||||
|
formatNumberPercentage(
|
||||||
|
new BigNumber(trigger.probability).times(100)
|
||||||
|
),
|
||||||
|
])}
|
||||||
|
</p>
|
||||||
|
<p className="col-span-1 text-right">
|
||||||
|
{t('Within %s seconds', [formatNumber(trigger.horizonSecs)])}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="pl-2 pb-0 text-xs border-l-2">
|
||||||
|
{bounds && (
|
||||||
|
<MarketInfoTable
|
||||||
|
data={{
|
||||||
|
highestPrice: bounds.maxValidPrice,
|
||||||
|
lowestPrice: bounds.minValidPrice,
|
||||||
|
}}
|
||||||
|
decimalPlaces={market.decimalPlaces}
|
||||||
|
assetSymbol={quoteUnit}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="mt-4">
|
||||||
|
{t('Results in %s seconds auction if breached', [
|
||||||
|
trigger.auctionExtensionSecs.toString(),
|
||||||
|
])}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LiquidityMonitoringParametersInfoPanel = ({
|
||||||
|
market,
|
||||||
|
...props
|
||||||
|
}: MarketInfoProps & PanelProps) => (
|
||||||
|
<MarketInfoTable
|
||||||
|
data={{
|
||||||
|
triggeringRatio: market.liquidityMonitoringParameters.triggeringRatio,
|
||||||
|
...market.liquidityMonitoringParameters.targetStakeParameters,
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const LiquidityInfoPanel = ({
|
||||||
|
market,
|
||||||
|
...props
|
||||||
|
}: MarketInfoWithDataProps & PanelProps) => {
|
||||||
|
const assetDecimals =
|
||||||
|
market.tradableInstrument.instrument.product.settlementAsset.decimals;
|
||||||
|
const assetSymbol =
|
||||||
|
market?.tradableInstrument.instrument.product?.settlementAsset.symbol || '';
|
||||||
|
return (
|
||||||
|
<MarketInfoTable
|
||||||
|
data={{
|
||||||
|
targetStake: market.data && market.data.targetStake,
|
||||||
|
suppliedStake: market.data && market.data?.suppliedStake,
|
||||||
|
marketValueProxy: market.data && market.data.marketValueProxy,
|
||||||
|
}}
|
||||||
|
decimalPlaces={assetDecimals}
|
||||||
|
assetSymbol={assetSymbol}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LiquidityPriceRangeInfoPanel = ({
|
||||||
|
market,
|
||||||
|
...props
|
||||||
|
}: MarketInfoWithDataProps & PanelProps) => {
|
||||||
|
const quoteUnit =
|
||||||
|
market?.tradableInstrument.instrument.product?.quoteName || '';
|
||||||
|
const liquidityPriceRange = formatNumberPercentage(
|
||||||
|
new BigNumber(market.lpPriceRange).times(100)
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p className="text-xs mb-4">
|
||||||
|
{`For liquidity orders to count towards a commitment, they must be
|
||||||
|
within the liquidity monitoring bounds.`}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs mb-4">
|
||||||
|
{`The liquidity price range is a ${liquidityPriceRange} difference from the mid
|
||||||
|
price.`}
|
||||||
|
</p>
|
||||||
|
<div className="pl-2 pb-0 text-xs border-l-2">
|
||||||
|
<MarketInfoTable
|
||||||
|
data={{
|
||||||
|
liquidityPriceRange: `${liquidityPriceRange} of mid price`,
|
||||||
|
lowestPrice:
|
||||||
|
market.data?.midPrice &&
|
||||||
|
`${addDecimalsFormatNumber(
|
||||||
|
new BigNumber(1)
|
||||||
|
.minus(market.lpPriceRange)
|
||||||
|
.times(market.data.midPrice)
|
||||||
|
.toString(),
|
||||||
|
market.decimalPlaces
|
||||||
|
)} ${quoteUnit}`,
|
||||||
|
highestPrice:
|
||||||
|
market.data?.midPrice &&
|
||||||
|
`${addDecimalsFormatNumber(
|
||||||
|
new BigNumber(1)
|
||||||
|
.plus(market.lpPriceRange)
|
||||||
|
.times(market.data.midPrice)
|
||||||
|
.toString(),
|
||||||
|
market.decimalPlaces
|
||||||
|
)} ${quoteUnit}`,
|
||||||
|
}}
|
||||||
|
></MarketInfoTable>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OracleInfoPanel = ({
|
||||||
|
market,
|
||||||
|
...props
|
||||||
|
}: MarketInfoProps & PanelProps) => (
|
||||||
|
<MarketInfoTable
|
||||||
|
data={market.tradableInstrument.instrument.product.dataSourceSpecBinding}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { useCallback, useRef } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||||
import type { Position } from '../';
|
import type { Position } from '../';
|
||||||
import { usePositionsData, PositionsTable } from '../';
|
import { usePositionsData, PositionsTable } from '../';
|
||||||
|
import type { FilterChangedEvent } from 'ag-grid-community';
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
import { useVegaTransactionStore } from '@vegaprotocol/wallet';
|
import { useVegaTransactionStore } from '@vegaprotocol/wallet';
|
||||||
@@ -22,6 +23,7 @@ export const PositionsManager = ({
|
|||||||
noBottomPlaceholder,
|
noBottomPlaceholder,
|
||||||
}: PositionsManagerProps) => {
|
}: PositionsManagerProps) => {
|
||||||
const gridRef = useRef<AgGridReact | null>(null);
|
const gridRef = useRef<AgGridReact | null>(null);
|
||||||
|
const [dataCount, setDataCount] = useState(0);
|
||||||
const { data, error, loading, reload } = usePositionsData(
|
const { data, error, loading, reload } = usePositionsData(
|
||||||
partyId,
|
partyId,
|
||||||
gridRef,
|
gridRef,
|
||||||
@@ -66,8 +68,14 @@ export const PositionsManager = ({
|
|||||||
const bottomPlaceholderProps = useBottomPlaceholder<Position>({
|
const bottomPlaceholderProps = useBottomPlaceholder<Position>({
|
||||||
gridRef,
|
gridRef,
|
||||||
setId,
|
setId,
|
||||||
|
disabled: noBottomPlaceholder,
|
||||||
});
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
|
||||||
|
}, [data?.length]);
|
||||||
|
const onFilterChanged = useCallback((event: FilterChangedEvent) => {
|
||||||
|
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
|
||||||
|
}, []);
|
||||||
return (
|
return (
|
||||||
<div className="h-full relative">
|
<div className="h-full relative">
|
||||||
<PositionsTable
|
<PositionsTable
|
||||||
@@ -75,9 +83,11 @@ export const PositionsManager = ({
|
|||||||
ref={gridRef}
|
ref={gridRef}
|
||||||
onMarketClick={onMarketClick}
|
onMarketClick={onMarketClick}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
noRowsOverlayComponent={() => null}
|
suppressLoadingOverlay
|
||||||
|
suppressNoRowsOverlay
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
{...(noBottomPlaceholder ? null : bottomPlaceholderProps)}
|
onFilterChanged={onFilterChanged}
|
||||||
|
{...bottomPlaceholderProps}
|
||||||
/>
|
/>
|
||||||
<div className="pointer-events-none absolute inset-0">
|
<div className="pointer-events-none absolute inset-0">
|
||||||
<AsyncRenderer
|
<AsyncRenderer
|
||||||
@@ -85,7 +95,7 @@ export const PositionsManager = ({
|
|||||||
error={error}
|
error={error}
|
||||||
data={data}
|
data={data}
|
||||||
noDataMessage={t('No positions')}
|
noDataMessage={t('No positions')}
|
||||||
noDataCondition={(data) => !(data && data.length)}
|
noDataCondition={(data) => !dataCount}
|
||||||
reload={reload}
|
reload={reload}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
|||||||
value
|
value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
minWidth={190}
|
||||||
/>
|
/>
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
headerName={t('Notional')}
|
headerName={t('Notional')}
|
||||||
@@ -141,6 +142,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
|||||||
data.marketDecimalPlaces
|
data.marketDecimalPlaces
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
minWidth={80}
|
||||||
/>
|
/>
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
headerName={t('Open volume')}
|
headerName={t('Open volume')}
|
||||||
@@ -174,6 +176,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
cellRenderer={OpenVolumeCell}
|
cellRenderer={OpenVolumeCell}
|
||||||
|
minWidth={100}
|
||||||
/>
|
/>
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
headerName={t('Mark price')}
|
headerName={t('Mark price')}
|
||||||
@@ -213,8 +216,13 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
|||||||
data.marketDecimalPlaces
|
data.marketDecimalPlaces
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
minWidth={100}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Settlement asset')}
|
||||||
|
field="assetSymbol"
|
||||||
|
minWidth={100}
|
||||||
/>
|
/>
|
||||||
<AgGridColumn headerName={t('Settlement asset')} field="assetSymbol" />
|
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
headerName={t('Entry price')}
|
headerName={t('Entry price')}
|
||||||
field="averageEntryPrice"
|
field="averageEntryPrice"
|
||||||
@@ -248,6 +256,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
|||||||
data.marketDecimalPlaces
|
data.marketDecimalPlaces
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
minWidth={100}
|
||||||
/>
|
/>
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
headerName={t('Leverage')}
|
headerName={t('Leverage')}
|
||||||
@@ -264,6 +273,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
|||||||
}: VegaValueFormatterParams<Position, 'currentLeverage'>) =>
|
}: VegaValueFormatterParams<Position, 'currentLeverage'>) =>
|
||||||
value === undefined ? undefined : formatNumber(value.toString(), 1)
|
value === undefined ? undefined : formatNumber(value.toString(), 1)
|
||||||
}
|
}
|
||||||
|
minWidth={100}
|
||||||
/>
|
/>
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
headerName={t('Margin allocated')}
|
headerName={t('Margin allocated')}
|
||||||
@@ -295,6 +305,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
|||||||
data.decimals
|
data.decimals
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
minWidth={100}
|
||||||
/>
|
/>
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
headerName={t('Realised PNL')}
|
headerName={t('Realised PNL')}
|
||||||
@@ -321,6 +332,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
|||||||
'Profit or loss is realised whenever your position is reduced to zero and the margin is released back to your collateral balance. P&L excludes any fees paid.'
|
'Profit or loss is realised whenever your position is reduced to zero and the margin is released back to your collateral balance. P&L excludes any fees paid.'
|
||||||
)}
|
)}
|
||||||
cellRenderer={PNLCell}
|
cellRenderer={PNLCell}
|
||||||
|
minWidth={100}
|
||||||
/>
|
/>
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
headerName={t('Unrealised PNL')}
|
headerName={t('Unrealised PNL')}
|
||||||
@@ -347,6 +359,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
|||||||
'Unrealised profit is the current profit on your open position. Margin is still allocated to your position.'
|
'Unrealised profit is the current profit on your open position. Margin is still allocated to your position.'
|
||||||
)}
|
)}
|
||||||
cellRenderer={PNLCell}
|
cellRenderer={PNLCell}
|
||||||
|
minWidth={100}
|
||||||
/>
|
/>
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
headerName={t('Updated')}
|
headerName={t('Updated')}
|
||||||
@@ -361,6 +374,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
|||||||
}
|
}
|
||||||
return getDateTimeFormat().format(new Date(value));
|
return getDateTimeFormat().format(new Date(value));
|
||||||
}}
|
}}
|
||||||
|
minWidth={150}
|
||||||
/>
|
/>
|
||||||
{onClose && !props.isReadOnly ? (
|
{onClose && !props.isReadOnly ? (
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
@@ -375,6 +389,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
|||||||
</ButtonLink>
|
</ButtonLink>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
minWidth={80}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</AgGrid>
|
</AgGrid>
|
||||||
|
|||||||
@@ -11,11 +11,13 @@ const isFullWidthRow = (params: IsFullWidthRowParams) =>
|
|||||||
interface Props<T> {
|
interface Props<T> {
|
||||||
gridRef: RefObject<AgGridReact>;
|
gridRef: RefObject<AgGridReact>;
|
||||||
setId?: (data: T) => T;
|
setId?: (data: T) => T;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
export const useBottomPlaceholder = <T extends {}>({
|
export const useBottomPlaceholder = <T extends {}>({
|
||||||
gridRef,
|
gridRef,
|
||||||
setId,
|
setId,
|
||||||
|
disabled,
|
||||||
}: Props<T>) => {
|
}: Props<T>) => {
|
||||||
const onBodyScrollEnd = useCallback(() => {
|
const onBodyScrollEnd = useCallback(() => {
|
||||||
const rowCont = gridRef.current?.api.getModel().getRowCount() ?? 0;
|
const rowCont = gridRef.current?.api.getModel().getRowCount() ?? 0;
|
||||||
@@ -58,14 +60,17 @@ export const useBottomPlaceholder = <T extends {}>({
|
|||||||
}, [gridRef, onBodyScrollEnd]);
|
}, [gridRef, onBodyScrollEnd]);
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({
|
() =>
|
||||||
onBodyScrollEnd,
|
!disabled
|
||||||
rowClassRules: NO_HOVER_CSS_RULE,
|
? {
|
||||||
isFullWidthRow,
|
onBodyScrollEnd,
|
||||||
fullWidthCellRenderer,
|
rowClassRules: NO_HOVER_CSS_RULE,
|
||||||
onSortChanged: onRowsChanged,
|
isFullWidthRow,
|
||||||
onFilterChange: onRowsChanged,
|
fullWidthCellRenderer,
|
||||||
}),
|
onSortChanged: onRowsChanged,
|
||||||
[onBodyScrollEnd, onRowsChanged]
|
onFilterChange: onRowsChanged,
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
[onBodyScrollEnd, onRowsChanged, disabled]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
import { useRef, useEffect, useState } from 'react';
|
import { useRef, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
const SERVER_SIDE_DIMENSIONS = {
|
||||||
|
width: 1200,
|
||||||
|
height: 900,
|
||||||
|
};
|
||||||
|
|
||||||
export const useResize = () => {
|
export const useResize = () => {
|
||||||
const [windowSize, setWindowSize] = useState({
|
const [windowSize, setWindowSize] = useState(
|
||||||
width: window.innerWidth,
|
typeof window !== undefined
|
||||||
height: window.innerHeight,
|
? {
|
||||||
});
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
}
|
||||||
|
: { ...SERVER_SIDE_DIMENSIONS }
|
||||||
|
);
|
||||||
|
|
||||||
const timeout = useRef(0);
|
const timeout = useRef(0);
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,31 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
// @ts-ignore avoid adding declaration file
|
|
||||||
import { theme } from '@vegaprotocol/tailwindcss-config';
|
import { theme } from '@vegaprotocol/tailwindcss-config';
|
||||||
import { useResize } from './use-resize';
|
import { useResize } from './use-resize';
|
||||||
|
|
||||||
type Screen = keyof typeof theme.screens;
|
export type Screen = keyof typeof theme.screens;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
screenSize: Screen;
|
screenSize: Screen;
|
||||||
width: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useScreenDimensions = (): Props => {
|
export const useScreenDimensions = (): Props => {
|
||||||
const { width } = useResize();
|
const { width } = useResize();
|
||||||
|
const isMobile = width < parseInt(theme.screens.md);
|
||||||
|
const screenSize = Object.entries(theme.screens).reduce(
|
||||||
|
(agg: Screen, entry) => {
|
||||||
|
if (width > parseInt(entry[1])) {
|
||||||
|
agg = entry[0] as Screen;
|
||||||
|
}
|
||||||
|
return agg;
|
||||||
|
},
|
||||||
|
'xs'
|
||||||
|
);
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
width,
|
isMobile,
|
||||||
isMobile: width < parseInt(theme.screens.md),
|
screenSize,
|
||||||
screenSize: Object.entries(theme.screens).reduce((agg: Screen, entry) => {
|
|
||||||
if (width > parseInt(entry[1])) {
|
|
||||||
agg = entry[0] as Screen;
|
|
||||||
}
|
|
||||||
return agg;
|
|
||||||
}, 'xs'),
|
|
||||||
}),
|
}),
|
||||||
[width]
|
[isMobile, screenSize]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ module.exports = {
|
|||||||
lg: '960px',
|
lg: '960px',
|
||||||
xl: '1280px',
|
xl: '1280px',
|
||||||
xxl: '1536px',
|
xxl: '1536px',
|
||||||
|
xxxl: '1800px',
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
transparent: 'transparent',
|
transparent: 'transparent',
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ interface Props extends AgGridReactProps {
|
|||||||
export const TradesTable = forwardRef<AgGridReact, Props>((props, ref) => {
|
export const TradesTable = forwardRef<AgGridReact, Props>((props, ref) => {
|
||||||
return (
|
return (
|
||||||
<AgGrid
|
<AgGrid
|
||||||
style={{ width: '100%', height: '100%', background: 'red' }}
|
style={{ width: '100%', height: '100%' }}
|
||||||
overlayNoRowsTemplate={t('No trades')}
|
overlayNoRowsTemplate={t('No trades')}
|
||||||
getRowId={({ data }) => data.id}
|
getRowId={({ data }) => data.id}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"name": "@vegaprotocol/ui-toolkit",
|
"name": "@vegaprotocol/ui-toolkit",
|
||||||
"version": "0.7.0"
|
"version": "0.8.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import type {
|
|||||||
ReactNode,
|
ReactNode,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
import type { IconName } from '../icon';
|
|
||||||
import { Icon } from '../icon';
|
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
export type ButtonVariant = 'default' | 'primary' | 'secondary' | 'ternary';
|
export type ButtonVariant = 'default' | 'primary' | 'secondary' | 'ternary';
|
||||||
@@ -76,8 +74,8 @@ interface CommonProps {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
fill?: boolean;
|
fill?: boolean;
|
||||||
size?: ButtonSize;
|
size?: ButtonSize;
|
||||||
icon?: IconName;
|
icon?: ReactNode;
|
||||||
rightIcon?: IconName;
|
rightIcon?: ReactNode;
|
||||||
}
|
}
|
||||||
export interface ButtonProps
|
export interface ButtonProps
|
||||||
extends ButtonHTMLAttributes<HTMLButtonElement>,
|
extends ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
@@ -151,17 +149,13 @@ export const ButtonLink = forwardRef<HTMLButtonElement, ButtonLinkProps>(
|
|||||||
|
|
||||||
interface ButtonContentProps {
|
interface ButtonContentProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
icon?: IconName;
|
icon?: ReactNode;
|
||||||
rightIcon?: IconName;
|
rightIcon?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ButtonContent = ({ children, icon, rightIcon }: ButtonContentProps) => {
|
const ButtonContent = ({ children, icon, rightIcon }: ButtonContentProps) => {
|
||||||
const iconEl = icon ? (
|
const iconEl = icon ? icon : null;
|
||||||
<Icon name={icon} className="fill-current mr-2 align-text-top" />
|
const rightIconEl = rightIcon ? rightIcon : null;
|
||||||
) : null;
|
|
||||||
const rightIconEl = rightIcon ? (
|
|
||||||
<Icon name={rightIcon} className="fill-current ml-2 align-text-top" />
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import { countryCodeToFlagEmoji, FALLBACK_FLAG } from './flag-emoji';
|
|||||||
// 🇿
|
// 🇿
|
||||||
|
|
||||||
const cases = [
|
const cases = [
|
||||||
['AC', '🇦🇨'],
|
|
||||||
['AD', '🇦🇩'],
|
['AD', '🇦🇩'],
|
||||||
['AE', '🇦🇪'],
|
['AE', '🇦🇪'],
|
||||||
['AF', '🇦🇫'],
|
['AF', '🇦🇫'],
|
||||||
@@ -38,11 +37,13 @@ const cases = [
|
|||||||
['AL', '🇦🇱'],
|
['AL', '🇦🇱'],
|
||||||
['AM', '🇦🇲'],
|
['AM', '🇦🇲'],
|
||||||
['AO', '🇦🇴'],
|
['AO', '🇦🇴'],
|
||||||
|
['AQ', '🇦🇶'],
|
||||||
['AR', '🇦🇷'],
|
['AR', '🇦🇷'],
|
||||||
['AS', '🇦🇸'],
|
['AS', '🇦🇸'],
|
||||||
['AT', '🇦🇹'],
|
['AT', '🇦🇹'],
|
||||||
['AQ', '🇦🇶'],
|
['AU', '🇦🇺'],
|
||||||
['AW', '🇦🇼'],
|
['AW', '🇦🇼'],
|
||||||
|
['AX', '🇦🇽'],
|
||||||
['AZ', '🇦🇿'],
|
['AZ', '🇦🇿'],
|
||||||
['BA', '🇧🇦'],
|
['BA', '🇧🇦'],
|
||||||
['BB', '🇧🇧'],
|
['BB', '🇧🇧'],
|
||||||
@@ -57,10 +58,11 @@ const cases = [
|
|||||||
['BM', '🇧🇲'],
|
['BM', '🇧🇲'],
|
||||||
['BN', '🇧🇳'],
|
['BN', '🇧🇳'],
|
||||||
['BO', '🇧🇴'],
|
['BO', '🇧🇴'],
|
||||||
|
['BQ', '🇧🇶'],
|
||||||
['BR', '🇧🇷'],
|
['BR', '🇧🇷'],
|
||||||
['BS', '🇧🇸'],
|
['BS', '🇧🇸'],
|
||||||
['BT', '🇧🇹'],
|
['BT', '🇧🇹'],
|
||||||
['BQ', '🇧🇶'],
|
['BV', '🇧🇻'],
|
||||||
['BW', '🇧🇼'],
|
['BW', '🇧🇼'],
|
||||||
['BY', '🇧🇾'],
|
['BY', '🇧🇾'],
|
||||||
['BZ', '🇧🇿'],
|
['BZ', '🇧🇿'],
|
||||||
@@ -76,19 +78,19 @@ const cases = [
|
|||||||
['CM', '🇨🇲'],
|
['CM', '🇨🇲'],
|
||||||
['CN', '🇨🇳'],
|
['CN', '🇨🇳'],
|
||||||
['CO', '🇨🇴'],
|
['CO', '🇨🇴'],
|
||||||
['CP', '🇨🇵'],
|
|
||||||
['CR', '🇨🇷'],
|
['CR', '🇨🇷'],
|
||||||
|
['CU', '🇨🇺'],
|
||||||
|
['CV', '🇨🇻'],
|
||||||
['CW', '🇨🇼'],
|
['CW', '🇨🇼'],
|
||||||
|
['CX', '🇨🇽'],
|
||||||
['CY', '🇨🇾'],
|
['CY', '🇨🇾'],
|
||||||
['CZ', '🇨🇿'],
|
['CZ', '🇨🇿'],
|
||||||
['DE', '🇩🇪'],
|
['DE', '🇩🇪'],
|
||||||
['DG', '🇩🇬'],
|
|
||||||
['DJ', '🇩🇯'],
|
['DJ', '🇩🇯'],
|
||||||
['DK', '🇩🇰'],
|
['DK', '🇩🇰'],
|
||||||
['DM', '🇩🇲'],
|
['DM', '🇩🇲'],
|
||||||
['DO', '🇩🇴'],
|
['DO', '🇩🇴'],
|
||||||
['DZ', '🇩🇿'],
|
['DZ', '🇩🇿'],
|
||||||
['EA', '🇪🇦'],
|
|
||||||
['EC', '🇪🇨'],
|
['EC', '🇪🇨'],
|
||||||
['EE', '🇪🇪'],
|
['EE', '🇪🇪'],
|
||||||
['EG', '🇪🇬'],
|
['EG', '🇪🇬'],
|
||||||
@@ -114,10 +116,11 @@ const cases = [
|
|||||||
['GM', '🇬🇲'],
|
['GM', '🇬🇲'],
|
||||||
['GN', '🇬🇳'],
|
['GN', '🇬🇳'],
|
||||||
['GP', '🇬🇵'],
|
['GP', '🇬🇵'],
|
||||||
|
['GQ', '🇬🇶'],
|
||||||
['GR', '🇬🇷'],
|
['GR', '🇬🇷'],
|
||||||
['GS', '🇬🇸'],
|
['GS', '🇬🇸'],
|
||||||
['GT', '🇬🇹'],
|
['GT', '🇬🇹'],
|
||||||
['GQ', '🇬🇶'],
|
['GU', '🇬🇺'],
|
||||||
['GW', '🇬🇼'],
|
['GW', '🇬🇼'],
|
||||||
['GY', '🇬🇾'],
|
['GY', '🇬🇾'],
|
||||||
['HK', '🇭🇰'],
|
['HK', '🇭🇰'],
|
||||||
@@ -125,17 +128,17 @@ const cases = [
|
|||||||
['HN', '🇭🇳'],
|
['HN', '🇭🇳'],
|
||||||
['HR', '🇭🇷'],
|
['HR', '🇭🇷'],
|
||||||
['HT', '🇭🇹'],
|
['HT', '🇭🇹'],
|
||||||
['IC', '🇮🇨'],
|
['HU', '🇭🇺'],
|
||||||
['ID', '🇮🇩'],
|
['ID', '🇮🇩'],
|
||||||
['IE', '🇮🇪'],
|
['IE', '🇮🇪'],
|
||||||
['IL', '🇮🇱'],
|
['IL', '🇮🇱'],
|
||||||
['IM', '🇮🇲'],
|
['IM', '🇮🇲'],
|
||||||
['IN', '🇮🇳'],
|
['IN', '🇮🇳'],
|
||||||
['IO', '🇮🇴'],
|
['IO', '🇮🇴'],
|
||||||
|
['IQ', '🇮🇶'],
|
||||||
['IR', '🇮🇷'],
|
['IR', '🇮🇷'],
|
||||||
['IS', '🇮🇸'],
|
['IS', '🇮🇸'],
|
||||||
['IT', '🇮🇹'],
|
['IT', '🇮🇹'],
|
||||||
['IQ', '🇮🇶'],
|
|
||||||
['JE', '🇯🇪'],
|
['JE', '🇯🇪'],
|
||||||
['JM', '🇯🇲'],
|
['JM', '🇯🇲'],
|
||||||
['JO', '🇯🇴'],
|
['JO', '🇯🇴'],
|
||||||
@@ -159,6 +162,8 @@ const cases = [
|
|||||||
['LR', '🇱🇷'],
|
['LR', '🇱🇷'],
|
||||||
['LS', '🇱🇸'],
|
['LS', '🇱🇸'],
|
||||||
['LT', '🇱🇹'],
|
['LT', '🇱🇹'],
|
||||||
|
['LU', '🇱🇺'],
|
||||||
|
['LV', '🇱🇻'],
|
||||||
['LY', '🇱🇾'],
|
['LY', '🇱🇾'],
|
||||||
['MA', '🇲🇦'],
|
['MA', '🇲🇦'],
|
||||||
['MC', '🇲🇨'],
|
['MC', '🇲🇨'],
|
||||||
@@ -173,11 +178,14 @@ const cases = [
|
|||||||
['MN', '🇲🇳'],
|
['MN', '🇲🇳'],
|
||||||
['MO', '🇲🇴'],
|
['MO', '🇲🇴'],
|
||||||
['MP', '🇲🇵'],
|
['MP', '🇲🇵'],
|
||||||
|
['MQ', '🇲🇶'],
|
||||||
['MR', '🇲🇷'],
|
['MR', '🇲🇷'],
|
||||||
['MS', '🇲🇸'],
|
['MS', '🇲🇸'],
|
||||||
['MT', '🇲🇹'],
|
['MT', '🇲🇹'],
|
||||||
['MQ', '🇲🇶'],
|
['MU', '🇲🇺'],
|
||||||
|
['MV', '🇲🇻'],
|
||||||
['MW', '🇲🇼'],
|
['MW', '🇲🇼'],
|
||||||
|
['MX', '🇲🇽'],
|
||||||
['MY', '🇲🇾'],
|
['MY', '🇲🇾'],
|
||||||
['MZ', '🇲🇿'],
|
['MZ', '🇲🇿'],
|
||||||
['NA', '🇳🇦'],
|
['NA', '🇳🇦'],
|
||||||
@@ -190,6 +198,7 @@ const cases = [
|
|||||||
['NO', '🇳🇴'],
|
['NO', '🇳🇴'],
|
||||||
['NP', '🇳🇵'],
|
['NP', '🇳🇵'],
|
||||||
['NR', '🇳🇷'],
|
['NR', '🇳🇷'],
|
||||||
|
['NU', '🇳🇺'],
|
||||||
['NZ', '🇳🇿'],
|
['NZ', '🇳🇿'],
|
||||||
['OM', '🇴🇲'],
|
['OM', '🇴🇲'],
|
||||||
['PA', '🇵🇦'],
|
['PA', '🇵🇦'],
|
||||||
@@ -206,9 +215,11 @@ const cases = [
|
|||||||
['PT', '🇵🇹'],
|
['PT', '🇵🇹'],
|
||||||
['PW', '🇵🇼'],
|
['PW', '🇵🇼'],
|
||||||
['PY', '🇵🇾'],
|
['PY', '🇵🇾'],
|
||||||
|
['QA', '🇶🇦'],
|
||||||
['RE', '🇷🇪'],
|
['RE', '🇷🇪'],
|
||||||
['RO', '🇷🇴'],
|
['RO', '🇷🇴'],
|
||||||
['RS', '🇷🇸'],
|
['RS', '🇷🇸'],
|
||||||
|
['RU', '🇷🇺'],
|
||||||
['RW', '🇷🇼'],
|
['RW', '🇷🇼'],
|
||||||
['SA', '🇸🇦'],
|
['SA', '🇸🇦'],
|
||||||
['SB', '🇸🇧'],
|
['SB', '🇸🇧'],
|
||||||
@@ -227,9 +238,10 @@ const cases = [
|
|||||||
['SR', '🇸🇷'],
|
['SR', '🇸🇷'],
|
||||||
['SS', '🇸🇸'],
|
['SS', '🇸🇸'],
|
||||||
['ST', '🇸🇹'],
|
['ST', '🇸🇹'],
|
||||||
|
['SV', '🇸🇻'],
|
||||||
|
['SX', '🇸🇽'],
|
||||||
['SY', '🇸🇾'],
|
['SY', '🇸🇾'],
|
||||||
['SZ', '🇸🇿'],
|
['SZ', '🇸🇿'],
|
||||||
['TA', '🇹🇦'],
|
|
||||||
['TC', '🇹🇨'],
|
['TC', '🇹🇨'],
|
||||||
['TD', '🇹🇩'],
|
['TD', '🇹🇩'],
|
||||||
['TF', '🇹🇫'],
|
['TF', '🇹🇫'],
|
||||||
@@ -243,10 +255,22 @@ const cases = [
|
|||||||
['TO', '🇹🇴'],
|
['TO', '🇹🇴'],
|
||||||
['TR', '🇹🇷'],
|
['TR', '🇹🇷'],
|
||||||
['TT', '🇹🇹'],
|
['TT', '🇹🇹'],
|
||||||
|
['TV', '🇹🇻'],
|
||||||
['TW', '🇹🇼'],
|
['TW', '🇹🇼'],
|
||||||
['TZ', '🇹🇿'],
|
['TZ', '🇹🇿'],
|
||||||
['QA', '🇶🇦'],
|
['UA', '🇺🇦'],
|
||||||
|
['UG', '🇺🇬'],
|
||||||
|
['UM', '🇺🇲'],
|
||||||
|
['US', '🇺🇸'],
|
||||||
|
['UY', '🇺🇾'],
|
||||||
|
['UZ', '🇺🇿'],
|
||||||
|
['VA', '🇻🇦'],
|
||||||
|
['VC', '🇻🇨'],
|
||||||
|
['VE', '🇻🇪'],
|
||||||
['VG', '🇻🇬'],
|
['VG', '🇻🇬'],
|
||||||
|
['VI', '🇻🇮'],
|
||||||
|
['VN', '🇻🇳'],
|
||||||
|
['VU', '🇻🇺'],
|
||||||
['WF', '🇼🇫'],
|
['WF', '🇼🇫'],
|
||||||
['WS', '🇼🇸'],
|
['WS', '🇼🇸'],
|
||||||
['YE', '🇾🇪'],
|
['YE', '🇾🇪'],
|
||||||
@@ -254,6 +278,23 @@ const cases = [
|
|||||||
['ZA', '🇿🇦'],
|
['ZA', '🇿🇦'],
|
||||||
['ZM', '🇿🇲'],
|
['ZM', '🇿🇲'],
|
||||||
['ZW', '🇿🇼'],
|
['ZW', '🇿🇼'],
|
||||||
|
// UK
|
||||||
|
['ENGLAND', '🏴'],
|
||||||
|
['GB-ENG', '🏴'],
|
||||||
|
['ENG', '🏴'],
|
||||||
|
['SCOTLAND', '🏴'],
|
||||||
|
['GB-SCT', '🏴'],
|
||||||
|
['SCT', '🏴'],
|
||||||
|
['WALES', '🏴'],
|
||||||
|
['GB-WLS', '🏴'],
|
||||||
|
['WLS', '🏴'],
|
||||||
|
['CYMRU', '🏴'],
|
||||||
|
['GB-CYM', '🏴'],
|
||||||
|
['CYM', '🏴'],
|
||||||
|
['NORTHERN IRELAND', '🇬🇧'],
|
||||||
|
['GB-NIR', '🇬🇧'],
|
||||||
|
['NIR', '🇬🇧'],
|
||||||
|
['UK', '🇬🇧'],
|
||||||
// unknown
|
// unknown
|
||||||
['AA', FALLBACK_FLAG],
|
['AA', FALLBACK_FLAG],
|
||||||
['XX', FALLBACK_FLAG],
|
['XX', FALLBACK_FLAG],
|
||||||
|
|||||||
@@ -2,23 +2,303 @@ import compact from 'lodash/compact';
|
|||||||
|
|
||||||
export const FALLBACK_FLAG = '🏳';
|
export const FALLBACK_FLAG = '🏳';
|
||||||
|
|
||||||
const KNOWN_CODES = `AC AD AE AF AG AI AL AM AO AR AS AT AQ AW AZ BA BB BD BE BF
|
const KNOWN_CODES = [
|
||||||
BG BH BI BJ BL BM BN BO BR BS BT BQ BW BY BZ CA CC CD CF CG CH CI CK CL CM CN
|
'AD',
|
||||||
CO CP CR CW CY CZ DE DG DJ DK DM DO DZ EA EC EE EG EH ER ES ET FI FJ FK FM FO
|
'AE',
|
||||||
FR GA GB GD GE GF GG GH GI GL GM GN GP GR GS GT GQ GW GY HK HM HN HR HT IC ID
|
'AF',
|
||||||
IE IL IM IN IO IR IS IT IQ JE JM JO JP KE KG KH KI KM KN KP KR KW KY KZ LA LB
|
'AG',
|
||||||
LC LI LK LR LS LT LY MA MC MD ME MF MG MH MK ML MM MN MO MP MR MS MT MQ MW MY
|
'AI',
|
||||||
MZ NA NC NE NF NG NI NL NO NP NR NZ OM PA PE PF PG PH PK PL PM PN PR PS PT PW
|
'AL',
|
||||||
PY RE RO RS RW SA SB SC SD SE SG SH SI SJ SK SL SM SN SO SR SS ST SY SZ TA TC
|
'AM',
|
||||||
TD TF TG TH TJ TK TL TM TN TO TR TT TW TZ QA VG WF WS YE YT ZA ZM ZW`;
|
'AO',
|
||||||
|
'AQ',
|
||||||
|
'AR',
|
||||||
|
'AS',
|
||||||
|
'AT',
|
||||||
|
'AU',
|
||||||
|
'AW',
|
||||||
|
'AX',
|
||||||
|
'AZ',
|
||||||
|
'BA',
|
||||||
|
'BB',
|
||||||
|
'BD',
|
||||||
|
'BE',
|
||||||
|
'BF',
|
||||||
|
'BG',
|
||||||
|
'BH',
|
||||||
|
'BI',
|
||||||
|
'BJ',
|
||||||
|
'BL',
|
||||||
|
'BM',
|
||||||
|
'BN',
|
||||||
|
'BO',
|
||||||
|
'BQ',
|
||||||
|
'BR',
|
||||||
|
'BS',
|
||||||
|
'BT',
|
||||||
|
'BV',
|
||||||
|
'BW',
|
||||||
|
'BY',
|
||||||
|
'BZ',
|
||||||
|
'CA',
|
||||||
|
'CC',
|
||||||
|
'CD',
|
||||||
|
'CF',
|
||||||
|
'CG',
|
||||||
|
'CH',
|
||||||
|
'CI',
|
||||||
|
'CK',
|
||||||
|
'CL',
|
||||||
|
'CM',
|
||||||
|
'CN',
|
||||||
|
'CO',
|
||||||
|
'CR',
|
||||||
|
'CU',
|
||||||
|
'CV',
|
||||||
|
'CW',
|
||||||
|
'CX',
|
||||||
|
'CY',
|
||||||
|
'CZ',
|
||||||
|
'DE',
|
||||||
|
'DJ',
|
||||||
|
'DK',
|
||||||
|
'DM',
|
||||||
|
'DO',
|
||||||
|
'DZ',
|
||||||
|
'EC',
|
||||||
|
'EE',
|
||||||
|
'EG',
|
||||||
|
'EH',
|
||||||
|
'ER',
|
||||||
|
'ES',
|
||||||
|
'ET',
|
||||||
|
'FI',
|
||||||
|
'FJ',
|
||||||
|
'FK',
|
||||||
|
'FM',
|
||||||
|
'FO',
|
||||||
|
'FR',
|
||||||
|
'GA',
|
||||||
|
'GB',
|
||||||
|
'GD',
|
||||||
|
'GE',
|
||||||
|
'GF',
|
||||||
|
'GG',
|
||||||
|
'GH',
|
||||||
|
'GI',
|
||||||
|
'GL',
|
||||||
|
'GM',
|
||||||
|
'GN',
|
||||||
|
'GP',
|
||||||
|
'GQ',
|
||||||
|
'GR',
|
||||||
|
'GS',
|
||||||
|
'GT',
|
||||||
|
'GU',
|
||||||
|
'GW',
|
||||||
|
'GY',
|
||||||
|
'HK',
|
||||||
|
'HM',
|
||||||
|
'HN',
|
||||||
|
'HR',
|
||||||
|
'HT',
|
||||||
|
'HU',
|
||||||
|
'ID',
|
||||||
|
'IE',
|
||||||
|
'IL',
|
||||||
|
'IM',
|
||||||
|
'IN',
|
||||||
|
'IO',
|
||||||
|
'IQ',
|
||||||
|
'IR',
|
||||||
|
'IS',
|
||||||
|
'IT',
|
||||||
|
'JE',
|
||||||
|
'JM',
|
||||||
|
'JO',
|
||||||
|
'JP',
|
||||||
|
'KE',
|
||||||
|
'KG',
|
||||||
|
'KH',
|
||||||
|
'KI',
|
||||||
|
'KM',
|
||||||
|
'KN',
|
||||||
|
'KP',
|
||||||
|
'KR',
|
||||||
|
'KW',
|
||||||
|
'KY',
|
||||||
|
'KZ',
|
||||||
|
'LA',
|
||||||
|
'LB',
|
||||||
|
'LC',
|
||||||
|
'LI',
|
||||||
|
'LK',
|
||||||
|
'LR',
|
||||||
|
'LS',
|
||||||
|
'LT',
|
||||||
|
'LU',
|
||||||
|
'LV',
|
||||||
|
'LY',
|
||||||
|
'MA',
|
||||||
|
'MC',
|
||||||
|
'MD',
|
||||||
|
'ME',
|
||||||
|
'MF',
|
||||||
|
'MG',
|
||||||
|
'MH',
|
||||||
|
'MK',
|
||||||
|
'ML',
|
||||||
|
'MM',
|
||||||
|
'MN',
|
||||||
|
'MO',
|
||||||
|
'MP',
|
||||||
|
'MQ',
|
||||||
|
'MR',
|
||||||
|
'MS',
|
||||||
|
'MT',
|
||||||
|
'MU',
|
||||||
|
'MV',
|
||||||
|
'MW',
|
||||||
|
'MX',
|
||||||
|
'MY',
|
||||||
|
'MZ',
|
||||||
|
'NA',
|
||||||
|
'NC',
|
||||||
|
'NE',
|
||||||
|
'NF',
|
||||||
|
'NG',
|
||||||
|
'NI',
|
||||||
|
'NL',
|
||||||
|
'NO',
|
||||||
|
'NP',
|
||||||
|
'NR',
|
||||||
|
'NU',
|
||||||
|
'NZ',
|
||||||
|
'OM',
|
||||||
|
'PA',
|
||||||
|
'PE',
|
||||||
|
'PF',
|
||||||
|
'PG',
|
||||||
|
'PH',
|
||||||
|
'PK',
|
||||||
|
'PL',
|
||||||
|
'PM',
|
||||||
|
'PN',
|
||||||
|
'PR',
|
||||||
|
'PS',
|
||||||
|
'PT',
|
||||||
|
'PW',
|
||||||
|
'PY',
|
||||||
|
'QA',
|
||||||
|
'RE',
|
||||||
|
'RO',
|
||||||
|
'RS',
|
||||||
|
'RU',
|
||||||
|
'RW',
|
||||||
|
'SA',
|
||||||
|
'SB',
|
||||||
|
'SC',
|
||||||
|
'SD',
|
||||||
|
'SE',
|
||||||
|
'SG',
|
||||||
|
'SH',
|
||||||
|
'SI',
|
||||||
|
'SJ',
|
||||||
|
'SK',
|
||||||
|
'SL',
|
||||||
|
'SM',
|
||||||
|
'SN',
|
||||||
|
'SO',
|
||||||
|
'SR',
|
||||||
|
'SS',
|
||||||
|
'ST',
|
||||||
|
'SV',
|
||||||
|
'SX',
|
||||||
|
'SY',
|
||||||
|
'SZ',
|
||||||
|
'TC',
|
||||||
|
'TD',
|
||||||
|
'TF',
|
||||||
|
'TG',
|
||||||
|
'TH',
|
||||||
|
'TJ',
|
||||||
|
'TK',
|
||||||
|
'TL',
|
||||||
|
'TM',
|
||||||
|
'TN',
|
||||||
|
'TO',
|
||||||
|
'TR',
|
||||||
|
'TT',
|
||||||
|
'TV',
|
||||||
|
'TW',
|
||||||
|
'TZ',
|
||||||
|
'UA',
|
||||||
|
'UG',
|
||||||
|
'UM',
|
||||||
|
'US',
|
||||||
|
'UY',
|
||||||
|
'UZ',
|
||||||
|
'VA',
|
||||||
|
'VC',
|
||||||
|
'VE',
|
||||||
|
'VG',
|
||||||
|
'VI',
|
||||||
|
'VN',
|
||||||
|
'VU',
|
||||||
|
'WF',
|
||||||
|
'WS',
|
||||||
|
'YE',
|
||||||
|
'YT',
|
||||||
|
'ZA',
|
||||||
|
'ZM',
|
||||||
|
'ZW',
|
||||||
|
];
|
||||||
|
|
||||||
export const countryCodeToFlagEmoji = (countryCode: string) => {
|
const ENGLAND = '🏴';
|
||||||
|
const SCOTLAND = '🏴';
|
||||||
|
const WALES = '🏴';
|
||||||
|
const NORTHERN_IRELAND = '🇬🇧';
|
||||||
|
|
||||||
|
const UK = new Map([
|
||||||
|
['ENGLAND', ENGLAND],
|
||||||
|
['GB-ENG', ENGLAND],
|
||||||
|
['ENG', ENGLAND],
|
||||||
|
['SCOTLAND', SCOTLAND],
|
||||||
|
['GB-SCT', SCOTLAND],
|
||||||
|
['SCT', SCOTLAND],
|
||||||
|
['WALES', WALES],
|
||||||
|
['GB-WLS', WALES],
|
||||||
|
['WLS', WALES],
|
||||||
|
['CYMRU', WALES],
|
||||||
|
['GB-CYM', WALES],
|
||||||
|
['CYM', WALES],
|
||||||
|
['NORTHERN IRELAND', NORTHERN_IRELAND],
|
||||||
|
['GB-NIR', NORTHERN_IRELAND],
|
||||||
|
['NIR', NORTHERN_IRELAND],
|
||||||
|
['UNITED KINGDOM', '🇬🇧'],
|
||||||
|
['UK', '🇬🇧'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const EU = new Map([['EU', '🇪🇺']]);
|
||||||
|
|
||||||
|
const getCode = (countryCode: string): string => {
|
||||||
const code = countryCode.trim().toUpperCase();
|
const code = countryCode.trim().toUpperCase();
|
||||||
const known = compact(KNOWN_CODES.split(' ').map((ch) => ch.trim()));
|
return code;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const countryCodeToFlagEmoji = (countryCode: string): string => {
|
||||||
|
const code = getCode(countryCode);
|
||||||
|
const known = compact(KNOWN_CODES.map((ch) => ch.trim()));
|
||||||
if (known.includes(code)) {
|
if (known.includes(code)) {
|
||||||
return code.replace(/./g, (char) =>
|
return code.replace(/./g, (char) =>
|
||||||
String.fromCodePoint(0x1f1a5 + char.toUpperCase().charCodeAt(0))
|
String.fromCodePoint(0x1f1a5 + char.toUpperCase().charCodeAt(0))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (UK.has(code)) {
|
||||||
|
return UK.get(code) as string;
|
||||||
|
}
|
||||||
|
if (EU.has(code)) {
|
||||||
|
return EU.get(code) as string;
|
||||||
|
}
|
||||||
return FALLBACK_FLAG;
|
return FALLBACK_FLAG;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -98,6 +98,8 @@ interface ProposalNewMarketTerms {
|
|||||||
decimalPlaces: string;
|
decimalPlaces: string;
|
||||||
positionDecimalPlaces: string;
|
positionDecimalPlaces: string;
|
||||||
lpPriceRange: string;
|
lpPriceRange: string;
|
||||||
|
linearSlippageFactor: string;
|
||||||
|
quadraticSlippageFactor: string;
|
||||||
instrument: {
|
instrument: {
|
||||||
name: string;
|
name: string;
|
||||||
code: string;
|
code: string;
|
||||||
@@ -130,6 +132,8 @@ interface ProposalUpdateMarketTerms {
|
|||||||
updateMarket: {
|
updateMarket: {
|
||||||
marketId: string;
|
marketId: string;
|
||||||
changes: {
|
changes: {
|
||||||
|
linearSlippageFactor: string;
|
||||||
|
quadraticSlippageFactor: string;
|
||||||
instrument: {
|
instrument: {
|
||||||
code: string;
|
code: string;
|
||||||
future: {
|
future: {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
import { EtherscanLink, useEnvironment } from '@vegaprotocol/environment';
|
||||||
import { EthTxStatus } from '../use-ethereum-transaction';
|
import { EthTxStatus } from '../use-ethereum-transaction';
|
||||||
|
|
||||||
const ACTIVE_CLASSES = 'text-black dark:text-white';
|
const ACTIVE_CLASSES = 'text-black dark:text-white';
|
||||||
@@ -62,14 +62,14 @@ export const TxRow = ({
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span>{t('Ethereum transaction complete')}</span>
|
<span>{t('Ethereum transaction complete')}</span>
|
||||||
<Link
|
{txHash && (
|
||||||
href={`${ETHERSCAN_URL}/tx/${txHash}`}
|
<EtherscanLink
|
||||||
title={t('View on Etherscan')}
|
tx={txHash}
|
||||||
className="text-vega-pink dark:text-vega-yellow"
|
className="text-vega-pink dark:text-vega-yellow"
|
||||||
target="_blank"
|
>
|
||||||
>
|
{t('View transaction on Etherscan')}
|
||||||
{t('View transaction on Etherscan')}
|
</EtherscanLink>
|
||||||
</Link>
|
)}
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ import {
|
|||||||
} from '@vegaprotocol/utils';
|
} from '@vegaprotocol/utils';
|
||||||
import { useBottomPlaceholder } from '@vegaprotocol/react-helpers';
|
import { useBottomPlaceholder } from '@vegaprotocol/react-helpers';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { Link, ButtonLink } from '@vegaprotocol/ui-toolkit';
|
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||||
import { AgGridDynamic as AgGrid } from '@vegaprotocol/datagrid';
|
import { AgGridDynamic as AgGrid } from '@vegaprotocol/datagrid';
|
||||||
import type {
|
import type {
|
||||||
TypedDataAgGrid,
|
TypedDataAgGrid,
|
||||||
VegaICellRendererParams,
|
VegaICellRendererParams,
|
||||||
VegaValueFormatterParams,
|
VegaValueFormatterParams,
|
||||||
} from '@vegaprotocol/datagrid';
|
} from '@vegaprotocol/datagrid';
|
||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
import { EtherscanLink } from '@vegaprotocol/environment';
|
||||||
import type { WithdrawalFieldsFragment } from './__generated__/Withdrawal';
|
import type { WithdrawalFieldsFragment } from './__generated__/Withdrawal';
|
||||||
import { useEthWithdrawApprovalsStore } from '@vegaprotocol/web3';
|
import { useEthWithdrawApprovalsStore } from '@vegaprotocol/web3';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
@@ -27,7 +27,6 @@ export const WithdrawalsTable = (
|
|||||||
props: TypedDataAgGrid<WithdrawalFieldsFragment>
|
props: TypedDataAgGrid<WithdrawalFieldsFragment>
|
||||||
) => {
|
) => {
|
||||||
const gridRef = useRef<AgGridReact | null>(null);
|
const gridRef = useRef<AgGridReact | null>(null);
|
||||||
const { ETHERSCAN_URL } = useEnvironment();
|
|
||||||
const createWithdrawApproval = useEthWithdrawApprovalsStore(
|
const createWithdrawApproval = useEthWithdrawApprovalsStore(
|
||||||
(store) => store.create
|
(store) => store.create
|
||||||
);
|
);
|
||||||
@@ -66,7 +65,6 @@ export const WithdrawalsTable = (
|
|||||||
headerName={t('Recipient')}
|
headerName={t('Recipient')}
|
||||||
field="details.receiverAddress"
|
field="details.receiverAddress"
|
||||||
cellRenderer="RecipientCell"
|
cellRenderer="RecipientCell"
|
||||||
cellRendererParams={{ ethUrl: ETHERSCAN_URL }}
|
|
||||||
valueFormatter={({
|
valueFormatter={({
|
||||||
value,
|
value,
|
||||||
data,
|
data,
|
||||||
@@ -126,7 +124,6 @@ export const WithdrawalsTable = (
|
|||||||
complete: (withdrawal: WithdrawalFieldsFragment) => {
|
complete: (withdrawal: WithdrawalFieldsFragment) => {
|
||||||
createWithdrawApproval(withdrawal);
|
createWithdrawApproval(withdrawal);
|
||||||
},
|
},
|
||||||
ethUrl: ETHERSCAN_URL,
|
|
||||||
}}
|
}}
|
||||||
cellRendererSelector={({
|
cellRendererSelector={({
|
||||||
data,
|
data,
|
||||||
@@ -160,20 +157,12 @@ export const CompleteCell = ({ data, complete }: CompleteCellProps) => {
|
|||||||
|
|
||||||
export const EtherscanLinkCell = ({
|
export const EtherscanLinkCell = ({
|
||||||
value,
|
value,
|
||||||
ethUrl,
|
}: VegaValueFormatterParams<WithdrawalFieldsFragment, 'txHash'>) => {
|
||||||
}: VegaValueFormatterParams<WithdrawalFieldsFragment, 'txHash'> & {
|
|
||||||
ethUrl: string;
|
|
||||||
}) => {
|
|
||||||
if (!value) return '-';
|
if (!value) return '-';
|
||||||
return (
|
return (
|
||||||
<Link
|
<EtherscanLink tx={value} data-testid="etherscan-link">
|
||||||
title={t('View transaction on Etherscan')}
|
|
||||||
href={`${ethUrl}/tx/${value}`}
|
|
||||||
data-testid="etherscan-link"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{truncateByChars(value)}
|
{truncateByChars(value)}
|
||||||
</Link>
|
</EtherscanLink>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -193,28 +182,17 @@ export const StatusCell = ({ data }: { data: WithdrawalFieldsFragment }) => {
|
|||||||
return <span>{t('Failed')}</span>;
|
return <span>{t('Failed')}</span>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface RecipientCellProps
|
|
||||||
extends VegaICellRendererParams<
|
|
||||||
WithdrawalFieldsFragment,
|
|
||||||
'details.receiverAddress'
|
|
||||||
> {
|
|
||||||
ethUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RecipientCell = ({
|
const RecipientCell = ({
|
||||||
ethUrl,
|
|
||||||
value,
|
value,
|
||||||
valueFormatted,
|
valueFormatted,
|
||||||
}: RecipientCellProps) => {
|
}: VegaICellRendererParams<
|
||||||
|
WithdrawalFieldsFragment,
|
||||||
|
'details.receiverAddress'
|
||||||
|
>) => {
|
||||||
return (
|
return (
|
||||||
<Link
|
<EtherscanLink address={value} data-testid="etherscan-link">
|
||||||
title={t('View on Etherscan (opens in a new tab)')}
|
|
||||||
href={`${ethUrl}/address/${value}`}
|
|
||||||
data-testid="etherscan-link"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{valueFormatted}
|
{valueFormatted}
|
||||||
</Link>
|
</EtherscanLink>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user