Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4a0aa50c7 | ||
|
|
eeda2ffb24 | ||
|
|
52ab0562b0 | ||
|
|
4e2b0d1b1d | ||
|
|
eac26c1966 | ||
|
|
e5d4d2b0b8 | ||
|
|
d4e801cfc6 | ||
|
|
d9dc43b359 | ||
|
|
06a6fe6d67 |
@@ -52,3 +52,4 @@ cypress.env.json
|
||||
/apps/**/cypress/reports/
|
||||
/apps/**/cypress/downloads/
|
||||
/apps/**/fixtures/wallet/node**
|
||||
.nx/
|
||||
|
||||
@@ -15,15 +15,12 @@ export const LedgerContainer = () => {
|
||||
});
|
||||
|
||||
const assets = (data?.party?.accountsConnection?.edges ?? [])
|
||||
.map<PartyAssetFieldsFragment>(
|
||||
(item) => item?.node?.asset ?? ({} as PartyAssetFieldsFragment)
|
||||
)
|
||||
.reduce((aggr, item) => {
|
||||
if ('id' in item && 'symbol' in item) {
|
||||
aggr[item.id as string] = item.symbol as string;
|
||||
}
|
||||
return aggr;
|
||||
}, {} as Record<string, string>);
|
||||
.map((item) => item?.node?.asset)
|
||||
.filter((asset): asset is PartyAssetFieldsFragment => !!asset?.id)
|
||||
.reduce(
|
||||
(aggr, item) => Object.assign(aggr, { [item.id]: item.symbol }),
|
||||
{} as Record<string, string>
|
||||
);
|
||||
|
||||
if (!pubKey) {
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Settings } from './settings';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
describe('Settings', () => {
|
||||
it('should the settings component with all the options', () => {
|
||||
render(<Settings />);
|
||||
expect(screen.getByText('Dark mode')).toBeInTheDocument();
|
||||
expect(screen.getByText('Share usage data')).toBeInTheDocument();
|
||||
expect(screen.getByText('Toast location')).toBeInTheDocument();
|
||||
expect(screen.getByText('Reset to default')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,19 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Switch, ToastPositionSetter } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
Dialog,
|
||||
Intent,
|
||||
Switch,
|
||||
ToastPositionSetter,
|
||||
TradingButton,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||
import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useState, type ReactNode } from 'react';
|
||||
|
||||
export const Settings = () => {
|
||||
const { theme, setTheme } = useThemeSwitcher();
|
||||
const [isApproved, setIsApproved] = useTelemetryApproval();
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<div>
|
||||
<SettingsGroup label={t('Dark mode')}>
|
||||
@@ -31,6 +38,52 @@ export const Settings = () => {
|
||||
<SettingsGroup label={t('Toast location')}>
|
||||
<ToastPositionSetter />
|
||||
</SettingsGroup>
|
||||
<SettingsGroup label={t('Reset to default')}>
|
||||
<TradingButton
|
||||
name="reset-to-defaults"
|
||||
size="small"
|
||||
intent={Intent.None}
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
{t('Reset')}
|
||||
</TradingButton>
|
||||
<Dialog open={open} title={t('Reset')}>
|
||||
<div className="mb-4">
|
||||
<p>
|
||||
{t(
|
||||
'You will lose all persisted settings and you will be logged out.'
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{t('Are you sure you want to reset all settings to default?')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<TradingButton
|
||||
name="reset-to-defaults-cancel"
|
||||
intent={Intent.Primary}
|
||||
onClick={() => {
|
||||
localStorage.clear();
|
||||
window.location.reload();
|
||||
}}
|
||||
>
|
||||
{t('Yes, clear cache and refresh')}
|
||||
</TradingButton>
|
||||
<TradingButton
|
||||
name="reset-to-defaults-cancel"
|
||||
intent={Intent.None}
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
{t('No, keep settings')}
|
||||
</TradingButton>
|
||||
</div>
|
||||
</Dialog>
|
||||
</SettingsGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -170,7 +170,7 @@ html [data-theme='dark'] {
|
||||
|
||||
.ag-theme-balham,
|
||||
.ag-theme-balham-dark {
|
||||
--ag-grid-size: 2px; /* Used for compactness */
|
||||
--ag-grid-size: 3px; /* Used for compactness */
|
||||
--ag-row-height: 36px;
|
||||
--ag-header-height: 28px;
|
||||
}
|
||||
@@ -184,7 +184,7 @@ html [data-theme='dark'] {
|
||||
|
||||
/* Light variables */
|
||||
.ag-theme-balham {
|
||||
--ag-background-color: transparent;
|
||||
--ag-background-color: theme(colors.vega.clight.900);
|
||||
--ag-border-color: theme(colors.vega.clight.600);
|
||||
--ag-header-background-color: theme(colors.vega.clight.700);
|
||||
--ag-odd-row-background-color: transparent;
|
||||
@@ -196,7 +196,7 @@ html [data-theme='dark'] {
|
||||
|
||||
/* Dark variables */
|
||||
.ag-theme-balham-dark {
|
||||
--ag-background-color: transparent;
|
||||
--ag-background-color: theme(colors.vega.cdark.900);
|
||||
--ag-border-color: theme(colors.vega.cdark.600);
|
||||
--ag-header-background-color: theme(colors.vega.cdark.700);
|
||||
--ag-odd-row-background-color: transparent;
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import { act, render, screen, within } from '@testing-library/react';
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
import type { AccountFields } from './accounts-data-provider';
|
||||
import { getAccountData } from './accounts-data-provider';
|
||||
import { AccountTable } from './accounts-table';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
const asset1 = {
|
||||
__typename: 'Asset',
|
||||
id: 'asset-1',
|
||||
symbol: 'tBTC',
|
||||
decimals: 5,
|
||||
name: 'T BTC',
|
||||
};
|
||||
const asset2 = {
|
||||
__typename: 'Asset',
|
||||
id: 'asset-2',
|
||||
symbol: 'aBTC',
|
||||
decimals: 5,
|
||||
name: 'A BTC',
|
||||
};
|
||||
const singleRow = {
|
||||
__typename: 'AccountBalance',
|
||||
type: Types.AccountType.ACCOUNT_TYPE_MARGIN,
|
||||
@@ -13,12 +26,7 @@ const singleRow = {
|
||||
__typename: 'Market',
|
||||
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
|
||||
},
|
||||
asset: {
|
||||
__typename: 'Asset',
|
||||
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||
symbol: 'tBTC',
|
||||
decimals: 5,
|
||||
},
|
||||
asset: asset1,
|
||||
available: '125600000',
|
||||
used: '125600000',
|
||||
total: '251200000',
|
||||
@@ -33,12 +41,7 @@ const secondRow = {
|
||||
__typename: 'Market',
|
||||
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
|
||||
},
|
||||
asset: {
|
||||
__typename: 'Asset',
|
||||
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||
symbol: 'aBTC',
|
||||
decimals: 5,
|
||||
},
|
||||
asset: asset2,
|
||||
available: '125600001',
|
||||
used: '125600001',
|
||||
total: '251200002',
|
||||
@@ -134,7 +137,7 @@ describe('AccountsTable', () => {
|
||||
|
||||
it('should sort assets', async () => {
|
||||
// 7001-COLL-010
|
||||
const { container } = render(
|
||||
render(
|
||||
<AccountTable
|
||||
rowData={multiRowData}
|
||||
onClickAsset={() => null}
|
||||
@@ -142,13 +145,13 @@ describe('AccountsTable', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
const headerCell = screen.getByText('Asset');
|
||||
await userEvent.click(headerCell);
|
||||
const rows = container.querySelectorAll(
|
||||
'.ag-center-cols-container .ag-row'
|
||||
);
|
||||
expect(rows[0].textContent).toContain('aBTC');
|
||||
expect(rows[1].textContent).toContain('tBTC');
|
||||
const headerCell = screen
|
||||
.getAllByRole('columnheader')
|
||||
.find((h) => h?.getAttribute('col-id') === 'asset.symbol') as HTMLElement;
|
||||
|
||||
await userEvent.click(within(headerCell).getByText(/asset/i));
|
||||
|
||||
expect(headerCell).toHaveAttribute('aria-sort', 'ascending');
|
||||
});
|
||||
|
||||
it('should apply correct formatting in view as user mode', async () => {
|
||||
@@ -176,12 +179,7 @@ describe('AccountsTable', () => {
|
||||
rowData={singleRowData}
|
||||
onClickAsset={() => null}
|
||||
isReadOnly={false}
|
||||
pinnedAsset={{
|
||||
decimals: 5,
|
||||
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||
symbol: 'tBTC',
|
||||
name: 'tBTC',
|
||||
}}
|
||||
pinnedAsset={asset1}
|
||||
/>
|
||||
);
|
||||
await screen.findAllByRole('rowgroup');
|
||||
@@ -213,23 +211,13 @@ describe('AccountsTable', () => {
|
||||
const result = getAccountData([singleRow]);
|
||||
const expected = [
|
||||
{
|
||||
asset: {
|
||||
__typename: 'Asset',
|
||||
decimals: 5,
|
||||
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||
symbol: 'tBTC',
|
||||
},
|
||||
asset: asset1,
|
||||
available: '0',
|
||||
balance: '0',
|
||||
breakdown: [
|
||||
{
|
||||
__typename: 'AccountBalance',
|
||||
asset: {
|
||||
__typename: 'Asset',
|
||||
decimals: 5,
|
||||
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||
symbol: 'tBTC',
|
||||
},
|
||||
asset: asset1,
|
||||
available: '0',
|
||||
balance: '125600000',
|
||||
total: '125600000',
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import {
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
within,
|
||||
} from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import {
|
||||
@@ -7,7 +13,7 @@ import {
|
||||
TransferForm,
|
||||
type TransferFormProps,
|
||||
} from './transfer-form';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import { AccountType, AccountTypeMapping } from '@vegaprotocol/types';
|
||||
import { removeDecimal } from '@vegaprotocol/utils';
|
||||
|
||||
describe('TransferForm', () => {
|
||||
@@ -39,8 +45,7 @@ describe('TransferForm', () => {
|
||||
};
|
||||
|
||||
const amount = '100';
|
||||
const pubKey =
|
||||
'70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680';
|
||||
const pubKey = '1'.repeat(64);
|
||||
const asset = {
|
||||
id: 'eur',
|
||||
symbol: '€',
|
||||
@@ -50,10 +55,7 @@ describe('TransferForm', () => {
|
||||
};
|
||||
const props = {
|
||||
pubKey,
|
||||
pubKeys: [
|
||||
pubKey,
|
||||
'a4b6e3de5d7ef4e31ae1b090be49d1a2ef7bcefff60cccf7658a0d4922651cce',
|
||||
],
|
||||
pubKeys: [pubKey, '2'.repeat(64)],
|
||||
feeFactor: '0.001',
|
||||
submitTransfer: jest.fn(),
|
||||
accounts: [
|
||||
@@ -182,7 +184,7 @@ describe('TransferForm', () => {
|
||||
|
||||
// Test use max button
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Use max' }));
|
||||
expect(amountInput).toHaveValue('1000');
|
||||
expect(amountInput).toHaveValue('1000.00');
|
||||
|
||||
// Test amount validation
|
||||
await userEvent.clear(amountInput);
|
||||
@@ -267,7 +269,7 @@ describe('TransferForm', () => {
|
||||
|
||||
// Test use max button
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Use max' }));
|
||||
expect(amountInput).toHaveValue('100');
|
||||
expect(amountInput).toHaveValue('100.00');
|
||||
|
||||
// If transfering from a vested account 'include fees' checkbox should
|
||||
// be disabled and fees should be 0
|
||||
@@ -296,6 +298,88 @@ describe('TransferForm', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('handles lots of decimal places', async () => {
|
||||
const balance = '904195168829277777';
|
||||
const expectedBalance = '0.904195168829277777';
|
||||
|
||||
const longDecimalAsset = {
|
||||
id: 'assetId',
|
||||
symbol: 'VEGA',
|
||||
name: 'VEGA',
|
||||
decimals: 18,
|
||||
quantum: '1',
|
||||
};
|
||||
|
||||
const account = {
|
||||
type: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||
asset: longDecimalAsset,
|
||||
balance,
|
||||
};
|
||||
|
||||
const mockSubmit = jest.fn();
|
||||
|
||||
renderComponent({
|
||||
...props,
|
||||
accounts: [account],
|
||||
submitTransfer: mockSubmit,
|
||||
minQuantumMultiple: '100000',
|
||||
});
|
||||
|
||||
// Select a pubkey
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('To Vega key'),
|
||||
props.pubKeys[1] // Use not current pubkey so we can check it switches to current pubkey later
|
||||
);
|
||||
|
||||
// Select asset
|
||||
await selectAsset(longDecimalAsset);
|
||||
|
||||
const accountSelect = screen.getByLabelText('From account');
|
||||
const option = within(accountSelect)
|
||||
.getAllByRole('option')
|
||||
.find(
|
||||
(o) => o.getAttribute('value') === `${account.type}-${account.asset.id}`
|
||||
);
|
||||
// plus one for disabled 'please select' option
|
||||
|
||||
expect(option).toHaveTextContent(
|
||||
`${AccountTypeMapping[account.type]} (${expectedBalance} ${
|
||||
account.asset.symbol
|
||||
})`
|
||||
);
|
||||
|
||||
await userEvent.selectOptions(
|
||||
accountSelect,
|
||||
`${AccountType.ACCOUNT_TYPE_VESTED_REWARDS}-${longDecimalAsset.id}`
|
||||
);
|
||||
|
||||
expect(accountSelect).toHaveValue(
|
||||
`${AccountType.ACCOUNT_TYPE_VESTED_REWARDS}-${longDecimalAsset.id}`
|
||||
);
|
||||
|
||||
// Check switch back to connected key
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
|
||||
// Test use max button
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Use max' }));
|
||||
expect(amountInput).toHaveValue(expectedBalance);
|
||||
|
||||
await submit();
|
||||
|
||||
await waitFor(() => {
|
||||
// 1003-TRAN-023
|
||||
expect(mockSubmit).toHaveBeenCalledTimes(1);
|
||||
expect(mockSubmit).toHaveBeenCalledWith({
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
to: props.pubKey,
|
||||
asset: longDecimalAsset.id,
|
||||
amount: balance,
|
||||
oneOff: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('IncludeFeesCheckbox', () => {
|
||||
it('validates fields and submits when checkbox is checked', async () => {
|
||||
const mockSubmit = jest.fn();
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
vegaPublicKey,
|
||||
addDecimal,
|
||||
formatNumber,
|
||||
addDecimalsFormatNumber,
|
||||
toBigNum,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
@@ -214,12 +213,7 @@ export const TransferForm = ({
|
||||
<AssetOption
|
||||
key={a.key}
|
||||
asset={a}
|
||||
balance={
|
||||
<Balance
|
||||
balance={formatNumber(a.balance, a.decimals)}
|
||||
symbol={a.symbol}
|
||||
/>
|
||||
}
|
||||
balance={<Balance balance={a.balance} symbol={a.symbol} />}
|
||||
/>
|
||||
))}
|
||||
</TradingRichSelect>
|
||||
@@ -286,8 +280,8 @@ export const TransferForm = ({
|
||||
return (
|
||||
<option value={id} key={id}>
|
||||
{AccountTypeMapping[a.type]} (
|
||||
{addDecimalsFormatNumber(a.balance, a.asset.decimals)}{' '}
|
||||
{a.asset.symbol})
|
||||
{addDecimal(a.balance, a.asset.decimals)} {a.asset.symbol}
|
||||
)
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
@@ -413,7 +407,7 @@ export const TransferForm = ({
|
||||
type="button"
|
||||
className="absolute top-0 right-0 ml-auto text-xs underline"
|
||||
onClick={() =>
|
||||
setValue('amount', parseFloat(accountBalance).toString(), {
|
||||
setValue('amount', accountBalance, {
|
||||
shouldValidate: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -37,6 +37,9 @@ export const AgGridThemed = ({
|
||||
<div className={wrapperClasses}>
|
||||
<AgGridReact
|
||||
defaultColDef={defaultColDef}
|
||||
overlayLoadingTemplate={t('Loading...')}
|
||||
overlayNoRowsTemplate={t('No data')}
|
||||
suppressDragLeaveHidesColumns
|
||||
ref={gridRef}
|
||||
{...defaultProps}
|
||||
{...props}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { getAsset, getProductType, getQuoteName } from '@vegaprotocol/markets';
|
||||
import { getAsset, getQuoteName } from '@vegaprotocol/markets';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
|
||||
@@ -151,6 +151,7 @@ export const DealTicketMarginDetails = ({
|
||||
const { decimals: assetDecimals, quantum } = asset;
|
||||
let marginRequiredBestCase: string | undefined = undefined;
|
||||
let marginRequiredWorstCase: string | undefined = undefined;
|
||||
|
||||
if (marginEstimate) {
|
||||
if (currentMargins) {
|
||||
marginRequiredBestCase = (
|
||||
@@ -286,115 +287,99 @@ export const DealTicketMarginDetails = ({
|
||||
);
|
||||
|
||||
const quoteName = getQuoteName(market);
|
||||
const productType = getProductType(market);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full gap-2 pt-2">
|
||||
{/*
|
||||
TODO: remove this conditional check once the following PRs are deployed
|
||||
and the estimatePosition query is working for perps
|
||||
|
||||
- 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
|
||||
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={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>
|
||||
</AccordionPrimitive.Trigger>
|
||||
}
|
||||
<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="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={TOTAL_MARGIN_AVAILABLE(
|
||||
formatValue(generalAccountBalance, assetDecimals, quantum),
|
||||
formatValue(marginAccountBalance, assetDecimals, quantum),
|
||||
formatValue(
|
||||
currentMargins?.maintenanceLevel,
|
||||
<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={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
|
||||
)}
|
||||
/>
|
||||
{deductionFromCollateral}
|
||||
<KeyValue
|
||||
label={t('Current margin allocation')}
|
||||
indent
|
||||
onClick={
|
||||
generalAccountBalance
|
||||
? () => setBreakdownDialog(true)
|
||||
: undefined
|
||||
}
|
||||
value={formatValue(marginAccountBalance, assetDecimals)}
|
||||
symbol={assetSymbol}
|
||||
labelDescription={MARGIN_ACCOUNT_TOOLTIP_TEXT}
|
||||
formattedValue={formatValue(
|
||||
marginAccountBalance,
|
||||
assetDecimals,
|
||||
quantum
|
||||
)}
|
||||
/>
|
||||
)}{' '}
|
||||
{assetSymbol || ''}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
{projectedMargin}
|
||||
</>
|
||||
)}
|
||||
</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={TOTAL_MARGIN_AVAILABLE(
|
||||
formatValue(generalAccountBalance, assetDecimals, quantum),
|
||||
formatValue(marginAccountBalance, assetDecimals, quantum),
|
||||
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={MARGIN_ACCOUNT_TOOLTIP_TEXT}
|
||||
formattedValue={formatValue(
|
||||
marginAccountBalance,
|
||||
assetDecimals,
|
||||
quantum
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
{projectedMargin}
|
||||
<KeyValue
|
||||
label={t('Liquidation')}
|
||||
value={liquidationPriceEstimateRange}
|
||||
|
||||
@@ -278,18 +278,4 @@ describe('createDownloadUrl', () => {
|
||||
)}&dateRange.endTimestamp=${toNanoSeconds(dateTo)}`
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if invalid args are provided', () => {
|
||||
// invalid url
|
||||
expect(() => {
|
||||
// @ts-ignore override z.infer type
|
||||
createDownloadUrl({ ...args, protohost: 'foo' });
|
||||
}).toThrow();
|
||||
|
||||
// invalid partyId
|
||||
expect(() => {
|
||||
// @ts-ignore override z.infer type
|
||||
createDownloadUrl({ ...args, partyId: 'z'.repeat(64) });
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import { format, subDays } from 'date-fns';
|
||||
import { useRef, useCallback } from 'react';
|
||||
import { subDays } from 'date-fns';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import {
|
||||
InputError,
|
||||
Intent,
|
||||
Loader,
|
||||
TradingButton,
|
||||
TradingFormGroup,
|
||||
TradingInput,
|
||||
TradingSelect,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
formatForInput,
|
||||
getDateTimeFormat,
|
||||
toNanoSeconds,
|
||||
VEGA_ID_REGEX,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { localLoggerFactory } from '@vegaprotocol/logger';
|
||||
@@ -35,18 +35,18 @@ const getProtoHost = (vegaurl: string) => {
|
||||
return `${loc.protocol}//${loc.host}`;
|
||||
};
|
||||
|
||||
const downloadSchema = z.object({
|
||||
protohost: z.string().url().nonempty(),
|
||||
partyId: z.string().regex(VEGA_ID_REGEX).nonempty(),
|
||||
assetId: z.string().regex(VEGA_ID_REGEX).nonempty(),
|
||||
dateFrom: z.string().nonempty(),
|
||||
dateTo: z.string().optional(),
|
||||
});
|
||||
|
||||
export const createDownloadUrl = (args: z.infer<typeof downloadSchema>) => {
|
||||
// check args from form inputs
|
||||
downloadSchema.parse(args);
|
||||
type LedgerFormValues = {
|
||||
assetId: string;
|
||||
dateFrom: string;
|
||||
dateTo?: string;
|
||||
};
|
||||
|
||||
export const createDownloadUrl = (
|
||||
args: LedgerFormValues & {
|
||||
partyId: string;
|
||||
protohost: string;
|
||||
}
|
||||
) => {
|
||||
const params = new URLSearchParams();
|
||||
params.append('partyId', args.partyId);
|
||||
params.append('assetId', args.assetId);
|
||||
@@ -71,114 +71,101 @@ interface Props {
|
||||
|
||||
export const LedgerExportForm = ({ partyId, vegaUrl, assets }: Props) => {
|
||||
const now = useRef(new Date());
|
||||
const [dateFrom, setDateFrom] = useState(() => {
|
||||
return formatForInput(subDays(now.current, 7));
|
||||
const { control, handleSubmit, watch } = useForm<LedgerFormValues>({
|
||||
defaultValues: {
|
||||
dateFrom: formatForInput(subDays(now.current, 7)),
|
||||
dateTo: '',
|
||||
assetId: Object.keys(assets)[0],
|
||||
},
|
||||
});
|
||||
const [dateTo, setDateTo] = useState('');
|
||||
const dateTo = watch('dateTo');
|
||||
const maxFromDate = formatForInput(new Date(dateTo || now.current));
|
||||
const maxToDate = formatForInput(now.current);
|
||||
|
||||
const [assetId, setAssetId] = useState(Object.keys(assets)[0]);
|
||||
const protohost = getProtoHost(vegaUrl);
|
||||
const disabled = Boolean(!assetId);
|
||||
|
||||
const hasItem = useLedgerDownloadFile((store) => store.hasItem);
|
||||
const updateDownloadQueue = useLedgerDownloadFile(
|
||||
(store) => store.updateQueue
|
||||
);
|
||||
|
||||
const assetDropDown = (
|
||||
<TradingSelect
|
||||
id="select-ledger-asset"
|
||||
value={assetId}
|
||||
onChange={(e) => {
|
||||
setAssetId(e.target.value);
|
||||
}}
|
||||
className="w-full"
|
||||
data-testid="select-ledger-asset"
|
||||
>
|
||||
{Object.keys(assets).map((assetKey) => (
|
||||
<option key={assetKey} value={assetKey}>
|
||||
{assets[assetKey]}
|
||||
</option>
|
||||
))}
|
||||
</TradingSelect>
|
||||
);
|
||||
const startDownload = useCallback(
|
||||
async (formValues: LedgerFormValues) => {
|
||||
const link = createDownloadUrl({
|
||||
protohost,
|
||||
partyId,
|
||||
...formValues,
|
||||
});
|
||||
|
||||
const link = createDownloadUrl({
|
||||
protohost,
|
||||
partyId,
|
||||
assetId,
|
||||
dateFrom,
|
||||
dateTo,
|
||||
});
|
||||
const dateTimeFormatter = getDateTimeFormat();
|
||||
const title = t('Downloading for %s from %s till %s', [
|
||||
assets[formValues.assetId],
|
||||
dateTimeFormatter.format(new Date(formValues.dateFrom)),
|
||||
dateTimeFormatter.format(new Date(formValues.dateTo || Date.now())),
|
||||
]);
|
||||
|
||||
const startDownload = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
const title = t('Downloading for %s from %s till %s', [
|
||||
assets[assetId],
|
||||
format(new Date(dateFrom), 'dd MMMM yyyy HH:mm'),
|
||||
format(new Date(dateTo || Date.now()), 'dd MMMM yyyy HH:mm'),
|
||||
]);
|
||||
|
||||
const downloadStoreItem = {
|
||||
title,
|
||||
link,
|
||||
isChanged: true,
|
||||
};
|
||||
if (hasItem(link)) {
|
||||
updateDownloadQueue(downloadStoreItem);
|
||||
return;
|
||||
}
|
||||
const ts = setTimeout(() => {
|
||||
updateDownloadQueue({
|
||||
...downloadStoreItem,
|
||||
intent: Intent.Warning,
|
||||
isDelayed: true,
|
||||
const downloadStoreItem = {
|
||||
title,
|
||||
link,
|
||||
isChanged: true,
|
||||
});
|
||||
}, 1000 * 30);
|
||||
|
||||
try {
|
||||
updateDownloadQueue(downloadStoreItem);
|
||||
const resp = await fetch(link);
|
||||
if (!resp?.ok) {
|
||||
if (resp?.status === 429) {
|
||||
throw new Error('Too many requests. Try again later.');
|
||||
}
|
||||
throw new Error('Download of ledger entries failed');
|
||||
};
|
||||
if (hasItem(link)) {
|
||||
updateDownloadQueue(downloadStoreItem);
|
||||
return;
|
||||
}
|
||||
const { headers } = resp;
|
||||
const nameHeader = headers.get('content-disposition');
|
||||
const filename = nameHeader?.split('=').pop() ?? DEFAULT_EXPORT_FILE_NAME;
|
||||
updateDownloadQueue({
|
||||
...downloadStoreItem,
|
||||
filename,
|
||||
});
|
||||
const blob = await resp.blob();
|
||||
if (blob) {
|
||||
const ts = setTimeout(() => {
|
||||
updateDownloadQueue({
|
||||
...downloadStoreItem,
|
||||
blob,
|
||||
isDownloaded: true,
|
||||
intent: Intent.Warning,
|
||||
isDelayed: true,
|
||||
isChanged: true,
|
||||
intent: Intent.Success,
|
||||
});
|
||||
}, 1000 * 30);
|
||||
|
||||
try {
|
||||
updateDownloadQueue(downloadStoreItem);
|
||||
const resp = await fetch(link);
|
||||
if (!resp?.ok) {
|
||||
if (resp?.status === 429) {
|
||||
throw new Error('Too many requests. Try again later.');
|
||||
}
|
||||
throw new Error('Download of ledger entries failed');
|
||||
}
|
||||
const { headers } = resp;
|
||||
const nameHeader = headers.get('content-disposition');
|
||||
const filename =
|
||||
nameHeader?.split('=').pop() ?? DEFAULT_EXPORT_FILE_NAME;
|
||||
updateDownloadQueue({
|
||||
...downloadStoreItem,
|
||||
filename,
|
||||
});
|
||||
const blob = await resp.blob();
|
||||
if (blob) {
|
||||
updateDownloadQueue({
|
||||
...downloadStoreItem,
|
||||
blob,
|
||||
isDownloaded: true,
|
||||
isChanged: true,
|
||||
intent: Intent.Success,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
localLoggerFactory({ application: 'ledger' }).error(
|
||||
'Download file',
|
||||
err
|
||||
);
|
||||
updateDownloadQueue({
|
||||
...downloadStoreItem,
|
||||
intent: Intent.Danger,
|
||||
isError: true,
|
||||
isChanged: true,
|
||||
errorMessage: (err as Error).message || undefined,
|
||||
});
|
||||
} finally {
|
||||
clearTimeout(ts);
|
||||
}
|
||||
} catch (err) {
|
||||
localLoggerFactory({ application: 'ledger' }).error('Download file', err);
|
||||
updateDownloadQueue({
|
||||
...downloadStoreItem,
|
||||
intent: Intent.Danger,
|
||||
isError: true,
|
||||
isChanged: true,
|
||||
errorMessage: (err as Error).message || undefined,
|
||||
});
|
||||
} finally {
|
||||
clearTimeout(ts);
|
||||
}
|
||||
};
|
||||
},
|
||||
[assets, hasItem, partyId, protohost, updateDownloadQueue]
|
||||
);
|
||||
|
||||
if (!protohost || Object.keys(assets).length === 0) {
|
||||
return null;
|
||||
@@ -187,49 +174,117 @@ export const LedgerExportForm = ({ partyId, vegaUrl, assets }: Props) => {
|
||||
const offset = new Date().getTimezoneOffset();
|
||||
|
||||
return (
|
||||
<form onSubmit={startDownload} className="p-4 w-[350px]">
|
||||
<form
|
||||
onSubmit={handleSubmit(startDownload)}
|
||||
className="p-4 w-[350px]"
|
||||
noValidate
|
||||
>
|
||||
<h2 className="mb-4">{t('Export ledger entries')}</h2>
|
||||
<TradingFormGroup label={t('Select asset')} labelFor="asset">
|
||||
{assetDropDown}
|
||||
</TradingFormGroup>
|
||||
<TradingFormGroup label={t('Date from')} labelFor="date-from">
|
||||
<TradingInput
|
||||
type="datetime-local"
|
||||
data-testid="date-from"
|
||||
id="date-from"
|
||||
value={dateFrom}
|
||||
onChange={(e) => setDateFrom(e.target.value)}
|
||||
max={maxFromDate}
|
||||
/>
|
||||
</TradingFormGroup>
|
||||
<TradingFormGroup label={t('Date to')} labelFor="date-to">
|
||||
<TradingInput
|
||||
type="datetime-local"
|
||||
data-testid="date-to"
|
||||
id="date-to"
|
||||
value={dateTo}
|
||||
onChange={(e) => setDateTo(e.target.value)}
|
||||
max={maxToDate}
|
||||
/>
|
||||
</TradingFormGroup>
|
||||
<Controller
|
||||
name="assetId"
|
||||
control={control}
|
||||
rules={{
|
||||
required: t('You need to select an asset'),
|
||||
}}
|
||||
render={({ field, fieldState }) => (
|
||||
<div className="mb-2">
|
||||
<TradingFormGroup
|
||||
label={t('Select asset')}
|
||||
labelFor="asset"
|
||||
compact
|
||||
>
|
||||
<TradingSelect
|
||||
{...field}
|
||||
id="select-ledger-asset"
|
||||
className="w-full"
|
||||
data-testid="select-ledger-asset"
|
||||
>
|
||||
{Object.keys(assets).map((assetKey) => (
|
||||
<option key={assetKey} value={assetKey}>
|
||||
{assets[assetKey]}
|
||||
</option>
|
||||
))}
|
||||
</TradingSelect>
|
||||
</TradingFormGroup>
|
||||
{fieldState.error && (
|
||||
<InputError>{fieldState.error.message}</InputError>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="dateFrom"
|
||||
control={control}
|
||||
rules={{
|
||||
required: t('You need to provide a date from'),
|
||||
max: {
|
||||
value: maxFromDate,
|
||||
message: dateTo
|
||||
? t('Date from cannot be greater than date to')
|
||||
: t('Date from cannot be in the future'),
|
||||
},
|
||||
deps: ['dateTo'],
|
||||
}}
|
||||
render={({ field, fieldState }) => (
|
||||
<div className="mb-2">
|
||||
<TradingFormGroup
|
||||
label={t('Date from')}
|
||||
labelFor="date-from"
|
||||
compact
|
||||
>
|
||||
<TradingInput
|
||||
{...field}
|
||||
type="datetime-local"
|
||||
data-testid="date-from"
|
||||
id="date-from"
|
||||
max={maxFromDate}
|
||||
/>
|
||||
</TradingFormGroup>
|
||||
{fieldState.error && (
|
||||
<InputError>{fieldState.error.message}</InputError>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="dateTo"
|
||||
control={control}
|
||||
rules={{
|
||||
max: {
|
||||
value: maxToDate,
|
||||
message: t('Date to cannot be in the future'),
|
||||
},
|
||||
}}
|
||||
render={({ field, fieldState }) => (
|
||||
<div className="mb-2">
|
||||
<TradingFormGroup label={t('Date to')} labelFor="date-to" compact>
|
||||
<TradingInput
|
||||
{...field}
|
||||
type="datetime-local"
|
||||
data-testid="date-to"
|
||||
id="date-to"
|
||||
max={maxToDate}
|
||||
/>
|
||||
</TradingFormGroup>
|
||||
{fieldState.error && (
|
||||
<InputError>{fieldState.error.message}</InputError>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<div className="relative text-sm" title={t('Download all to .csv file')}>
|
||||
<TradingButton
|
||||
fill
|
||||
disabled={disabled}
|
||||
type="submit"
|
||||
data-testid="ledger-download-button"
|
||||
>
|
||||
<TradingButton fill type="submit" data-testid="ledger-download-button">
|
||||
{t('Download')}
|
||||
</TradingButton>
|
||||
</div>
|
||||
{offset && (
|
||||
{offset ? (
|
||||
<p className="text-xs text-neutral-400 mt-1">
|
||||
{t(
|
||||
'The downloaded file uses the UTC time zone for all listed times. Your time zone is UTC%s.',
|
||||
[toHoursAndMinutes(offset)]
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
) : null}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -106,9 +106,6 @@ export const LiquidityTable = ({
|
||||
|
||||
const feesAccruedTooltip = ({ value, data }: ITooltipParams) => {
|
||||
if (!value) return '-';
|
||||
const newValue = new BigNumber(value)
|
||||
.times(Number(stakeToCcyVolume) || 1)
|
||||
.toString();
|
||||
let lessThanFull = false,
|
||||
lessThanMinimum = false;
|
||||
if (data.sla) {
|
||||
@@ -153,7 +150,7 @@ export const LiquidityTable = ({
|
||||
]
|
||||
);
|
||||
}
|
||||
return addDecimalsFormatNumber(newValue, assetDecimalPlaces ?? 0);
|
||||
return addDecimalsFormatNumber(value, assetDecimalPlaces ?? 0);
|
||||
};
|
||||
|
||||
const stakeToCcyVolumeQuantumFormatter = ({
|
||||
|
||||
@@ -27,7 +27,9 @@ jest.mock('@vegaprotocol/data-provider', () => ({
|
||||
}));
|
||||
|
||||
describe('PositionsManager', () => {
|
||||
it('should close position with max uint64', async () => {
|
||||
// TODO: temporarily disable close position
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('should close position with max uint64', async () => {
|
||||
render(<PositionsManager partyIds={['partyId']} isReadOnly={false} />, {
|
||||
wrapper: MockedProvider,
|
||||
});
|
||||
|
||||
@@ -29,6 +29,7 @@ export const PositionsManager = ({
|
||||
}: PositionsManagerProps) => {
|
||||
const { pubKeys, pubKey } = useVegaWallet();
|
||||
const create = useVegaTransactionStore((store) => store.create);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const onClose = useCallback(
|
||||
({ marketId, openVolume }: { marketId: string; openVolume: string }) =>
|
||||
create({
|
||||
@@ -73,7 +74,8 @@ export const PositionsManager = ({
|
||||
pubKeys={pubKeys}
|
||||
rowData={data}
|
||||
onMarketClick={onMarketClick}
|
||||
onClose={onClose}
|
||||
// TODO: temporarily disable close position
|
||||
// onClose={onClose}
|
||||
isReadOnly={isReadOnly}
|
||||
multipleKeys={partyIds.length > 1}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No positions')}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,17 +3,37 @@ 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(vegaUrl.replace('graphql', 'statistics'))
|
||||
fetch(statisticsUrl)
|
||||
.then((response) => response.json())
|
||||
.then((response) => {
|
||||
if (isCancelled) {
|
||||
@@ -32,6 +52,6 @@ export const useChainId = () => {
|
||||
return () => {
|
||||
isCancelled = true;
|
||||
};
|
||||
}, [fetchAttempt, vegaUrl]);
|
||||
}, [fetchAttempt, statisticsUrl, vegaUrl]);
|
||||
return chainId;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user