feat(trading): transfer to and from accounts on same key (#5161)

This commit is contained in:
Matthew Russell 2023-11-01 04:11:17 -07:00 committed by GitHub
parent 673c896e2f
commit e5d3f90d45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 268 additions and 206 deletions

View File

@ -9,12 +9,17 @@ import { useDataProvider } from '@vegaprotocol/data-provider';
import type { Transfer } from '@vegaprotocol/wallet'; import type { Transfer } from '@vegaprotocol/wallet';
import { useVegaTransactionStore } from '@vegaprotocol/web3'; import { useVegaTransactionStore } from '@vegaprotocol/web3';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import { useCallback, useMemo } from 'react'; import { useCallback } from 'react';
import { accountsDataProvider } from './accounts-data-provider'; import { accountsDataProvider } from './accounts-data-provider';
import { TransferForm } from './transfer-form'; import { TransferForm } from './transfer-form';
import sortBy from 'lodash/sortBy'; import sortBy from 'lodash/sortBy';
import { Lozenge } from '@vegaprotocol/ui-toolkit'; 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 }) => { export const TransferContainer = ({ assetId }: { assetId?: string }) => {
const { pubKey, pubKeys } = useVegaWallet(); const { pubKey, pubKeys } = useVegaWallet();
const { param } = useNetworkParam(NetworkParams.transfer_fee_factor); const { param } = useNetworkParam(NetworkParams.transfer_fee_factor);
@ -33,20 +38,19 @@ export const TransferContainer = ({ assetId }: { assetId?: string }) => {
[create] [create]
); );
const assets = useMemo(() => { const accounts = data
if (!data) return []; ? data.filter((account) => ALLOWED_ACCOUNTS.includes(account.type))
return data : [];
.filter(
(account) => account.type === Schema.AccountType.ACCOUNT_TYPE_GENERAL const assets = accounts.map((account) => ({
) id: account.asset.id,
.map((account) => ({ symbol: account.asset.symbol,
id: account.asset.id, name: account.asset.name,
symbol: account.asset.symbol, decimals: account.asset.decimals,
name: account.asset.name, balance: addDecimal(account.balance, account.asset.decimals),
decimals: account.asset.decimals, }));
balance: addDecimal(account.balance, account.asset.decimals),
})); if (data === null) return null;
}, [data]);
return ( return (
<> <>
@ -69,6 +73,7 @@ export const TransferContainer = ({ assetId }: { assetId?: string }) => {
assetId={assetId} assetId={assetId}
feeFactor={param} feeFactor={param}
submitTransfer={transfer} submitTransfer={transfer}
accounts={accounts}
/> />
</> </>
); );

View File

@ -1,18 +1,38 @@
import { import { fireEvent, render, screen, waitFor } from '@testing-library/react';
act, import userEvent from '@testing-library/user-event';
fireEvent,
render,
screen,
waitFor,
} from '@testing-library/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { AddressField, TransferFee, TransferForm } from './transfer-form'; import { AddressField, TransferFee, TransferForm } from './transfer-form';
import { AccountType } from '@vegaprotocol/types'; import { AccountType } from '@vegaprotocol/types';
import { addDecimal, formatNumber, removeDecimal } from '@vegaprotocol/utils'; import { addDecimal, formatNumber, removeDecimal } from '@vegaprotocol/utils';
import userEvent from '@testing-library/user-event';
describe('TransferForm', () => { 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 amount = '100';
const pubKey = const pubKey =
'70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680'; '70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680';
@ -32,6 +52,18 @@ describe('TransferForm', () => {
assets: [asset], assets: [asset],
feeFactor: '0.001', feeFactor: '0.001',
submitTransfer: jest.fn(), 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 () => { it('form tooltips correctly displayed', async () => {
@ -42,49 +74,45 @@ describe('TransferForm', () => {
// 1003-TRAN-019 // 1003-TRAN-019
render(<TransferForm {...props} />); render(<TransferForm {...props} />);
// Select a pubkey // Select a pubkey
fireEvent.change(screen.getByLabelText('Vega key'), { await userEvent.selectOptions(
target: { value: props.pubKeys[1] }, screen.getByLabelText('Vega key'),
}); props.pubKeys[1]
);
// Select asset // Select asset
fireEvent.change( await selectAsset(asset);
// Bypass RichSelect and target hidden native select
// eslint-disable-next-line
document.querySelector('select[name="asset"]')!,
{ target: { value: asset.id } }
);
// set valid amount // set valid amount
fireEvent.change(screen.getByLabelText('Amount'), { const amountInput = screen.getByLabelText('Amount');
target: { value: 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 transferFee = screen.getByText('Transfer fee');
const tooltips = screen.getAllByTestId('tooltip-content'); await userEvent.hover(transferFee);
expect(tooltips[0]).toBeVisible(); 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 totalAmountWithFee = screen.getByText('Total amount (with fee)');
const tooltips = screen.getAllByTestId('tooltip-content'); await userEvent.hover(totalAmountWithFee);
expect(tooltips[0]).toBeVisible(); expect(await screen.findByRole('tooltip')).toHaveTextContent(
}); /total amount taken from your account/
);
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();
});
}); });
it('validates a manually entered address', async () => { it('validates a manually entered address', async () => {
@ -92,30 +120,17 @@ describe('TransferForm', () => {
// 1003-TRAN-013 // 1003-TRAN-013
// 1003-TRAN-004 // 1003-TRAN-004
render(<TransferForm {...props} />); render(<TransferForm {...props} />);
submit(); await submit();
expect(await screen.findAllByText('Required')).toHaveLength(3); expect(await screen.findAllByText('Required')).toHaveLength(4);
const toggle = screen.getByText('Enter manually'); const toggle = screen.getByText('Enter manually');
fireEvent.click(toggle); await userEvent.click(toggle);
// has switched to input // has switched to input
expect(toggle).toHaveTextContent('Select from wallet'); expect(toggle).toHaveTextContent('Select from wallet');
expect(screen.getByLabelText('Vega key')).toHaveAttribute('type', 'text'); expect(screen.getByLabelText('Vega key')).toHaveAttribute('type', 'text');
fireEvent.change(screen.getByLabelText('Vega key'), { await userEvent.type(screen.getByLabelText('Vega key'), 'invalid-address');
target: { value: 'invalid-address' }, expect(screen.getAllByTestId('input-error-text')[0]).toHaveTextContent(
}); 'Invalid Vega key'
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');
});
}); });
it('validates fields and submits', async () => { it('validates fields and submits', async () => {
@ -127,28 +142,25 @@ describe('TransferForm', () => {
render(<TransferForm {...props} />); render(<TransferForm {...props} />);
// check current pubkey not shown // check current pubkey not shown
const keySelect: HTMLSelectElement = screen.getByLabelText('Vega key'); const keySelect = screen.getByLabelText<HTMLSelectElement>('Vega key');
expect(keySelect.children).toHaveLength(2); expect(keySelect.children).toHaveLength(3);
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual([ expect(Array.from(keySelect.options).map((o) => o.value)).toEqual([
'', '',
pubKey,
props.pubKeys[1], props.pubKeys[1],
]); ]);
submit(); await submit();
expect(await screen.findAllByText('Required')).toHaveLength(3); expect(await screen.findAllByText('Required')).toHaveLength(4);
// Select a pubkey // Select a pubkey
fireEvent.change(screen.getByLabelText('Vega key'), { await userEvent.selectOptions(
target: { value: props.pubKeys[1] }, screen.getByLabelText('Vega key'),
}); props.pubKeys[1]
);
// Select asset // Select asset
fireEvent.change( await selectAsset(asset);
// 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 // assert rich select as updated
expect(await screen.findByTestId('select-asset')).toHaveTextContent( expect(await screen.findByTestId('select-asset')).toHaveTextContent(
@ -158,37 +170,38 @@ describe('TransferForm', () => {
formatNumber(asset.balance, asset.decimals) formatNumber(asset.balance, asset.decimals)
); );
await userEvent.selectOptions(
screen.getByLabelText('From account'),
AccountType.ACCOUNT_TYPE_VESTED_REWARDS
);
const amountInput = screen.getByLabelText('Amount'); const amountInput = screen.getByLabelText('Amount');
// Test amount validation // Test amount validation
fireEvent.change(amountInput, { await userEvent.type(amountInput, '0.00000001');
target: { value: '0.00000001' },
});
expect( expect(
await screen.findByText('Value is below minimum') await screen.findByText('Value is below minimum')
).toBeInTheDocument(); ).toBeInTheDocument();
fireEvent.change(amountInput, { await userEvent.clear(amountInput);
target: { value: '9999999' }, await userEvent.type(amountInput, '9999999');
});
expect( expect(
await screen.findByText(/cannot transfer more/i) await screen.findByText(/cannot transfer more/i)
).toBeInTheDocument(); ).toBeInTheDocument();
// set valid amount // set valid amount
fireEvent.change(amountInput, { await userEvent.clear(amountInput);
target: { value: amount }, await userEvent.type(amountInput, amount);
});
expect(screen.getByTestId('transfer-fee')).toHaveTextContent( expect(screen.getByTestId('transfer-fee')).toHaveTextContent(
new BigNumber(props.feeFactor).times(amount).toFixed() new BigNumber(props.feeFactor).times(amount).toFixed()
); );
submit(); await submit();
await waitFor(() => { await waitFor(() => {
expect(props.submitTransfer).toHaveBeenCalledTimes(1); expect(props.submitTransfer).toHaveBeenCalledTimes(1);
expect(props.submitTransfer).toHaveBeenCalledWith({ expect(props.submitTransfer).toHaveBeenCalledWith({
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL, fromAccountType: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL, toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
to: props.pubKeys[1], to: props.pubKeys[1],
asset: asset.id, asset: asset.id,
@ -200,59 +213,50 @@ describe('TransferForm', () => {
describe('IncludeFeesCheckbox', () => { describe('IncludeFeesCheckbox', () => {
it('validates fields and submits when checkbox is checked', async () => { 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 // check current pubkey not shown
const keySelect: HTMLSelectElement = screen.getByLabelText('Vega key'); const keySelect = screen.getByLabelText<HTMLSelectElement>('Vega key');
expect(keySelect.children).toHaveLength(2); const pubKeyOptions = ['', pubKey, props.pubKeys[1]];
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual([ expect(keySelect.children).toHaveLength(pubKeyOptions.length);
'', expect(Array.from(keySelect.options).map((o) => o.value)).toEqual(
props.pubKeys[1], pubKeyOptions
]); );
submit(); await submit();
expect(await screen.findAllByText('Required')).toHaveLength(3); expect(await screen.findAllByText('Required')).toHaveLength(4);
// Select a pubkey // Select a pubkey
fireEvent.change(screen.getByLabelText('Vega key'), { await userEvent.selectOptions(
target: { value: props.pubKeys[1] }, screen.getByLabelText('Vega key'),
}); props.pubKeys[1]
);
// Select asset // Select asset
fireEvent.change( await selectAsset(asset);
// 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 await userEvent.selectOptions(
expect(await screen.findByTestId('select-asset')).toHaveTextContent( screen.getByLabelText('From account'),
asset.name AccountType.ACCOUNT_TYPE_VESTED_REWARDS
);
expect(await screen.findByTestId('asset-balance')).toHaveTextContent(
formatNumber(asset.balance, asset.decimals)
); );
const amountInput = screen.getByLabelText('Amount'); const amountInput = screen.getByLabelText('Amount');
const checkbox = screen.getByTestId('include-transfer-fee'); const checkbox = screen.getByTestId('include-transfer-fee');
// 1003-TRAN-022 // 1003-TRAN-022
expect(checkbox).not.toBeChecked(); expect(checkbox).not.toBeChecked();
act(() => {
/* fire events that update state */ await userEvent.clear(amountInput);
// set valid amount await userEvent.type(amountInput, amount);
fireEvent.change(amountInput, { await userEvent.click(checkbox);
target: { value: amount },
});
// check include fees checkbox
fireEvent.click(checkbox);
});
expect(checkbox).toBeChecked(); expect(checkbox).toBeChecked();
const expectedFee = new BigNumber(amount) const expectedFee = new BigNumber(amount)
.times(props.feeFactor) .times(props.feeFactor)
.toFixed(); .toFixed();
const expectedAmount = new BigNumber(amount).minus(expectedFee).toFixed(); const expectedAmount = new BigNumber(amount).minus(expectedFee).toFixed();
// 1003-TRAN-020 // 1003-TRAN-020
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee); expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
expect(screen.getByTestId('transfer-amount')).toHaveTextContent( expect(screen.getByTestId('transfer-amount')).toHaveTextContent(
@ -262,18 +266,17 @@ describe('TransferForm', () => {
amount amount
); );
submit(); await submit();
await waitFor(() => { await waitFor(() => {
// 1003-TRAN-023 // 1003-TRAN-023
expect(mockSubmit).toHaveBeenCalledTimes(1);
expect(props.submitTransfer).toHaveBeenCalledTimes(1); expect(mockSubmit).toHaveBeenCalledWith({
expect(props.submitTransfer).toHaveBeenCalledWith({ fromAccountType: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL, toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
to: props.pubKeys[1], to: props.pubKeys[1],
asset: asset.id, asset: asset.id,
amount: removeDecimal(amount, asset.decimals), amount: removeDecimal(expectedAmount, asset.decimals),
oneOff: {}, oneOff: {},
}); });
}); });
@ -284,46 +287,29 @@ describe('TransferForm', () => {
// check current pubkey not shown // check current pubkey not shown
const keySelect: HTMLSelectElement = screen.getByLabelText('Vega key'); const keySelect: HTMLSelectElement = screen.getByLabelText('Vega key');
expect(keySelect.children).toHaveLength(2); const pubKeyOptions = ['', pubKey, props.pubKeys[1]];
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual([ expect(keySelect.children).toHaveLength(pubKeyOptions.length);
'', expect(Array.from(keySelect.options).map((o) => o.value)).toEqual(
props.pubKeys[1], pubKeyOptions
]); );
submit(); await submit();
expect(await screen.findAllByText('Required')).toHaveLength(3); expect(await screen.findAllByText('Required')).toHaveLength(4);
// Select a pubkey // Select a pubkey
fireEvent.change(screen.getByLabelText('Vega key'), { await userEvent.selectOptions(
target: { value: props.pubKeys[1] }, screen.getByLabelText('Vega key'),
}); props.pubKeys[1]
);
// Select asset // Select asset
fireEvent.change( await selectAsset(asset);
// 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)
);
const amountInput = screen.getByLabelText('Amount'); const amountInput = screen.getByLabelText('Amount');
const checkbox = screen.getByTestId('include-transfer-fee'); const checkbox = screen.getByTestId('include-transfer-fee');
expect(checkbox).not.toBeChecked(); expect(checkbox).not.toBeChecked();
act(() => {
/* fire events that update state */ await userEvent.type(amountInput, amount);
// set valid amount
fireEvent.change(amountInput, {
target: { value: amount },
});
});
expect(checkbox).not.toBeChecked(); expect(checkbox).not.toBeChecked();
const expectedFee = new BigNumber(amount) const expectedFee = new BigNumber(amount)
.times(props.feeFactor) .times(props.feeFactor)
@ -351,11 +337,11 @@ describe('TransferForm', () => {
// select should be shown as multiple pubkeys provided // select should be shown as multiple pubkeys provided
expect(screen.getByText('select')).toBeInTheDocument(); expect(screen.getByText('select')).toBeInTheDocument();
expect(screen.queryByText('input')).not.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.queryByText('select')).not.toBeInTheDocument();
expect(screen.getByText('input')).toBeInTheDocument(); expect(screen.getByText('input')).toBeInTheDocument();
expect(mockOnChange).toHaveBeenCalledTimes(1); 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.getByText('select')).toBeInTheDocument();
expect(screen.queryByText('input')).not.toBeInTheDocument(); expect(screen.queryByText('input')).not.toBeInTheDocument();
expect(mockOnChange).toHaveBeenCalledTimes(2); expect(mockOnChange).toHaveBeenCalledTimes(2);

View File

@ -24,22 +24,30 @@ import type { ReactNode } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import { AssetOption, Balance } from '@vegaprotocol/assets'; import { AssetOption, Balance } from '@vegaprotocol/assets';
import { AccountType, AccountTypeMapping } from '@vegaprotocol/types';
interface FormFields { interface FormFields {
toAddress: string; toAddress: string;
asset: string; asset: string;
amount: string; amount: string;
fromAccount: AccountType;
}
interface Asset {
id: string;
symbol: string;
name: string;
decimals: number;
balance: string;
} }
interface TransferFormProps { interface TransferFormProps {
pubKey: string | null; pubKey: string | null;
pubKeys: string[] | null; pubKeys: string[] | null;
assets: Array<{ assets: Array<Asset>;
id: string; accounts: Array<{
symbol: string; type: AccountType;
name: string; asset: { id: string; symbol: string };
decimals: number;
balance: string;
}>; }>;
assetId?: string; assetId?: string;
feeFactor: string | null; feeFactor: string | null;
@ -53,6 +61,7 @@ export const TransferForm = ({
assetId: initialAssetId, assetId: initialAssetId,
feeFactor, feeFactor,
submitTransfer, submitTransfer,
accounts,
}: TransferFormProps) => { }: TransferFormProps) => {
const { const {
control, control,
@ -67,6 +76,7 @@ export const TransferForm = ({
}, },
}); });
const selectedPubKey = watch('toAddress');
const amount = watch('amount'); const amount = watch('amount');
const assetId = watch('asset'); const assetId = watch('asset');
@ -102,10 +112,16 @@ export const TransferForm = ({
if (!transferAmount) { if (!transferAmount) {
throw new Error('Submitted transfer with no amount selected'); throw new Error('Submitted transfer with no amount selected');
} }
const transfer = normalizeTransfer(fields.toAddress, transferAmount, { const transfer = normalizeTransfer(
id: asset.id, fields.toAddress,
decimals: asset.decimals, transferAmount,
}); fields.fromAccount,
AccountType.ACCOUNT_TYPE_GENERAL, // field is readonly in the form
{
id: asset.id,
decimals: asset.decimals,
}
);
submitTransfer(transfer); submitTransfer(transfer);
}, },
[asset, submitTransfer, transferAmount] [asset, submitTransfer, transferAmount]
@ -137,57 +153,53 @@ export const TransferForm = ({
className="text-sm" className="text-sm"
data-testid="transfer-form" data-testid="transfer-form"
> >
<TradingFormGroup label="Vega key" labelFor="to-address"> <TradingFormGroup label="Vega key" labelFor="toAddress">
<AddressField <AddressField
pubKeys={pubKeys} pubKeys={pubKeys}
onChange={() => setValue('toAddress', '')} onChange={() => setValue('toAddress', '')}
select={ select={
<TradingSelect <TradingSelect
{...register('toAddress')} {...register('toAddress')}
id="to-address" id="toAddress"
defaultValue="" defaultValue=""
> >
<option value="" disabled={true}> <option value="" disabled={true}>
{t('Please select')} {t('Please select')}
</option> </option>
{pubKeys?.length && {pubKeys?.length &&
pubKeys pubKeys.map((pk) => {
.filter((pk) => pk !== pubKey) // remove currently selected pubkey const text = pk === pubKey ? t('Current key: ') + pk : pk;
.map((pk) => (
return (
<option key={pk} value={pk}> <option key={pk} value={pk}>
{pk} {text}
</option> </option>
))} );
})}
</TradingSelect> </TradingSelect>
} }
input={ input={
<TradingInput <TradingInput
// eslint-disable-next-line jsx-a11y/no-autofocus // eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={true} // focus input immediately after is shown autoFocus={true} // focus input immediately after is shown
id="to-address" id="toAddress"
type="text" type="text"
{...register('toAddress', { {...register('toAddress', {
validate: { validate: {
required, required,
vegaPublicKey, vegaPublicKey,
sameKey: (value) => {
if (value === pubKey) {
return t('Vega key is the same as current key');
}
return true;
},
}, },
})} })}
/> />
} }
/> />
{errors.toAddress?.message && ( {errors.toAddress?.message && (
<TradingInputError forInput="to-address"> <TradingInputError forInput="toAddress">
{errors.toAddress.message} {errors.toAddress.message}
</TradingInputError> </TradingInputError>
)} )}
</TradingFormGroup> </TradingFormGroup>
<TradingFormGroup label="Asset" labelFor="asset"> <TradingFormGroup label={t('Asset')} labelFor="asset">
<Controller <Controller
control={control} control={control}
name="asset" name="asset"
@ -228,6 +240,63 @@ export const TransferForm = ({
</TradingInputError> </TradingInputError>
)} )}
</TradingFormGroup> </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]} ({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}>
{asset
? `${AccountTypeMapping[AccountType.ACCOUNT_TYPE_GENERAL]} (${
asset.symbol
})`
: AccountTypeMapping[AccountType.ACCOUNT_TYPE_GENERAL]}
</option>
</TradingSelect>
</TradingFormGroup>
<TradingFormGroup label="Amount" labelFor="amount"> <TradingFormGroup label="Amount" labelFor="amount">
<TradingInput <TradingInput
id="amount" id="amount"
@ -391,7 +460,7 @@ export const AddressField = ({
setIsInput((curr) => !curr); setIsInput((curr) => !curr);
onChange(); 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')} {isInput ? t('Select from wallet') : t('Enter manually')}
</button> </button>

View File

@ -1,6 +1,6 @@
import { removeDecimal, toNanoSeconds } from '@vegaprotocol/utils'; import { removeDecimal, toNanoSeconds } from '@vegaprotocol/utils';
import type { Market, Order } from '@vegaprotocol/types'; import type { Market, Order } from '@vegaprotocol/types';
import { AccountType } from '@vegaprotocol/types'; import type { AccountType } from '@vegaprotocol/types';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { ethers } from 'ethers'; import { ethers } from 'ethers';
import { sha3_256 } from 'js-sha3'; 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>>( export const normalizeTransfer = <T extends Exact<Transfer, T>>(
address: string, address: string,
amount: string, amount: string,
fromAccountType: AccountType,
toAccountType: AccountType,
asset: { asset: {
id: string; id: string;
decimals: number; decimals: number;
} }
): Transfer => { ): Transfer => {
return { return {
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
to: address, to: address,
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL, fromAccountType,
toAccountType,
asset: asset.id, asset: asset.id,
amount: removeDecimal(amount, asset.decimals), amount: removeDecimal(amount, asset.decimals),
// oneOff or recurring required otherwise wallet will error // oneOff or recurring required otherwise wallet will error