Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2859cd066 | ||
|
|
3bc620c6ce | ||
|
|
3054a0b4fb | ||
|
|
3a1c356886 | ||
|
|
374df6a500 | ||
|
|
f8cc45a63f |
@@ -1,8 +1,5 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
# Auto-format all files
|
||||
yarn nx format:write
|
||||
|
||||
# Lint all staged files
|
||||
yarn lint-staged
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
# Lint all staged files - this brings more value as pre-commit
|
||||
# yarn nx format:check
|
||||
# Lint all staged files
|
||||
yarn nx format:check
|
||||
|
||||
# Test all projects with changes
|
||||
# yarn nx affected -t test --exclude trading
|
||||
yarn nx affected -t test --exclude trading
|
||||
|
||||
@@ -54,7 +54,7 @@ const Block = () => {
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
{blockData && 'result' in blockData && (
|
||||
{blockData && (
|
||||
<>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableRow modifier="bordered">
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ export const ProtocolUpgradeProposalContainer = () => {
|
||||
time={
|
||||
pending && time ? (
|
||||
convertToCountdownString(time, '0:00:00:00')
|
||||
) : blockInfo && 'result' in blockInfo && blockInfo?.result ? (
|
||||
) : blockInfo?.result ? (
|
||||
<span title={blockInfo.result.block.header.time}>
|
||||
{formatDateWithLocalTimezone(
|
||||
new Date(blockInfo.result.block.header.time)
|
||||
|
||||
@@ -22,9 +22,10 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_REFERRALS=true
|
||||
# NX_DISABLE_CLOSE_POSITION=false
|
||||
NX_REFERRALS=false
|
||||
|
||||
NX_TENDERMINT_URL=https://be.vega.community
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||
NX_DISABLE_CLOSE_POSITION=true
|
||||
|
||||
@@ -24,7 +24,4 @@ NX_STOP_ORDERS=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_REFERRALS=true
|
||||
|
||||
NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
|
||||
NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=
|
||||
NX_REFERRALS=true
|
||||
@@ -23,11 +23,9 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_REFERRALS=true
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
|
||||
NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
|
||||
NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=
|
||||
|
||||
@@ -9,6 +9,8 @@ import { TradeGrid } from './trade-grid';
|
||||
import { TradePanels } from './trade-panels';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Links } from '../../lib/links';
|
||||
import { ViewType, useSidebar } from '../../components/sidebar';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
import { useT, ns } from '../../lib/use-t';
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
@@ -59,6 +61,9 @@ export const MarketPage = () => {
|
||||
const t = useT();
|
||||
const { marketId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const { setViews, getView } = useSidebar();
|
||||
const view = getView(currentRouteId);
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize);
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
@@ -67,11 +72,20 @@ export const MarketPage = () => {
|
||||
const { data, loading } = useMarket(marketId);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.id && data.id !== lastMarketId) {
|
||||
if (data?.id && data.id !== lastMarketId && !closed) {
|
||||
update({ marketId: data.id });
|
||||
}
|
||||
}, [update, lastMarketId, data?.id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (largeScreen && view === undefined) {
|
||||
setViews(
|
||||
{ type: closed ? ViewType.Info : ViewType.Order },
|
||||
currentRouteId
|
||||
);
|
||||
}
|
||||
}, [setViews, view, currentRouteId, largeScreen]);
|
||||
|
||||
const pinnedAsset = data && getAsset(data);
|
||||
|
||||
const tradeView = useMemo(() => {
|
||||
|
||||
@@ -62,10 +62,10 @@ const MainGrid = memo(
|
||||
id="chart"
|
||||
overflowHidden
|
||||
name={t('Chart')}
|
||||
menu={<TradingViews.chart.menu />}
|
||||
menu={<TradingViews.candles.menu />}
|
||||
>
|
||||
<ErrorBoundary feature="chart">
|
||||
<TradingViews.chart.component marketId={marketId} />
|
||||
<TradingViews.candles.component marketId={marketId} />
|
||||
</ErrorBoundary>
|
||||
</Tab>
|
||||
<Tab id="depth" name={t('Depth')}>
|
||||
|
||||
@@ -23,7 +23,7 @@ interface TradePanelsProps {
|
||||
|
||||
export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
const [view, setView] = useState<TradingView>('chart');
|
||||
const [view, setView] = useState<TradingView>('candles');
|
||||
|
||||
const renderView = () => {
|
||||
const Component = TradingViews[view].component;
|
||||
@@ -50,7 +50,7 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
||||
const Menu = viewCfg.menu;
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-end gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
|
||||
<div className="flex gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
|
||||
<Menu />
|
||||
</div>
|
||||
);
|
||||
@@ -149,7 +149,7 @@ const useViewLabel = (view: TradingView) => {
|
||||
const t = useT();
|
||||
|
||||
const labels = {
|
||||
chart: t('Chart'),
|
||||
candles: t('Candles'),
|
||||
depth: t('Depth'),
|
||||
liquidity: t('Liquidity'),
|
||||
funding: t('Funding'),
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { DepthChartContainer } from '@vegaprotocol/market-depth';
|
||||
import {
|
||||
CandlesChartContainer,
|
||||
CandlesMenu,
|
||||
} from '@vegaprotocol/candles-chart';
|
||||
import { Filter, OpenOrdersMenu } from '@vegaprotocol/orders';
|
||||
import { TradesContainer } from '../../components/trades-container';
|
||||
import { OrderbookContainer } from '../../components/orderbook-container';
|
||||
@@ -12,14 +16,13 @@ import { OrdersContainer } from '../../components/orders-container';
|
||||
import { StopOrdersContainer } from '../../components/stop-orders-container';
|
||||
import { AccountsMenu } from '../../components/accounts-menu';
|
||||
import { PositionsMenu } from '../../components/positions-menu';
|
||||
import { ChartContainer, ChartMenu } from '../../components/chart-container';
|
||||
|
||||
export type TradingView = keyof typeof TradingViews;
|
||||
|
||||
export const TradingViews = {
|
||||
chart: {
|
||||
component: ChartContainer,
|
||||
menu: ChartMenu,
|
||||
candles: {
|
||||
component: CandlesChartContainer,
|
||||
menu: CandlesMenu,
|
||||
},
|
||||
depth: {
|
||||
component: DepthChartContainer,
|
||||
|
||||
@@ -4,26 +4,9 @@ import {
|
||||
SidebarButton,
|
||||
SidebarDivider,
|
||||
ViewType,
|
||||
useSidebar,
|
||||
} from '../../components/sidebar';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const ViewInitializer = () => {
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const { setViews, getView } = useSidebar();
|
||||
const view = getView(currentRouteId);
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize);
|
||||
useEffect(() => {
|
||||
if (largeScreen && view === undefined) {
|
||||
setViews({ type: ViewType.Order }, currentRouteId);
|
||||
}
|
||||
}, [setViews, view, currentRouteId, largeScreen]);
|
||||
return null;
|
||||
};
|
||||
|
||||
export const MarketsSidebar = () => {
|
||||
const t = useT();
|
||||
@@ -54,7 +37,6 @@ export const MarketsSidebar = () => {
|
||||
path=":marketId"
|
||||
element={
|
||||
<>
|
||||
<ViewInitializer />
|
||||
<SidebarDivider />
|
||||
<SidebarButton
|
||||
view={ViewType.Order}
|
||||
|
||||
@@ -39,21 +39,11 @@ const WithdrawalsIndicator = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const SidebarViewInitializer = () => {
|
||||
export const Portfolio = () => {
|
||||
const t = useT();
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const { getView, setViews } = useSidebar();
|
||||
const view = getView(currentRouteId);
|
||||
// Make transfer sidebar open by default
|
||||
useEffect(() => {
|
||||
if (view === undefined) {
|
||||
setViews({ type: ViewType.Transfer }, currentRouteId);
|
||||
}
|
||||
}, [view, setViews, currentRouteId]);
|
||||
return null;
|
||||
};
|
||||
|
||||
export const Portfolio = () => {
|
||||
const t = useT();
|
||||
|
||||
const { updateTitle } = usePageTitleStore((store) => ({
|
||||
updateTitle: store.updateTitle,
|
||||
@@ -63,11 +53,17 @@ export const Portfolio = () => {
|
||||
updateTitle(titlefy([t('Portfolio')]));
|
||||
}, [updateTitle, t]);
|
||||
|
||||
// Make transfer sidebar open by default
|
||||
useEffect(() => {
|
||||
if (view === undefined) {
|
||||
setViews({ type: ViewType.Transfer }, currentRouteId);
|
||||
}
|
||||
}, [view, setViews, currentRouteId]);
|
||||
|
||||
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'portfolio' });
|
||||
const wrapperClasses = 'p-0.5 h-full max-h-full flex flex-col';
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<SidebarViewInitializer />
|
||||
<ResizableGrid vertical onChange={handleOnLayoutChange}>
|
||||
<ResizableGridPanel minSize={75}>
|
||||
<PortfolioGridChild>
|
||||
|
||||
@@ -18,35 +18,13 @@ import { Routes } from '../../lib/links';
|
||||
import { useTransactionEventSubscription } from '@vegaprotocol/web3';
|
||||
import { Statistics, useStats } from './referral-statistics';
|
||||
import { useReferralProgram } from './hooks/use-referral-program';
|
||||
import { ns, useT } from '../../lib/use-t';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { useFundsAvailable } from './hooks/use-funds-available';
|
||||
import { ViewType, useSidebar } from '../../components/sidebar';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
import { QUSDTooltip } from './qusd-tooltip';
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
const RELOAD_DELAY = 3000;
|
||||
|
||||
const SPAM_PROTECTION_ERR = 'SPAM_PROTECTION_ERR';
|
||||
const SpamProtectionErr = ({
|
||||
requiredFunds,
|
||||
}: {
|
||||
requiredFunds?: string | number | bigint;
|
||||
}) => {
|
||||
if (!requiredFunds) return null;
|
||||
// eslint-disable-next-line react/jsx-no-undef
|
||||
return (
|
||||
<Trans
|
||||
defaults="To protect the network from spam, you must have at least {{requiredFunds}} <0>qUSD</0> of any asset on the network to proceed."
|
||||
values={{
|
||||
requiredFunds,
|
||||
}}
|
||||
components={[<QUSDTooltip key="qusd" />]}
|
||||
ns={ns}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const validateCode = (value: string, t: ReturnType<typeof useT>) => {
|
||||
const number = +`0x${value}`;
|
||||
if (!value || value.length !== 64) {
|
||||
@@ -98,6 +76,7 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
||||
setValue,
|
||||
setError,
|
||||
watch,
|
||||
clearErrors,
|
||||
} = useForm();
|
||||
const [params] = useSearchParams();
|
||||
|
||||
@@ -111,11 +90,32 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
||||
*/
|
||||
const validateFundsAvailable = useCallback(() => {
|
||||
if (requiredFunds && !isEligible) {
|
||||
const err = SPAM_PROTECTION_ERR;
|
||||
const err = t(
|
||||
'To protect the network from spam, you must have at least {{requiredFunds}} qUSD of any asset on the network to proceed.',
|
||||
{
|
||||
requiredFunds,
|
||||
}
|
||||
);
|
||||
return err;
|
||||
}
|
||||
return true;
|
||||
}, [isEligible, requiredFunds]);
|
||||
}, [isEligible, requiredFunds, t]);
|
||||
|
||||
useEffect(() => {
|
||||
if (codeField) {
|
||||
const err = validateFundsAvailable();
|
||||
if (err !== true) {
|
||||
setStatus('no-funds');
|
||||
setError('code', {
|
||||
type: 'required',
|
||||
message: err,
|
||||
});
|
||||
} else {
|
||||
setStatus(null);
|
||||
clearErrors('code');
|
||||
}
|
||||
}
|
||||
}, [clearErrors, codeField, isEligible, setError, validateFundsAvailable]);
|
||||
|
||||
/**
|
||||
* Validates the set a user tries to apply to.
|
||||
@@ -140,15 +140,6 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
||||
if (code) setValue('code', code);
|
||||
}, [params, setValue]);
|
||||
|
||||
useEffect(() => {
|
||||
const err = validateFundsAvailable();
|
||||
if (err !== true) {
|
||||
setStatus('no-funds');
|
||||
} else {
|
||||
setStatus(null);
|
||||
}
|
||||
}, [isEligible, validateFundsAvailable]);
|
||||
|
||||
const onSubmit = ({ code }: FieldValues) => {
|
||||
if (isReadOnly || !pubKey || !code || code.length === 0) {
|
||||
return;
|
||||
@@ -332,26 +323,10 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
|
||||
</label>
|
||||
<RainbowButton variant="border" {...getButtonProps()} />
|
||||
</form>
|
||||
{status === 'no-funds' ? (
|
||||
<InputError intent="warning" className="overflow-auto break-words">
|
||||
<span>
|
||||
<SpamProtectionErr requiredFunds={requiredFunds?.toString()} />
|
||||
</span>
|
||||
{errors.code && (
|
||||
<InputError className="overflow-auto break-words">
|
||||
{errors.code.message?.toString()}
|
||||
</InputError>
|
||||
) : (
|
||||
errors.code && (
|
||||
<InputError intent="warning" className="overflow-auto break-words">
|
||||
{errors.code.message === SPAM_PROTECTION_ERR ? (
|
||||
<span>
|
||||
<SpamProtectionErr
|
||||
requiredFunds={requiredFunds?.toString()}
|
||||
/>
|
||||
</span>
|
||||
) : (
|
||||
errors.code.message?.toString()
|
||||
)}
|
||||
</InputError>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
{validateCode(codeField, t) === true && previewLoading && !previewData ? (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useFundsAvailableQuery } from './__generated__/FundsAvailable';
|
||||
import compact from 'lodash/compact';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import sum from 'lodash/sum';
|
||||
|
||||
/**
|
||||
* Gets the funds for given public key and required min for
|
||||
@@ -24,16 +24,14 @@ export const useFundsAvailable = (pubKey?: string) => {
|
||||
? compact(data.party?.accountsConnection?.edges?.map((e) => e?.node))
|
||||
: undefined;
|
||||
const requiredFunds = data
|
||||
? BigNumber(data.networkParameter?.value || '0')
|
||||
? BigInt(data.networkParameter?.value || '0')
|
||||
: undefined;
|
||||
|
||||
const sumOfFunds =
|
||||
fundsAvailable
|
||||
?.filter((fa) => fa.balance)
|
||||
.reduce((sum, fa) => sum.plus(BigNumber(fa.balance)), BigNumber(0)) ||
|
||||
BigNumber(0);
|
||||
const sumOfFunds = sum(
|
||||
fundsAvailable?.filter((fa) => fa.balance).map((fa) => BigInt(fa.balance))
|
||||
);
|
||||
|
||||
if (requiredFunds && sumOfFunds.isGreaterThanOrEqualTo(requiredFunds)) {
|
||||
if (requiredFunds && sumOfFunds >= requiredFunds) {
|
||||
stopPolling();
|
||||
}
|
||||
|
||||
@@ -43,6 +41,6 @@ export const useFundsAvailable = (pubKey?: string) => {
|
||||
isEligible:
|
||||
fundsAvailable != null &&
|
||||
requiredFunds != null &&
|
||||
sumOfFunds.isGreaterThanOrEqualTo(requiredFunds),
|
||||
sumOfFunds >= requiredFunds,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ export const LandingBanner = () => {
|
||||
</div>
|
||||
<div className="pt-20 sm:w-[50%]">
|
||||
<h1 className="text-6xl font-alpha calt mb-10">
|
||||
{t('Vega community referrals')}
|
||||
{t('Vega community referral program')}
|
||||
</h1>
|
||||
<p className="text-lg mb-1">
|
||||
{t(
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
import { ExternalLink, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import { useT } from '../../lib/use-t';
|
||||
|
||||
export const QUSDTooltip = () => {
|
||||
const t = useT();
|
||||
return (
|
||||
<Tooltip
|
||||
description={
|
||||
<>
|
||||
<p className="mb-1">
|
||||
{t(
|
||||
'qUSD provides a rough USD equivalent of balances across all assets using the value of "Quantum" for that asset'
|
||||
)}
|
||||
</p>
|
||||
{DocsLinks && (
|
||||
<ExternalLink href={DocsLinks.QUANTUM}>
|
||||
{t('Find out more')}
|
||||
</ExternalLink>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
underline={true}
|
||||
>
|
||||
<span>{t('qUSD')}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
truncateMiddle,
|
||||
ExternalLink,
|
||||
Tooltip,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
@@ -26,10 +28,10 @@ import sortBy from 'lodash/sortBy';
|
||||
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCurrentEpochInfoQuery } from './hooks/__generated__/Epoch';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
import { useT, ns } from '../../lib/use-t';
|
||||
import { Trans } from 'react-i18next';
|
||||
import { ApplyCodeForm, ApplyCodeFormContainer } from './apply-code-form';
|
||||
import { QUSDTooltip } from './qusd-tooltip';
|
||||
|
||||
export const ReferralStatistics = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
@@ -212,7 +214,6 @@ export const Statistics = ({
|
||||
).toString(),
|
||||
}
|
||||
)}
|
||||
testId="base-commission-rate"
|
||||
overrideWithNoProgram={!details}
|
||||
>
|
||||
{baseCommissionValue * 100}%
|
||||
@@ -222,7 +223,6 @@ export const Statistics = ({
|
||||
const stakingMultiplierTile = (
|
||||
<StatTile
|
||||
title={t('Staking multiplier')}
|
||||
testId="staking-multiplier"
|
||||
description={
|
||||
<span
|
||||
className={classNames({
|
||||
@@ -256,7 +256,6 @@ export const Statistics = ({
|
||||
? `(${baseCommissionFormatted}% ⨉ ${multiplier} = ${finalCommissionFormatted}%)`
|
||||
: undefined
|
||||
}
|
||||
testId="final-commission-rate"
|
||||
overrideWithNoProgram={!details}
|
||||
>
|
||||
{finalCommissionFormatted}%
|
||||
@@ -264,9 +263,7 @@ export const Statistics = ({
|
||||
);
|
||||
const numberOfTradersValue = data.referees.length;
|
||||
const numberOfTradersTile = (
|
||||
<StatTile title={t('Number of traders')} testId="number-of-traders">
|
||||
{numberOfTradersValue}
|
||||
</StatTile>
|
||||
<StatTile title={t('Number of traders')}>{numberOfTradersValue}</StatTile>
|
||||
);
|
||||
|
||||
const codeTile = (
|
||||
@@ -281,7 +278,6 @@ export const Statistics = ({
|
||||
title={t('myVolume', 'My volume (last {{count}} epochs)', {
|
||||
count: details?.windowLength || DEFAULT_AGGREGATION_DAYS,
|
||||
})}
|
||||
testId="my-volume"
|
||||
overrideWithNoProgram={!details}
|
||||
>
|
||||
{compactNumFormat.format(referrerVolumeValue)}
|
||||
@@ -297,7 +293,6 @@ export const Statistics = ({
|
||||
count: details?.windowLength || DEFAULT_AGGREGATION_DAYS,
|
||||
})}
|
||||
description={<QUSDTooltip />}
|
||||
testId="total-commission"
|
||||
>
|
||||
{getNumberFormat(0).format(Number(totalCommissionValue))}
|
||||
</StatTile>
|
||||
@@ -323,7 +318,6 @@ export const Statistics = ({
|
||||
const currentBenefitTierTile = (
|
||||
<StatTile
|
||||
title={t('Current tier')}
|
||||
testId="current-tier"
|
||||
description={
|
||||
nextBenefitTierValue?.tier
|
||||
? t('(Next tier: {{nextTier}})', {
|
||||
@@ -339,11 +333,7 @@ export const Statistics = ({
|
||||
</StatTile>
|
||||
);
|
||||
const discountFactorTile = (
|
||||
<StatTile
|
||||
title={t('Discount')}
|
||||
testId="discount"
|
||||
overrideWithNoProgram={!details}
|
||||
>
|
||||
<StatTile title={t('Discount')} overrideWithNoProgram={!details}>
|
||||
{isApplyCodePreview && benefitTiers.length >= 1
|
||||
? benefitTiers[0].discountFactor * 100
|
||||
: discountFactorValue * 100}
|
||||
@@ -359,34 +349,23 @@ export const Statistics = ({
|
||||
count: details?.windowLength,
|
||||
}
|
||||
)}
|
||||
testId="combined-volume"
|
||||
overrideWithNoProgram={!details}
|
||||
>
|
||||
{compactNumFormat.format(runningVolumeValue)}
|
||||
</StatTile>
|
||||
);
|
||||
const epochsTile = (
|
||||
<StatTile title={t('Epochs in set')} testId="epochs-in-set">
|
||||
{epochsValue}
|
||||
</StatTile>
|
||||
<StatTile title={t('Epochs in set')}>{epochsValue}</StatTile>
|
||||
);
|
||||
const nextTierVolumeTile = (
|
||||
<StatTile
|
||||
title={t('Volume to next tier')}
|
||||
testId="vol-to-next-tier"
|
||||
overrideWithNoProgram={!details}
|
||||
>
|
||||
<StatTile title={t('Volume to next tier')} overrideWithNoProgram={!details}>
|
||||
{nextBenefitTierVolumeValue <= 0
|
||||
? '0'
|
||||
: compactNumFormat.format(nextBenefitTierVolumeValue)}
|
||||
</StatTile>
|
||||
);
|
||||
const nextTierEpochsTile = (
|
||||
<StatTile
|
||||
title={t('Epochs to next tier')}
|
||||
testId="epochs-to-next-tier"
|
||||
overrideWithNoProgram={!details}
|
||||
>
|
||||
<StatTile title={t('Epochs to next tier')} overrideWithNoProgram={!details}>
|
||||
{nextBenefitTierEpochsValue <= 0 ? '0' : nextBenefitTierEpochsValue}
|
||||
</StatTile>
|
||||
);
|
||||
@@ -540,3 +519,28 @@ export const RefereesTable = ({
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const QUSDTooltip = () => {
|
||||
const t = useT();
|
||||
return (
|
||||
<Tooltip
|
||||
description={
|
||||
<>
|
||||
<p className="mb-1">
|
||||
{t(
|
||||
'qUSD provides a rough USD equivalent of balances across all assets using the value of "Quantum" for that asset'
|
||||
)}
|
||||
</p>
|
||||
{DocsLinks && (
|
||||
<ExternalLink href={DocsLinks.QUANTUM}>
|
||||
{t('Find out more')}
|
||||
</ExternalLink>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
underline={true}
|
||||
>
|
||||
<span>{t('qUSD')}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -159,11 +159,11 @@ export const TiersContainer = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 className="text-3xl mt-10">{t('Current program details')}</h2>
|
||||
<h2 className="text-3xl mt-10">{t('Current Program Details')}</h2>
|
||||
{details?.id && (
|
||||
<p>
|
||||
<Trans
|
||||
defaults="As a result of governance proposal <0>{{proposal}}</0> the program below is currently active on the Vega network."
|
||||
defaults="As a result of <0>{{proposal}}</0> the program below is currently active on the Vega network."
|
||||
values={{ proposal: truncateMiddle(details.id) }}
|
||||
components={[
|
||||
<ExternalLink
|
||||
|
||||
@@ -32,7 +32,6 @@ export const Tile = ({
|
||||
|
||||
type StatTileProps = {
|
||||
title: string;
|
||||
testId?: string;
|
||||
description?: ReactNode;
|
||||
children?: ReactNode;
|
||||
overrideWithNoProgram?: boolean;
|
||||
@@ -41,7 +40,6 @@ export const StatTile = ({
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
testId,
|
||||
overrideWithNoProgram = false,
|
||||
}: StatTileProps) => {
|
||||
if (overrideWithNoProgram) {
|
||||
@@ -49,15 +47,10 @@ export const StatTile = ({
|
||||
}
|
||||
return (
|
||||
<Tile>
|
||||
<h3
|
||||
data-testid={testId}
|
||||
className="mb-1 text-sm text-vega-clight-100 dark:text-vega-cdark-100 calt"
|
||||
>
|
||||
<h3 className="mb-1 text-sm text-vega-clight-100 dark:text-vega-cdark-100 calt">
|
||||
{title}
|
||||
</h3>
|
||||
<div data-testid={`${testId}-value`} className="text-5xl text-left">
|
||||
{children}
|
||||
</div>
|
||||
<div className="text-5xl text-left">{children}</div>
|
||||
{description && (
|
||||
<div className="text-sm text-left text-vega-clight-100 dark:text-vega-cdark-100">
|
||||
{description}
|
||||
|
||||
@@ -13,7 +13,6 @@ import { ViewType, useSidebar } from '../sidebar';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { usePersistentDepositStore } from '@vegaprotocol/deposits';
|
||||
|
||||
export const AccountsContainer = ({
|
||||
pinnedAsset,
|
||||
@@ -26,7 +25,6 @@ export const AccountsContainer = ({
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const setViews = useSidebar((store) => store.setViews);
|
||||
const setDepositAsset = usePersistentDepositStore((store) => store.saveValue);
|
||||
|
||||
const gridStore = useAccountStore((store) => store.gridStore);
|
||||
const updateGridStore = useAccountStore((store) => store.updateGridStore);
|
||||
@@ -57,10 +55,7 @@ export const AccountsContainer = ({
|
||||
setViews({ type: ViewType.Withdraw, assetId }, currentRouteId);
|
||||
}}
|
||||
onClickDeposit={(assetId) => {
|
||||
setViews({ type: ViewType.Deposit }, currentRouteId);
|
||||
if (assetId) {
|
||||
setDepositAsset({ assetId });
|
||||
}
|
||||
setViews({ type: ViewType.Deposit, assetId }, currentRouteId);
|
||||
}}
|
||||
onClickTransfer={(assetId) => {
|
||||
setViews({ type: ViewType.Transfer, assetId }, currentRouteId);
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { ChartContainer } from './chart-container';
|
||||
import { useChartSettingsStore } from './use-chart-settings';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
|
||||
jest.mock('@vegaprotocol/candles-chart', () => ({
|
||||
...jest.requireActual('@vegaprotocol/candles-chart'),
|
||||
CandlesChartContainer: ({ marketId }: { marketId: string }) => (
|
||||
<div data-testid="pennant">{marketId}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('@vegaprotocol/trading-view', () => ({
|
||||
...jest.requireActual('@vegaprotocol/trading-view'),
|
||||
TradingViewContainer: ({ marketId }: { marketId: string }) => (
|
||||
<div data-testid="tradingview">{marketId}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
describe('ChartContainer', () => {
|
||||
it('renders pennant if no library path is set', () => {
|
||||
useChartSettingsStore.setState({
|
||||
chartlib: 'tradingview',
|
||||
});
|
||||
|
||||
useEnvironment.setState({
|
||||
CHARTING_LIBRARY_PATH: undefined,
|
||||
CHARTING_LIBRARY_HASH: undefined,
|
||||
});
|
||||
|
||||
const marketId = 'market-id';
|
||||
|
||||
render(<ChartContainer marketId={marketId} />);
|
||||
|
||||
expect(screen.getByTestId('pennant')).toHaveTextContent(marketId);
|
||||
});
|
||||
|
||||
it('renders trading view if library path is set', () => {
|
||||
useChartSettingsStore.setState({
|
||||
chartlib: 'tradingview',
|
||||
});
|
||||
|
||||
useEnvironment.setState({
|
||||
CHARTING_LIBRARY_PATH: 'dummy-path',
|
||||
CHARTING_LIBRARY_HASH: 'hash',
|
||||
});
|
||||
|
||||
const marketId = 'market-id';
|
||||
|
||||
render(<ChartContainer marketId={marketId} />);
|
||||
|
||||
expect(screen.getByTestId('tradingview')).toHaveTextContent(marketId);
|
||||
});
|
||||
|
||||
it('renders pennant chart if stored in settings', () => {
|
||||
useChartSettingsStore.setState({
|
||||
chartlib: 'pennant',
|
||||
});
|
||||
|
||||
const marketId = 'market-id';
|
||||
|
||||
render(<ChartContainer marketId={marketId} />);
|
||||
|
||||
expect(screen.getByTestId('pennant')).toHaveTextContent(marketId);
|
||||
});
|
||||
});
|
||||
@@ -1,120 +0,0 @@
|
||||
import invert from 'lodash/invert';
|
||||
import { type Interval } from '@vegaprotocol/types';
|
||||
import {
|
||||
TradingViewContainer,
|
||||
ALLOWED_TRADINGVIEW_HOSTNAMES,
|
||||
TRADINGVIEW_INTERVAL_MAP,
|
||||
} from '@vegaprotocol/trading-view';
|
||||
import {
|
||||
CandlesChartContainer,
|
||||
PENNANT_INTERVAL_MAP,
|
||||
} from '@vegaprotocol/candles-chart';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useChartSettings, STUDY_SIZE } from './use-chart-settings';
|
||||
|
||||
/**
|
||||
* Renders either the pennant chart or the tradingview chart
|
||||
*/
|
||||
export const ChartContainer = ({ marketId }: { marketId: string }) => {
|
||||
const { CHARTING_LIBRARY_PATH, CHARTING_LIBRARY_HASH } = useEnvironment();
|
||||
|
||||
const {
|
||||
chartlib,
|
||||
interval,
|
||||
chartType,
|
||||
overlays,
|
||||
studies,
|
||||
studySizes,
|
||||
tradingViewStudies,
|
||||
setInterval,
|
||||
setStudies,
|
||||
setStudySizes,
|
||||
setOverlays,
|
||||
setTradingViewStudies,
|
||||
} = useChartSettings();
|
||||
|
||||
const pennantChart = (
|
||||
<CandlesChartContainer
|
||||
marketId={marketId}
|
||||
interval={toPennantInterval(interval)}
|
||||
chartType={chartType}
|
||||
overlays={overlays}
|
||||
studies={studies}
|
||||
studySizes={studySizes}
|
||||
setStudySizes={setStudySizes}
|
||||
setStudies={setStudies}
|
||||
setOverlays={setOverlays}
|
||||
defaultStudySize={STUDY_SIZE}
|
||||
/>
|
||||
);
|
||||
|
||||
if (!ALLOWED_TRADINGVIEW_HOSTNAMES.includes(window.location.hostname)) {
|
||||
return pennantChart;
|
||||
}
|
||||
|
||||
if (!CHARTING_LIBRARY_PATH || !CHARTING_LIBRARY_HASH) {
|
||||
return pennantChart;
|
||||
}
|
||||
|
||||
switch (chartlib) {
|
||||
case 'tradingview': {
|
||||
return (
|
||||
<TradingViewContainer
|
||||
libraryPath={CHARTING_LIBRARY_PATH}
|
||||
libraryHash={CHARTING_LIBRARY_HASH}
|
||||
marketId={marketId}
|
||||
interval={toTradingViewResolution(interval)}
|
||||
studies={tradingViewStudies}
|
||||
onIntervalChange={(newInterval) => {
|
||||
setInterval(fromTradingViewResolution(newInterval));
|
||||
}}
|
||||
onAutoSaveNeeded={(data: { studies: string[] }) => {
|
||||
setTradingViewStudies(data.studies);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'pennant': {
|
||||
return pennantChart;
|
||||
}
|
||||
default: {
|
||||
throw new Error('invalid chart lib');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const toTradingViewResolution = (interval: Interval) => {
|
||||
const resolution = TRADINGVIEW_INTERVAL_MAP[interval];
|
||||
|
||||
if (!resolution) {
|
||||
throw new Error(
|
||||
`failed to convert interval: ${interval} to valid resolution`
|
||||
);
|
||||
}
|
||||
|
||||
return resolution;
|
||||
};
|
||||
|
||||
const fromTradingViewResolution = (resolution: string) => {
|
||||
const interval = invert(TRADINGVIEW_INTERVAL_MAP)[resolution];
|
||||
|
||||
if (!interval) {
|
||||
throw new Error(
|
||||
`failed to convert resolution: ${resolution} to valid interval`
|
||||
);
|
||||
}
|
||||
|
||||
return interval as Interval;
|
||||
};
|
||||
|
||||
const toPennantInterval = (interval: Interval) => {
|
||||
const pennantInterval = PENNANT_INTERVAL_MAP[interval];
|
||||
|
||||
if (!pennantInterval) {
|
||||
throw new Error(
|
||||
`failed to convert interval: ${interval} to valid pennant interval`
|
||||
);
|
||||
}
|
||||
|
||||
return pennantInterval;
|
||||
};
|
||||
@@ -1,140 +0,0 @@
|
||||
import { render, screen, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { ChartMenu } from './chart-menu';
|
||||
import {
|
||||
useChartSettingsStore,
|
||||
DEFAULT_CHART_SETTINGS,
|
||||
} from './use-chart-settings';
|
||||
import { Overlay, Study, overlayLabels, studyLabels } from 'pennant';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
|
||||
describe('ChartMenu', () => {
|
||||
it('doesnt show trading view option if library path undefined', () => {
|
||||
useEnvironment.setState({ CHARTING_LIBRARY_PATH: undefined });
|
||||
|
||||
render(<ChartMenu />);
|
||||
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'TradingView' })
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'Vega chart' })
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('can switch between charts if library path', async () => {
|
||||
useEnvironment.setState({ CHARTING_LIBRARY_PATH: 'dummy' });
|
||||
|
||||
render(<ChartMenu />);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'TradingView' }));
|
||||
expect(useChartSettingsStore.getState().chartlib).toEqual('tradingview');
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Vega chart' }));
|
||||
expect(useChartSettingsStore.getState().chartlib).toEqual('pennant');
|
||||
});
|
||||
|
||||
describe('tradingview', () => {
|
||||
beforeEach(() => {
|
||||
useEnvironment.setState({ CHARTING_LIBRARY_PATH: 'dummy-path' });
|
||||
|
||||
// clear store each time to avoid conditional testing of defaults
|
||||
useChartSettingsStore.setState({
|
||||
chartlib: 'tradingview',
|
||||
});
|
||||
});
|
||||
|
||||
it('only shows chartlib switch and attribution', () => {
|
||||
render(<ChartMenu />);
|
||||
|
||||
const buttons = screen.getAllByRole('button');
|
||||
expect(buttons).toHaveLength(1);
|
||||
expect(buttons[0]).toHaveTextContent('Vega chart');
|
||||
|
||||
expect(screen.getByText('Chart by')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('pennant', () => {
|
||||
const openDropdown = async () => {
|
||||
await userEvent.click(
|
||||
screen.getByRole('button', {
|
||||
name: 'Indicators',
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// clear store each time to avoid conditional testing of defaults
|
||||
useChartSettingsStore.setState({
|
||||
chartlib: 'pennant',
|
||||
overlays: [],
|
||||
studies: [],
|
||||
});
|
||||
});
|
||||
|
||||
it.each(Object.values(Overlay))('can set %s overlay', async (overlay) => {
|
||||
render(<ChartMenu />);
|
||||
|
||||
await openDropdown();
|
||||
|
||||
const menu = within(await screen.findByRole('menu'));
|
||||
|
||||
await userEvent.click(menu.getByText(overlayLabels[overlay as Overlay]));
|
||||
|
||||
// re-open the dropdown
|
||||
await openDropdown();
|
||||
|
||||
expect(
|
||||
screen.getByText(overlayLabels[overlay as Overlay])
|
||||
).toHaveAttribute('data-state', 'checked');
|
||||
});
|
||||
|
||||
it.each(Object.values(Study))('can set %s study', async (study) => {
|
||||
render(<ChartMenu />);
|
||||
|
||||
await openDropdown();
|
||||
|
||||
const menu = within(await screen.findByRole('menu'));
|
||||
|
||||
await userEvent.click(menu.getByText(studyLabels[study as Study]));
|
||||
|
||||
// re-open the dropdown
|
||||
await openDropdown();
|
||||
|
||||
expect(screen.getByText(studyLabels[study as Study])).toHaveAttribute(
|
||||
'data-state',
|
||||
'checked'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render with the correct default studies and overlays', async () => {
|
||||
useChartSettingsStore.setState({
|
||||
...DEFAULT_CHART_SETTINGS,
|
||||
chartlib: 'pennant',
|
||||
});
|
||||
|
||||
render(<ChartMenu />);
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByRole('button', {
|
||||
name: 'Indicators',
|
||||
})
|
||||
);
|
||||
const menu = within(await screen.findByRole('menu'));
|
||||
|
||||
expect(menu.getByText(studyLabels.volume)).toHaveAttribute(
|
||||
'data-state',
|
||||
'checked'
|
||||
);
|
||||
expect(menu.getByText(studyLabels.macd)).toHaveAttribute(
|
||||
'data-state',
|
||||
'checked'
|
||||
);
|
||||
expect(menu.getByText(overlayLabels.movingAverage)).toHaveAttribute(
|
||||
'data-state',
|
||||
'checked'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,2 +0,0 @@
|
||||
export { ChartContainer } from './chart-container';
|
||||
export { ChartMenu } from './chart-menu';
|
||||
@@ -1,23 +1,17 @@
|
||||
import { useEffect } from 'react';
|
||||
import { matchPath, useLocation } from 'react-router-dom';
|
||||
import { useMatch } from 'react-router-dom';
|
||||
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { VegaConnectDialog } from '@vegaprotocol/wallet';
|
||||
import { useConnectors } from '../../lib/vega-connectors';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { Routes } from '../../lib/links';
|
||||
import { Links } from '../../lib/links';
|
||||
import { RiskMessage } from './risk-message';
|
||||
import { WelcomeDialogContent } from './welcome-dialog-content';
|
||||
import { useOnboardingStore } from './use-get-onboarding-step';
|
||||
import { ensureSuffix } from '@vegaprotocol/utils';
|
||||
|
||||
/**
|
||||
* A list of paths on which the welcome dialog should be omitted.
|
||||
*/
|
||||
const OMIT_ON_LIST = [ensureSuffix(Routes.REFERRALS, '/*')];
|
||||
|
||||
export const WelcomeDialog = () => {
|
||||
const { pathname } = useLocation();
|
||||
const isReferrals = useMatch(Links.REFERRALS());
|
||||
const t = useT();
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const connectors = useConnectors();
|
||||
@@ -33,14 +27,10 @@ export const WelcomeDialog = () => {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const shouldOmit = OMIT_ON_LIST.map((path) =>
|
||||
matchPath(path, pathname)
|
||||
).some((m) => !!m);
|
||||
|
||||
if (dismissed || shouldOmit) return;
|
||||
|
||||
if (dismissed) return;
|
||||
if (isReferrals) return;
|
||||
setDialogOpen(true);
|
||||
}, [dismissed, pathname, setDialogOpen]);
|
||||
}, [dismissed, isReferrals, setDialogOpen]);
|
||||
|
||||
const content = walletDialogOpen ? (
|
||||
<VegaConnectDialog
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest
|
||||
VEGA_VERSION=v0.73.9
|
||||
VEGA_VERSION=v0.73.8
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
CONSOLE_IMAGE_NAME=vegaprotocol/trading:develop
|
||||
VEGA_VERSION=v0.73.9
|
||||
VEGA_VERSION=v0.73.8
|
||||
|
||||
@@ -87,9 +87,10 @@ docker build -f docker/node-outside-docker.Dockerfile --build-arg APP=trading --
|
||||
|
||||
## Running Tests 🧪
|
||||
|
||||
Before running make sure the docker daemon is running.
|
||||
Before running make sure the docker daemon is runnign so that the app can be served.
|
||||
|
||||
To run a specific test, use the `-k` option followed by the name of the test.
|
||||
|
||||
Run all tests:
|
||||
|
||||
```bash
|
||||
@@ -108,25 +109,6 @@ Run from anywhere:
|
||||
yarn trading:test -- "test_name" -s --headed
|
||||
```
|
||||
|
||||
Run using your locally served console:
|
||||
|
||||
Within one terminal
|
||||
|
||||
```bash
|
||||
yarn nx build trading
|
||||
```
|
||||
|
||||
```bash
|
||||
yarn nx serve trading
|
||||
|
||||
```
|
||||
|
||||
Once console is served you can use the flag --local-server
|
||||
|
||||
```bash
|
||||
poetry run pytest -k "test_name" -s --headed --local-server
|
||||
```
|
||||
|
||||
## Running Tests in Parallel 🔢
|
||||
|
||||
To run tests in parallel, use the `--numprocesses auto` option. The `--dist loadfile` setting ensures that multiple runners are not assigned to a single test file.
|
||||
|
||||
@@ -6,27 +6,23 @@ from typing import Optional
|
||||
WalletConfig = namedtuple("WalletConfig", ["name", "passphrase"])
|
||||
ASSET_NAME = "tDAI"
|
||||
|
||||
|
||||
def wait_for_toast_confirmation(page: Page, timeout: int = 30000):
|
||||
page.wait_for_function("""
|
||||
document.querySelector('[data-testid="toast-content"]') &&
|
||||
document.querySelector('[data-testid="toast-content"]').innerText.includes('AWAITING CONFIRMATION')
|
||||
""", timeout=timeout)
|
||||
|
||||
|
||||
def create_and_faucet_wallet(
|
||||
vega: VegaServiceNull,
|
||||
wallet: WalletConfig,
|
||||
symbol: Optional[str] = None,
|
||||
amount: float = 1e4,
|
||||
|
||||
|
||||
):
|
||||
asset_id = vega.find_asset_id(
|
||||
symbol=symbol if symbol is not None else ASSET_NAME)
|
||||
asset_id = vega.find_asset_id(symbol=symbol if symbol is not None else ASSET_NAME)
|
||||
vega.create_key(wallet.name)
|
||||
vega.mint(wallet.name, asset_id, amount)
|
||||
|
||||
|
||||
def next_epoch(vega: VegaServiceNull):
|
||||
forwards = 0
|
||||
epoch_seq = vega.statistics().epoch_seq
|
||||
@@ -40,34 +36,13 @@ def next_epoch(vega: VegaServiceNull):
|
||||
vega.wait_fn(1)
|
||||
vega.wait_for_total_catchup()
|
||||
|
||||
|
||||
def truncate_middle(market_id, start=6, end=4):
|
||||
if len(market_id) < 11:
|
||||
return market_id
|
||||
return market_id[:start] + '\u2026' + market_id[-end:]
|
||||
|
||||
|
||||
def change_keys(page: Page, vega: VegaServiceNull, key_name):
|
||||
def change_keys(page: Page, vega:VegaServiceNull, key_name):
|
||||
page.get_by_test_id("manage-vega-wallet").click()
|
||||
page.get_by_test_id("key-" + vega.wallet.public_key(key_name)).click()
|
||||
page.click(
|
||||
f'data-testid=key-{vega.wallet.public_key(key_name)} >> .inline-flex')
|
||||
page.click(f'data-testid=key-{vega.wallet.public_key(key_name)} >> .inline-flex')
|
||||
page.reload()
|
||||
|
||||
|
||||
def forward_time(vega: VegaServiceNull, forward_epoch: bool = False):
|
||||
vega.wait_fn(1)
|
||||
vega.wait_for_total_catchup()
|
||||
|
||||
if forward_epoch:
|
||||
next_epoch(vega)
|
||||
|
||||
|
||||
# This is for when the element will initially load but contain an outdated value. It will wait for the element to contain the expected text, returning False after a timeout or exception
|
||||
def selector_contains_text(page: Page, selector, expected_text, timeout=5000):
|
||||
try:
|
||||
page.wait_for_selector(
|
||||
f'{selector} >> text={expected_text}', timeout=timeout)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from typing import List, Tuple, Optional
|
||||
from vega_sim.service import VegaService, PeggedOrder
|
||||
|
||||
|
||||
def submit_order(
|
||||
vega: VegaService,
|
||||
wallet_name: str,
|
||||
@@ -36,7 +35,7 @@ def submit_multiple_orders(
|
||||
submit_order(vega, wallet_name, market_id, side, volume, price)
|
||||
|
||||
|
||||
def submit_liquidity(vega: VegaService, wallet_name: str, market_id: str, buy_vol=99, sell_vol=99, custom_price=None):
|
||||
def submit_liquidity(vega: VegaService, wallet_name: str, market_id: str):
|
||||
vega.submit_simple_liquidity(
|
||||
key_name=wallet_name,
|
||||
market_id=market_id,
|
||||
@@ -52,7 +51,7 @@ def submit_liquidity(vega: VegaService, wallet_name: str, market_id: str, buy_vo
|
||||
pegged_order=PeggedOrder(reference="PEGGED_REFERENCE_MID", offset=1),
|
||||
wait=False,
|
||||
time_in_force="TIME_IN_FORCE_GTC",
|
||||
volume=buy_vol,
|
||||
volume=99,
|
||||
)
|
||||
vega.submit_order(
|
||||
market_id=market_id,
|
||||
@@ -62,5 +61,5 @@ def submit_liquidity(vega: VegaService, wallet_name: str, market_id: str, buy_vo
|
||||
pegged_order=PeggedOrder(reference="PEGGED_REFERENCE_MID", offset=1),
|
||||
wait=False,
|
||||
time_in_force="TIME_IN_FORCE_GTC",
|
||||
volume=sell_vol,
|
||||
)
|
||||
volume=99,
|
||||
)
|
||||
@@ -7,6 +7,7 @@ import time
|
||||
import docker
|
||||
import http.server
|
||||
|
||||
|
||||
from contextlib import contextmanager
|
||||
from vega_sim.null_service import VegaServiceNull
|
||||
from playwright.sync_api import Browser, Page
|
||||
@@ -101,23 +102,11 @@ def init_vega(request=None):
|
||||
logger.info(f"Removing container {container.id}")
|
||||
container.remove()
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption(
|
||||
"--local-server", action="store_true", default=False,
|
||||
help="Build and serve locally instead of using a container"
|
||||
)
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def local_server(pytestconfig):
|
||||
return pytestconfig.getoption("--local-server")
|
||||
|
||||
@contextmanager
|
||||
def init_page(vega: VegaServiceNull, browser: Browser, request: pytest.FixtureRequest, local_server: bool):
|
||||
server_port = "4200" if local_server else str(vega.console_port)
|
||||
def init_page(vega: VegaServiceNull, browser: Browser, request: pytest.FixtureRequest):
|
||||
with browser.new_context(
|
||||
viewport={"width": 1920, "height": 1080},
|
||||
base_url=f"http://localhost:{server_port}",
|
||||
base_url=f"http://localhost:{vega.console_port}",
|
||||
) as context, context.new_page() as page:
|
||||
context.tracing.start(screenshots=True, snapshots=True, sources=True)
|
||||
try:
|
||||
@@ -126,7 +115,7 @@ def init_page(vega: VegaServiceNull, browser: Browser, request: pytest.FixtureRe
|
||||
while attempts < 100:
|
||||
try:
|
||||
code = requests.get(
|
||||
f"http://localhost:{server_port}/"
|
||||
f"http://localhost:{vega.console_port}/"
|
||||
).status_code
|
||||
if code == 200:
|
||||
break
|
||||
@@ -172,8 +161,8 @@ def vega(request):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def page(vega, browser, request, local_server):
|
||||
with init_page(vega, browser, request, local_server) as page_instance:
|
||||
def page(vega, browser, request):
|
||||
with init_page(vega, browser, request) as page_instance:
|
||||
yield page_instance
|
||||
|
||||
|
||||
|
||||
@@ -8,17 +8,12 @@ logger = logging.getLogger()
|
||||
mint_amount: float = 10e5
|
||||
market_name = "BTC:DAI_2023"
|
||||
|
||||
default_sell_orders = [[1, 110], [1, 105]]
|
||||
default_buy_orders = [[1, 90], [1, 95]]
|
||||
|
||||
|
||||
def setup_simple_market(
|
||||
vega: VegaService,
|
||||
approve_proposal=True,
|
||||
custom_market_name=market_name,
|
||||
custom_asset_name="tDAI",
|
||||
custom_asset_symbol="tDAI",
|
||||
custom_quantum=1
|
||||
):
|
||||
for wallet in wallets:
|
||||
vega.create_key(wallet.name)
|
||||
@@ -42,7 +37,6 @@ def setup_simple_market(
|
||||
symbol=custom_asset_symbol,
|
||||
decimals=5,
|
||||
max_faucet_amount=1e10,
|
||||
quantum=custom_quantum,
|
||||
)
|
||||
vega.wait_fn(1)
|
||||
vega.wait_for_total_catchup()
|
||||
@@ -117,17 +111,16 @@ def setup_simple_successor_market(
|
||||
return market_id
|
||||
|
||||
|
||||
def setup_opening_auction_market(vega: VegaService, market_id: str = None, buy_orders=default_buy_orders, sell_orders=default_sell_orders, add_liquidity=True, **kwargs):
|
||||
if not market_exists(vega, market_id):
|
||||
def setup_opening_auction_market(vega: VegaService, market_id: str = None, **kwargs):
|
||||
if market_id is None or market_id not in vega.all_markets():
|
||||
market_id = setup_simple_market(vega, **kwargs)
|
||||
|
||||
if add_liquidity:
|
||||
submit_liquidity(vega, MM_WALLET.name, market_id)
|
||||
submit_liquidity(vega, MM_WALLET.name, market_id)
|
||||
submit_multiple_orders(
|
||||
vega, MM_WALLET.name, market_id, "SIDE_SELL", sell_orders
|
||||
vega, MM_WALLET.name, market_id, "SIDE_SELL", [[1, 110], [1, 105]]
|
||||
)
|
||||
submit_multiple_orders(
|
||||
vega, MM_WALLET2.name, market_id, "SIDE_BUY", buy_orders
|
||||
vega, MM_WALLET2.name, market_id, "SIDE_BUY", [[1, 90], [1, 95]]
|
||||
)
|
||||
|
||||
vega.forward("10s")
|
||||
@@ -137,22 +130,11 @@ def setup_opening_auction_market(vega: VegaService, market_id: str = None, buy_o
|
||||
return market_id
|
||||
|
||||
|
||||
def market_exists(vega: VegaService, market_id: str):
|
||||
if market_id is None:
|
||||
return False
|
||||
all_markets = vega.all_markets()
|
||||
market_ids = [market.id for market in all_markets]
|
||||
return market_id in market_ids
|
||||
def setup_continuous_market(vega: VegaService, market_id: str = None, **kwargs):
|
||||
if market_id is None or market_id not in vega.all_markets():
|
||||
market_id = setup_opening_auction_market(vega, **kwargs)
|
||||
|
||||
|
||||
# Add sell orders and buy orders to put on the book
|
||||
def setup_continuous_market(vega: VegaService, market_id: str = None, buy_orders=default_buy_orders, sell_orders=default_sell_orders, add_liquidity=True, **kwargs):
|
||||
if not market_exists(vega, market_id) or buy_orders != default_buy_orders or sell_orders != default_sell_orders:
|
||||
market_id = setup_opening_auction_market(
|
||||
vega, market_id, buy_orders, sell_orders, add_liquidity, **kwargs)
|
||||
|
||||
submit_order(vega, "Key 1", market_id, "SIDE_BUY",
|
||||
sell_orders[0][0], sell_orders[0][1])
|
||||
submit_order(vega, "Key 1", market_id, "SIDE_BUY", 1, 110)
|
||||
|
||||
vega.forward("10s")
|
||||
vega.wait_fn(1)
|
||||
@@ -160,7 +142,6 @@ def setup_continuous_market(vega: VegaService, market_id: str = None, buy_orders
|
||||
|
||||
return market_id
|
||||
|
||||
|
||||
def setup_perps_market(
|
||||
vega: VegaService,
|
||||
custom_asset_name="tDAI",
|
||||
@@ -229,7 +210,7 @@ def setup_perps_market(
|
||||
settlement_data_key=TERMINATE_WALLET.name,
|
||||
funding_payment_frequency_in_seconds=10,
|
||||
market_decimals=5,
|
||||
)
|
||||
)
|
||||
vega.wait_for_total_catchup()
|
||||
|
||||
submit_liquidity(vega, MM_WALLET.name, market_id)
|
||||
@@ -244,4 +225,4 @@ def setup_perps_market(
|
||||
vega.wait_fn(1)
|
||||
vega.wait_for_total_catchup()
|
||||
|
||||
return market_id
|
||||
return market_id
|
||||
@@ -17,8 +17,8 @@ def vega():
|
||||
|
||||
# setting up everything in this single fixture, as all of the tests need the same setup, so no point in creating separate ones
|
||||
@pytest.fixture(scope="module")
|
||||
def page(vega, browser, request, local_server):
|
||||
with init_page(vega, browser, request, local_server) as page:
|
||||
def page(vega, browser, request):
|
||||
with init_page(vega, browser, request) as page:
|
||||
setup_continuous_market(vega)
|
||||
risk_accepted_setup(page)
|
||||
page.goto("/")
|
||||
|
||||
@@ -12,8 +12,8 @@ def vega(request):
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def page(vega, browser, request, local_server):
|
||||
with init_page(vega, browser, request, local_server) as page:
|
||||
def page(vega, browser, request):
|
||||
with init_page(vega, browser, request) as page:
|
||||
risk_accepted_setup(page)
|
||||
page.goto("/#/markets/all")
|
||||
yield page
|
||||
|
||||
@@ -12,8 +12,8 @@ def vega():
|
||||
|
||||
# we can reuse single page instance in all tests
|
||||
@pytest.fixture(scope="module")
|
||||
def page(vega, browser, request, local_server):
|
||||
with init_page(vega, browser, request, local_server) as page:
|
||||
def page(vega, browser, request):
|
||||
with init_page(vega, browser, request) as page:
|
||||
yield page
|
||||
|
||||
|
||||
|
||||
@@ -223,8 +223,8 @@ def markets(vega: VegaService):
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def page(vega, browser, request, local_server):
|
||||
with init_page(vega, browser, request, local_server) as page:
|
||||
def page(vega, browser, request):
|
||||
with init_page(vega, browser, request) as page:
|
||||
risk_accepted_setup(page)
|
||||
auth_setup(vega, page)
|
||||
page.goto("/")
|
||||
|
||||
@@ -131,10 +131,9 @@ def test_perps_market_terminated(page: Page, vega: VegaService):
|
||||
vega.wait_for_total_catchup()
|
||||
|
||||
page.goto(f"/#/markets/{perpetual_market}")
|
||||
# TODO cahnge back to have text once bug #5465 is fixed
|
||||
expect(page.get_by_test_id("market-price")).to_have_text("Mark Price100.00")
|
||||
expect(page.get_by_test_id("market-change")).to_contain_text("Change (24h)")
|
||||
expect(page.get_by_test_id("market-volume")).to_contain_text("Volume (24h)")
|
||||
expect(page.get_by_test_id("market-change")).to_have_text("Change (24h)-")
|
||||
expect(page.get_by_test_id("market-volume")).to_have_text("Volume (24h)-")
|
||||
expect(page.get_by_test_id("market-trading-mode")).to_have_text("Trading modeNo trading")
|
||||
expect(page.get_by_test_id("market-state")).to_have_text("StatusClosed")
|
||||
expect(page.get_by_test_id("liquidity-supplied")).to_have_text("Liquidity supplied 0.00 (0.00%)")
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
import pytest
|
||||
from playwright.sync_api import Page, expect
|
||||
from vega_sim.service import VegaService
|
||||
from conftest import init_vega
|
||||
from fixtures.market import setup_continuous_market, setup_simple_market
|
||||
from actions.utils import change_keys, create_and_faucet_wallet, forward_time, selector_contains_text
|
||||
from actions.vega import submit_order, submit_liquidity
|
||||
from wallet_config import MM_WALLET, PARTY_A, PARTY_B
|
||||
|
||||
SELL_ORDERS = [[1, 111], [1, 111], [1, 112], [1, 112], [
|
||||
1, 113], [1, 113], [1, 114], [1, 114], [1, 115], [1, 115]]
|
||||
BUY_ORDERS = [[1, 106], [1, 107], [1, 108]]
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega(request):
|
||||
with init_vega(request) as vega:
|
||||
yield vega
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def continuous_market(vega):
|
||||
market = setup_simple_market(vega, custom_quantum=100000)
|
||||
return setup_continuous_market(vega, market, BUY_ORDERS, SELL_ORDERS, add_liquidity=False)
|
||||
|
||||
|
||||
def generate_referrer_expected_value_dic(expected_base_commission, expected_staking_multiplier, expected_final_commission_rate, expected_volume, expected_num_traders, expected_total_commission):
|
||||
return {
|
||||
'[data-testid=my-volume-value]': expected_volume,
|
||||
'[data-testid=total-commission-value]': expected_total_commission,
|
||||
'[data-testid=base-commission-rate-value]': expected_base_commission,
|
||||
'[data-testid=number-of-traders-value]': expected_num_traders,
|
||||
'[data-testid=final-commission-rate-value]': expected_final_commission_rate,
|
||||
'[data-testid=staking-multiplier-value]': expected_staking_multiplier
|
||||
}
|
||||
|
||||
|
||||
def generate_referral_expected_value_dic(expected_volume, expected_tier, expected_discount, expected_epochs, expected_epochs_to_next_tier):
|
||||
return {
|
||||
'[data-testid=combined-volume-value]': expected_volume,
|
||||
'[data-testid=current-tier-value]': expected_tier,
|
||||
'[data-testid=discount-value]': expected_discount,
|
||||
'[data-testid=epochs-in-set-value]': expected_epochs,
|
||||
'[data-testid=epochs-to-next-tier-value]': expected_epochs_to_next_tier
|
||||
}
|
||||
|
||||
|
||||
def check_tile_values(page: Page, expected_results: dict):
|
||||
if "referrals" in page.url:
|
||||
page.reload()
|
||||
else:
|
||||
page.goto("/#/referrals/")
|
||||
|
||||
for selector, expected_text in expected_results.items():
|
||||
assert selector_contains_text(
|
||||
page, selector, expected_text), f"Expected text '{expected_text}' not found in selector '{selector}'"
|
||||
|
||||
|
||||
def create_benefit_tier(minimum_running_notional_taker_volume, minimum_epochs, referral_reward_factor, referral_discount_factor):
|
||||
return {
|
||||
"minimum_running_notional_taker_volume": minimum_running_notional_taker_volume,
|
||||
"minimum_epochs": minimum_epochs,
|
||||
"referral_reward_factor": referral_reward_factor,
|
||||
"referral_discount_factor": referral_discount_factor,
|
||||
}
|
||||
|
||||
|
||||
def create_staking_tier(minimum_staked_tokens, referral_reward_multiplier):
|
||||
return {
|
||||
"minimum_staked_tokens": minimum_staked_tokens,
|
||||
"referral_reward_multiplier": referral_reward_multiplier,
|
||||
}
|
||||
|
||||
|
||||
def setup_market_and_referral_scheme(vega: VegaService, continuous_market: str, page: Page):
|
||||
page.goto(f"/#/markets/{continuous_market}")
|
||||
|
||||
create_and_faucet_wallet(vega=vega, wallet=PARTY_A)
|
||||
create_and_faucet_wallet(vega=vega, wallet=PARTY_B)
|
||||
forward_time(vega)
|
||||
|
||||
benefit_tiers = []
|
||||
staking_tiers = []
|
||||
for i in range(1, 4):
|
||||
benefit_tiers.append(create_benefit_tier(
|
||||
i * 100, i, i * 0.01, i * 0.01))
|
||||
staking_tiers.append(create_staking_tier(
|
||||
i * 100, i))
|
||||
|
||||
vega.update_referral_program(
|
||||
proposal_key=MM_WALLET.name,
|
||||
benefit_tiers=benefit_tiers,
|
||||
staking_tiers=staking_tiers,
|
||||
window_length=1,
|
||||
)
|
||||
forward_time(vega, True)
|
||||
|
||||
vega.create_referral_set(key_name=PARTY_A.name)
|
||||
forward_time(vega, True)
|
||||
|
||||
referral_set_id = list(vega.list_referral_sets().keys())[0]
|
||||
vega.apply_referral_code(key_name=PARTY_B.name, id=referral_set_id)
|
||||
|
||||
tdai_id = vega.find_asset_id(symbol="tDAI")
|
||||
vega.mint(
|
||||
"Key 1",
|
||||
asset=tdai_id,
|
||||
amount=10e6,
|
||||
)
|
||||
vega.mint(
|
||||
PARTY_B.name,
|
||||
asset=tdai_id,
|
||||
amount=10e6,
|
||||
)
|
||||
|
||||
submit_liquidity(vega, MM_WALLET.name, continuous_market, 100, 100)
|
||||
forward_time(vega)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
|
||||
def test_can_traverse_up_and_down_through_tiers(continuous_market, vega: VegaService, page: Page):
|
||||
setup_market_and_referral_scheme(vega, continuous_market, page)
|
||||
change_keys(page, vega, PARTY_B.name)
|
||||
submit_order(vega, PARTY_B.name, continuous_market, "SIDE_BUY", 1, 115)
|
||||
forward_time(vega, True)
|
||||
check_tile_values(page, generate_referral_expected_value_dic(
|
||||
"110", "1", "1%", "1", "1"))
|
||||
|
||||
change_keys(page, vega, PARTY_A.name)
|
||||
check_tile_values(page, generate_referrer_expected_value_dic(
|
||||
"1%", "1", "1%", "0", "1", "0"))
|
||||
|
||||
change_keys(page, vega, PARTY_B.name)
|
||||
submit_order(vega, PARTY_B.name, continuous_market, "SIDE_BUY", 2, 115)
|
||||
forward_time(vega, True)
|
||||
check_tile_values(page, generate_referral_expected_value_dic(
|
||||
"221", "2", "2%", "2", "1"))
|
||||
|
||||
change_keys(page, vega, PARTY_A.name)
|
||||
check_tile_values(page, generate_referrer_expected_value_dic(
|
||||
"2%", "1", "2%", "0", "1", "0"))
|
||||
|
||||
change_keys(page, vega, PARTY_B.name)
|
||||
submit_order(vega, PARTY_B.name, continuous_market, "SIDE_BUY", 3, 115)
|
||||
forward_time(vega, True)
|
||||
check_tile_values(page, generate_referral_expected_value_dic(
|
||||
"331", "3", "3%", "3", "0"))
|
||||
|
||||
change_keys(page, vega, PARTY_A.name)
|
||||
check_tile_values(page, generate_referrer_expected_value_dic(
|
||||
"3%", "1", "3%", "0", "1", "1"))
|
||||
|
||||
change_keys(page, vega, PARTY_B.name)
|
||||
submit_order(vega, PARTY_B.name, continuous_market, "SIDE_BUY", 1, 115)
|
||||
forward_time(vega, True)
|
||||
check_tile_values(page, generate_referral_expected_value_dic(
|
||||
"110", "1", "1%", "4", "0"))
|
||||
|
||||
change_keys(page, vega, PARTY_A.name)
|
||||
check_tile_values(page, generate_referrer_expected_value_dic(
|
||||
"1%", "1", "1%", "0", "1", "1"))
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("page", "auth", "risk_accepted")
|
||||
def test_does_not_move_up_tiers_when_not_enough_epochs(continuous_market, vega: VegaService, page: Page):
|
||||
setup_market_and_referral_scheme(vega, continuous_market, page)
|
||||
change_keys(page, vega, PARTY_B.name)
|
||||
submit_order(vega, PARTY_B.name, continuous_market, "SIDE_BUY", 2, 115)
|
||||
forward_time(vega, True)
|
||||
check_tile_values(page, generate_referral_expected_value_dic(
|
||||
"221", "1", "1%", "1", "1"))
|
||||
|
||||
change_keys(page, vega, PARTY_A.name)
|
||||
check_tile_values(page, generate_referrer_expected_value_dic(
|
||||
"2%", "1", "2%", "0", "1", "0"))
|
||||
@@ -7,9 +7,6 @@ WalletConfig = namedtuple("WalletConfig", ["name", "passphrase"])
|
||||
MM_WALLET = WalletConfig("market_maker", "pin")
|
||||
MM_WALLET2 = WalletConfig("market_maker_2", "pin2")
|
||||
TERMINATE_WALLET = WalletConfig("FJMKnwfZdd48C8NqvYrG", "bY3DxwtsCstMIIZdNpKs")
|
||||
GOVERNANCE_WALLET = WalletConfig(
|
||||
"FJMKnwfZdd48C8NqvYrG", "bY3DxwtsCstMIIZdNpKs")
|
||||
PARTY_A = WalletConfig("party_a", "party_a")
|
||||
PARTY_B = WalletConfig("party_b", "party_b")
|
||||
GOVERNANCE_WALLET = WalletConfig("FJMKnwfZdd48C8NqvYrG", "bY3DxwtsCstMIIZdNpKs")
|
||||
|
||||
wallets = [MM_WALLET, MM_WALLET2, TERMINATE_WALLET, GOVERNANCE_WALLET]
|
||||
|
||||
@@ -4,18 +4,9 @@ export default {
|
||||
preset: '../../jest.preset.js',
|
||||
transform: {
|
||||
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest',
|
||||
'^.+\\.[tj]sx?$': [
|
||||
'babel-jest',
|
||||
{
|
||||
presets: ['@nx/next/babel'],
|
||||
// required for pennant to work in jest, due to having untranspiled exports
|
||||
plugins: [['@babel/plugin-proposal-private-methods']],
|
||||
},
|
||||
],
|
||||
'^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/next/babel'] }],
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
coverageDirectory: '../../coverage/apps/trading',
|
||||
setupFilesAfterEnv: ['./setup-tests.ts'],
|
||||
// dont ignore pennant from transpilation
|
||||
transformIgnorePatterns: ['<rootDir>/node_modules/pennant'],
|
||||
};
|
||||
|
||||
@@ -75,7 +75,6 @@ i18n
|
||||
'positions',
|
||||
'trades',
|
||||
'trading',
|
||||
'trading-view',
|
||||
'ui-toolkit',
|
||||
'utils',
|
||||
'wallet',
|
||||
|
||||
@@ -1,52 +1,43 @@
|
||||
import 'pennant/dist/style.css';
|
||||
import {
|
||||
CandlestickChart,
|
||||
type Overlay,
|
||||
type ChartType,
|
||||
type Interval,
|
||||
type Study,
|
||||
} from 'pennant';
|
||||
import { CandlestickChart } from 'pennant';
|
||||
import { VegaDataSource } from './data-source';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { useMemo } from 'react';
|
||||
import debounce from 'lodash/debounce';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
STUDY_SIZE,
|
||||
useCandlesChartSettings,
|
||||
} from './use-candles-chart-settings';
|
||||
import { useT } from './use-t';
|
||||
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export type CandlesChartContainerProps = {
|
||||
marketId: string;
|
||||
interval: Interval;
|
||||
chartType: ChartType;
|
||||
overlays: Overlay[];
|
||||
studies: Study[];
|
||||
studySizes: number[];
|
||||
defaultStudySize: number;
|
||||
setStudies: (studies?: Study[]) => void;
|
||||
setStudySizes: (sizes: number[]) => void;
|
||||
setOverlays: (overlays?: Overlay[]) => void;
|
||||
};
|
||||
|
||||
const CANDLES_TO_WIDTH_FACTOR = 0.2;
|
||||
|
||||
export const CandlesChartContainer = ({
|
||||
marketId,
|
||||
interval,
|
||||
chartType,
|
||||
overlays,
|
||||
studies,
|
||||
studySizes,
|
||||
defaultStudySize,
|
||||
setStudies,
|
||||
setStudySizes,
|
||||
setOverlays,
|
||||
}: CandlesChartContainerProps) => {
|
||||
const client = useApolloClient();
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { theme } = useThemeSwitcher();
|
||||
const t = useT();
|
||||
|
||||
const {
|
||||
interval,
|
||||
chartType,
|
||||
overlays,
|
||||
studies,
|
||||
studySizes,
|
||||
setStudies,
|
||||
setStudySizes,
|
||||
setOverlays,
|
||||
} = useCandlesChartSettings();
|
||||
|
||||
const handlePaneChange = useMemo(
|
||||
() =>
|
||||
debounce((sizes: number[]) => {
|
||||
@@ -78,7 +69,7 @@ export const CandlesChartContainer = ({
|
||||
</span>
|
||||
),
|
||||
initialNumCandlesToDisplay: candlesCount,
|
||||
studySize: defaultStudySize,
|
||||
studySize: STUDY_SIZE,
|
||||
studySizes,
|
||||
}}
|
||||
interval={interval}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { render, screen, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { CandlesMenu } from './candles-menu';
|
||||
import {
|
||||
useCandlesChartSettingsStore,
|
||||
DEFAULT_CHART_SETTINGS,
|
||||
} from './use-candles-chart-settings';
|
||||
import { Overlay, Study, overlayLabels, studyLabels } from 'pennant';
|
||||
|
||||
describe('CandlesMenu', () => {
|
||||
const openDropdown = async () => {
|
||||
await userEvent.click(
|
||||
screen.getByRole('button', {
|
||||
name: 'Indicators',
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// clear store each time to avoid conditional testing of defaults
|
||||
useCandlesChartSettingsStore.setState({ overlays: [], studies: [] });
|
||||
});
|
||||
|
||||
it.each(Object.values(Overlay))('can set %s overlay', async (overlay) => {
|
||||
render(<CandlesMenu />);
|
||||
|
||||
await openDropdown();
|
||||
|
||||
const menu = within(await screen.findByRole('menu'));
|
||||
|
||||
await userEvent.click(menu.getByText(overlayLabels[overlay as Overlay]));
|
||||
|
||||
// re-open the dropdown
|
||||
await openDropdown();
|
||||
|
||||
expect(screen.getByText(overlayLabels[overlay as Overlay])).toHaveAttribute(
|
||||
'data-state',
|
||||
'checked'
|
||||
);
|
||||
});
|
||||
|
||||
it.each(Object.values(Study))('can set %s study', async (study) => {
|
||||
render(<CandlesMenu />);
|
||||
|
||||
await openDropdown();
|
||||
|
||||
const menu = within(await screen.findByRole('menu'));
|
||||
|
||||
await userEvent.click(menu.getByText(studyLabels[study as Study]));
|
||||
|
||||
// re-open the dropdown
|
||||
await openDropdown();
|
||||
|
||||
expect(screen.getByText(studyLabels[study as Study])).toHaveAttribute(
|
||||
'data-state',
|
||||
'checked'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render with the correct default studies and overlays', async () => {
|
||||
useCandlesChartSettingsStore.setState(DEFAULT_CHART_SETTINGS);
|
||||
|
||||
render(<CandlesMenu />);
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByRole('button', {
|
||||
name: 'Indicators',
|
||||
})
|
||||
);
|
||||
const menu = within(await screen.findByRole('menu'));
|
||||
|
||||
expect(menu.getByText(studyLabels.volume)).toHaveAttribute(
|
||||
'data-state',
|
||||
'checked'
|
||||
);
|
||||
expect(menu.getByText(studyLabels.macd)).toHaveAttribute(
|
||||
'data-state',
|
||||
'checked'
|
||||
);
|
||||
expect(menu.getByText(overlayLabels.movingAverage)).toHaveAttribute(
|
||||
'data-state',
|
||||
'checked'
|
||||
);
|
||||
});
|
||||
});
|
||||
+14
-85
@@ -1,12 +1,14 @@
|
||||
import 'pennant/dist/style.css';
|
||||
import {
|
||||
ChartType,
|
||||
Interval,
|
||||
Overlay,
|
||||
Study,
|
||||
chartTypeLabels,
|
||||
intervalLabels,
|
||||
overlayLabels,
|
||||
studyLabels,
|
||||
} from 'pennant';
|
||||
import { Trans } from 'react-i18next';
|
||||
import {
|
||||
TradingButton,
|
||||
TradingDropdown,
|
||||
@@ -18,21 +20,10 @@ import {
|
||||
TradingDropdownTrigger,
|
||||
Icon,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { Interval } from '@vegaprotocol/types';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { ALLOWED_TRADINGVIEW_HOSTNAMES } from '@vegaprotocol/trading-view';
|
||||
import { IconNames, type IconName } from '@blueprintjs/icons';
|
||||
import { useChartSettings } from './use-chart-settings';
|
||||
import { useT } from '../../lib/use-t';
|
||||
|
||||
const INTERVALS = [
|
||||
Interval.INTERVAL_I1M,
|
||||
Interval.INTERVAL_I5M,
|
||||
Interval.INTERVAL_I15M,
|
||||
Interval.INTERVAL_I1H,
|
||||
Interval.INTERVAL_I6H,
|
||||
Interval.INTERVAL_I1D,
|
||||
];
|
||||
import { type IconName } from '@blueprintjs/icons';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import { useCandlesChartSettings } from './use-candles-chart-settings';
|
||||
import { useT } from './use-t';
|
||||
|
||||
const chartTypeIcon = new Map<ChartType, IconName>([
|
||||
[ChartType.AREA, IconNames.TIMELINE_AREA_CHART],
|
||||
@@ -41,46 +32,30 @@ const chartTypeIcon = new Map<ChartType, IconName>([
|
||||
[ChartType.OHLC, IconNames.WATERFALL_CHART],
|
||||
]);
|
||||
|
||||
export const ChartMenu = () => {
|
||||
const { CHARTING_LIBRARY_PATH } = useEnvironment();
|
||||
export const CandlesMenu = () => {
|
||||
const {
|
||||
chartlib,
|
||||
interval,
|
||||
chartType,
|
||||
studies,
|
||||
overlays,
|
||||
setChartlib,
|
||||
setInterval,
|
||||
setType,
|
||||
setStudies,
|
||||
setOverlays,
|
||||
} = useChartSettings();
|
||||
} = useCandlesChartSettings();
|
||||
const t = useT();
|
||||
|
||||
const contentAlign = 'end';
|
||||
const triggerClasses = 'text-xs';
|
||||
const contentAlign = 'end';
|
||||
const triggerButtonProps = { size: 'extra-small' } as const;
|
||||
|
||||
const isPennant = chartlib === 'pennant';
|
||||
const commonMenuItems = (
|
||||
<TradingButton
|
||||
onClick={() => {
|
||||
setChartlib(isPennant ? 'tradingview' : 'pennant');
|
||||
}}
|
||||
size="extra-small"
|
||||
>
|
||||
{isPennant ? 'TradingView' : t('Vega chart')}
|
||||
</TradingButton>
|
||||
);
|
||||
|
||||
const pennantMenuItems = (
|
||||
return (
|
||||
<>
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<TradingDropdownTrigger className={triggerClasses}>
|
||||
<TradingButton {...triggerButtonProps}>
|
||||
{t('Interval: {{interval}}', {
|
||||
interval: t(interval),
|
||||
interval: intervalLabels[interval],
|
||||
})}
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
@@ -93,13 +68,13 @@ export const ChartMenu = () => {
|
||||
setInterval(value as Interval);
|
||||
}}
|
||||
>
|
||||
{INTERVALS.map((timeInterval) => (
|
||||
{Object.values(Interval).map((timeInterval) => (
|
||||
<TradingDropdownRadioItem
|
||||
key={timeInterval}
|
||||
inset
|
||||
value={timeInterval}
|
||||
>
|
||||
{t(timeInterval)}
|
||||
{intervalLabels[timeInterval]}
|
||||
<TradingDropdownItemIndicator />
|
||||
</TradingDropdownRadioItem>
|
||||
))}
|
||||
@@ -183,50 +158,4 @@ export const ChartMenu = () => {
|
||||
</TradingDropdown>
|
||||
</>
|
||||
);
|
||||
|
||||
const tradingViewMenuItems = (
|
||||
<p className="text-xs mr-2 whitespace-nowrap">
|
||||
<Trans
|
||||
i18nKey="Chart by <0>TradingView</0>"
|
||||
components={[
|
||||
// eslint-disable-next-line
|
||||
<a
|
||||
className="underline"
|
||||
target="_blank"
|
||||
href="https://www.tradingview.com"
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
|
||||
if (!ALLOWED_TRADINGVIEW_HOSTNAMES.includes(window.location.hostname)) {
|
||||
return pennantMenuItems;
|
||||
}
|
||||
|
||||
if (!CHARTING_LIBRARY_PATH) {
|
||||
return pennantMenuItems;
|
||||
}
|
||||
|
||||
switch (chartlib) {
|
||||
case 'tradingview': {
|
||||
return (
|
||||
<>
|
||||
{tradingViewMenuItems}
|
||||
{commonMenuItems}
|
||||
</>
|
||||
);
|
||||
}
|
||||
case 'pennant': {
|
||||
return (
|
||||
<>
|
||||
{pennantMenuItems}
|
||||
{commonMenuItems}
|
||||
</>
|
||||
);
|
||||
}
|
||||
default: {
|
||||
throw new Error('invalid chart lib');
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
import { Interval as PennantInterval } from 'pennant';
|
||||
import { Interval } from '@vegaprotocol/types';
|
||||
|
||||
export const PENNANT_INTERVAL_MAP = {
|
||||
[Interval.INTERVAL_BLOCK]: undefined, // TODO: handle block tick
|
||||
[Interval.INTERVAL_I1M]: PennantInterval.I1M,
|
||||
[Interval.INTERVAL_I5M]: PennantInterval.I5M,
|
||||
[Interval.INTERVAL_I15M]: PennantInterval.I15M,
|
||||
[Interval.INTERVAL_I1H]: PennantInterval.I1H,
|
||||
[Interval.INTERVAL_I6H]: PennantInterval.I6H,
|
||||
[Interval.INTERVAL_I1D]: PennantInterval.I1D,
|
||||
} as const;
|
||||
@@ -56,7 +56,6 @@ const defaultConfig = {
|
||||
*/
|
||||
export class VegaDataSource implements DataSource {
|
||||
client: ApolloClient<object>;
|
||||
from?: Date;
|
||||
marketId: string;
|
||||
partyId: null | string;
|
||||
_decimalPlaces = 0;
|
||||
@@ -159,7 +158,6 @@ export class VegaDataSource implements DataSource {
|
||||
*/
|
||||
async query(interval: PennantInterval, from: string) {
|
||||
try {
|
||||
this.from = new Date(from);
|
||||
const { data } = await this.client.query<
|
||||
CandlesQuery,
|
||||
CandlesQueryVariables
|
||||
@@ -217,9 +215,7 @@ export class VegaDataSource implements DataSource {
|
||||
this.decimalPlaces,
|
||||
this.positionDecimalPlaces
|
||||
);
|
||||
if (!this.from || candle.date < this.from) {
|
||||
return;
|
||||
}
|
||||
|
||||
onSubscriptionData(candle);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export * from './__generated__/Candles';
|
||||
export * from './__generated__/Chart';
|
||||
export * from './candles-chart';
|
||||
export { PENNANT_INTERVAL_MAP } from './constants';
|
||||
export * from './candles-menu';
|
||||
export * from './data-source';
|
||||
|
||||
+8
-31
@@ -1,23 +1,18 @@
|
||||
import { ChartType, Overlay, Study } from 'pennant';
|
||||
import { getValidItem, getValidSubset } from '@vegaprotocol/react-helpers';
|
||||
import { ChartType, Interval, Study } from 'pennant';
|
||||
import { Overlay } from 'pennant';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
import { Interval } from '@vegaprotocol/types';
|
||||
import { getValidItem, getValidSubset } from '@vegaprotocol/react-helpers';
|
||||
|
||||
type StudySizes = { [S in Study]?: number };
|
||||
export type Chartlib = 'pennant' | 'tradingview';
|
||||
|
||||
interface StoredSettings {
|
||||
chartlib: Chartlib;
|
||||
// For interval we use the enum from @vegaprotocol/types, this is to make mapping between different
|
||||
// chart types easier and more consistent
|
||||
interval: Interval;
|
||||
type: ChartType;
|
||||
overlays: Overlay[];
|
||||
studies: Study[];
|
||||
studySizes: StudySizes;
|
||||
tradingViewStudies: string[];
|
||||
}
|
||||
|
||||
export const STUDY_SIZE = 90;
|
||||
@@ -30,24 +25,20 @@ const STUDY_ORDER: Study[] = [
|
||||
];
|
||||
|
||||
export const DEFAULT_CHART_SETTINGS = {
|
||||
chartlib: 'pennant' as const,
|
||||
interval: Interval.INTERVAL_I15M,
|
||||
interval: Interval.I15M,
|
||||
type: ChartType.CANDLE,
|
||||
overlays: [Overlay.MOVING_AVERAGE],
|
||||
studies: [Study.MACD, Study.VOLUME],
|
||||
studySizes: {},
|
||||
tradingViewStudies: ['Volume'],
|
||||
};
|
||||
|
||||
export const useChartSettingsStore = create<
|
||||
export const useCandlesChartSettingsStore = create<
|
||||
StoredSettings & {
|
||||
setType: (type: ChartType) => void;
|
||||
setInterval: (interval: Interval) => void;
|
||||
setOverlays: (overlays?: Overlay[]) => void;
|
||||
setStudies: (studies?: Study[]) => void;
|
||||
setStudySizes: (sizes: number[]) => void;
|
||||
setChartlib: (lib: Chartlib) => void;
|
||||
setTradingViewStudies: (studies: string[]) => void;
|
||||
}
|
||||
>()(
|
||||
persist(
|
||||
@@ -90,16 +81,6 @@ export const useChartSettingsStore = create<
|
||||
});
|
||||
});
|
||||
},
|
||||
setChartlib: (lib) => {
|
||||
set((state) => {
|
||||
state.chartlib = lib;
|
||||
});
|
||||
},
|
||||
setTradingViewStudies: (studies: string[]) => {
|
||||
set((state) => {
|
||||
state.tradingViewStudies = studies;
|
||||
});
|
||||
},
|
||||
})),
|
||||
{
|
||||
name: 'vega_candles_chart_store',
|
||||
@@ -107,13 +88,13 @@ export const useChartSettingsStore = create<
|
||||
)
|
||||
);
|
||||
|
||||
export const useChartSettings = () => {
|
||||
const settings = useChartSettingsStore();
|
||||
export const useCandlesChartSettings = () => {
|
||||
const settings = useCandlesChartSettingsStore();
|
||||
|
||||
const interval: Interval = getValidItem(
|
||||
settings.interval,
|
||||
Object.values(Interval),
|
||||
Interval.INTERVAL_I15M
|
||||
Interval.I15M
|
||||
);
|
||||
|
||||
const chartType: ChartType = getValidItem(
|
||||
@@ -141,19 +122,15 @@ export const useChartSettings = () => {
|
||||
});
|
||||
|
||||
return {
|
||||
chartlib: settings.chartlib,
|
||||
interval,
|
||||
chartType,
|
||||
overlays,
|
||||
studies,
|
||||
studySizes,
|
||||
tradingViewStudies: settings.tradingViewStudies,
|
||||
setInterval: settings.setInterval,
|
||||
setType: settings.setType,
|
||||
setStudies: settings.setStudies,
|
||||
setOverlays: settings.setOverlays,
|
||||
setStudySizes: settings.setStudySizes,
|
||||
setChartlib: settings.setChartlib,
|
||||
setTradingViewStudies: settings.setTradingViewStudies,
|
||||
};
|
||||
};
|
||||
@@ -5,7 +5,7 @@ import { prepend0x } from '@vegaprotocol/smart-contracts';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import { useSubmitApproval } from './use-submit-approval';
|
||||
import { useSubmitFaucet } from './use-submit-faucet';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useDepositBalances } from './use-deposit-balances';
|
||||
import type { Asset } from '@vegaprotocol/assets';
|
||||
import {
|
||||
@@ -30,7 +30,7 @@ export const DepositManager = ({
|
||||
const { config } = useEthereumConfig();
|
||||
const [persistentDeposit, savePersistentDeposit] =
|
||||
usePersistentDeposit(initialAssetId);
|
||||
const assetId = persistentDeposit?.assetId;
|
||||
const [assetId, setAssetId] = useState(persistentDeposit?.assetId);
|
||||
const asset = assets.find((a) => a.id === assetId);
|
||||
const bridgeContract = useBridgeContract();
|
||||
|
||||
@@ -70,19 +70,18 @@ export const DepositManager = ({
|
||||
[savePersistentDeposit, persistentDeposit]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// When we change asset, also clear the tracked faucet/approve transactions so
|
||||
// we dont render stale UI
|
||||
approve.reset();
|
||||
faucet.reset();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [assetId]);
|
||||
|
||||
return (
|
||||
<DepositForm
|
||||
selectedAsset={asset}
|
||||
onDisconnect={reset}
|
||||
onSelectAsset={(assetId) => savePersistentDeposit({ assetId })}
|
||||
onSelectAsset={(id) => {
|
||||
setAssetId(id);
|
||||
savePersistentDeposit({ assetId: id });
|
||||
// When we change asset, also clear the tracked faucet/approve transactions so
|
||||
// we dont render stale UI
|
||||
approve.reset();
|
||||
faucet.reset();
|
||||
}}
|
||||
handleAmountChange={onAmountChange}
|
||||
assets={sortBy(assets, 'name')}
|
||||
submitApprove={approve.perform}
|
||||
|
||||
@@ -13,4 +13,3 @@ export * from './use-get-deposit-maximum';
|
||||
export * from './use-get-deposited-amount';
|
||||
export * from './use-submit-approval';
|
||||
export * from './use-submit-faucet';
|
||||
export * from './use-persistent-deposit';
|
||||
|
||||
@@ -10,7 +10,7 @@ interface PersistedDeposit {
|
||||
}
|
||||
type PersistedDepositData = Record<string, PersistedDeposit>;
|
||||
|
||||
export const usePersistentDepositStore = create<{
|
||||
const usePersistentDepositStore = create<{
|
||||
deposits: PersistedDepositData;
|
||||
saveValue: (entry: PersistedDeposit) => void;
|
||||
lastVisited?: PersistedDeposit;
|
||||
|
||||
@@ -154,7 +154,6 @@ const compileEnvVars = () => {
|
||||
'VEGA_ENV',
|
||||
process.env['NX_VEGA_ENV']
|
||||
) as Networks;
|
||||
|
||||
const env: Environment = {
|
||||
VEGA_URL: windowOrDefault('VEGA_URL', process.env['NX_VEGA_URL']),
|
||||
VEGA_ENV,
|
||||
@@ -254,14 +253,6 @@ const compileEnvVars = () => {
|
||||
'NX_MOZILLA_EXTENSION_URL',
|
||||
process.env['NX_MOZILLA_EXTENSION_URL']
|
||||
),
|
||||
CHARTING_LIBRARY_PATH: windowOrDefault(
|
||||
'NX_CHARTING_LIBRARY_PATH',
|
||||
process.env['NX_CHARTING_LIBRARY_PATH']
|
||||
),
|
||||
CHARTING_LIBRARY_HASH: windowOrDefault(
|
||||
'NX_CHARTING_LIBRARY_HASH',
|
||||
process.env['NX_CHARTING_LIBRARY_HASH']
|
||||
),
|
||||
};
|
||||
|
||||
return env;
|
||||
@@ -369,7 +360,6 @@ export const compileFeatureFlags = (refresh = false): FeatureFlags => {
|
||||
) as string
|
||||
),
|
||||
};
|
||||
|
||||
const EXPLORER_FLAGS = {
|
||||
EXPLORER_ASSETS: TRUTHY.includes(
|
||||
windowOrDefault(
|
||||
@@ -426,7 +416,6 @@ export const compileFeatureFlags = (refresh = false): FeatureFlags => {
|
||||
) as string
|
||||
),
|
||||
};
|
||||
|
||||
const GOVERNANCE_FLAGS = {
|
||||
GOVERNANCE_NETWORK_DOWN: TRUTHY.includes(
|
||||
windowOrDefault(
|
||||
|
||||
@@ -60,8 +60,6 @@ export const envSchema = z
|
||||
TENDERMINT_WEBSOCKET_URL: z.optional(z.string()),
|
||||
CHROME_EXTENSION_URL: z.optional(z.string()),
|
||||
MOZILLA_EXTENSION_URL: z.optional(z.string()),
|
||||
CHARTING_LIBRARY_PATH: z.optional(z.string()),
|
||||
CHARTING_LIBRARY_HASH: z.optional(z.string()),
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
|
||||
@@ -11,7 +11,6 @@ import en_fills from './locales/en/fills.json';
|
||||
import en_funding_payments from './locales/en/funding-payments.json';
|
||||
import en_governance from './locales/en/governance.json';
|
||||
import en_trading from './locales/en/trading.json';
|
||||
import en_trading_view from './locales/en/trading-view.json';
|
||||
import en_markets from './locales/en/markets.json';
|
||||
import en_web3 from './locales/en/web3.json';
|
||||
import en_proposals from './locales/en/proposals.json';
|
||||
@@ -33,7 +32,6 @@ export const locales = {
|
||||
'funding-payments': en_funding_payments,
|
||||
governance: en_governance,
|
||||
trading: en_trading,
|
||||
trading_view: en_trading_view,
|
||||
markets: en_markets,
|
||||
web3: en_web3,
|
||||
positions: en_positions,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
{
|
||||
"Indicators": "Indicators",
|
||||
"Interval: {{interval}}": "Interval: {{interval}}",
|
||||
"No open orders": "No open orders"
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"Failed to initialize Trading view": "Failed to initialize Trading view",
|
||||
"Loading Trading View": "Loading Trading View"
|
||||
}
|
||||
@@ -26,10 +26,9 @@
|
||||
"Best offer": "Best offer",
|
||||
"Browse": "Browse",
|
||||
"By using the Vega Console, you acknowledge that you have read and understood the <0>Vega Console Disclaimer</0>": "By using the Vega Console, you acknowledge that you have read and understood the <0>Vega Console Disclaimer</0>",
|
||||
"Chart": "Chart",
|
||||
"Candles": "Candles",
|
||||
"Change (24h)": "Change (24h)",
|
||||
"Chart": "Chart",
|
||||
"Chart by <0>TradingView</0>": "Chart by <0>TradingView</0>",
|
||||
"checkOutProposalsAndVote": "Check out the terms of the proposals and vote:",
|
||||
"checkOutProposalsAndVote_one": "Check out the terms of the proposal and vote:",
|
||||
"checkOutProposalsAndVote_other": "Check out the terms of the proposals and vote:",
|
||||
@@ -121,15 +120,7 @@
|
||||
"Improve vega console": "Improve vega console",
|
||||
"Inactive": "Inactive",
|
||||
"Index Price": "Index Price",
|
||||
"Indicators": "Indicators",
|
||||
"Infrastructure": "Infrastructure",
|
||||
"Interval: {{interval}}": "Interval: {{interval}}",
|
||||
"INTERVAL_I1M": "1m",
|
||||
"INTERVAL_I5M": "5m",
|
||||
"INTERVAL_I15M": "15m",
|
||||
"INTERVAL_I1H": "1H",
|
||||
"INTERVAL_I6H": "6H",
|
||||
"INTERVAL_I1D": "1D",
|
||||
"Invite friends and earn rewards from the trading fees they pay. Stake those rewards to earn multipliers on future rewards.": "Invite friends and earn rewards from the trading fees they pay. Stake those rewards to earn multipliers on future rewards.",
|
||||
"Learn about providing liquidity": "Learn about providing liquidity",
|
||||
"Learn more": "Learn more",
|
||||
@@ -201,7 +192,6 @@
|
||||
"pastEpochs": "Past {{count}} epochs",
|
||||
"pastEpochs_one": "Past {{count}} epoch",
|
||||
"pastEpochs_other": "Past {{count}} epochs",
|
||||
"Pennant": "Pennant",
|
||||
"Perpetuals": "Perpetuals",
|
||||
"Please choose another market from the <0>market list</0>": "Please choose another market from the <0>market list</0>",
|
||||
"Please connect Vega wallet": "Please connect Vega wallet",
|
||||
@@ -301,7 +291,6 @@
|
||||
"Trader": "Trader",
|
||||
"Trades": "Trades",
|
||||
"Trading": "Trading",
|
||||
"TradingView": "TradingView",
|
||||
"Trading has been terminated as a result of the product definition": "Trading has been terminated as a result of the product definition",
|
||||
"Trading mode": "Trading mode",
|
||||
"Trading on Market {{name}} may stop on {{date}}. There is open proposal to close this market.": "Trading on Market {{name}} may stop on {{date}}. There is open proposal to close this market.",
|
||||
@@ -313,7 +302,6 @@
|
||||
"totalCommission_other": "Total commission (last {{count}} epochs)",
|
||||
"Unknown": "Unknown",
|
||||
"Unknown settlement date": "Unknown settlement date",
|
||||
"Vega chart": "Vega chart",
|
||||
"Vega Reward pot": "Vega Reward pot",
|
||||
"Vega Wallet <0>full featured<0>": "Vega Wallet <0>full featured<0>",
|
||||
"Vesting": "Vesting",
|
||||
|
||||
@@ -198,7 +198,7 @@ export const Orderbook = ({
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className="overflow-hidden grid relative"
|
||||
className="overflow-hidden grid"
|
||||
data-testid="orderbook-grid-element"
|
||||
style={{
|
||||
width,
|
||||
|
||||
@@ -135,7 +135,7 @@ describe('MarketInfoPanels', () => {
|
||||
render(<DataSourceProof dataSourceSpecId={''} {...props} />);
|
||||
expect(screen.getByText('Internal conditions')).toBeInTheDocument();
|
||||
const dateFromUnixTimestamp = condition.value
|
||||
? getDateTimeFormat().format(new Date(parseInt(condition.value) * 1000))
|
||||
? getDateTimeFormat().format(new Date(parseInt(condition.value)))
|
||||
: '-';
|
||||
expect(
|
||||
screen.getByText(
|
||||
|
||||
@@ -1263,9 +1263,7 @@ export const DataSourceProof = ({
|
||||
{data.sourceType.sourceType?.conditions?.map((condition, i) => {
|
||||
if (!condition) return null;
|
||||
const dateFromUnixTimestamp = condition.value
|
||||
? getDateTimeFormat().format(
|
||||
new Date(parseInt(condition.value) * 1000)
|
||||
)
|
||||
? getDateTimeFormat().format(new Date(parseInt(condition.value)))
|
||||
: '-';
|
||||
return (
|
||||
<p key={i}>
|
||||
|
||||
@@ -8,13 +8,11 @@ export const LiquidationPrice = ({
|
||||
openVolume,
|
||||
collateralAvailable,
|
||||
decimalPlaces,
|
||||
className,
|
||||
}: {
|
||||
marketId: string;
|
||||
openVolume: string;
|
||||
collateralAvailable: string;
|
||||
decimalPlaces: number;
|
||||
className?: string;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const { data: currentData, previousData } = useEstimatePositionQuery({
|
||||
@@ -41,19 +39,22 @@ export const LiquidationPrice = ({
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
align="end"
|
||||
description={
|
||||
<dl className="grid grid-cols-2">
|
||||
<dt>{t('Worst case')}</dt>
|
||||
<dd className="pl-2 text-right font-mono">{worstCase}</dd>
|
||||
<dt className="font-normal">{t('Best case')}</dt>
|
||||
<dd className="pl-2 text-right font-mono">{bestCase}</dd>
|
||||
</dl>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{t('Worst case')}</th>
|
||||
<td className="pl-2 text-right font-mono">{worstCase}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('Best case')}</th>
|
||||
<td className="pl-2 text-right font-mono">{bestCase}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
>
|
||||
<span data-testid="liquidation-price" className={className}>
|
||||
{worstCase}
|
||||
</span>
|
||||
<span data-testid="liquidation-price">{worstCase}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -314,9 +314,7 @@ describe('Positions', () => {
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const cell = cells[1];
|
||||
const tooltipTrigger = cell.querySelector('[data-state="closed"]');
|
||||
expect(tooltipTrigger).not.toBeNull();
|
||||
await userEvent.hover(tooltipTrigger as Element);
|
||||
await userEvent.hover(cell);
|
||||
const tooltip = within(await screen.findByRole('tooltip'));
|
||||
expect(tooltip.getByText(data.text)).toBeInTheDocument();
|
||||
});
|
||||
@@ -331,9 +329,8 @@ describe('Positions', () => {
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const cell = cells[5];
|
||||
const tooltipTrigger = cell.querySelector('[data-state="closed"]');
|
||||
expect(tooltipTrigger).not.toBeNull();
|
||||
await userEvent.hover(tooltipTrigger as Element);
|
||||
|
||||
await userEvent.hover(cell);
|
||||
const tooltip = within(await screen.findByRole('tooltip'));
|
||||
expect(tooltip.getByText('Realised PNL: 1.23')).toBeInTheDocument();
|
||||
expect(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo, type CSSProperties, type ReactNode } from 'react';
|
||||
import { type ColDef } from 'ag-grid-community';
|
||||
import { type ColDef, type ITooltipParams } from 'ag-grid-community';
|
||||
import {
|
||||
AgGrid,
|
||||
COL_DEFS,
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
ExternalLink,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
Tooltip,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
volumePrefix,
|
||||
@@ -149,6 +148,15 @@ export const PositionsTable = ({
|
||||
data.positionDecimalPlaces
|
||||
).toNumber();
|
||||
},
|
||||
tooltipValueGetter: ({ data }: ITooltipParams<Position>) => {
|
||||
if (
|
||||
!data ||
|
||||
data.status === PositionStatus.POSITION_STATUS_UNSPECIFIED
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return data.status;
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Position, 'openVolume'>): string => {
|
||||
@@ -163,6 +171,68 @@ export const PositionsTable = ({
|
||||
|
||||
return vol;
|
||||
},
|
||||
tooltipComponent: (args: ITooltipParams<Position>) => {
|
||||
if (!args.data) {
|
||||
return null;
|
||||
}
|
||||
const POSITION_RESOLUTION_LINK =
|
||||
DocsLinks?.POSITION_RESOLUTION ?? '';
|
||||
let primaryTooltip;
|
||||
switch (args.data.status) {
|
||||
case PositionStatus.POSITION_STATUS_CLOSED_OUT:
|
||||
primaryTooltip = t('Your position was closed.');
|
||||
break;
|
||||
case PositionStatus.POSITION_STATUS_ORDERS_CLOSED:
|
||||
primaryTooltip = t('Your open orders were cancelled.');
|
||||
break;
|
||||
case PositionStatus.POSITION_STATUS_DISTRESSED:
|
||||
primaryTooltip = t('Your position is distressed.');
|
||||
break;
|
||||
}
|
||||
|
||||
let secondaryTooltip;
|
||||
switch (args.data.status) {
|
||||
case PositionStatus.POSITION_STATUS_CLOSED_OUT:
|
||||
secondaryTooltip = t(
|
||||
`You did not have enough {{assetSymbol}} collateral to meet the maintenance margin requirements for your position, so it was closed by the network.`,
|
||||
{ assetSymbol: args.data.assetSymbol }
|
||||
);
|
||||
break;
|
||||
case PositionStatus.POSITION_STATUS_ORDERS_CLOSED:
|
||||
secondaryTooltip = t(
|
||||
'The position was distressed, but removing open orders from the book brought the margin level back to a point where the open position could be maintained.'
|
||||
);
|
||||
break;
|
||||
case PositionStatus.POSITION_STATUS_DISTRESSED:
|
||||
secondaryTooltip = t(
|
||||
'The position was distressed, but could not be closed out - orders were removed from the book, and the open volume will be closed out once there is sufficient volume on the book.'
|
||||
);
|
||||
break;
|
||||
default:
|
||||
secondaryTooltip = t('Maintained by network');
|
||||
}
|
||||
return (
|
||||
<TooltipCellComponent
|
||||
{...args}
|
||||
value={
|
||||
<>
|
||||
<p className="mb-2">{primaryTooltip}</p>
|
||||
<p className="mb-2">{secondaryTooltip}</p>
|
||||
<p className="mb-2">
|
||||
{t('Status: {{status}}', {
|
||||
status: PositionStatusMapping[args.data.status],
|
||||
})}
|
||||
</p>
|
||||
{POSITION_RESOLUTION_LINK && (
|
||||
<ExternalLink href={POSITION_RESOLUTION_LINK}>
|
||||
{t('Read more about position resolution')}
|
||||
</ExternalLink>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
cellRenderer: OpenVolumeCell,
|
||||
},
|
||||
{
|
||||
@@ -267,15 +337,12 @@ export const PositionsTable = ({
|
||||
return '-';
|
||||
}
|
||||
return (
|
||||
<div className="flex h-[45px] items-center">
|
||||
<LiquidationPrice
|
||||
className="block text-right grow"
|
||||
marketId={data.marketId}
|
||||
openVolume={data.openVolume}
|
||||
collateralAvailable={data.totalBalance}
|
||||
decimalPlaces={data.marketDecimalPlaces}
|
||||
/>
|
||||
</div>
|
||||
<LiquidationPrice
|
||||
marketId={data.marketId}
|
||||
openVolume={data.openVolume}
|
||||
collateralAvailable={data.totalBalance}
|
||||
decimalPlaces={data.marketDecimalPlaces}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
@@ -287,13 +354,13 @@ export const PositionsTable = ({
|
||||
cellClass: 'font-mono text-right',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: realisedPNLValueGetter,
|
||||
cellRenderer: (
|
||||
args: VegaICellRendererParams<Position, 'realisedPNL'>
|
||||
) => {
|
||||
// @ts-ignore no type overlap, but the functions are identical
|
||||
tooltipValueGetter: realisedPNLValueGetter,
|
||||
tooltipComponent: (args: ITooltipParams) => {
|
||||
const LOSS_SOCIALIZATION_LINK =
|
||||
DocsLinks?.LOSS_SOCIALIZATION ?? '';
|
||||
|
||||
if (!args.data || args.value === undefined) {
|
||||
if (!args.data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -304,11 +371,7 @@ export const PositionsTable = ({
|
||||
if (losses <= 0) {
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return (
|
||||
<Tooltip description={args.valueFormatted} align="end">
|
||||
<div>
|
||||
<PNLCell {...args} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<TooltipCellComponent {...args} value={args.valueFormatted} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -318,24 +381,20 @@ export const PositionsTable = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
align="end"
|
||||
description={
|
||||
<TooltipCellComponent
|
||||
{...args}
|
||||
value={
|
||||
<>
|
||||
<p className="mb-2">
|
||||
{t('Realised PNL: {{value}}', {
|
||||
nsSeparator: '*',
|
||||
replace: { value: args.value },
|
||||
value: args.value,
|
||||
})}
|
||||
</p>
|
||||
<p className="mb-2">
|
||||
{t(
|
||||
'Lifetime loss socialisation deductions: {{losses}}',
|
||||
{
|
||||
nsSeparator: '*',
|
||||
replace: {
|
||||
losses: lossesFormatted,
|
||||
},
|
||||
losses: lossesFormatted,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
@@ -352,11 +411,7 @@ export const PositionsTable = ({
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<PNLCell {...args} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
/>
|
||||
);
|
||||
},
|
||||
valueFormatter: ({
|
||||
@@ -373,6 +428,7 @@ export const PositionsTable = ({
|
||||
headerTooltip: t(
|
||||
'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,
|
||||
},
|
||||
{
|
||||
headerName: t('Unrealised PNL'),
|
||||
@@ -464,66 +520,10 @@ export const OpenVolumeCell = ({
|
||||
valueFormatted,
|
||||
data,
|
||||
}: VegaICellRendererParams<Position, 'openVolume'>) => {
|
||||
const t = useT();
|
||||
if (!valueFormatted || !data || !data.notional) {
|
||||
return <>-</>;
|
||||
}
|
||||
|
||||
const POSITION_RESOLUTION_LINK = DocsLinks?.POSITION_RESOLUTION ?? '';
|
||||
let primaryTooltip;
|
||||
switch (data.status) {
|
||||
case PositionStatus.POSITION_STATUS_CLOSED_OUT:
|
||||
primaryTooltip = t('Your position was closed.');
|
||||
break;
|
||||
case PositionStatus.POSITION_STATUS_ORDERS_CLOSED:
|
||||
primaryTooltip = t('Your open orders were cancelled.');
|
||||
break;
|
||||
case PositionStatus.POSITION_STATUS_DISTRESSED:
|
||||
primaryTooltip = t('Your position is distressed.');
|
||||
break;
|
||||
}
|
||||
|
||||
let secondaryTooltip;
|
||||
switch (data.status) {
|
||||
case PositionStatus.POSITION_STATUS_CLOSED_OUT:
|
||||
secondaryTooltip = t(
|
||||
`You did not have enough {{assetSymbol}} collateral to meet the maintenance margin requirements for your position, so it was closed by the network.`,
|
||||
{ assetSymbol: data.assetSymbol }
|
||||
);
|
||||
break;
|
||||
case PositionStatus.POSITION_STATUS_ORDERS_CLOSED:
|
||||
secondaryTooltip = t(
|
||||
'The position was distressed, but removing open orders from the book brought the margin level back to a point where the open position could be maintained.'
|
||||
);
|
||||
break;
|
||||
case PositionStatus.POSITION_STATUS_DISTRESSED:
|
||||
secondaryTooltip = t(
|
||||
'The position was distressed, but could not be closed out - orders were removed from the book, and the open volume will be closed out once there is sufficient volume on the book.'
|
||||
);
|
||||
break;
|
||||
default:
|
||||
secondaryTooltip = t('Maintained by network');
|
||||
}
|
||||
const description = (
|
||||
<>
|
||||
<p className="mb-2">{primaryTooltip}</p>
|
||||
<p className="mb-2">{secondaryTooltip}</p>
|
||||
<p className="mb-2">
|
||||
{t('Status: {{status}}', {
|
||||
nsSeparator: '*',
|
||||
replace: {
|
||||
status: PositionStatusMapping[data.status],
|
||||
},
|
||||
})}
|
||||
</p>
|
||||
{POSITION_RESOLUTION_LINK && (
|
||||
<ExternalLink href={POSITION_RESOLUTION_LINK}>
|
||||
{t('Read more about position resolution')}
|
||||
</ExternalLink>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
const notional = addDecimalsFormatNumber(
|
||||
data.notional,
|
||||
data.marketDecimalPlaces
|
||||
@@ -539,11 +539,16 @@ export const OpenVolumeCell = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip description={description}>
|
||||
<div>
|
||||
<WarningCell showIcon>{cellContent}</WarningCell>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<WarningCell
|
||||
showIcon={
|
||||
// not sure why but data.status has become a union of all the enum values
|
||||
// rather than just being the enum itself
|
||||
(data.status as PositionStatus) !==
|
||||
PositionStatus.POSITION_STATUS_UNSPECIFIED
|
||||
}
|
||||
>
|
||||
{cellContent}
|
||||
</WarningCell>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -68,13 +68,10 @@ export const useBlockRising = (skip = false) => {
|
||||
}
|
||||
);
|
||||
|
||||
const heights = compact([...results.map((r) => r?.blockHeight)]);
|
||||
|
||||
// Handles TendermintErrorResponses
|
||||
if (blockInfo && 'result' in blockInfo) {
|
||||
heights.push(blockInfo.result.block.header.height);
|
||||
}
|
||||
|
||||
const heights = compact([
|
||||
...results.map((r) => r?.blockHeight),
|
||||
blockInfo?.result.block.header.height,
|
||||
]);
|
||||
const current = max(heights);
|
||||
if (current && Number(current) > prev) {
|
||||
setBlock(Number(current));
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './use-copy-timeout';
|
||||
export * from './use-fetch';
|
||||
export * from './use-local-storage';
|
||||
export * from './use-mutation-observer';
|
||||
@@ -12,4 +11,4 @@ export * from './use-theme-switcher';
|
||||
export * from './use-storybook-theme-observer';
|
||||
export * from './use-yesterday';
|
||||
export * from './use-previous';
|
||||
export { useScript } from './use-script';
|
||||
export * from './use-copy-timeout';
|
||||
|
||||
@@ -76,26 +76,16 @@ export const useFetch = <T>(
|
||||
...options,
|
||||
body: body ? body : options?.body,
|
||||
});
|
||||
|
||||
data = (await response.json()) as T;
|
||||
|
||||
if (!response.ok && !data) {
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
|
||||
data = (await response.json()) as T;
|
||||
// @ts-ignore - 'error' in data
|
||||
if (data && data.error) {
|
||||
// Explicit check for TendermintErrorResponse style error
|
||||
// @ts-ignore - 'error' in data
|
||||
if (data.error.data) {
|
||||
// @ts-ignore - 'error' in data
|
||||
|
||||
throw new Error(data.error.data);
|
||||
}
|
||||
if (data && 'error' in data) {
|
||||
// @ts-ignore - data.error
|
||||
throw new Error(data.error);
|
||||
}
|
||||
|
||||
if (cancelRequest.current) return;
|
||||
|
||||
dispatch({ type: ActionType.FETCHED, payload: data });
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { useScript } from './use-script';
|
||||
|
||||
describe('useScript', () => {
|
||||
it('appends a script to the body', async () => {
|
||||
const url = 'http://localhost:8080/foo.js';
|
||||
|
||||
const { result } = renderHook(() => useScript(url, 'integrity-hash'));
|
||||
|
||||
expect(result.current).toBe('loading');
|
||||
|
||||
await waitFor(() => {
|
||||
const script = document.body.getElementsByTagName('script')[0];
|
||||
expect(script).toBeInTheDocument();
|
||||
expect(script).toHaveAttribute('src', url);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,57 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
type State = 'idle' | 'loading' | 'ready' | 'error';
|
||||
|
||||
// keep record of loaded script state, so if the component re-mounts but the script
|
||||
// was already appended its not re-appended and will have the correct state
|
||||
type Url = string;
|
||||
const cache: Record<Url, State> = {};
|
||||
|
||||
export const useScript = (url: string, integrity: string) => {
|
||||
// track state of the script as it loads or possibly fails
|
||||
const [state, setState] = useState<State>(url ? 'loading' : 'idle');
|
||||
|
||||
useEffect(() => {
|
||||
if (!url) {
|
||||
setState('idle');
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the integrity hash of the script as an identifier
|
||||
let script = document.getElementById(integrity) as HTMLScriptElement | null;
|
||||
|
||||
if (script) {
|
||||
// script already on the page
|
||||
setState(cache[url]);
|
||||
} else {
|
||||
// script not found, create and append script
|
||||
script = document.createElement('script');
|
||||
script.id = integrity;
|
||||
script.src = url;
|
||||
script.async = true;
|
||||
script.crossOrigin = 'anonymous'; // make sure sri is respected with cross origin request
|
||||
script.integrity = `sha256-${integrity}`;
|
||||
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
|
||||
// Setup/teardown listeners to notify component when script has loaded
|
||||
const _setState = (event: Event) => {
|
||||
const result = event.type === 'load' ? 'ready' : 'error';
|
||||
setState(result);
|
||||
cache[url] = result;
|
||||
};
|
||||
|
||||
script.addEventListener('load', _setState);
|
||||
script.addEventListener('error', _setState);
|
||||
|
||||
return () => {
|
||||
if (script) {
|
||||
script.removeEventListener('load', _setState);
|
||||
script.removeEventListener('error', _setState);
|
||||
}
|
||||
};
|
||||
}, [url, integrity]);
|
||||
|
||||
return state;
|
||||
};
|
||||
@@ -1,11 +1,6 @@
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useFetch } from '@vegaprotocol/react-helpers';
|
||||
import type {
|
||||
TendermintBlockResponse,
|
||||
TendermintErrorResponse,
|
||||
} from '../types';
|
||||
|
||||
type TendermintResponse = TendermintBlockResponse | TendermintErrorResponse;
|
||||
import { type TendermintBlockResponse } from '../types';
|
||||
|
||||
export const useBlockInfo = (blockHeight?: number, canFetch = true) => {
|
||||
const { TENDERMINT_URL } = useEnvironment();
|
||||
@@ -15,7 +10,7 @@ export const useBlockInfo = (blockHeight?: number, canFetch = true) => {
|
||||
TENDERMINT_URL && blockHeight && !isNaN(blockHeight) && canFetch
|
||||
);
|
||||
|
||||
const { state, refetch } = useFetch<TendermintResponse>(
|
||||
const { state, refetch } = useFetch<TendermintBlockResponse>(
|
||||
url,
|
||||
{ cache: 'force-cache' },
|
||||
canFetchData
|
||||
|
||||
@@ -7,16 +7,6 @@ export type TendermintBlockResponse = {
|
||||
};
|
||||
};
|
||||
|
||||
export type TendermintErrorResponse = {
|
||||
jsonrpc: string;
|
||||
id: number;
|
||||
error: {
|
||||
code: number;
|
||||
message: string;
|
||||
data: string;
|
||||
};
|
||||
};
|
||||
|
||||
type Id = {
|
||||
hash: string;
|
||||
parts: {
|
||||
@@ -44,7 +34,7 @@ type Header = {
|
||||
proposer_address: string;
|
||||
};
|
||||
|
||||
export type Block = {
|
||||
type Block = {
|
||||
header: Header;
|
||||
data: {
|
||||
txs: string[];
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nx/react/babel",
|
||||
{
|
||||
"runtime": "automatic",
|
||||
"useBuiltIns": "usage"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"extends": ["plugin:@nx/react", "../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*", "__generated__"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
# trading-view
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test trading-view` to execute the unit tests via [Jest](https://jestjs.io).
|
||||
@@ -1,12 +0,0 @@
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
displayName: 'trading-view',
|
||||
preset: '../../jest.preset.js',
|
||||
transform: {
|
||||
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest',
|
||||
'^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }],
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
coverageDirectory: '../../coverage/libs/trading-view',
|
||||
setupFilesAfterEnv: ['./src/setup-tests.ts'],
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "trading-view",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
{
|
||||
"name": "trading-view",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/trading-view/src",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/trading-view/**/*.{ts,tsx,js,jsx}"]
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"executor": "@nx/rollup:rollup",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/trading-view",
|
||||
"tsConfig": "libs/trading-view/tsconfig.lib.json",
|
||||
"project": "libs/trading-view/package.json",
|
||||
"entryFile": "libs/trading-view/src/index.ts",
|
||||
"external": ["react", "react-dom", "react/jsx-runtime"],
|
||||
"rollupConfig": "@nx/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "libs/trading-view/README.md",
|
||||
"input": ".",
|
||||
"output": "."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"jestConfig": "libs/trading-view/jest.config.ts"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
export { TradingViewContainer } from './lib/trading-view-container';
|
||||
export {
|
||||
ALLOWED_TRADINGVIEW_HOSTNAMES,
|
||||
TRADINGVIEW_INTERVAL_MAP,
|
||||
} from './lib/constants';
|
||||
@@ -1,40 +0,0 @@
|
||||
fragment Bar on Candle {
|
||||
periodStart
|
||||
lastUpdateInPeriod
|
||||
high
|
||||
low
|
||||
open
|
||||
close
|
||||
volume
|
||||
}
|
||||
|
||||
query GetBars(
|
||||
$marketId: ID!
|
||||
$interval: Interval!
|
||||
$since: String!
|
||||
$to: String
|
||||
) {
|
||||
market(id: $marketId) {
|
||||
id
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
candlesConnection(
|
||||
interval: $interval
|
||||
since: $since
|
||||
to: $to
|
||||
pagination: { last: 5000 }
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
...Bar
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subscription LastBar($marketId: ID!, $interval: Interval!) {
|
||||
candles(marketId: $marketId, interval: $interval) {
|
||||
...Bar
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
query Symbol($marketId: ID!) {
|
||||
market(id: $marketId) {
|
||||
id
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
code
|
||||
name
|
||||
metadata {
|
||||
tags
|
||||
}
|
||||
product {
|
||||
... on Future {
|
||||
__typename
|
||||
}
|
||||
... on Perpetual {
|
||||
__typename
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-119
@@ -1,119 +0,0 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type BarFragment = { __typename?: 'Candle', periodStart: any, lastUpdateInPeriod: any, high: string, low: string, open: string, close: string, volume: string };
|
||||
|
||||
export type GetBarsQueryVariables = Types.Exact<{
|
||||
marketId: Types.Scalars['ID'];
|
||||
interval: Types.Interval;
|
||||
since: Types.Scalars['String'];
|
||||
to?: Types.InputMaybe<Types.Scalars['String']>;
|
||||
}>;
|
||||
|
||||
|
||||
export type GetBarsQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, candlesConnection?: { __typename?: 'CandleDataConnection', edges?: Array<{ __typename?: 'CandleEdge', node: { __typename?: 'Candle', periodStart: any, lastUpdateInPeriod: any, high: string, low: string, open: string, close: string, volume: string } } | null> | null } | null } | null };
|
||||
|
||||
export type LastBarSubscriptionVariables = Types.Exact<{
|
||||
marketId: Types.Scalars['ID'];
|
||||
interval: Types.Interval;
|
||||
}>;
|
||||
|
||||
|
||||
export type LastBarSubscription = { __typename?: 'Subscription', candles: { __typename?: 'Candle', periodStart: any, lastUpdateInPeriod: any, high: string, low: string, open: string, close: string, volume: string } };
|
||||
|
||||
export const BarFragmentDoc = gql`
|
||||
fragment Bar on Candle {
|
||||
periodStart
|
||||
lastUpdateInPeriod
|
||||
high
|
||||
low
|
||||
open
|
||||
close
|
||||
volume
|
||||
}
|
||||
`;
|
||||
export const GetBarsDocument = gql`
|
||||
query GetBars($marketId: ID!, $interval: Interval!, $since: String!, $to: String) {
|
||||
market(id: $marketId) {
|
||||
id
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
candlesConnection(
|
||||
interval: $interval
|
||||
since: $since
|
||||
to: $to
|
||||
pagination: {last: 5000}
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
...Bar
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${BarFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useGetBarsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useGetBarsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useGetBarsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useGetBarsQuery({
|
||||
* variables: {
|
||||
* marketId: // value for 'marketId'
|
||||
* interval: // value for 'interval'
|
||||
* since: // value for 'since'
|
||||
* to: // value for 'to'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGetBarsQuery(baseOptions: Apollo.QueryHookOptions<GetBarsQuery, GetBarsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<GetBarsQuery, GetBarsQueryVariables>(GetBarsDocument, options);
|
||||
}
|
||||
export function useGetBarsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetBarsQuery, GetBarsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<GetBarsQuery, GetBarsQueryVariables>(GetBarsDocument, options);
|
||||
}
|
||||
export type GetBarsQueryHookResult = ReturnType<typeof useGetBarsQuery>;
|
||||
export type GetBarsLazyQueryHookResult = ReturnType<typeof useGetBarsLazyQuery>;
|
||||
export type GetBarsQueryResult = Apollo.QueryResult<GetBarsQuery, GetBarsQueryVariables>;
|
||||
export const LastBarDocument = gql`
|
||||
subscription LastBar($marketId: ID!, $interval: Interval!) {
|
||||
candles(marketId: $marketId, interval: $interval) {
|
||||
...Bar
|
||||
}
|
||||
}
|
||||
${BarFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useLastBarSubscription__
|
||||
*
|
||||
* To run a query within a React component, call `useLastBarSubscription` and pass it any options that fit your needs.
|
||||
* When your component renders, `useLastBarSubscription` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useLastBarSubscription({
|
||||
* variables: {
|
||||
* marketId: // value for 'marketId'
|
||||
* interval: // value for 'interval'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useLastBarSubscription(baseOptions: Apollo.SubscriptionHookOptions<LastBarSubscription, LastBarSubscriptionVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useSubscription<LastBarSubscription, LastBarSubscriptionVariables>(LastBarDocument, options);
|
||||
}
|
||||
export type LastBarSubscriptionHookResult = ReturnType<typeof useLastBarSubscription>;
|
||||
export type LastBarSubscriptionResult = Apollo.SubscriptionResult<LastBarSubscription>;
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type SymbolQueryVariables = Types.Exact<{
|
||||
marketId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type SymbolQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', code: string, name: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename: 'Future' } | { __typename: 'Perpetual' } | { __typename?: 'Spot' } } } } | null };
|
||||
|
||||
|
||||
export const SymbolDocument = gql`
|
||||
query Symbol($marketId: ID!) {
|
||||
market(id: $marketId) {
|
||||
id
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
code
|
||||
name
|
||||
metadata {
|
||||
tags
|
||||
}
|
||||
product {
|
||||
... on Future {
|
||||
__typename
|
||||
}
|
||||
... on Perpetual {
|
||||
__typename
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useSymbolQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useSymbolQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useSymbolQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useSymbolQuery({
|
||||
* variables: {
|
||||
* marketId: // value for 'marketId'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useSymbolQuery(baseOptions: Apollo.QueryHookOptions<SymbolQuery, SymbolQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<SymbolQuery, SymbolQueryVariables>(SymbolDocument, options);
|
||||
}
|
||||
export function useSymbolLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<SymbolQuery, SymbolQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<SymbolQuery, SymbolQueryVariables>(SymbolDocument, options);
|
||||
}
|
||||
export type SymbolQueryHookResult = ReturnType<typeof useSymbolQuery>;
|
||||
export type SymbolLazyQueryHookResult = ReturnType<typeof useSymbolLazyQuery>;
|
||||
export type SymbolQueryResult = Apollo.QueryResult<SymbolQuery, SymbolQueryVariables>;
|
||||
@@ -1,24 +0,0 @@
|
||||
import { Interval } from '@vegaprotocol/types';
|
||||
|
||||
export const ALLOWED_TRADINGVIEW_HOSTNAMES = [
|
||||
'localhost',
|
||||
'vegafairground.eth.limo',
|
||||
'vegafairground.eth',
|
||||
'vegaprotocol.eth',
|
||||
'vegaprotocol.eth.limo',
|
||||
];
|
||||
|
||||
export const CHARTING_LIBRARY_FILE = 'charting_library.standalone.js';
|
||||
|
||||
export const TRADINGVIEW_INTERVAL_MAP = {
|
||||
[Interval.INTERVAL_BLOCK]: undefined, // TODO: handle block tick
|
||||
[Interval.INTERVAL_I1M]: '1',
|
||||
[Interval.INTERVAL_I5M]: '5',
|
||||
[Interval.INTERVAL_I15M]: '15',
|
||||
[Interval.INTERVAL_I1H]: '60',
|
||||
[Interval.INTERVAL_I6H]: '360',
|
||||
[Interval.INTERVAL_I1D]: '1D',
|
||||
} as const;
|
||||
|
||||
export type ResolutionRecord = typeof TRADINGVIEW_INTERVAL_MAP;
|
||||
export type ResolutionString = ResolutionRecord[keyof ResolutionRecord];
|
||||
@@ -1,56 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { TradingViewContainer } from './trading-view-container';
|
||||
import * as useScriptModule from '@vegaprotocol/react-helpers';
|
||||
import { CHARTING_LIBRARY_FILE } from './constants';
|
||||
|
||||
jest.mock('./trading-view', () => ({
|
||||
TradingView: ({ marketId }: { marketId: string }) => (
|
||||
<div data-testid="trading-view">{marketId}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
describe('TradingView', () => {
|
||||
const props = {
|
||||
libraryPath: 'foo',
|
||||
libraryHash: 'hash',
|
||||
marketId: 'marketId',
|
||||
};
|
||||
const renderComponent = () => render(<TradingViewContainer {...props} />);
|
||||
|
||||
it.each(['loading', 'idle'])(
|
||||
'renders loading state when script is %s',
|
||||
(state: string) => {
|
||||
const spyOnScript = jest
|
||||
.spyOn(useScriptModule, 'useScript')
|
||||
.mockReturnValue(state as 'idle' | 'loading');
|
||||
|
||||
renderComponent();
|
||||
|
||||
expect(screen.getByText('Loading Trading View')).toBeInTheDocument();
|
||||
expect(spyOnScript).toHaveBeenCalledWith(
|
||||
props.libraryPath + CHARTING_LIBRARY_FILE,
|
||||
props.libraryHash
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it('renders error state if script fails to load', () => {
|
||||
jest.spyOn(useScriptModule, 'useScript').mockReturnValue('error');
|
||||
|
||||
renderComponent();
|
||||
|
||||
expect(
|
||||
screen.getByText('Failed to initialize Trading view')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders TradingView if script loads successfully', () => {
|
||||
jest.spyOn(useScriptModule, 'useScript').mockReturnValue('ready');
|
||||
|
||||
renderComponent();
|
||||
|
||||
expect(screen.getByTestId('trading-view')).toHaveTextContent(
|
||||
props.marketId
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,56 +0,0 @@
|
||||
import { useScript } from '@vegaprotocol/react-helpers';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useT } from './use-t';
|
||||
import { TradingView, type OnAutoSaveNeededCallback } from './trading-view';
|
||||
import { CHARTING_LIBRARY_FILE, type ResolutionString } from './constants';
|
||||
|
||||
export const TradingViewContainer = ({
|
||||
libraryPath,
|
||||
libraryHash,
|
||||
marketId,
|
||||
interval,
|
||||
studies,
|
||||
onIntervalChange,
|
||||
onAutoSaveNeeded,
|
||||
}: {
|
||||
libraryPath: string;
|
||||
libraryHash: string;
|
||||
marketId: string;
|
||||
interval: ResolutionString;
|
||||
studies: string[];
|
||||
onIntervalChange: (interval: string) => void;
|
||||
onAutoSaveNeeded: OnAutoSaveNeededCallback;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const scriptState = useScript(
|
||||
libraryPath + CHARTING_LIBRARY_FILE,
|
||||
libraryHash
|
||||
);
|
||||
|
||||
if (scriptState === 'loading' || scriptState === 'idle') {
|
||||
return (
|
||||
<Splash>
|
||||
<p>{t('Loading Trading View')}</p>
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
if (scriptState === 'error') {
|
||||
return (
|
||||
<Splash>
|
||||
<p>{t('Failed to initialize Trading view')}</p>
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TradingView
|
||||
libraryPath={libraryPath}
|
||||
marketId={marketId}
|
||||
interval={interval}
|
||||
studies={studies}
|
||||
onIntervalChange={onIntervalChange}
|
||||
onAutoSaveNeeded={onAutoSaveNeeded}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,131 +0,0 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import {
|
||||
useScreenDimensions,
|
||||
useThemeSwitcher,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import { useLanguage } from './use-t';
|
||||
import { useDatafeed } from './use-datafeed';
|
||||
import { type ResolutionString } from './constants';
|
||||
|
||||
export type OnAutoSaveNeededCallback = (data: { studies: string[] }) => void;
|
||||
|
||||
export const TradingView = ({
|
||||
marketId,
|
||||
libraryPath,
|
||||
interval,
|
||||
studies,
|
||||
onIntervalChange,
|
||||
onAutoSaveNeeded,
|
||||
}: {
|
||||
marketId: string;
|
||||
libraryPath: string;
|
||||
interval: ResolutionString;
|
||||
studies: string[];
|
||||
onIntervalChange: (interval: string) => void;
|
||||
onAutoSaveNeeded: OnAutoSaveNeededCallback;
|
||||
}) => {
|
||||
const { isMobile } = useScreenDimensions();
|
||||
const { theme } = useThemeSwitcher();
|
||||
const language = useLanguage();
|
||||
const chartContainerRef =
|
||||
useRef<HTMLDivElement>() as React.MutableRefObject<HTMLInputElement>;
|
||||
// Cant get types as charting_library is externally loaded
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const widgetRef = useRef<any>();
|
||||
|
||||
const datafeed = useDatafeed();
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
const disableOnSmallScreens = isMobile ? ['left_toolbar'] : [];
|
||||
|
||||
const overrides = getOverrides(theme);
|
||||
|
||||
const widgetOptions = {
|
||||
symbol: marketId,
|
||||
datafeed,
|
||||
interval: interval,
|
||||
container: chartContainerRef.current,
|
||||
library_path: libraryPath,
|
||||
custom_css_url: 'vega_styles.css',
|
||||
// Trading view accepts just 'en' rather than 'en-US' which is what react-i18next provides
|
||||
// https://www.tradingview.com/charting-library-docs/latest/core_concepts/Localization?_highlight=language#supported-languages
|
||||
locale: language.split('-')[0],
|
||||
enabled_features: ['tick_resolution'],
|
||||
disabled_features: [
|
||||
'header_symbol_search',
|
||||
'header_compare',
|
||||
'show_object_tree',
|
||||
'timeframes_toolbar',
|
||||
...disableOnSmallScreens,
|
||||
],
|
||||
fullscreen: false,
|
||||
autosize: true,
|
||||
theme,
|
||||
overrides,
|
||||
loading_screen: {
|
||||
backgroundColor: overrides['paneProperties.background'],
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore parent component loads TradingView onto window obj
|
||||
widgetRef.current = new window.TradingView.widget(widgetOptions);
|
||||
|
||||
widgetRef.current.onChartReady(() => {
|
||||
widgetRef.current.applyOverrides(getOverrides(theme));
|
||||
|
||||
widgetRef.current.subscribe('onAutoSaveNeeded', () => {
|
||||
const studies = widgetRef.current
|
||||
.activeChart()
|
||||
.getAllStudies()
|
||||
.map((s: { id: string; name: string }) => s.name);
|
||||
onAutoSaveNeeded({ studies });
|
||||
});
|
||||
|
||||
const activeChart = widgetRef.current.activeChart();
|
||||
|
||||
// Show volume study by default, second bool arg adds it as a overlay on top of the chart
|
||||
studies.forEach((study) => {
|
||||
const asOverlay = study === 'Volume';
|
||||
activeChart.createStudy(study, asOverlay);
|
||||
});
|
||||
|
||||
// Subscribe to interval changes so it can be persisted in chart settings
|
||||
activeChart.onIntervalChanged().subscribe(null, onIntervalChange);
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (!widgetRef.current) return;
|
||||
widgetRef.current.remove();
|
||||
};
|
||||
},
|
||||
|
||||
// No theme in deps to avoid full chart reload when the theme changes
|
||||
// Instead the theme is changed programmitcally in a separate useEffect
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[datafeed, marketId, language, libraryPath, isMobile]
|
||||
);
|
||||
|
||||
// Update the trading view theme every time the app theme updates, doen separately
|
||||
// to avoid full chart reload
|
||||
useEffect(() => {
|
||||
if (!widgetRef.current || !widgetRef.current._ready) return;
|
||||
|
||||
// Calling changeTheme will reset the default dark/light background to the TV default
|
||||
// so we need to re-apply the pane bg override. A promise is also required
|
||||
// https://github.com/tradingview/charting_library/issues/6546#issuecomment-1139517908
|
||||
widgetRef.current.changeTheme(theme).then(() => {
|
||||
widgetRef.current.applyOverrides(getOverrides(theme));
|
||||
});
|
||||
}, [theme]);
|
||||
|
||||
return <div ref={chartContainerRef} className="w-full h-full" />;
|
||||
};
|
||||
|
||||
const getOverrides = (theme: 'dark' | 'light') => {
|
||||
return {
|
||||
// colors set here, trading view lets the user set a color
|
||||
'paneProperties.background': theme === 'dark' ? '#05060C' : '#fff',
|
||||
'paneProperties.backgroundType': 'solid',
|
||||
};
|
||||
};
|
||||
@@ -1,298 +0,0 @@
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
import compact from 'lodash/compact';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { type Subscription } from 'zen-observable-ts';
|
||||
/*
|
||||
* TODO: figure out how we can get the chart types
|
||||
import {
|
||||
type LibrarySymbolInfo,
|
||||
type IBasicDataFeed,
|
||||
type ResolutionString,
|
||||
type SeriesFormat,
|
||||
} from '../charting_library/charting_library';
|
||||
*/
|
||||
import {
|
||||
GetBarsDocument,
|
||||
LastBarDocument,
|
||||
type BarFragment,
|
||||
type GetBarsQuery,
|
||||
type GetBarsQueryVariables,
|
||||
type LastBarSubscription,
|
||||
type LastBarSubscriptionVariables,
|
||||
} from './__generated__/Bars';
|
||||
import { Interval } from '@vegaprotocol/types';
|
||||
import {
|
||||
SymbolDocument,
|
||||
type SymbolQuery,
|
||||
type SymbolQueryVariables,
|
||||
} from './__generated__/Symbol';
|
||||
import { getMarketExpiryDate, toBigNum } from '@vegaprotocol/utils';
|
||||
|
||||
const EXCHANGE = 'VEGA';
|
||||
|
||||
const resolutionMap: Record<string, Interval> = {
|
||||
'1T': Interval.INTERVAL_BLOCK,
|
||||
'1': Interval.INTERVAL_I1M,
|
||||
'5': Interval.INTERVAL_I5M,
|
||||
'15': Interval.INTERVAL_I15M,
|
||||
'60': Interval.INTERVAL_I1H,
|
||||
'360': Interval.INTERVAL_I6H,
|
||||
'1D': Interval.INTERVAL_I1D,
|
||||
} as const;
|
||||
|
||||
const supportedResolutions = Object.keys(resolutionMap);
|
||||
|
||||
const configurationData = {
|
||||
// only showing Vega ofc
|
||||
exchanges: [EXCHANGE],
|
||||
|
||||
// Represents the resolutions for bars supported by your datafeed
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
supported_resolutions: supportedResolutions as ResolutionString[],
|
||||
} as const;
|
||||
|
||||
export const useDatafeed = () => {
|
||||
const hasHistory = useRef(false);
|
||||
const subRef = useRef<Subscription>();
|
||||
const client = useApolloClient();
|
||||
|
||||
const datafeed = useMemo(() => {
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
const feed: IBasicDataFeed = {
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
onReady: (callback) => {
|
||||
setTimeout(() => callback(configurationData));
|
||||
},
|
||||
|
||||
searchSymbols: () => {
|
||||
/* no op, we handle finding markets in app */
|
||||
},
|
||||
|
||||
resolveSymbol: async (
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
marketId,
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
onSymbolResolvedCallback,
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
onResolveErrorCallback
|
||||
) => {
|
||||
try {
|
||||
const result = await client.query<SymbolQuery, SymbolQueryVariables>({
|
||||
query: SymbolDocument,
|
||||
variables: {
|
||||
marketId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result.data.market) {
|
||||
onResolveErrorCallback('Cannot resolve symbol: market not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const market = result.data.market;
|
||||
const instrument = market.tradableInstrument.instrument;
|
||||
const productType = instrument.product.__typename;
|
||||
|
||||
if (!productType) {
|
||||
onResolveErrorCallback(
|
||||
'Cannot resolve symbol: invalid product type'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let type = 'undefined'; // https://www.tradingview.com/charting-library-docs/latest/api/modules/Charting_Library#symboltype
|
||||
if (productType === 'Future' || productType === 'Perpetual') {
|
||||
type = 'futures';
|
||||
} else if (productType === 'Spot') {
|
||||
type = 'spot';
|
||||
}
|
||||
|
||||
const expirationDate = getMarketExpiryDate(instrument.metadata.tags);
|
||||
const expirationTimestamp = expirationDate
|
||||
? Math.floor(expirationDate.getTime() / 1000)
|
||||
: null;
|
||||
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
const symbolInfo: LibrarySymbolInfo = {
|
||||
ticker: market.id, // use ticker as our unique identifier so that code/name can be used for name/description
|
||||
name: instrument.code,
|
||||
full_name: `${EXCHANGE}:${instrument.code}`,
|
||||
description: instrument.name,
|
||||
listed_exchange: EXCHANGE,
|
||||
expired: productType === 'Perpetual' ? false : true,
|
||||
expirationDate: expirationTimestamp,
|
||||
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
format: 'price' as SeriesFormat,
|
||||
type,
|
||||
session: '24x7',
|
||||
timezone: 'Etc/UTC',
|
||||
exchange: EXCHANGE,
|
||||
minmov: 1,
|
||||
pricescale: Number('1' + '0'.repeat(market.decimalPlaces)), // for number of decimal places
|
||||
visible_plots_set: 'ohlc',
|
||||
volume_precision: market.positionDecimalPlaces,
|
||||
data_status: 'streaming',
|
||||
delay: 1000, // around 1 block time
|
||||
has_intraday: true, // required for less than 1 day interval
|
||||
has_empty_bars: true, // library will generate bars if there are gaps, useful for auctions
|
||||
has_ticks: false, // switch to true when enabling block intervals
|
||||
|
||||
// @ts-ignore required for data conversion
|
||||
vegaDecimalPlaces: market.decimalPlaces,
|
||||
// @ts-ignore required for data conversion
|
||||
vegaPositionDecimalPlaces: market.positionDecimalPlaces,
|
||||
};
|
||||
|
||||
onSymbolResolvedCallback(symbolInfo);
|
||||
} catch (err) {
|
||||
onResolveErrorCallback('Cannot resolve symbol');
|
||||
}
|
||||
},
|
||||
|
||||
getBars: async (
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
symbolInfo,
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
resolution,
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
periodParams,
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
onHistoryCallback,
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
onErrorCallback
|
||||
) => {
|
||||
if (!symbolInfo.ticker) {
|
||||
onErrorCallback('No symbol.ticker');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await client.query<
|
||||
GetBarsQuery,
|
||||
GetBarsQueryVariables
|
||||
>({
|
||||
query: GetBarsDocument,
|
||||
variables: {
|
||||
marketId: symbolInfo.ticker,
|
||||
since: unixTimestampToDate(periodParams.from).toISOString(),
|
||||
to: unixTimestampToDate(periodParams.to).toISOString(),
|
||||
interval: resolutionMap[resolution],
|
||||
},
|
||||
});
|
||||
|
||||
const candleEdges = compact(
|
||||
result.data.market?.candlesConnection?.edges
|
||||
);
|
||||
|
||||
if (!candleEdges.length) {
|
||||
onHistoryCallback([], { noData: true });
|
||||
return;
|
||||
}
|
||||
|
||||
const bars = candleEdges.map((edge) => {
|
||||
return prepareBar(
|
||||
edge.node,
|
||||
// @ts-ignore added in resolveSymbol
|
||||
symbolInfo.vegaDecimalPlaces,
|
||||
// @ts-ignore added in resolveSymbol
|
||||
symbolInfo.vegaPositionDecimalPlaces
|
||||
);
|
||||
});
|
||||
|
||||
hasHistory.current = true;
|
||||
|
||||
onHistoryCallback(bars, { noData: false });
|
||||
} catch (err) {
|
||||
onErrorCallback(
|
||||
err instanceof Error ? err.message : 'Failed to get bars'
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
subscribeBars: (
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
symbolInfo,
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
resolution,
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
onTick
|
||||
|
||||
// subscriberUID, // chart will subscribe and unsbuscribe when the parent market of the page changes so we don't need to use subscriberUID as of now
|
||||
) => {
|
||||
if (!symbolInfo.ticker) {
|
||||
throw new Error('No symbolInfo.ticker');
|
||||
}
|
||||
|
||||
// Dont start the subscription if there is no candle history. This protects against a
|
||||
// problem where drawing on the chart throws an error if there is no prior history, instead
|
||||
// no you'll just get the no data message
|
||||
if (!hasHistory.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
subRef.current = client
|
||||
.subscribe<LastBarSubscription, LastBarSubscriptionVariables>({
|
||||
query: LastBarDocument,
|
||||
variables: {
|
||||
marketId: symbolInfo.ticker,
|
||||
interval: resolutionMap[resolution],
|
||||
},
|
||||
})
|
||||
.subscribe(({ data }) => {
|
||||
if (data) {
|
||||
const bar = prepareBar(
|
||||
data.candles,
|
||||
// @ts-ignore added in resolveSymbol
|
||||
symbolInfo.vegaDecimalPlaces,
|
||||
// @ts-ignore added in resolveSymbol
|
||||
symbolInfo.vegaPositionDecimalPlaces
|
||||
);
|
||||
|
||||
onTick(bar);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* We only have one active subscription no need to use the uid provided by unsubscribeBars
|
||||
*/
|
||||
unsubscribeBars: () => {
|
||||
if (subRef.current) {
|
||||
subRef.current.unsubscribe();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return feed;
|
||||
}, [client]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (subRef.current) {
|
||||
subRef.current.unsubscribe();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return datafeed;
|
||||
};
|
||||
|
||||
const prepareBar = (
|
||||
bar: BarFragment,
|
||||
decimalPlaces: number,
|
||||
positionDecimalPlaces: number
|
||||
) => {
|
||||
return {
|
||||
time: new Date(bar.periodStart).getTime(),
|
||||
low: toBigNum(bar.low, decimalPlaces).toNumber(),
|
||||
high: toBigNum(bar.high, decimalPlaces).toNumber(),
|
||||
open: toBigNum(bar.open, decimalPlaces).toNumber(),
|
||||
close: toBigNum(bar.close, decimalPlaces).toNumber(),
|
||||
volume: toBigNum(bar.volume, positionDecimalPlaces).toNumber(),
|
||||
};
|
||||
};
|
||||
|
||||
const unixTimestampToDate = (timestamp: number) => {
|
||||
return new Date(timestamp * 1000);
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
export const ns = 'trading-view';
|
||||
export const useT = () => useTranslation(ns).t;
|
||||
export const useLanguage = () => useTranslation(ns).i18n.language;
|
||||
@@ -1,15 +0,0 @@
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
import { locales } from '@vegaprotocol/i18n';
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
// Set up i18n instance so that components have the correct default
|
||||
// en translations
|
||||
i18n.use(initReactI18next).init({
|
||||
// we init with resources
|
||||
resources: locales,
|
||||
fallbackLng: 'en',
|
||||
ns: ['trading-view'],
|
||||
defaultNS: 'trading-view',
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": false,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
],
|
||||
"extends": "../../tsconfig.base.json"
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": [
|
||||
"node",
|
||||
|
||||
"@nx/react/typings/cssmodule.d.ts",
|
||||
"@nx/react/typings/image.d.ts"
|
||||
]
|
||||
},
|
||||
"exclude": [
|
||||
"jest.config.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.test.jsx"
|
||||
],
|
||||
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node", "@testing-library/jest-dom"]
|
||||
},
|
||||
"include": [
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.jsx",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -89,6 +89,7 @@ export const Tabs = ({
|
||||
ref={menuRef}
|
||||
className={classNames('flex-1 p-1', {
|
||||
'bg-vega-clight-700 dark:bg-vega-cdark-700': wrapped,
|
||||
'': wrapped,
|
||||
})}
|
||||
>
|
||||
{Children.map(children, (child) => {
|
||||
@@ -96,7 +97,7 @@ export const Tabs = ({
|
||||
return (
|
||||
<TabsPrimitive.Content
|
||||
value={child.props.id}
|
||||
className={classNames('flex items-center flex-nowrap gap-1', {
|
||||
className={classNames('flex flex-nowrap gap-1', {
|
||||
'justify-end': !wrapped,
|
||||
})}
|
||||
>
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
shorten,
|
||||
titlefy,
|
||||
stripFullStops,
|
||||
ensureSuffix,
|
||||
} from './strings';
|
||||
|
||||
describe('truncateByChars', () => {
|
||||
@@ -89,15 +88,3 @@ describe('stripFullStops', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ensureSuffix', () => {
|
||||
it.each([
|
||||
['', 'abc', 'abc'],
|
||||
['abc', '', 'abc'],
|
||||
['def', 'abc', 'abcdef'],
|
||||
['ąę', 'ae', 'aeąę'],
|
||||
['🥪', '🍞+🔪=', '🍞+🔪=🥪'],
|
||||
])('ensures "%s" at the end of "%s": "%s"', (suffix, input, expected) => {
|
||||
expect(ensureSuffix(input, suffix)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,9 +33,3 @@ export function titlefy(words: (string | null | undefined)[]) {
|
||||
export function stripFullStops(input: string) {
|
||||
return input.replace(/\./g, '');
|
||||
}
|
||||
|
||||
export function ensureSuffix(input: string, suffix: string) {
|
||||
const maybeSuffix = input.substring(input.length - suffix.length);
|
||||
if (maybeSuffix === suffix) return input;
|
||||
return input + suffix;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { addDecimal } from '@vegaprotocol/utils';
|
||||
import { localLoggerFactory } from '@vegaprotocol/logger';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import type { AccountFieldsFragment } from '@vegaprotocol/accounts';
|
||||
import {
|
||||
useGetWithdrawDelay,
|
||||
@@ -18,7 +18,6 @@ export const useWithdrawAsset = (
|
||||
assetId?: string
|
||||
) => {
|
||||
const { asset, balance, min, threshold, delay, update } = useWithdrawStore();
|
||||
const currentAssetId = useRef(asset?.id);
|
||||
const getThreshold = useGetWithdrawThreshold();
|
||||
const getDelay = useGetWithdrawDelay();
|
||||
const { param } = useNetworkParam(
|
||||
@@ -55,8 +54,6 @@ export const useWithdrawAsset = (
|
||||
)
|
||||
)
|
||||
: new BigNumber(0);
|
||||
currentAssetId.current = asset?.id;
|
||||
update({ asset, balance, min, threshold: undefined, delay: undefined });
|
||||
// Query collateral bridge for threshold for selected asset
|
||||
// and subsequent delay if withdrawal amount is larger than it
|
||||
let threshold = new BigNumber(0);
|
||||
@@ -69,14 +66,9 @@ export const useWithdrawAsset = (
|
||||
if (result[1] != null) delay = result[1];
|
||||
} catch (err) {
|
||||
logger.error('get withdraw asset data', err);
|
||||
} finally {
|
||||
if (currentAssetId.current === asset?.id) {
|
||||
update({
|
||||
threshold,
|
||||
delay,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
update({ asset, balance, min, threshold, delay });
|
||||
},
|
||||
[
|
||||
assets,
|
||||
|
||||
@@ -239,7 +239,7 @@ export const WithdrawForm = ({
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
{selectedAsset && (
|
||||
{selectedAsset && threshold && (
|
||||
<div className="mb-4">
|
||||
<WithdrawLimits
|
||||
amount={amount}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useT } from './use-t';
|
||||
|
||||
interface WithdrawLimitsProps {
|
||||
amount: string;
|
||||
threshold: BigNumber | undefined;
|
||||
threshold: BigNumber;
|
||||
balance: BigNumber;
|
||||
delay: number | undefined;
|
||||
asset: Asset;
|
||||
@@ -27,7 +27,7 @@ export const WithdrawLimits = ({
|
||||
}: WithdrawLimitsProps) => {
|
||||
const t = useT();
|
||||
const delayTime =
|
||||
threshold && delay && new BigNumber(amount).isGreaterThan(threshold)
|
||||
new BigNumber(amount).isGreaterThan(threshold) && delay
|
||||
? formatDistanceToNow(Date.now() + delay * 1000)
|
||||
: t('None');
|
||||
|
||||
@@ -48,23 +48,21 @@ export const WithdrawLimits = ({
|
||||
'-'
|
||||
),
|
||||
},
|
||||
{
|
||||
];
|
||||
if (threshold.isFinite()) {
|
||||
limits.push({
|
||||
key: 'WITHDRAWAL_THRESHOLD',
|
||||
label: t('Delayed withdrawal threshold'),
|
||||
tooltip: WITHDRAW_THRESHOLD_TOOLTIP_TEXT,
|
||||
rawValue: threshold,
|
||||
value: threshold ? (
|
||||
<CompactNumber number={threshold} decimals={asset.decimals} />
|
||||
) : (
|
||||
'-'
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'DELAY_TIME',
|
||||
label: t('Delay time'),
|
||||
value: threshold && delay ? delayTime : '-',
|
||||
},
|
||||
];
|
||||
value: <CompactNumber number={threshold} decimals={asset.decimals} />,
|
||||
});
|
||||
}
|
||||
limits.push({
|
||||
key: 'DELAY_TIME',
|
||||
label: t('Delay time'),
|
||||
value: delayTime,
|
||||
});
|
||||
|
||||
return (
|
||||
<KeyValueTable>
|
||||
|
||||
+2
-3
@@ -146,7 +146,6 @@
|
||||
"@storybook/react-webpack5": "7.5.3",
|
||||
"@svgr/rollup": "^8.0.1",
|
||||
"@svgr/webpack": "^6.1.2",
|
||||
"@swc-node/register": "~1.6.7",
|
||||
"@swc/cli": "^0.1.62",
|
||||
"@swc/core": "~1.3.85",
|
||||
"@swc/jest": "0.2.20",
|
||||
@@ -162,9 +161,9 @@
|
||||
"@types/lodash": "^4.14.171",
|
||||
"@types/node": "18.14.2",
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"@types/react": "18.2.33",
|
||||
"@types/react": "18.2.24",
|
||||
"@types/react-copy-to-clipboard": "5.0.7",
|
||||
"@types/react-dom": "18.2.14",
|
||||
"@types/react-dom": "18.2.9",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-syntax-highlighter": "^15.5.5",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||
|
||||
+19
-32
@@ -1,14 +1,6 @@
|
||||
# Chart
|
||||
|
||||
## Chart lib type
|
||||
|
||||
- **Must** be able to view the Vega chart by default
|
||||
- **Must** be able to switch to the TradingView chart
|
||||
- **Must** have the interval persisted between chart types
|
||||
|
||||
## Pennant chart
|
||||
|
||||
### Display options
|
||||
## Display options
|
||||
|
||||
- **Must** be able to change time interval from a list of intervals (<a name="6007-CHAR-001" href="#6007-CHAR-001">6007-CHAR-001</a>)
|
||||
- 1m
|
||||
@@ -44,7 +36,7 @@
|
||||
- **Must** be able to add multiple overlays at the same time (<a name="6007-CHAR-008" href="#6007-CHAR-008">6007-CHAR-008</a>)
|
||||
- **Must** be able to close any overlays selected (<a name="6007-CHAR-009" href="#6007-CHAR-009">6007-CHAR-009</a>)
|
||||
|
||||
### Price and Time
|
||||
## Price and Time
|
||||
|
||||
- **Must** see details of price from where my mouse cursor is on the chart(<a name="6007-CHAR-010" href="#6007-CHAR-010">6007-CHAR-010</a>)
|
||||
|
||||
@@ -62,13 +54,13 @@
|
||||
- **Must** y axis shows price range(<a name="6007-CHAR-014" href="#6007-CHAR-014">6007-CHAR-014</a>)
|
||||
- **Must** show the last price line on the Y axis (<a name="6007-CHAR-015" href="#6007-CHAR-015">6007-CHAR-015</a>)
|
||||
|
||||
### Display Types
|
||||
# Display Types
|
||||
|
||||
#### Mountain
|
||||
## Mountain
|
||||
|
||||
- **Must** show area line chart with the line being at the last price (<a name="6007-CHAR-016" href="#6007-CHAR-016">6007-CHAR-016</a>)
|
||||
|
||||
#### Candlestick
|
||||
## Candlestick
|
||||
|
||||
- **Must** body is green if the close is higher than the open (<a name="6007-CHAR-017" href="#6007-CHAR-017">6007-CHAR-017</a>)
|
||||
- **Must** body is red if the close is lower than the open (<a name="6007-CHAR-018" href="#6007-CHAR-018">6007-CHAR-018</a>)
|
||||
@@ -77,11 +69,11 @@
|
||||
- **Must** show low price (<a name="6007-CHAR-021" href="#6007-CHAR-021">6007-CHAR-021</a>)
|
||||
- **Must** show close price (<a name="6007-CHAR-022" href="#6007-CHAR-022">6007-CHAR-022</a>)
|
||||
|
||||
#### Line
|
||||
## Line
|
||||
|
||||
- **Must** show line on the chart with the line being the the last price (<a name="6007-CHAR-023" href="#6007-CHAR-023">6007-CHAR-023</a>)
|
||||
|
||||
#### OHLC
|
||||
## OHLC
|
||||
|
||||
- **Must** show open price (<a name="6007-CHAR-024" href="#6007-CHAR-024">6007-CHAR-024</a>)
|
||||
- **Must** show high price (<a name="6007-CHAR-025" href="#6007-CHAR-025">6007-CHAR-025</a>)
|
||||
@@ -90,68 +82,63 @@
|
||||
- **Must** show in green if the close is higher than the open (<a name="6007-CHAR-028" href="#6007-CHAR-028">6007-CHAR-028</a>)
|
||||
- **Must** show in red if the close is lower than the open (<a name="6007-CHAR-029" href="#6007-CHAR-029">6007-CHAR-029</a>)
|
||||
|
||||
### Overlays
|
||||
# Overlays
|
||||
|
||||
#### Bollinger bands
|
||||
## Bollinger bands
|
||||
|
||||
- **Must** show upper band (<a name="6007-CHAR-030" href="#6007-CHAR-030">6007-CHAR-030</a>)
|
||||
- **Must** show lower band (<a name="6007-CHAR-031" href="#6007-CHAR-031">6007-CHAR-031</a>)
|
||||
- **Must** show band values at time of cursor position (<a name="6007-CHAR-032" href="#6007-CHAR-032">6007-CHAR-032</a>)
|
||||
|
||||
#### Envelope
|
||||
## Envelope
|
||||
|
||||
- **Must** show upper line (<a name="6007-CHAR-033" href="#6007-CHAR-033">6007-CHAR-033</a>)
|
||||
- **Must** show lower line (<a name="6007-CHAR-034" href="#6007-CHAR-034">6007-CHAR-034</a>)
|
||||
- **Must** show line values at time of cursor position (<a name="6007-CHAR-035" href="#6007-CHAR-035">6007-CHAR-035</a>)
|
||||
|
||||
#### EMA
|
||||
## EMA
|
||||
|
||||
- **Must** show line (<a name="6007-CHAR-036" href="#6007-CHAR-036">6007-CHAR-036</a>)
|
||||
- **Must** show line value at time of cursor position (<a name="6007-CHAR-037" href="#6007-CHAR-037">6007-CHAR-037</a>)
|
||||
|
||||
#### Moving Average
|
||||
## Moving Average
|
||||
|
||||
- **Must** show line (<a name="6007-CHAR-038" href="#6007-CHAR-038">6007-CHAR-038</a>)
|
||||
- **Must** show line value at time of cursor position (<a name="6007-CHAR-039" href="#6007-CHAR-039">6007-CHAR-039</a>)
|
||||
|
||||
#### Price monitoring bounds
|
||||
## Price monitoring bounds
|
||||
|
||||
- **Must** show min line (<a name="6007-CHAR-040" href="#6007-CHAR-040">6007-CHAR-040</a>)
|
||||
- **Must** show max line (<a name="6007-CHAR-041" href="#6007-CHAR-041">6007-CHAR-041</a>)
|
||||
- **Must** show reference line (<a name="6007-CHAR-042" href="#6007-CHAR-042">6007-CHAR-042</a>)
|
||||
- **Must** show line values at time of cursor position (<a name="6007-CHAR-043" href="#6007-CHAR-043">6007-CHAR-043</a>)
|
||||
|
||||
### Studies
|
||||
# Studies
|
||||
|
||||
#### Eldar-ray
|
||||
## Eldar-ray
|
||||
|
||||
- **Must** show bear power line (<a name="6007-CHAR-044" href="#6007-CHAR-044">6007-CHAR-044</a>)
|
||||
- **Must** show bull power line (<a name="6007-CHAR-045" href="#6007-CHAR-045">6007-CHAR-045</a>)
|
||||
- **Must** show line values at time of cursor position (<a name="6007-CHAR-046" href="#6007-CHAR-046">6007-CHAR-046</a>)
|
||||
|
||||
#### Force index
|
||||
## Force index
|
||||
|
||||
- **Must** show force line (<a name="6007-CHAR-047" href="#6007-CHAR-047">6007-CHAR-047</a>)
|
||||
- **Must** show line value at time of cursor position (<a name="6007-CHAR-048" href="#6007-CHAR-048">6007-CHAR-048</a>)
|
||||
|
||||
#### MACD
|
||||
## MACD
|
||||
|
||||
- **Must** show MACD line (<a name="6007-CHAR-049" href="#6007-CHAR-049">6007-CHAR-049</a>)
|
||||
- **Must** show signal line (<a name="6007-CHAR-050" href="#6007-CHAR-050">6007-CHAR-050</a>)
|
||||
- **Must** show histogram (<a name="6007-CHAR-051" href="#6007-CHAR-051">6007-CHAR-051</a>)
|
||||
- **Must** show line values at time of cursor position (<a name="6007-CHAR-052" href="#6007-CHAR-052">6007-CHAR-052</a>)
|
||||
|
||||
#### RSI
|
||||
## RSI
|
||||
|
||||
- **Must** show RSI line (<a name="6007-CHAR-053" href="#6007-CHAR-053">6007-CHAR-053</a>)
|
||||
- **Must** show line value at time of cursor position (<a name="6007-CHAR-054" href="#6007-CHAR-054">6007-CHAR-054</a>)
|
||||
|
||||
#### Volume
|
||||
## Volume
|
||||
|
||||
- **Must** show volume bars (<a name="6007-CHAR-055" href="#6007-CHAR-055">6007-CHAR-055</a>)
|
||||
- **Must** show bar value at time of cursor position (<a name="6007-CHAR-056" href="#6007-CHAR-056">6007-CHAR-056</a>)
|
||||
|
||||
## TradingView
|
||||
|
||||
- **Must** persist interval in chart settings
|
||||
- **Must** must show an attribution to trading view
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user