Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eceb608318 | ||
|
|
395f779c18 | ||
|
|
3ddaf28b2c | ||
|
|
f75a53c11a | ||
|
|
1c4975c619 | ||
|
|
20233db706 | ||
|
|
d4f50eb70c | ||
|
|
e5d3f90d45 | ||
|
|
673c896e2f | ||
|
|
deea63fa5e | ||
|
|
d31333538b | ||
|
|
4af9979a21 |
+2
-2
@@ -36,13 +36,13 @@ describe('ProposalReferralProgramDetails helper functions', () => {
|
||||
it('should format referral discount factor correctly', () => {
|
||||
const input = '0.05';
|
||||
const formatted = formatReferralDiscountFactor(input);
|
||||
expect(formatted).toBe('5.00%');
|
||||
expect(formatted).toBe('5%');
|
||||
});
|
||||
|
||||
it('should format referral reward factor correctly', () => {
|
||||
const input = '0.1';
|
||||
const formatted = formatReferralRewardFactor(input);
|
||||
expect(formatted).toBe('10.00%');
|
||||
expect(formatted).toBe('10%');
|
||||
});
|
||||
|
||||
it('should format minimum staked tokens correctly', () => {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"next",
|
||||
"next/core-web-vitals"
|
||||
],
|
||||
"ignorePatterns": ["!**/*", "__generated__"],
|
||||
"ignorePatterns": ["!**/*", "__generated__", ".next"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
|
||||
@@ -71,7 +71,7 @@ export const useReferral = (args: UseReferralArgs) => {
|
||||
variables: {
|
||||
code: referralSet?.id as string,
|
||||
aggregationEpochs:
|
||||
args.aggregationEpochs != null
|
||||
args.aggregationEpochs !== null
|
||||
? args.aggregationEpochs
|
||||
: DEFAULT_AGGREGATION_DAYS,
|
||||
},
|
||||
|
||||
@@ -3,6 +3,8 @@ import {
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
truncateMiddle,
|
||||
ExternalLink,
|
||||
Tooltip,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
@@ -28,6 +30,7 @@ import { useCurrentEpochInfoQuery } from './hooks/__generated__/Epoch';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import maxBy from 'lodash/maxBy';
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
|
||||
export const ReferralStatistics = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
@@ -201,7 +204,7 @@ export const Statistics = ({
|
||||
'Total commission (last %s epochs)',
|
||||
(details?.windowLength || DEFAULT_AGGREGATION_DAYS).toString()
|
||||
)}
|
||||
description={t('(qUSD)')}
|
||||
description={<QUSDTooltip />}
|
||||
>
|
||||
{getNumberFormat(0).format(Number(totalCommissionValue))}
|
||||
</StatTile>
|
||||
@@ -233,7 +236,12 @@ export const Statistics = ({
|
||||
<StatTile title={t('Discount')}>{discountFactorValue * 100}%</StatTile>
|
||||
);
|
||||
const runningVolumeTile = (
|
||||
<StatTile title={t('Combined volume')}>
|
||||
<StatTile
|
||||
title={t(
|
||||
'Combined volume (last %s epochs)',
|
||||
details?.windowLength.toString()
|
||||
)}
|
||||
>
|
||||
{compactNumFormat.format(runningVolumeValue)}
|
||||
</StatTile>
|
||||
);
|
||||
@@ -241,24 +249,14 @@ export const Statistics = ({
|
||||
<StatTile title={t('Epochs in set')}>{epochsValue}</StatTile>
|
||||
);
|
||||
const nextTierVolumeTile = (
|
||||
<StatTile
|
||||
title={t(
|
||||
'Volume to next tier %s',
|
||||
nextBenefitTierValue?.tier ? `(${nextBenefitTierValue.tier})` : ''
|
||||
)}
|
||||
>
|
||||
<StatTile title={t('Volume to next tier')}>
|
||||
{nextBenefitTierVolumeValue <= 0
|
||||
? '0'
|
||||
: compactNumFormat.format(nextBenefitTierVolumeValue)}
|
||||
</StatTile>
|
||||
);
|
||||
const nextTierEpochsTile = (
|
||||
<StatTile
|
||||
title={t(
|
||||
'Epochs to next tier %s',
|
||||
nextBenefitTierValue?.tier ? `(${nextBenefitTierValue.tier})` : ''
|
||||
)}
|
||||
>
|
||||
<StatTile title={t('Epochs to next tier')}>
|
||||
{nextBenefitTierEpochsValue <= 0 ? '0' : nextBenefitTierEpochsValue}
|
||||
</StatTile>
|
||||
);
|
||||
@@ -339,11 +337,16 @@ export const Statistics = ({
|
||||
},
|
||||
{
|
||||
name: 'commission',
|
||||
displayName: t(
|
||||
'Commission earned (last %s epochs)',
|
||||
(
|
||||
details?.windowLength || DEFAULT_AGGREGATION_DAYS
|
||||
).toString()
|
||||
displayName: (
|
||||
<>
|
||||
{t('Commission earned in')} <QUSDTooltip />{' '}
|
||||
{t(
|
||||
'(last %s epochs)',
|
||||
(
|
||||
details?.windowLength || DEFAULT_AGGREGATION_DAYS
|
||||
).toString()
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
]}
|
||||
@@ -373,3 +376,25 @@ export const Statistics = ({
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const QUSDTooltip = () => (
|
||||
<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>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Tooltip, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import classNames from 'classnames';
|
||||
import { forwardRef, type HTMLAttributes } from 'react';
|
||||
import { forwardRef, type ReactNode, type HTMLAttributes } from 'react';
|
||||
import { BORDER_COLOR, GRADIENT } from './constants';
|
||||
|
||||
type TableColumnDefinition = {
|
||||
displayName?: string;
|
||||
displayName?: ReactNode;
|
||||
name: string;
|
||||
tooltip?: string;
|
||||
className?: string;
|
||||
@@ -46,7 +46,7 @@ export const Table = forwardRef<
|
||||
INNER_BORDER_STYLE
|
||||
)}
|
||||
>
|
||||
<span className="flex flex-row gap-2 items-center">
|
||||
<span className="flex flex-row items-center gap-2">
|
||||
<span>{displayName}</span>
|
||||
{tooltip ? (
|
||||
<Tooltip description={tooltip}>
|
||||
@@ -102,17 +102,14 @@ export const Table = forwardRef<
|
||||
key={`${i}-${name}`}
|
||||
>
|
||||
{/** display column name in mobile view */}
|
||||
{!noCollapse &&
|
||||
!noHeader &&
|
||||
displayName &&
|
||||
displayName.length > 0 && (
|
||||
<span
|
||||
aria-hidden
|
||||
className="md:hidden font-mono text-xs px-0 text-vega-clight-100 dark:text-vega-cdark-100"
|
||||
>
|
||||
{displayName}
|
||||
</span>
|
||||
)}
|
||||
{!noCollapse && !noHeader && displayName && (
|
||||
<span
|
||||
aria-hidden
|
||||
className="px-0 font-mono text-xs md:hidden text-vega-clight-100 dark:text-vega-cdark-100"
|
||||
>
|
||||
{displayName}
|
||||
</span>
|
||||
)}
|
||||
<span>{d[name]}</span>
|
||||
</td>
|
||||
))}
|
||||
|
||||
@@ -29,7 +29,7 @@ export const Tile = ({
|
||||
|
||||
type StatTileProps = {
|
||||
title: string;
|
||||
description?: string;
|
||||
description?: ReactNode;
|
||||
children?: ReactNode;
|
||||
};
|
||||
export const StatTile = ({ title, description, children }: StatTileProps) => {
|
||||
@@ -67,11 +67,11 @@ export const CodeTile = ({
|
||||
title={t('Your referral code')}
|
||||
description={createdAt ? t('(Created at: %s)', createdAt) : undefined}
|
||||
>
|
||||
<div className="flex gap-2 items-center justify-between">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<Tooltip
|
||||
description={
|
||||
<div className="break-all">
|
||||
<span className="text-xl bg-rainbow bg-clip-text text-transparent">
|
||||
<span className="text-xl text-transparent bg-rainbow bg-clip-text">
|
||||
{code}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -112,6 +112,8 @@ const USE_ACCOUNT_TYPES = [
|
||||
AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||
AccountType.ACCOUNT_TYPE_FEES_MAKER,
|
||||
AccountType.ACCOUNT_TYPE_PENDING_TRANSFERS,
|
||||
AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||
AccountType.ACCOUNT_TYPE_VESTING_REWARDS,
|
||||
];
|
||||
|
||||
const getAssetIds = (data: Account[]) =>
|
||||
|
||||
@@ -9,12 +9,17 @@ import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type { Transfer } from '@vegaprotocol/wallet';
|
||||
import { useVegaTransactionStore } from '@vegaprotocol/web3';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { accountsDataProvider } from './accounts-data-provider';
|
||||
import { TransferForm } from './transfer-form';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import { Lozenge } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const ALLOWED_ACCOUNTS = [
|
||||
Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||
];
|
||||
|
||||
export const TransferContainer = ({ assetId }: { assetId?: string }) => {
|
||||
const { pubKey, pubKeys } = useVegaWallet();
|
||||
const { param } = useNetworkParam(NetworkParams.transfer_fee_factor);
|
||||
@@ -33,20 +38,21 @@ export const TransferContainer = ({ assetId }: { assetId?: string }) => {
|
||||
[create]
|
||||
);
|
||||
|
||||
const assets = useMemo(() => {
|
||||
if (!data) return [];
|
||||
return data
|
||||
.filter(
|
||||
(account) => account.type === Schema.AccountType.ACCOUNT_TYPE_GENERAL
|
||||
)
|
||||
.map((account) => ({
|
||||
id: account.asset.id,
|
||||
symbol: account.asset.symbol,
|
||||
name: account.asset.name,
|
||||
decimals: account.asset.decimals,
|
||||
balance: addDecimal(account.balance, account.asset.decimals),
|
||||
}));
|
||||
}, [data]);
|
||||
const accounts = data
|
||||
? data.filter((account) => ALLOWED_ACCOUNTS.includes(account.type))
|
||||
: [];
|
||||
|
||||
const assets = accounts
|
||||
// Theres only one general account for each asset, this will give us a list
|
||||
// of assets the user has accounts for
|
||||
.filter((a) => a.type === Schema.AccountType.ACCOUNT_TYPE_GENERAL)
|
||||
.map((account) => ({
|
||||
id: account.asset.id,
|
||||
symbol: account.asset.symbol,
|
||||
name: account.asset.name,
|
||||
decimals: account.asset.decimals,
|
||||
balance: addDecimal(account.balance, account.asset.decimals),
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -69,6 +75,7 @@ export const TransferContainer = ({ assetId }: { assetId?: string }) => {
|
||||
assetId={assetId}
|
||||
feeFactor={param}
|
||||
submitTransfer={transfer}
|
||||
accounts={accounts}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,18 +1,38 @@
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { AddressField, TransferFee, TransferForm } from './transfer-form';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import { addDecimal, formatNumber, removeDecimal } from '@vegaprotocol/utils';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
describe('TransferForm', () => {
|
||||
const submit = () => fireEvent.submit(screen.getByTestId('transfer-form'));
|
||||
const submit = async () => {
|
||||
await userEvent.click(
|
||||
screen.getByRole('button', { name: 'Confirm transfer' })
|
||||
);
|
||||
};
|
||||
|
||||
const selectAsset = async (asset: {
|
||||
id: string;
|
||||
balance: string;
|
||||
name: string;
|
||||
decimals: number;
|
||||
}) => {
|
||||
// Bypass RichSelect and target hidden native select
|
||||
// eslint-disable-next-line
|
||||
fireEvent.change(document.querySelector('select[name="asset"]')!, {
|
||||
target: { value: asset.id },
|
||||
});
|
||||
|
||||
// assert rich select as updated
|
||||
expect(await screen.findByTestId('select-asset')).toHaveTextContent(
|
||||
asset.name
|
||||
);
|
||||
expect(await screen.findByTestId('asset-balance')).toHaveTextContent(
|
||||
formatNumber(asset.balance, asset.decimals)
|
||||
);
|
||||
};
|
||||
|
||||
const amount = '100';
|
||||
const pubKey =
|
||||
'70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680';
|
||||
@@ -32,6 +52,18 @@ describe('TransferForm', () => {
|
||||
assets: [asset],
|
||||
feeFactor: '0.001',
|
||||
submitTransfer: jest.fn(),
|
||||
accounts: [
|
||||
{
|
||||
type: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
asset,
|
||||
balance: '100',
|
||||
},
|
||||
{
|
||||
type: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||
asset,
|
||||
balance: '100',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
it('form tooltips correctly displayed', async () => {
|
||||
@@ -42,49 +74,45 @@ describe('TransferForm', () => {
|
||||
// 1003-TRAN-019
|
||||
render(<TransferForm {...props} />);
|
||||
// Select a pubkey
|
||||
fireEvent.change(screen.getByLabelText('Vega key'), {
|
||||
target: { value: props.pubKeys[1] },
|
||||
});
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('Vega key'),
|
||||
props.pubKeys[1]
|
||||
);
|
||||
|
||||
// Select asset
|
||||
fireEvent.change(
|
||||
// Bypass RichSelect and target hidden native select
|
||||
// eslint-disable-next-line
|
||||
document.querySelector('select[name="asset"]')!,
|
||||
{ target: { value: asset.id } }
|
||||
);
|
||||
await selectAsset(asset);
|
||||
|
||||
// set valid amount
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: amount },
|
||||
});
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
await userEvent.type(amountInput, amount);
|
||||
expect(amountInput).toHaveValue(amount);
|
||||
|
||||
userEvent.hover(screen.getByText('Include transfer fee'));
|
||||
const includeTransferLabel = screen.getByText('Include transfer fee');
|
||||
await userEvent.hover(includeTransferLabel);
|
||||
expect(await screen.findByRole('tooltip')).toHaveTextContent(
|
||||
'The fee will be taken from the amount you are transferring.'
|
||||
);
|
||||
await userEvent.unhover(screen.getByText('Include transfer fee'));
|
||||
|
||||
await waitFor(() => {
|
||||
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||
expect(tooltips[0]).toBeVisible();
|
||||
});
|
||||
const transferFee = screen.getByText('Transfer fee');
|
||||
await userEvent.hover(transferFee);
|
||||
expect(await screen.findByRole('tooltip')).toHaveTextContent(
|
||||
/transfer.fee.factor/
|
||||
);
|
||||
await userEvent.unhover(transferFee);
|
||||
|
||||
userEvent.hover(screen.getByText('Transfer fee'));
|
||||
const amountToBeTransferred = screen.getByText('Amount to be transferred');
|
||||
await userEvent.hover(amountToBeTransferred);
|
||||
expect(await screen.findByRole('tooltip')).toHaveTextContent(
|
||||
/without the fee/
|
||||
);
|
||||
await userEvent.unhover(amountToBeTransferred);
|
||||
|
||||
await waitFor(() => {
|
||||
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||
expect(tooltips[0]).toBeVisible();
|
||||
});
|
||||
|
||||
userEvent.hover(screen.getByText('Amount to be transferred'));
|
||||
|
||||
await waitFor(() => {
|
||||
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||
expect(tooltips[0]).toBeVisible();
|
||||
});
|
||||
|
||||
userEvent.hover(screen.getByText('Total amount (with fee)'));
|
||||
|
||||
await waitFor(() => {
|
||||
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||
expect(tooltips[0]).toBeVisible();
|
||||
});
|
||||
const totalAmountWithFee = screen.getByText('Total amount (with fee)');
|
||||
await userEvent.hover(totalAmountWithFee);
|
||||
expect(await screen.findByRole('tooltip')).toHaveTextContent(
|
||||
/total amount taken from your account/
|
||||
);
|
||||
});
|
||||
|
||||
it('validates a manually entered address', async () => {
|
||||
@@ -92,30 +120,17 @@ describe('TransferForm', () => {
|
||||
// 1003-TRAN-013
|
||||
// 1003-TRAN-004
|
||||
render(<TransferForm {...props} />);
|
||||
submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(3);
|
||||
await submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(4);
|
||||
const toggle = screen.getByText('Enter manually');
|
||||
fireEvent.click(toggle);
|
||||
await userEvent.click(toggle);
|
||||
// has switched to input
|
||||
expect(toggle).toHaveTextContent('Select from wallet');
|
||||
expect(screen.getByLabelText('Vega key')).toHaveAttribute('type', 'text');
|
||||
fireEvent.change(screen.getByLabelText('Vega key'), {
|
||||
target: { value: 'invalid-address' },
|
||||
});
|
||||
await waitFor(() => {
|
||||
const errors = screen.getAllByTestId('input-error-text');
|
||||
expect(errors[0]).toHaveTextContent('Invalid Vega key');
|
||||
});
|
||||
|
||||
// same pubkey
|
||||
fireEvent.change(screen.getByLabelText('Vega key'), {
|
||||
target: { value: pubKey },
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const errors = screen.getAllByTestId('input-error-text');
|
||||
expect(errors[0]).toHaveTextContent('Vega key is the same');
|
||||
});
|
||||
await userEvent.type(screen.getByLabelText('Vega key'), 'invalid-address');
|
||||
expect(screen.getAllByTestId('input-error-text')[0]).toHaveTextContent(
|
||||
'Invalid Vega key'
|
||||
);
|
||||
});
|
||||
|
||||
it('validates fields and submits', async () => {
|
||||
@@ -127,28 +142,25 @@ describe('TransferForm', () => {
|
||||
render(<TransferForm {...props} />);
|
||||
|
||||
// check current pubkey not shown
|
||||
const keySelect: HTMLSelectElement = screen.getByLabelText('Vega key');
|
||||
expect(keySelect.children).toHaveLength(2);
|
||||
const keySelect = screen.getByLabelText<HTMLSelectElement>('Vega key');
|
||||
expect(keySelect.children).toHaveLength(3);
|
||||
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual([
|
||||
'',
|
||||
pubKey,
|
||||
props.pubKeys[1],
|
||||
]);
|
||||
|
||||
submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(3);
|
||||
await submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(4);
|
||||
|
||||
// Select a pubkey
|
||||
fireEvent.change(screen.getByLabelText('Vega key'), {
|
||||
target: { value: props.pubKeys[1] },
|
||||
});
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('Vega key'),
|
||||
props.pubKeys[1]
|
||||
);
|
||||
|
||||
// Select asset
|
||||
fireEvent.change(
|
||||
// Bypass RichSelect and target hidden native select
|
||||
// eslint-disable-next-line
|
||||
document.querySelector('select[name="asset"]')!,
|
||||
{ target: { value: asset.id } }
|
||||
);
|
||||
await selectAsset(asset);
|
||||
|
||||
// assert rich select as updated
|
||||
expect(await screen.findByTestId('select-asset')).toHaveTextContent(
|
||||
@@ -158,37 +170,38 @@ describe('TransferForm', () => {
|
||||
formatNumber(asset.balance, asset.decimals)
|
||||
);
|
||||
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('From account'),
|
||||
AccountType.ACCOUNT_TYPE_VESTED_REWARDS
|
||||
);
|
||||
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
|
||||
// Test amount validation
|
||||
fireEvent.change(amountInput, {
|
||||
target: { value: '0.00000001' },
|
||||
});
|
||||
await userEvent.type(amountInput, '0.00000001');
|
||||
expect(
|
||||
await screen.findByText('Value is below minimum')
|
||||
).toBeInTheDocument();
|
||||
|
||||
fireEvent.change(amountInput, {
|
||||
target: { value: '9999999' },
|
||||
});
|
||||
await userEvent.clear(amountInput);
|
||||
await userEvent.type(amountInput, '9999999');
|
||||
expect(
|
||||
await screen.findByText(/cannot transfer more/i)
|
||||
).toBeInTheDocument();
|
||||
|
||||
// set valid amount
|
||||
fireEvent.change(amountInput, {
|
||||
target: { value: amount },
|
||||
});
|
||||
await userEvent.clear(amountInput);
|
||||
await userEvent.type(amountInput, amount);
|
||||
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(
|
||||
new BigNumber(props.feeFactor).times(amount).toFixed()
|
||||
);
|
||||
|
||||
submit();
|
||||
await submit();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(props.submitTransfer).toHaveBeenCalledTimes(1);
|
||||
expect(props.submitTransfer).toHaveBeenCalledWith({
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
to: props.pubKeys[1],
|
||||
asset: asset.id,
|
||||
@@ -200,59 +213,50 @@ describe('TransferForm', () => {
|
||||
|
||||
describe('IncludeFeesCheckbox', () => {
|
||||
it('validates fields and submits when checkbox is checked', async () => {
|
||||
render(<TransferForm {...props} />);
|
||||
const mockSubmit = jest.fn();
|
||||
render(<TransferForm {...props} submitTransfer={mockSubmit} />);
|
||||
|
||||
// check current pubkey not shown
|
||||
const keySelect: HTMLSelectElement = screen.getByLabelText('Vega key');
|
||||
expect(keySelect.children).toHaveLength(2);
|
||||
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual([
|
||||
'',
|
||||
props.pubKeys[1],
|
||||
]);
|
||||
const keySelect = screen.getByLabelText<HTMLSelectElement>('Vega key');
|
||||
const pubKeyOptions = ['', pubKey, props.pubKeys[1]];
|
||||
expect(keySelect.children).toHaveLength(pubKeyOptions.length);
|
||||
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual(
|
||||
pubKeyOptions
|
||||
);
|
||||
|
||||
submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(3);
|
||||
await submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(4);
|
||||
|
||||
// Select a pubkey
|
||||
fireEvent.change(screen.getByLabelText('Vega key'), {
|
||||
target: { value: props.pubKeys[1] },
|
||||
});
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('Vega key'),
|
||||
props.pubKeys[1]
|
||||
);
|
||||
|
||||
// Select asset
|
||||
fireEvent.change(
|
||||
// Bypass RichSelect and target hidden native select
|
||||
// eslint-disable-next-line
|
||||
document.querySelector('select[name="asset"]')!,
|
||||
{ target: { value: asset.id } }
|
||||
);
|
||||
await selectAsset(asset);
|
||||
|
||||
// assert rich select as updated
|
||||
expect(await screen.findByTestId('select-asset')).toHaveTextContent(
|
||||
asset.name
|
||||
);
|
||||
expect(await screen.findByTestId('asset-balance')).toHaveTextContent(
|
||||
formatNumber(asset.balance, asset.decimals)
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('From account'),
|
||||
AccountType.ACCOUNT_TYPE_VESTED_REWARDS
|
||||
);
|
||||
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
const checkbox = screen.getByTestId('include-transfer-fee');
|
||||
|
||||
// 1003-TRAN-022
|
||||
expect(checkbox).not.toBeChecked();
|
||||
act(() => {
|
||||
/* fire events that update state */
|
||||
// set valid amount
|
||||
fireEvent.change(amountInput, {
|
||||
target: { value: amount },
|
||||
});
|
||||
// check include fees checkbox
|
||||
fireEvent.click(checkbox);
|
||||
});
|
||||
|
||||
await userEvent.clear(amountInput);
|
||||
await userEvent.type(amountInput, amount);
|
||||
await userEvent.click(checkbox);
|
||||
|
||||
expect(checkbox).toBeChecked();
|
||||
const expectedFee = new BigNumber(amount)
|
||||
.times(props.feeFactor)
|
||||
.toFixed();
|
||||
const expectedAmount = new BigNumber(amount).minus(expectedFee).toFixed();
|
||||
|
||||
// 1003-TRAN-020
|
||||
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
|
||||
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(
|
||||
@@ -262,18 +266,17 @@ describe('TransferForm', () => {
|
||||
amount
|
||||
);
|
||||
|
||||
submit();
|
||||
await submit();
|
||||
|
||||
await waitFor(() => {
|
||||
// 1003-TRAN-023
|
||||
|
||||
expect(props.submitTransfer).toHaveBeenCalledTimes(1);
|
||||
expect(props.submitTransfer).toHaveBeenCalledWith({
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
expect(mockSubmit).toHaveBeenCalledTimes(1);
|
||||
expect(mockSubmit).toHaveBeenCalledWith({
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
to: props.pubKeys[1],
|
||||
asset: asset.id,
|
||||
amount: removeDecimal(amount, asset.decimals),
|
||||
amount: removeDecimal(expectedAmount, asset.decimals),
|
||||
oneOff: {},
|
||||
});
|
||||
});
|
||||
@@ -284,46 +287,29 @@ describe('TransferForm', () => {
|
||||
|
||||
// check current pubkey not shown
|
||||
const keySelect: HTMLSelectElement = screen.getByLabelText('Vega key');
|
||||
expect(keySelect.children).toHaveLength(2);
|
||||
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual([
|
||||
'',
|
||||
props.pubKeys[1],
|
||||
]);
|
||||
const pubKeyOptions = ['', pubKey, props.pubKeys[1]];
|
||||
expect(keySelect.children).toHaveLength(pubKeyOptions.length);
|
||||
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual(
|
||||
pubKeyOptions
|
||||
);
|
||||
|
||||
submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(3);
|
||||
await submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(4);
|
||||
|
||||
// Select a pubkey
|
||||
fireEvent.change(screen.getByLabelText('Vega key'), {
|
||||
target: { value: props.pubKeys[1] },
|
||||
});
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('Vega key'),
|
||||
props.pubKeys[1]
|
||||
);
|
||||
|
||||
// Select asset
|
||||
fireEvent.change(
|
||||
// Bypass RichSelect and target hidden native select
|
||||
// eslint-disable-next-line
|
||||
document.querySelector('select[name="asset"]')!,
|
||||
{ target: { value: asset.id } }
|
||||
);
|
||||
|
||||
// assert rich select as updated
|
||||
expect(await screen.findByTestId('select-asset')).toHaveTextContent(
|
||||
asset.name
|
||||
);
|
||||
expect(await screen.findByTestId('asset-balance')).toHaveTextContent(
|
||||
formatNumber(asset.balance, asset.decimals)
|
||||
);
|
||||
await selectAsset(asset);
|
||||
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
const checkbox = screen.getByTestId('include-transfer-fee');
|
||||
expect(checkbox).not.toBeChecked();
|
||||
act(() => {
|
||||
/* fire events that update state */
|
||||
// set valid amount
|
||||
fireEvent.change(amountInput, {
|
||||
target: { value: amount },
|
||||
});
|
||||
});
|
||||
|
||||
await userEvent.type(amountInput, amount);
|
||||
expect(checkbox).not.toBeChecked();
|
||||
const expectedFee = new BigNumber(amount)
|
||||
.times(props.feeFactor)
|
||||
@@ -351,11 +337,11 @@ describe('TransferForm', () => {
|
||||
// select should be shown as multiple pubkeys provided
|
||||
expect(screen.getByText('select')).toBeInTheDocument();
|
||||
expect(screen.queryByText('input')).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByText('Enter manually'));
|
||||
await userEvent.click(screen.getByText('Enter manually'));
|
||||
expect(screen.queryByText('select')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('input')).toBeInTheDocument();
|
||||
expect(mockOnChange).toHaveBeenCalledTimes(1);
|
||||
fireEvent.click(screen.getByText('Select from wallet'));
|
||||
await userEvent.click(screen.getByText('Select from wallet'));
|
||||
expect(screen.getByText('select')).toBeInTheDocument();
|
||||
expect(screen.queryByText('input')).not.toBeInTheDocument();
|
||||
expect(mockOnChange).toHaveBeenCalledTimes(2);
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
vegaPublicKey,
|
||||
addDecimal,
|
||||
formatNumber,
|
||||
addDecimalsFormatNumber,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
@@ -24,22 +25,31 @@ import type { ReactNode } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { AssetOption, Balance } from '@vegaprotocol/assets';
|
||||
import { AccountType, AccountTypeMapping } from '@vegaprotocol/types';
|
||||
|
||||
interface FormFields {
|
||||
toAddress: string;
|
||||
asset: string;
|
||||
amount: string;
|
||||
fromAccount: AccountType;
|
||||
}
|
||||
|
||||
interface Asset {
|
||||
id: string;
|
||||
symbol: string;
|
||||
name: string;
|
||||
decimals: number;
|
||||
balance: string;
|
||||
}
|
||||
|
||||
interface TransferFormProps {
|
||||
pubKey: string | null;
|
||||
pubKeys: string[] | null;
|
||||
assets: Array<{
|
||||
id: string;
|
||||
symbol: string;
|
||||
name: string;
|
||||
decimals: number;
|
||||
assets: Array<Asset>;
|
||||
accounts: Array<{
|
||||
type: AccountType;
|
||||
balance: string;
|
||||
asset: { id: string; symbol: string; decimals: number };
|
||||
}>;
|
||||
assetId?: string;
|
||||
feeFactor: string | null;
|
||||
@@ -53,6 +63,7 @@ export const TransferForm = ({
|
||||
assetId: initialAssetId,
|
||||
feeFactor,
|
||||
submitTransfer,
|
||||
accounts,
|
||||
}: TransferFormProps) => {
|
||||
const {
|
||||
control,
|
||||
@@ -67,8 +78,10 @@ export const TransferForm = ({
|
||||
},
|
||||
});
|
||||
|
||||
const selectedPubKey = watch('toAddress');
|
||||
const amount = watch('amount');
|
||||
const assetId = watch('asset');
|
||||
const asset = assets.find((a) => a.id === assetId);
|
||||
|
||||
const [includeFee, setIncludeFee] = useState(false);
|
||||
|
||||
@@ -90,10 +103,6 @@ export const TransferForm = ({
|
||||
);
|
||||
}, [amount, includeFee, transferAmount, feeFactor]);
|
||||
|
||||
const asset = useMemo(() => {
|
||||
return assets.find((a) => a.id === assetId);
|
||||
}, [assets, assetId]);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
(fields: FormFields) => {
|
||||
if (!asset) {
|
||||
@@ -102,10 +111,16 @@ export const TransferForm = ({
|
||||
if (!transferAmount) {
|
||||
throw new Error('Submitted transfer with no amount selected');
|
||||
}
|
||||
const transfer = normalizeTransfer(fields.toAddress, transferAmount, {
|
||||
id: asset.id,
|
||||
decimals: asset.decimals,
|
||||
});
|
||||
const transfer = normalizeTransfer(
|
||||
fields.toAddress,
|
||||
transferAmount,
|
||||
fields.fromAccount,
|
||||
AccountType.ACCOUNT_TYPE_GENERAL, // field is readonly in the form
|
||||
{
|
||||
id: asset.id,
|
||||
decimals: asset.decimals,
|
||||
}
|
||||
);
|
||||
submitTransfer(transfer);
|
||||
},
|
||||
[asset, submitTransfer, transferAmount]
|
||||
@@ -131,63 +146,66 @@ export const TransferForm = ({
|
||||
}
|
||||
}, [setValue, pubKey]);
|
||||
|
||||
// General account for the selected asset
|
||||
const generalAccount = accounts.find((a) => {
|
||||
return (
|
||||
a.asset.id === assetId && a.type === AccountType.ACCOUNT_TYPE_GENERAL
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="text-sm"
|
||||
data-testid="transfer-form"
|
||||
>
|
||||
<TradingFormGroup label="Vega key" labelFor="to-address">
|
||||
<TradingFormGroup label="Vega key" labelFor="toAddress">
|
||||
<AddressField
|
||||
pubKeys={pubKeys}
|
||||
onChange={() => setValue('toAddress', '')}
|
||||
select={
|
||||
<TradingSelect
|
||||
{...register('toAddress')}
|
||||
id="to-address"
|
||||
id="toAddress"
|
||||
defaultValue=""
|
||||
>
|
||||
<option value="" disabled={true}>
|
||||
{t('Please select')}
|
||||
</option>
|
||||
{pubKeys?.length &&
|
||||
pubKeys
|
||||
.filter((pk) => pk !== pubKey) // remove currently selected pubkey
|
||||
.map((pk) => (
|
||||
pubKeys.map((pk) => {
|
||||
const text = pk === pubKey ? t('Current key: ') + pk : pk;
|
||||
|
||||
return (
|
||||
<option key={pk} value={pk}>
|
||||
{pk}
|
||||
{text}
|
||||
</option>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</TradingSelect>
|
||||
}
|
||||
input={
|
||||
<TradingInput
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={true} // focus input immediately after is shown
|
||||
id="to-address"
|
||||
id="toAddress"
|
||||
type="text"
|
||||
{...register('toAddress', {
|
||||
validate: {
|
||||
required,
|
||||
vegaPublicKey,
|
||||
sameKey: (value) => {
|
||||
if (value === pubKey) {
|
||||
return t('Vega key is the same as current key');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
})}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{errors.toAddress?.message && (
|
||||
<TradingInputError forInput="to-address">
|
||||
<TradingInputError forInput="toAddress">
|
||||
{errors.toAddress.message}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
<TradingFormGroup label="Asset" labelFor="asset">
|
||||
<TradingFormGroup label={t('Asset')} labelFor="asset">
|
||||
<Controller
|
||||
control={control}
|
||||
name="asset"
|
||||
@@ -228,6 +246,68 @@ export const TransferForm = ({
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
<TradingFormGroup label={t('From account')} labelFor="fromAccount">
|
||||
<TradingSelect
|
||||
id="fromAccount"
|
||||
defaultValue=""
|
||||
{...register('fromAccount', {
|
||||
validate: {
|
||||
required,
|
||||
sameAccount: (value) => {
|
||||
if (
|
||||
pubKey === selectedPubKey &&
|
||||
value === AccountType.ACCOUNT_TYPE_GENERAL
|
||||
) {
|
||||
return t(
|
||||
'Cannot transfer to the same account type for the connected key'
|
||||
);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
})}
|
||||
>
|
||||
<option value="" disabled={true}>
|
||||
{t('Please select')}
|
||||
</option>
|
||||
{accounts
|
||||
.filter((a) => {
|
||||
if (!assetId) return true;
|
||||
return assetId === a.asset.id;
|
||||
})
|
||||
.map((a) => {
|
||||
return (
|
||||
<option value={a.type} key={`${a.type}-${a.asset.id}`}>
|
||||
{AccountTypeMapping[a.type]} (
|
||||
{addDecimalsFormatNumber(a.balance, a.asset.decimals)}{' '}
|
||||
{a.asset.symbol})
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</TradingSelect>
|
||||
{errors.fromAccount?.message && (
|
||||
<TradingInputError forInput="fromAccount">
|
||||
{errors.fromAccount.message}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
<TradingFormGroup label={t('To account')} labelFor="toAccount">
|
||||
<TradingSelect
|
||||
id="toAccount"
|
||||
defaultValue={AccountType.ACCOUNT_TYPE_GENERAL}
|
||||
>
|
||||
<option value={AccountType.ACCOUNT_TYPE_GENERAL}>
|
||||
{generalAccount
|
||||
? `${
|
||||
AccountTypeMapping[AccountType.ACCOUNT_TYPE_GENERAL]
|
||||
} (${addDecimalsFormatNumber(
|
||||
generalAccount.balance,
|
||||
generalAccount.asset.decimals
|
||||
)} ${generalAccount.asset.symbol})`
|
||||
: AccountTypeMapping[AccountType.ACCOUNT_TYPE_GENERAL]}
|
||||
</option>
|
||||
</TradingSelect>
|
||||
</TradingFormGroup>
|
||||
<TradingFormGroup label="Amount" labelFor="amount">
|
||||
<TradingInput
|
||||
id="amount"
|
||||
@@ -391,7 +471,7 @@ export const AddressField = ({
|
||||
setIsInput((curr) => !curr);
|
||||
onChange();
|
||||
}}
|
||||
className="absolute top-0 right-0 ml-auto text-sm underline"
|
||||
className="absolute top-0 right-0 ml-auto text-xs underline"
|
||||
>
|
||||
{isInput ? t('Select from wallet') : t('Enter manually')}
|
||||
</button>
|
||||
|
||||
@@ -20,7 +20,11 @@ query Candles($marketId: ID!, $interval: Interval!, $since: String!) {
|
||||
code
|
||||
}
|
||||
}
|
||||
candlesConnection(interval: $interval, since: $since) {
|
||||
candlesConnection(
|
||||
interval: $interval
|
||||
since: $since
|
||||
pagination: { last: 5000 }
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
...CandleFields
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ export const CandlesDocument = gql`
|
||||
code
|
||||
}
|
||||
}
|
||||
candlesConnection(interval: $interval, since: $since) {
|
||||
candlesConnection(interval: $interval, since: $since, pagination: {last: 5000}) {
|
||||
edges {
|
||||
node {
|
||||
...CandleFields
|
||||
|
||||
@@ -32,7 +32,7 @@ export const Pagination = ({
|
||||
{false}
|
||||
{showRetentionMessage &&
|
||||
t(
|
||||
'Depending on data node retention you may not be able see the "full" history'
|
||||
'Depending on data node retention you may not be able see the full history'
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center text-xs">
|
||||
|
||||
@@ -79,7 +79,7 @@ export const DealTicketFeeDetails = ({
|
||||
}
|
||||
formattedValue={
|
||||
<>
|
||||
{totalDiscountFactor && (
|
||||
{totalDiscountFactor ? (
|
||||
<Pill size="xxs" intent={Intent.Warning} className="mr-1">
|
||||
-
|
||||
{formatNumberPercentage(
|
||||
@@ -87,14 +87,14 @@ export const DealTicketFeeDetails = ({
|
||||
2
|
||||
)}
|
||||
</Pill>
|
||||
)}
|
||||
) : null}
|
||||
{totalDiscountedFeeAmount &&
|
||||
`~${formatValue(totalDiscountedFeeAmount, assetDecimals, quantum)}`}
|
||||
</>
|
||||
}
|
||||
labelDescription={
|
||||
<>
|
||||
<p className="mb-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<p>
|
||||
{t(
|
||||
`An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}. Fees estimated are "taker" fees and will only be payable if the order trades aggressively. Rebate equal to the maker portion will be paid to the trader if the order trades passively.`
|
||||
)}
|
||||
@@ -108,7 +108,7 @@ export const DealTicketFeeDetails = ({
|
||||
symbol={assetSymbol}
|
||||
decimals={assetDecimals}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
}
|
||||
symbol={assetSymbol}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { FeesBreakdown } from './fees-breakdown';
|
||||
|
||||
describe('FeesBreakdown', () => {
|
||||
it('formats fee factors correctly', () => {
|
||||
const feeFactors = {
|
||||
makerFee: '0.00005',
|
||||
infrastructureFee: '0.001',
|
||||
liquidityFee: '0.5',
|
||||
};
|
||||
const fees = {
|
||||
makerFee: '100',
|
||||
infrastructureFee: '100',
|
||||
liquidityFee: '100',
|
||||
};
|
||||
const props = {
|
||||
totalFeeAmount: '100',
|
||||
fees,
|
||||
feeFactors,
|
||||
symbol: 'USD',
|
||||
decimals: 2,
|
||||
referralDiscountFactor: '0.01',
|
||||
volumeDiscountFactor: '0.01',
|
||||
};
|
||||
render(<FeesBreakdown {...props} />);
|
||||
expect(screen.getByText('Maker fee').nextElementSibling).toHaveTextContent(
|
||||
'0.005%'
|
||||
);
|
||||
expect(
|
||||
screen.getByText('Infrastructure fee').nextElementSibling
|
||||
).toHaveTextContent('0.1%');
|
||||
expect(
|
||||
screen.getByText('Liquidity fee').nextElementSibling
|
||||
).toHaveTextContent('50%');
|
||||
});
|
||||
});
|
||||
@@ -33,7 +33,7 @@ const FeesBreakdownItem = ({
|
||||
<dt className="col-span-2">{label}</dt>
|
||||
{factor && (
|
||||
<dd className="text-right col-span-1">
|
||||
{formatNumberPercentage(new BigNumber(factor).times(100), 2)}
|
||||
{formatNumberPercentage(new BigNumber(factor).times(100))}
|
||||
</dd>
|
||||
)}
|
||||
<dd className="text-right col-span-3">
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { EstimateFeesQuery } from './__generated__/EstimateOrder';
|
||||
|
||||
const data: EstimateFeesQuery = {
|
||||
epoch: {
|
||||
id: '2',
|
||||
id: '3',
|
||||
},
|
||||
volumeDiscountStats: {
|
||||
edges: [
|
||||
@@ -117,7 +117,7 @@ describe('useEstimateFees', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 0 discounts if discount stats are not at the current epoch', () => {
|
||||
it('returns 0 discounts if discount stats are not at the current epoch -1', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useEstimateFees(
|
||||
{
|
||||
@@ -136,7 +136,7 @@ describe('useEstimateFees', () => {
|
||||
});
|
||||
|
||||
it('returns discounts', () => {
|
||||
data.epoch.id = '1';
|
||||
data.epoch.id = '2';
|
||||
const { result } = renderHook(() =>
|
||||
useEstimateFees(
|
||||
{
|
||||
|
||||
@@ -34,14 +34,13 @@ export const useEstimateFees = (
|
||||
skip: !pubKey || !order?.size || !order?.price || order.postOnly,
|
||||
});
|
||||
const data = loading ? currentData || previousData : currentData;
|
||||
const atEpoch = (Number(data?.epoch.id) || 0) - 1;
|
||||
const volumeDiscountFactor =
|
||||
(data?.volumeDiscountStats.edges[0]?.node.atEpoch.toString() ===
|
||||
data?.epoch.id &&
|
||||
(data?.volumeDiscountStats.edges[0]?.node.atEpoch === atEpoch &&
|
||||
data?.volumeDiscountStats.edges[0]?.node.discountFactor) ||
|
||||
'0';
|
||||
const referralDiscountFactor =
|
||||
(data?.referralSetStats.edges[0]?.node.atEpoch.toString() ===
|
||||
data?.epoch.id &&
|
||||
(data?.referralSetStats.edges[0]?.node.atEpoch === atEpoch &&
|
||||
data?.referralSetStats.edges[0]?.node.discountFactor) ||
|
||||
'0';
|
||||
if (order?.postOnly) {
|
||||
|
||||
@@ -84,6 +84,7 @@ export const DocsLinks = VEGA_DOCS_URL
|
||||
ETH_DATA_SOURCES: `${VEGA_DOCS_URL}/concepts/trading-on-vega/data-sources#ethereum-data-sources`,
|
||||
ICEBERG_ORDERS: `${VEGA_DOCS_URL}/concepts/trading-on-vega/orders#iceberg-order`,
|
||||
POST_REDUCE_ONLY: `${VEGA_DOCS_URL}/concepts/trading-on-vega/orders#conditional-order-parameters`,
|
||||
QUANTUM: `${VEGA_DOCS_URL}/concepts/assets/asset-framework#quantum`,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ export const MarketCandlesDocument = gql`
|
||||
marketsConnection(id: $marketId) {
|
||||
edges {
|
||||
node {
|
||||
candlesConnection(interval: $interval, since: $since) {
|
||||
candlesConnection(interval: $interval, since: $since, pagination: {last: 1000}) {
|
||||
edges {
|
||||
node {
|
||||
...MarketCandlesFields
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ export const MarketsCandlesDocument = gql`
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
candlesConnection(interval: $interval, since: $since) {
|
||||
candlesConnection(interval: $interval, since: $since, pagination: {last: 1000}) {
|
||||
edges {
|
||||
node {
|
||||
...MarketCandlesFields
|
||||
|
||||
@@ -11,7 +11,11 @@ query MarketCandles($interval: Interval!, $since: String!, $marketId: ID!) {
|
||||
marketsConnection(id: $marketId) {
|
||||
edges {
|
||||
node {
|
||||
candlesConnection(interval: $interval, since: $since) {
|
||||
candlesConnection(
|
||||
interval: $interval
|
||||
since: $since
|
||||
pagination: { last: 1000 }
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
...MarketCandlesFields
|
||||
|
||||
@@ -72,10 +72,10 @@ describe('totalFeesFactorsPercentage', () => {
|
||||
makerFee: f[2].toString(),
|
||||
});
|
||||
it.each([
|
||||
{ i: createFee(0, 0, 1), o: '100.00%' },
|
||||
{ i: createFee(0, 1, 0), o: '100.00%' },
|
||||
{ i: createFee(1, 0, 0), o: '100.00%' },
|
||||
{ i: createFee(0.01, 0.02, 0.003), o: '3.30%' },
|
||||
{ i: createFee(0, 0, 1), o: '100%' },
|
||||
{ i: createFee(0, 1, 0), o: '100%' },
|
||||
{ i: createFee(1, 0, 0), o: '100%' },
|
||||
{ i: createFee(0.01, 0.02, 0.003), o: '3.3%' },
|
||||
{ i: createFee(0.01, 0.056782, 0.003), o: '6.9782%' },
|
||||
{ i: createFee(0.01, 0.056782, 0), o: '6.6782%' },
|
||||
])('adds fees correctly', ({ i, o }) => {
|
||||
|
||||
@@ -12,7 +12,11 @@ query MarketsCandles($interval: Interval!, $since: String!) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
candlesConnection(interval: $interval, since: $since) {
|
||||
candlesConnection(
|
||||
interval: $interval
|
||||
since: $since
|
||||
pagination: { last: 1000 }
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
...MarketCandlesFields
|
||||
|
||||
@@ -3,10 +3,7 @@ import { tradesWithMarketProvider } from './trades-data-provider';
|
||||
import { TradesTable } from './trades-table';
|
||||
import { useDealTicketFormValues } from '@vegaprotocol/deal-ticket';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Pagination } from '@vegaprotocol/datagrid';
|
||||
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { useCallback, useState } from 'react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
|
||||
interface TradesContainerProps {
|
||||
marketId: string;
|
||||
@@ -19,43 +16,19 @@ export const TradesManager = ({
|
||||
}: TradesContainerProps) => {
|
||||
const update = useDealTicketFormValues((state) => state.updateAll);
|
||||
|
||||
const { data, error, load, pageInfo } = useDataProvider({
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: tradesWithMarketProvider,
|
||||
variables: { marketId },
|
||||
});
|
||||
const [hasDisplayedRow, setHasDisplayedRow] = useState<boolean | undefined>(
|
||||
undefined
|
||||
);
|
||||
const { onFilterChanged, ...props } = gridProps || {};
|
||||
const onRowDataUpdated = useCallback(
|
||||
({ api }: { api: AgGridReact['api'] }) => {
|
||||
setHasDisplayedRow(!!api.getDisplayedRowCount());
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<TradesTable
|
||||
rowData={data}
|
||||
onClick={(price?: string) => {
|
||||
update(marketId, { price });
|
||||
}}
|
||||
onFilterChanged={(event) => {
|
||||
onRowDataUpdated(event);
|
||||
onFilterChanged(event);
|
||||
}}
|
||||
onRowDataUpdated={onRowDataUpdated}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No trades')}
|
||||
{...props}
|
||||
/>
|
||||
<Pagination
|
||||
count={data?.length || 0}
|
||||
pageInfo={pageInfo}
|
||||
onLoad={load}
|
||||
hasDisplayedRows={hasDisplayedRow || false}
|
||||
showRetentionMessage={true}
|
||||
/>
|
||||
</div>
|
||||
<TradesTable
|
||||
rowData={data}
|
||||
onClick={(price?: string) => {
|
||||
update(marketId, { price });
|
||||
}}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No trades')}
|
||||
{...gridProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -77,8 +77,8 @@ describe('number utils', () => {
|
||||
{ v: new BigNumber(123.123), d: 3, o: '123.123%' },
|
||||
{ v: new BigNumber(123.123), d: 6, o: '123.123%' },
|
||||
{ v: new BigNumber(123.123), d: 0, o: '123%' },
|
||||
{ v: new BigNumber(123), d: undefined, o: '123.00%' }, // it default to 2 decimal places
|
||||
{ v: new BigNumber(30000), d: undefined, o: '30,000.00%' },
|
||||
{ v: new BigNumber(123), d: undefined, o: '123%' }, // it default to 2 decimal places
|
||||
{ v: new BigNumber(30000), d: undefined, o: '30,000%' },
|
||||
{ v: new BigNumber(3.000001), d: undefined, o: '3.000001%' },
|
||||
])('formats given number correctly', ({ v, d, o }) => {
|
||||
expect(formatNumberPercentage(v, d)).toStrictEqual(o);
|
||||
|
||||
@@ -158,7 +158,7 @@ export const addDecimalsFixedFormatNumber = (
|
||||
|
||||
export const formatNumberPercentage = (value: BigNumber, decimals?: number) => {
|
||||
const decimalPlaces =
|
||||
typeof decimals === 'undefined' ? Math.max(value.dp() || 0, 2) : decimals;
|
||||
typeof decimals === 'undefined' ? value.dp() || 0 : decimals;
|
||||
return `${formatNumber(value, decimalPlaces)}%`;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { removeDecimal, toNanoSeconds } from '@vegaprotocol/utils';
|
||||
import type { Market, Order } from '@vegaprotocol/types';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import type { AccountType } from '@vegaprotocol/types';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { ethers } from 'ethers';
|
||||
import { sha3_256 } from 'js-sha3';
|
||||
@@ -47,15 +47,17 @@ export const normalizeOrderAmendment = <T extends Exact<OrderAmendment, T>>(
|
||||
export const normalizeTransfer = <T extends Exact<Transfer, T>>(
|
||||
address: string,
|
||||
amount: string,
|
||||
fromAccountType: AccountType,
|
||||
toAccountType: AccountType,
|
||||
asset: {
|
||||
id: string;
|
||||
decimals: number;
|
||||
}
|
||||
): Transfer => {
|
||||
return {
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
to: address,
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
fromAccountType,
|
||||
toAccountType,
|
||||
asset: asset.id,
|
||||
amount: removeDecimal(amount, asset.decimals),
|
||||
// oneOff or recurring required otherwise wallet will error
|
||||
|
||||
Reference in New Issue
Block a user