Compare commits

..
10 changed files with 166 additions and 258 deletions
+2 -1
View File
@@ -59,4 +59,5 @@ apps/trading/e2e/logs/
apps/trading/e2e/.pytest_cache/
apps/trading/e2e/traces/
.nx/
.nx/cache
@@ -65,7 +65,7 @@ export const FundingContainer = ({ marketId }: { marketId: string }) => {
if (edge.node.endTime) {
acc?.push({
endTime: fromNanoSeconds(edge.node.endTime),
fundingRate: Number(edge.node.fundingRate),
fundingRate: Number(edge.node.fundingRate) * 100,
});
}
return acc;
@@ -82,8 +82,7 @@ export const FundingContainer = ({ marketId }: { marketId: string }) => {
<LineChart
data={values}
theme={theme}
priceFormat={(fundingRate) => `${(fundingRate * 100).toFixed(4)}%`}
yAxisTickFormat="%"
priceFormat={(fundingRate) => `${fundingRate.toFixed(4)}%`}
/>
);
};
@@ -29,7 +29,7 @@ def test_transfer_submit(continuous_market, vega: VegaService, page: Page):
page.select_option('[data-testid=transfer-form] [name="toVegaKey"]', index=2)
page.select_option('[data-testid=transfer-form] [name="fromAccount"]', index=1)
expected_asset_text = re.compile(r"tDAI tDAI999991.49731 tDAI.{6}….{4}")
expected_asset_text = re.compile(r"tDAI tDAI999,991.49731 tDAI.{6}….{4}")
actual_asset_text = page.get_by_test_id('select-asset').text_content().strip()
assert expected_asset_text.search(actual_asset_text), f"Expected pattern not found in {actual_asset_text}"
@@ -42,7 +42,7 @@ def test_transfer_submit(continuous_market, vega: VegaService, page: Page):
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
expected_confirmation_text = re.compile(r"Transfer completeYour transaction has been confirmedView in block explorerTransferTo .{6}….{6}1\.00 tDAI")
expected_confirmation_text = re.compile(r"Transfer completeYour transaction has been confirmed View in block explorerTransferTo .{6}….{6}1\.00 tDAI")
actual_confirmation_text = page.get_by_test_id('toast-content').text_content()
assert expected_confirmation_text.search(actual_confirmation_text), f"Expected pattern not found in {actual_confirmation_text}"
@@ -127,6 +127,6 @@ def test_transfer_vesting_below_minimum(continuous_market, vega: VegaService, pa
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
expected_confirmation_text = re.compile(r"Transfer completeYour transaction has been confirmedView in block explorerTransferTo .{6}….{6}0\.00001 tDAI")
expected_confirmation_text = re.compile(r"Transfer completeYour transaction has been confirmed View in block explorerTransferTo .{6}….{6}0\.00001 tDAI")
actual_confirmation_text = page.get_by_test_id('toast-content').text_content()
assert expected_confirmation_text.search(actual_confirmation_text), f"Expected pattern not found in {actual_confirmation_text}"
@@ -1,5 +1,5 @@
import { useCallback, useState } from 'react';
import { getAsset, getQuoteName } from '@vegaprotocol/markets';
import { getAsset, getProductType, getQuoteName } from '@vegaprotocol/markets';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet';
@@ -297,120 +297,134 @@ export const DealTicketMarginDetails = ({
);
const quoteName = getQuoteName(market);
const productType = getProductType(market);
return (
<div className="flex flex-col w-full gap-2 pt-2">
<Accordion>
<AccordionPanel
itemId="margin"
trigger={
<AccordionPrimitive.Trigger
data-testid="accordion-toggle"
className={classNames(
'w-full',
'flex items-center gap-2 text-xs',
'group'
)}
>
<div
data-testid={`deal-ticket-fee-margin-required`}
key={'value-dropdown'}
className="flex items-center justify-between w-full gap-2"
>
<div className="flex items-center text-left gap-1">
<Tooltip
description={t(
'MARGIN_DIFF_TOOLTIP_TEXT',
MARGIN_DIFF_TOOLTIP_TEXT,
{ assetSymbol }
)}
>
<span className="text-muted">{t('Margin required')}</span>
</Tooltip>
{/*
TODO: remove this conditional check once the following PRs are deployed
and the estimatePosition query is working for perps
<AccordionChevron size={10} />
</div>
<Tooltip
description={
formatRange(
marginRequiredBestCase,
marginRequiredWorstCase,
assetDecimals
) ?? '-'
}
- https://github.com/vegaprotocol/vega/pull/10119
- https://github.com/vegaprotocol/vega/pull/10122
*/}
{productType === 'Future' && (
<>
<Accordion>
<AccordionPanel
itemId="margin"
trigger={
<AccordionPrimitive.Trigger
data-testid="accordion-toggle"
className={classNames(
'w-full',
'flex items-center gap-2 text-xs',
'group'
)}
>
<div className="font-mono text-right">
{formatValue(
marginRequiredWorstCase,
assetDecimals,
quantum
)}{' '}
{assetSymbol || ''}
<div
data-testid={`deal-ticket-fee-margin-required`}
key={'value-dropdown'}
className="flex items-center justify-between w-full gap-2"
>
<div className="flex items-center text-left gap-1">
<Tooltip
description={t(
'MARGIN_DIFF_TOOLTIP_TEXT',
MARGIN_DIFF_TOOLTIP_TEXT,
{ assetSymbol }
)}
>
<span className="text-muted">
{t('Margin required')}
</span>
</Tooltip>
<AccordionChevron size={10} />
</div>
<Tooltip
description={
formatRange(
marginRequiredBestCase,
marginRequiredWorstCase,
assetDecimals
) ?? '-'
}
>
<div className="font-mono text-right">
{formatValue(
marginRequiredWorstCase,
assetDecimals,
quantum
)}{' '}
{assetSymbol || ''}
</div>
</Tooltip>
</div>
</Tooltip>
</div>
</AccordionPrimitive.Trigger>
}
>
<div className="flex flex-col w-full gap-2">
<KeyValue
label={t('Total margin available')}
indent
value={formatValue(totalMarginAvailable, assetDecimals)}
formattedValue={formatValue(
totalMarginAvailable,
assetDecimals,
quantum
)}
symbol={assetSymbol}
labelDescription={t(
'TOTAL_MARGIN_AVAILABLE',
TOTAL_MARGIN_AVAILABLE,
{
generalAccountBalance: formatValue(
generalAccountBalance,
</AccordionPrimitive.Trigger>
}
>
<div className="flex flex-col w-full gap-2">
<KeyValue
label={t('Total margin available')}
indent
value={formatValue(totalMarginAvailable, assetDecimals)}
formattedValue={formatValue(
totalMarginAvailable,
assetDecimals,
quantum
),
marginAccountBalance: formatValue(
)}
symbol={assetSymbol}
labelDescription={t(
'TOTAL_MARGIN_AVAILABLE',
TOTAL_MARGIN_AVAILABLE,
{
generalAccountBalance: formatValue(
generalAccountBalance,
assetDecimals,
quantum
),
marginAccountBalance: formatValue(
marginAccountBalance,
assetDecimals,
quantum
),
marginMaintenance: formatValue(
currentMargins?.maintenanceLevel,
assetDecimals,
quantum
),
assetSymbol,
}
)}
/>
{deductionFromCollateral}
<KeyValue
label={t('Current margin allocation')}
indent
onClick={
generalAccountBalance
? () => setBreakdownDialog(true)
: undefined
}
value={formatValue(marginAccountBalance, assetDecimals)}
symbol={assetSymbol}
labelDescription={t(
'MARGIN_ACCOUNT_TOOLTIP_TEXT',
MARGIN_ACCOUNT_TOOLTIP_TEXT
)}
formattedValue={formatValue(
marginAccountBalance,
assetDecimals,
quantum
),
marginMaintenance: formatValue(
currentMargins?.maintenanceLevel,
assetDecimals,
quantum
),
assetSymbol,
}
)}
/>
{deductionFromCollateral}
<KeyValue
label={t('Current margin allocation')}
indent
onClick={
generalAccountBalance
? () => setBreakdownDialog(true)
: undefined
}
value={formatValue(marginAccountBalance, assetDecimals)}
symbol={assetSymbol}
labelDescription={t(
'MARGIN_ACCOUNT_TOOLTIP_TEXT',
MARGIN_ACCOUNT_TOOLTIP_TEXT
)}
formattedValue={formatValue(
marginAccountBalance,
assetDecimals,
quantum
)}
/>
</div>
</AccordionPanel>
</Accordion>
{projectedMargin}
)}
/>
</div>
</AccordionPanel>
</Accordion>
{projectedMargin}
</>
)}
<KeyValue
label={t('Liquidation')}
value={liquidationPriceEstimateRange}
@@ -6,17 +6,14 @@ import { MockedProvider } from '@apollo/client/testing';
import { MAXGOINT64 } from '@vegaprotocol/utils';
const mockCreate = jest.fn();
jest.mock('@vegaprotocol/wallet', () => ({
...jest.requireActual('@vegaprotocol/wallet'),
useVegaWallet: jest.fn(() => ({ pubKey: 'partyId' })),
}));
jest.mock('@vegaprotocol/web3', () => ({
...jest.requireActual('@vegaprotocol/web3'),
useVegaTransactionStore: jest.fn(() => mockCreate),
}));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockUseDataProvider = (args: any) => {
if (args.dataProvider === positionsMarketsProvider) {
@@ -24,20 +21,17 @@ const mockUseDataProvider = (args: any) => {
}
return { data: [singleRow] };
};
jest.mock('@vegaprotocol/data-provider', () => ({
...jest.requireActual('@vegaprotocol/data-provider'),
useDataProvider: jest.fn((args) => mockUseDataProvider(args)),
}));
describe('PositionsManager', () => {
// TODO: Close position temporarily disabled in https://github.com/vegaprotocol/frontend-monorepo/pull/5350
// eslint-disable-next-line jest/no-disabled-tests
it.skip('should close position with max uint64', async () => {
it('should close position with max uint64', async () => {
render(<PositionsManager partyIds={['partyId']} isReadOnly={false} />, {
wrapper: MockedProvider,
});
expect(await screen.findByTestId('close-position')).toBeInTheDocument();
expect(await screen.getByTestId('close-position')).toBeInTheDocument();
fireEvent.click(screen.getByTestId('close-position'));
+32 -40
View File
@@ -1,4 +1,7 @@
import { useCallback } from 'react';
import { PositionsTable } from './positions-table';
import * as Schema from '@vegaprotocol/types';
import { useVegaTransactionStore } from '@vegaprotocol/web3';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useDataProvider } from '@vegaprotocol/data-provider';
import {
@@ -6,15 +9,9 @@ import {
positionsMarketsProvider,
} from './positions-data-providers';
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
import { MAXGOINT64 } from '@vegaprotocol/utils';
import { useT } from '../use-t';
// TODO: Close position temporarily disabled in https://github.com/vegaprotocol/frontend-monorepo/pull/5350
//
// import { useCallback } from 'react';
// import * as Schema from '@vegaprotocol/types';
// import { useVegaTransactionStore } from '@vegaprotocol/web3';
// import { MAXGOINT64 } from '@vegaprotocol/utils';
interface PositionsManagerProps {
partyIds: string[];
onMarketClick?: (marketId: string) => void;
@@ -32,37 +29,33 @@ export const PositionsManager = ({
}: PositionsManagerProps) => {
const t = useT();
const { pubKeys, pubKey } = useVegaWallet();
// TODO: Close position temporarily disabled in https://github.com/vegaprotocol/frontend-monorepo/pull/5350
//
// const create = useVegaTransactionStore((store) => store.create);
//
// const onClose = useCallback(
// ({ marketId, openVolume }: { marketId: string; openVolume: string }) =>
// create({
// batchMarketInstructions: {
// cancellations: [
// {
// marketId,
// orderId: '', // omit order id to cancel all active orders
// },
// ],
// submissions: [
// {
// marketId: marketId,
// type: Schema.OrderType.TYPE_MARKET as const,
// timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC as const,
// side: openVolume.startsWith('-')
// ? Schema.Side.SIDE_BUY
// : Schema.Side.SIDE_SELL,
// size: MAXGOINT64, // improvement for avoiding leftovers filled in the meantime when close request has been sent
// reduceOnly: true,
// },
// ],
// },
// }),
// [create]
// );
const create = useVegaTransactionStore((store) => store.create);
const onClose = useCallback(
({ marketId, openVolume }: { marketId: string; openVolume: string }) =>
create({
batchMarketInstructions: {
cancellations: [
{
marketId,
orderId: '', // omit order id to cancel all active orders
},
],
submissions: [
{
marketId: marketId,
type: Schema.OrderType.TYPE_MARKET as const,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC as const,
side: openVolume.startsWith('-')
? Schema.Side.SIDE_BUY
: Schema.Side.SIDE_SELL,
size: MAXGOINT64, // improvement for avoiding leftovers filled in the meantime when close request has been sent
reduceOnly: true,
},
],
},
}),
[create]
);
const { data: marketIds } = useDataProvider({
dataProvider: positionsMarketsProvider,
@@ -81,8 +74,7 @@ export const PositionsManager = ({
pubKeys={pubKeys}
rowData={data}
onMarketClick={onMarketClick}
// TODO: temporarily disable close position
// onClose={onClose}
onClose={onClose}
isReadOnly={isReadOnly}
multipleKeys={partyIds.length > 1}
overlayNoRowsTemplate={error ? error.message : t('No positions')}
@@ -1,72 +0,0 @@
import { renderHook, waitFor } from '@testing-library/react';
import { useVegaWallet } from '../use-vega-wallet';
import { useChainId } from './use-chain-id';
global.fetch = jest.fn();
const mockFetch = global.fetch as jest.Mock;
mockFetch.mockImplementation((url: string) => {
return Promise.resolve({ ok: true });
});
jest.mock('../use-vega-wallet', () => {
const original = jest.requireActual('../use-vega-wallet');
return {
...original,
useVegaWallet: jest.fn(),
};
});
describe('useChainId', () => {
it('does not call fetch when statistics url could not be determined', async () => {
(useVegaWallet as jest.Mock).mockReturnValue({
vegaUrl: '',
});
renderHook(() => useChainId());
await waitFor(() => {
expect(mockFetch).toHaveBeenCalledTimes(0);
});
});
it('calls fetch with correct statistics url', async () => {
(useVegaWallet as jest.Mock).mockReturnValue({
vegaUrl: 'http://localhost:1234/graphql',
});
renderHook(() => useChainId());
await waitFor(() => {
expect(mockFetch).toHaveBeenCalledWith(
'http://localhost:1234/statistics'
);
});
});
it('does not return chain id when chain id is not present in response', async () => {
(useVegaWallet as jest.Mock).mockReturnValue({
vegaUrl: 'http://localhost:1234/graphql',
});
const { result } = renderHook(() => useChainId());
await waitFor(() => {
expect(result.current).toBeUndefined();
});
});
it('returns chain id when chain id is present in response', async () => {
(useVegaWallet as jest.Mock).mockReturnValue({
vegaUrl: 'http://localhost:1234/graphql',
});
mockFetch.mockImplementation(() => {
return Promise.resolve({
ok: true,
json: () => ({
statistics: {
chainId: '1234',
},
}),
});
});
const { result } = renderHook(() => useChainId());
await waitFor(() => {
expect(result.current).not.toBeUndefined();
expect(result.current).toEqual('1234');
});
});
});
+2 -22
View File
@@ -3,37 +3,17 @@ import { useVegaWallet } from '../use-vega-wallet';
const cache: Record<string, string> = {};
/**
* Gets the statistics url for a given vega url.
* Example:
* https://graphql.example.com/graphql -> https://graphql.example.com/statistics
*/
const getNodeStatisticsUrl = (vegaUrl: string) => {
try {
const url = new URL(vegaUrl);
url.pathname = 'statistics';
return url.toString();
} catch (err) {
return undefined;
}
};
export const useChainId = () => {
const { vegaUrl } = useVegaWallet();
const [chainId, setChainId] = useState<undefined | string>(cache[vegaUrl]);
const [fetchAttempt, setFetchAttempt] = useState(1);
const statisticsUrl = getNodeStatisticsUrl(vegaUrl);
useEffect(() => {
// abort when `/statistics` URL could not be determined
if (!statisticsUrl) return;
let isCancelled = false;
if (cache[vegaUrl]) {
setChainId(cache[vegaUrl]);
return;
}
fetch(statisticsUrl)
fetch(vegaUrl.replace('graphql', 'statistics'))
.then((response) => response.json())
.then((response) => {
if (isCancelled) {
@@ -52,6 +32,6 @@ export const useChainId = () => {
return () => {
isCancelled = true;
};
}, [fetchAttempt, statisticsUrl, vegaUrl]);
}, [fetchAttempt, vegaUrl]);
return chainId;
};
+1 -1
View File
@@ -77,7 +77,7 @@
"jsondiffpatch": "^0.4.1",
"lodash": "^4.17.21",
"next": "13.3.0",
"pennant": "^1.15.0",
"pennant": "^1.14.1",
"react": "18.2.0",
"react-copy-to-clipboard": "5.1.0",
"react-dom": "18.2.0",
+4 -4
View File
@@ -17884,10 +17884,10 @@ pend@~1.2.0:
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
pennant@^1.15.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/pennant/-/pennant-1.15.0.tgz#21854cf78466cbd27eda8143c21abcde070d4d76"
integrity sha512-p3H4vu6BP7nUqn7s2pjyTl0FMJUT2U5lq5UKuxy55F2E66sJnvp5XFvNjRZvi42vyzsxG8WURgwQDQKNqQgWAw==
pennant@^1.14.1:
version "1.14.1"
resolved "https://registry.yarnpkg.com/pennant/-/pennant-1.14.1.tgz#8e7a53256095e398b03397af31c831b02a56032b"
integrity sha512-rjzo/tlFanO96OKhJiyjQtjug7sY6pjVZqVbieD3Tf4zt20bqIb6m4L2JfwOXEe0jqP/yddgCNPY+vlkVuJW1Q==
dependencies:
"@babel/runtime" "^7.13.10"
"@d3fc/d3fc-technical-indicator" "^8.0.1"