fix: withdrawal max threshold (#830)
* chore: fix remaning text for large number * feat: make use max only use account balance, add custom max messages * fix: withdraw threshold limit display * fix: assertions in deposit withdrawy e2e tests
This commit is contained in:
parent
0964d6dee5
commit
065b48535b
@ -53,6 +53,6 @@ describe('deposit form validation', () => {
|
||||
.clear()
|
||||
.type('100')
|
||||
.next(`[data-testid="${formFieldError}"]`)
|
||||
.should('have.text', 'Amount is above approved amount');
|
||||
.should('have.text', 'Insufficient amount in Ethereum wallet');
|
||||
});
|
||||
});
|
||||
|
@ -58,7 +58,7 @@ describe('withdraw', () => {
|
||||
.clear()
|
||||
.type('1') // Will be above maximum because the vega wallet doesnt have any collateral
|
||||
.next('[data-testid="input-error-text"]')
|
||||
.should('contain.text', 'Value is above maximum');
|
||||
.should('contain.text', 'Insufficient amount in account');
|
||||
});
|
||||
|
||||
it('can set amount using use maximum button', () => {
|
||||
|
@ -33,7 +33,7 @@ beforeEach(() => {
|
||||
assets: [asset],
|
||||
selectedAsset: undefined,
|
||||
onSelectAsset: jest.fn(),
|
||||
available: new BigNumber(5),
|
||||
balance: new BigNumber(5),
|
||||
submitApprove: jest.fn(),
|
||||
submitDeposit: jest.fn(),
|
||||
requestFaucet: jest.fn(),
|
||||
@ -125,7 +125,7 @@ describe('Deposit form', () => {
|
||||
fireEvent.submit(screen.getByTestId('deposit-form'));
|
||||
|
||||
expect(
|
||||
await screen.findByText('Amount is above permitted maximum')
|
||||
await screen.findByText('Insufficient amount in Ethereum wallet')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@ -133,6 +133,7 @@ describe('Deposit form', () => {
|
||||
render(
|
||||
<DepositForm
|
||||
{...props}
|
||||
balance={new BigNumber(100)}
|
||||
limits={{ max: new BigNumber(100), deposited: new BigNumber(10) }}
|
||||
/>
|
||||
);
|
||||
@ -216,12 +217,13 @@ describe('Deposit form', () => {
|
||||
max: new BigNumber(20),
|
||||
deposited: new BigNumber(10),
|
||||
};
|
||||
const balance = new BigNumber(50);
|
||||
|
||||
render(
|
||||
<DepositForm
|
||||
{...props}
|
||||
allowance={new BigNumber(100)}
|
||||
available={new BigNumber(50)}
|
||||
balance={balance}
|
||||
limits={limits}
|
||||
selectedAsset={asset}
|
||||
/>
|
||||
@ -229,12 +231,18 @@ describe('Deposit form', () => {
|
||||
|
||||
// Check deposit limit is displayed
|
||||
expect(
|
||||
screen.getByText('Max deposit total', { selector: 'th' })
|
||||
screen.getByText('Balance available', { selector: 'th' })
|
||||
.nextElementSibling
|
||||
).toHaveTextContent(balance.toString());
|
||||
expect(
|
||||
screen.getByText('Maximum total deposit amount', { selector: 'th' })
|
||||
.nextElementSibling
|
||||
).toHaveTextContent(limits.max.toString());
|
||||
expect(
|
||||
screen.getByText('Remaining available', { selector: 'th' })
|
||||
.nextElementSibling
|
||||
screen.getByText('Deposited', { selector: 'th' }).nextElementSibling
|
||||
).toHaveTextContent(limits.deposited.toString());
|
||||
expect(
|
||||
screen.getByText('Remaining', { selector: 'th' }).nextElementSibling
|
||||
).toHaveTextContent(limits.max.minus(limits.deposited).toString());
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
|
@ -37,7 +37,7 @@ export interface DepositFormProps {
|
||||
assets: Asset[];
|
||||
selectedAsset?: Asset;
|
||||
onSelectAsset: (assetId: string) => void;
|
||||
available: BigNumber | undefined;
|
||||
balance: BigNumber | undefined;
|
||||
submitApprove: () => Promise<void>;
|
||||
submitDeposit: (args: {
|
||||
assetSource: string;
|
||||
@ -57,7 +57,7 @@ export const DepositForm = ({
|
||||
assets,
|
||||
selectedAsset,
|
||||
onSelectAsset,
|
||||
available,
|
||||
balance,
|
||||
submitApprove,
|
||||
submitDeposit,
|
||||
requestFaucet,
|
||||
@ -99,7 +99,7 @@ export const DepositForm = ({
|
||||
|
||||
const max = useMemo(() => {
|
||||
const maxApproved = allowance ? allowance : new BigNumber(0);
|
||||
const maxAvailable = available ? available : new BigNumber(0);
|
||||
const maxAvailable = balance ? balance : new BigNumber(0);
|
||||
|
||||
// limits.max is a lifetime deposit limit, so the actual max value for form
|
||||
// input is the max minus whats already been deposited
|
||||
@ -116,7 +116,7 @@ export const DepositForm = ({
|
||||
limit: maxLimit,
|
||||
amount: BigNumber.minimum(maxLimit, maxApproved, maxAvailable),
|
||||
};
|
||||
}, [limits, allowance, available]);
|
||||
}, [limits, allowance, balance]);
|
||||
|
||||
const min = useMemo(() => {
|
||||
// Min viable amount given asset decimals EG for WEI 0.000000000000000001
|
||||
@ -198,7 +198,7 @@ export const DepositForm = ({
|
||||
</FormGroup>
|
||||
{selectedAsset && limits && (
|
||||
<div className="mb-20">
|
||||
<DepositLimits limits={limits} />
|
||||
<DepositLimits limits={limits} balance={balance} />
|
||||
</div>
|
||||
)}
|
||||
<FormGroup label={t('Amount')} labelFor="amount" className="relative">
|
||||
@ -212,12 +212,12 @@ export const DepositForm = ({
|
||||
minSafe: (value) => minSafe(new BigNumber(min))(value),
|
||||
maxSafe: (v) => {
|
||||
const value = new BigNumber(v);
|
||||
if (value.isGreaterThan(max.approved)) {
|
||||
return t('Amount is above approved amount');
|
||||
} else if (value.isGreaterThan(max.limit)) {
|
||||
return t('Amount is above permitted maximum');
|
||||
} else if (value.isGreaterThan(max.available)) {
|
||||
if (value.isGreaterThan(max.available)) {
|
||||
return t('Insufficient amount in Ethereum wallet');
|
||||
} else if (value.isGreaterThan(max.limit)) {
|
||||
return t('Amount is above temporary deposit limit');
|
||||
} else if (value.isGreaterThan(max.approved)) {
|
||||
return t('Amount is above approved amount');
|
||||
}
|
||||
return maxSafe(max.amount)(v);
|
||||
},
|
||||
@ -229,10 +229,10 @@ export const DepositForm = ({
|
||||
{errors.amount.message}
|
||||
</InputError>
|
||||
)}
|
||||
{account && selectedAsset && available && (
|
||||
{selectedAsset && balance && (
|
||||
<UseButton
|
||||
onClick={() => {
|
||||
setValue('amount', max.amount.toFixed(selectedAsset.decimals));
|
||||
setValue('amount', balance.toFixed(selectedAsset.decimals));
|
||||
clearErrors('amount');
|
||||
}}
|
||||
>
|
||||
|
@ -6,11 +6,11 @@ interface DepositLimitsProps {
|
||||
max: BigNumber;
|
||||
deposited: BigNumber;
|
||||
};
|
||||
balance?: BigNumber;
|
||||
}
|
||||
|
||||
export const DepositLimits = ({ limits }: DepositLimitsProps) => {
|
||||
export const DepositLimits = ({ limits, balance }: DepositLimitsProps) => {
|
||||
let maxLimit = '';
|
||||
|
||||
if (limits.max.isEqualTo(Infinity)) {
|
||||
maxLimit = t('No limit');
|
||||
} else if (limits.max.isGreaterThan(1_000_000)) {
|
||||
@ -19,29 +19,35 @@ export const DepositLimits = ({ limits }: DepositLimitsProps) => {
|
||||
maxLimit = limits.max.toString();
|
||||
}
|
||||
|
||||
let remaining = '';
|
||||
if (limits.deposited.isEqualTo(0)) {
|
||||
remaining = maxLimit;
|
||||
} else {
|
||||
remaining = limits.max.minus(limits.deposited).toString();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className="text-ui font-bold">{t('Deposit limits')}</p>
|
||||
<table className="w-full text-ui">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th className="text-left font-normal">{t('Max deposit total')}</th>
|
||||
<td className="text-right">{maxLimit}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className="text-left font-normal">{t('Deposited')}</th>
|
||||
<td className="text-right">{limits.deposited.toString()}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className="text-left font-normal">
|
||||
{t('Remaining available')}
|
||||
</th>
|
||||
<td className="text-right">
|
||||
{limits.max.minus(limits.deposited).toString()}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
<table className="w-full text-ui">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th className="text-left font-normal">{t('Balance available')}</th>
|
||||
<td className="text-right">{balance ? balance.toString() : 0}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className="text-left font-normal">
|
||||
{t('Maximum total deposit amount')}
|
||||
</th>
|
||||
<td className="text-right">{maxLimit}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className="text-left font-normal">{t('Deposited')}</th>
|
||||
<td className="text-right">{limits.deposited.toString()}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className="text-left font-normal">{t('Remaining')}</th>
|
||||
<td className="text-right">{remaining}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
@ -105,7 +105,7 @@ export const DepositManager = ({
|
||||
return (
|
||||
<>
|
||||
<DepositForm
|
||||
available={balance}
|
||||
balance={balance}
|
||||
selectedAsset={asset}
|
||||
onSelectAsset={(id) => setAssetId(id)}
|
||||
assets={sortBy(assets, 'name')}
|
||||
|
@ -20,9 +20,11 @@ export const useGetWithdrawLimits = (asset?: Asset) => {
|
||||
|
||||
if (!data || !asset) return null;
|
||||
|
||||
const max = new BigNumber(addDecimal(data.toString(), asset.decimals));
|
||||
|
||||
const value = new BigNumber(addDecimal(data.toString(), asset.decimals));
|
||||
const max = value.isEqualTo(0)
|
||||
? new BigNumber(Infinity)
|
||||
: value.minus(new BigNumber(addDecimal('1', asset.decimals)));
|
||||
return {
|
||||
max: max.isEqualTo(0) ? new BigNumber(Infinity) : max,
|
||||
max,
|
||||
};
|
||||
};
|
||||
|
@ -26,7 +26,10 @@ beforeEach(() => {
|
||||
props = {
|
||||
assets,
|
||||
min: new BigNumber(0.00001),
|
||||
max: new BigNumber(100),
|
||||
max: {
|
||||
balance: new BigNumber(100),
|
||||
threshold: new BigNumber(200),
|
||||
},
|
||||
limits: {
|
||||
max: new BigNumber(200),
|
||||
},
|
||||
@ -75,7 +78,9 @@ describe('Withdrawal form', () => {
|
||||
expect(
|
||||
await screen.findByText('Invalid Ethereum address')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('Value is above maximum')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('Insufficient amount in account')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('fails when submitted amount is less than the minimum limit', async () => {
|
||||
@ -111,14 +116,14 @@ describe('Withdrawal form', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('populates amount field with maximum value when clicking the "use maximum" button', () => {
|
||||
it('populates amount field with balance value when clicking the "use maximum" button', () => {
|
||||
const asset = props.assets[0];
|
||||
render(<WithdrawForm {...props} selectedAsset={asset} />);
|
||||
|
||||
fireEvent.click(screen.getByText('Use maximum'));
|
||||
|
||||
expect(screen.getByLabelText('Amount')).toHaveValue(
|
||||
Number(props.max.toFixed(asset.decimals))
|
||||
Number(props.max.balance.toFixed(asset.decimals))
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -1,10 +1,10 @@
|
||||
import {
|
||||
ethereumAddress,
|
||||
maxSafe,
|
||||
minSafe,
|
||||
t,
|
||||
removeDecimal,
|
||||
required,
|
||||
maxSafe,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
Button,
|
||||
@ -15,7 +15,7 @@ import {
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { Web3WalletInput } from '@vegaprotocol/web3';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import type BigNumber from 'bignumber.js';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import type { WithdrawalFields } from './use-withdraw';
|
||||
@ -30,7 +30,10 @@ interface FormFields {
|
||||
|
||||
export interface WithdrawFormProps {
|
||||
assets: Asset[];
|
||||
max: BigNumber;
|
||||
max: {
|
||||
balance: BigNumber;
|
||||
threshold: BigNumber;
|
||||
};
|
||||
min: BigNumber;
|
||||
selectedAsset?: Asset;
|
||||
limits: {
|
||||
@ -49,7 +52,7 @@ export const WithdrawForm = ({
|
||||
onSelectAsset,
|
||||
submitWithdraw,
|
||||
}: WithdrawFormProps) => {
|
||||
const { account } = useWeb3React();
|
||||
const { account: address } = useWeb3React();
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
@ -60,7 +63,7 @@ export const WithdrawForm = ({
|
||||
} = useForm<FormFields>({
|
||||
defaultValues: {
|
||||
asset: selectedAsset?.id,
|
||||
to: account,
|
||||
to: address,
|
||||
},
|
||||
});
|
||||
const onSubmit = async (fields: FormFields) => {
|
||||
@ -106,7 +109,6 @@ export const WithdrawForm = ({
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
|
||||
{errors.asset?.message && (
|
||||
<InputError intent="danger" className="mt-4">
|
||||
{errors.asset.message}
|
||||
@ -132,7 +134,7 @@ export const WithdrawForm = ({
|
||||
</FormGroup>
|
||||
{selectedAsset && limits && (
|
||||
<div className="mb-20">
|
||||
<WithdrawLimits limits={limits} />
|
||||
<WithdrawLimits limits={limits} balance={max.balance} />
|
||||
</div>
|
||||
)}
|
||||
<FormGroup label={t('Amount')} labelFor="amount" className="relative">
|
||||
@ -143,7 +145,17 @@ export const WithdrawForm = ({
|
||||
{...register('amount', {
|
||||
validate: {
|
||||
required,
|
||||
maxSafe: (value) => maxSafe(max)(value),
|
||||
maxSafe: (v) => {
|
||||
const value = new BigNumber(v);
|
||||
if (value.isGreaterThan(max.balance)) {
|
||||
return t('Insufficient amount in account');
|
||||
} else if (value.isGreaterThan(max.threshold)) {
|
||||
return t('Amount is above temporary withdrawal limit');
|
||||
}
|
||||
return maxSafe(BigNumber.minimum(max.balance, max.threshold))(
|
||||
v
|
||||
);
|
||||
},
|
||||
minSafe: (value) => minSafe(min)(value),
|
||||
},
|
||||
})}
|
||||
@ -157,7 +169,7 @@ export const WithdrawForm = ({
|
||||
<UseButton
|
||||
data-testid="use-maximum"
|
||||
onClick={() => {
|
||||
setValue('amount', max.toFixed(selectedAsset.decimals));
|
||||
setValue('amount', max.balance.toFixed(selectedAsset.decimals));
|
||||
clearErrors('amount');
|
||||
}}
|
||||
>
|
||||
|
@ -5,9 +5,10 @@ interface WithdrawLimitsProps {
|
||||
limits: {
|
||||
max: BigNumber;
|
||||
};
|
||||
balance: BigNumber;
|
||||
}
|
||||
|
||||
export const WithdrawLimits = ({ limits }: WithdrawLimitsProps) => {
|
||||
export const WithdrawLimits = ({ limits, balance }: WithdrawLimitsProps) => {
|
||||
let maxLimit = '';
|
||||
|
||||
if (limits.max.isEqualTo(Infinity)) {
|
||||
@ -19,16 +20,17 @@ export const WithdrawLimits = ({ limits }: WithdrawLimitsProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className="text-ui font-bold">{t('Withdraw limits')}</p>
|
||||
<table className="w-full text-ui">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th className="text-left font-normal">{t('Maximum')}</th>
|
||||
<td className="text-right">{maxLimit}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
<table className="w-full text-ui">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th className="text-left font-normal">{t('Balance available')}</th>
|
||||
<td className="text-right">{balance.toString()}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className="text-left font-normal">{t('Maximum withdrawal')}</th>
|
||||
<td className="text-right">{maxLimit}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
@ -111,7 +111,9 @@ it('Correct min max values provided to form', async () => {
|
||||
target: { value: '2' },
|
||||
});
|
||||
fireEvent.submit(screen.getByTestId('withdraw-form'));
|
||||
expect(await screen.findByText('Value is above maximum')).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText('Insufficient amount in account')
|
||||
).toBeInTheDocument();
|
||||
expect(mockSubmit).not.toBeCalled();
|
||||
});
|
||||
|
||||
|
@ -38,22 +38,31 @@ export const WithdrawManager = ({
|
||||
return assets?.find((a) => a.id === assetId);
|
||||
}, [assets, assetId]);
|
||||
|
||||
const account = useMemo(() => {
|
||||
return accounts.find(
|
||||
(a) => a.type === AccountType.General && a.asset.id === asset?.id
|
||||
);
|
||||
}, [asset, accounts]);
|
||||
|
||||
const limits = useGetWithdrawLimits(asset);
|
||||
|
||||
const max = useMemo(() => {
|
||||
if (!asset) {
|
||||
return new BigNumber(0);
|
||||
return {
|
||||
balance: new BigNumber(0),
|
||||
threshold: new BigNumber(0),
|
||||
};
|
||||
}
|
||||
|
||||
const account = accounts.find(
|
||||
(a) => a.type === AccountType.General && a.asset.id === asset.id
|
||||
);
|
||||
|
||||
const v = account
|
||||
const balance = account
|
||||
? new BigNumber(addDecimal(account.balance, asset.decimals))
|
||||
: new BigNumber(0);
|
||||
return BigNumber.minimum(v, limits ? limits.max : new BigNumber(Infinity));
|
||||
}, [asset, accounts, limits]);
|
||||
|
||||
return {
|
||||
balance,
|
||||
threshold: limits ? limits.max : new BigNumber(Infinity),
|
||||
};
|
||||
}, [asset, account, limits]);
|
||||
|
||||
const min = useMemo(() => {
|
||||
return asset
|
||||
|
Loading…
Reference in New Issue
Block a user