feat: add transfer dialog and form

This commit is contained in:
Matthew Russell
2023-02-06 10:43:33 -08:00
parent 74a2d15200
commit d552bf768f
7 changed files with 309 additions and 2 deletions
@@ -5,7 +5,7 @@ import { useWithdrawalDialog } from '@vegaprotocol/withdraws';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { AccountManager } from '@vegaprotocol/accounts';
import { AccountManager, useTransferDialog } from '@vegaprotocol/accounts';
import { useDepositDialog } from '@vegaprotocol/deposits';
export const AccountsContainer = () => {
@@ -13,6 +13,7 @@ export const AccountsContainer = () => {
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
const openWithdrawalDialog = useWithdrawalDialog((store) => store.open);
const openDepositDialog = useDepositDialog((store) => store.open);
const openTransferDialog = useTransferDialog((store) => store.open);
const onClickAsset = useCallback(
(assetId?: string) => {
@@ -41,7 +42,10 @@ export const AccountsContainer = () => {
/>
</div>
{!isReadOnly && (
<div className="flex justify-end p-2 px-[11px]">
<div className="flex gap-2 justify-end p-2 px-[11px]">
<Button size="sm" onClick={() => openTransferDialog()}>
{t('Transfer')}
</Button>
<Button size="sm" onClick={() => openDepositDialog()}>
{t('Deposit')}
</Button>
+2
View File
@@ -8,6 +8,7 @@ import { CreateWithdrawalDialog } from '@vegaprotocol/withdraws';
import { DepositDialog } from '@vegaprotocol/deposits';
import { Web3ConnectUncontrolledDialog } from '@vegaprotocol/web3';
import { WelcomeDialog } from '../components/welcome-dialog';
import { TransferDialog } from '@vegaprotocol/accounts';
const DialogsContainer = () => {
const { isOpen, id, trigger, setOpen } = useAssetDetailsDialogStore();
@@ -25,6 +26,7 @@ const DialogsContainer = () => {
<DepositDialog />
<Web3ConnectUncontrolledDialog />
<CreateWithdrawalDialog />
<TransferDialog />
</>
);
};
+1
View File
@@ -7,3 +7,4 @@ export * from './breakdown-table';
export * from './use-account-balance';
export * from './get-settlement-account';
export * from './use-market-account-balance';
export * from './transfer-dialog';
@@ -0,0 +1,45 @@
import * as Schema from '@vegaprotocol/types';
import {
addDecimal,
NetworkParams,
useDataProvider,
useNetworkParam,
} from '@vegaprotocol/react-helpers';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useMemo } from 'react';
import { accountsDataProvider } from './accounts-data-provider';
import { TransferForm } from './transfer-form';
export const TransferContainer = () => {
const { pubKey, pubKeys } = useVegaWallet();
const { param } = useNetworkParam(NetworkParams.transfer_fee_factor);
const { data } = useDataProvider({
dataProvider: accountsDataProvider,
variables: { partyId: pubKey },
skip: !pubKey,
});
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]);
return (
<TransferForm
pubKey={pubKey}
pubKeys={pubKeys ? pubKeys?.map((pk) => pk.publicKey) : null}
assets={assets}
feeFactor={param}
/>
);
};
+28
View File
@@ -0,0 +1,28 @@
import { t } from '@vegaprotocol/react-helpers';
import { Dialog } from '@vegaprotocol/ui-toolkit';
import { create } from 'zustand';
import { TransferContainer } from './transfer-container';
interface State {
isOpen: boolean;
}
interface Actions {
open: (open?: boolean) => void;
}
export const useTransferDialog = create<State & Actions>((set) => ({
isOpen: false,
open: (open = true) => {
set(() => ({ isOpen: open }));
},
}));
export const TransferDialog = () => {
const { isOpen, open } = useTransferDialog();
return (
<Dialog title={t('Transfer')} open={isOpen} onChange={open} size="small">
<TransferContainer />
</Dialog>
);
};
+226
View File
@@ -0,0 +1,226 @@
import {
t,
minSafe,
maxSafe,
required,
vegaPublicKey,
addDecimal,
formatNumber,
} from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types';
import {
Button,
FormGroup,
Input,
InputError,
Option,
RichSelect,
Select,
} from '@vegaprotocol/ui-toolkit';
import BigNumber from 'bignumber.js';
import { useMemo, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
interface FormFields {
toAddress: string;
asset: string;
amount: string;
}
interface TransferFormProps {
pubKey: string | null;
pubKeys: string[] | null;
assets: Array<{
id: string;
symbol: string;
name: string;
decimals: number;
balance: string;
}>;
feeFactor: string | null;
}
export const TransferForm = ({
pubKey,
pubKeys,
assets,
feeFactor,
}: TransferFormProps) => {
const [manualAddressEntry, setManualAddressEntry] = useState(false);
const {
control,
register,
watch,
handleSubmit,
setValue,
formState: { errors },
} = useForm<FormFields>();
const onSubmit = (fields: FormFields) => {
console.log(fields);
};
const amount = watch('amount');
const assetId = watch('asset');
const asset = useMemo(() => {
return assets.find((a) => a.id === assetId);
}, [assets, assetId]);
const min = useMemo(() => {
// Min viable amount given asset decimals EG for WEI 0.000000000000000001
const minViableAmount = asset
? new BigNumber(addDecimal('1', asset.decimals))
: new BigNumber(0);
return minViableAmount;
}, [asset]);
const max = useMemo(() => {
const maxAmount = asset ? new BigNumber(asset.balance) : new BigNumber(0);
return maxAmount;
}, [asset]);
console.log(asset, max.toString());
return (
<form onSubmit={handleSubmit(onSubmit)}>
<FormGroup label="From address" labelFor="from-address">
<Input
value={pubKey || ''}
id="from-address"
name="from-address"
readOnly={true}
disabled={true}
/>
</FormGroup>
<FormGroup label="To address" labelFor="to-address">
{!manualAddressEntry && pubKeys?.length ? (
<Select {...register('toAddress')}>
{pubKeys
.filter((pk) => pk !== pubKey)
.map((pk) => (
<option value={pk}>{pk}</option>
))}
</Select>
) : (
<Input
{...register('toAddress', {
validate: {
required,
vegaPublicKey,
},
})}
/>
)}
<button
onClick={() => {
setManualAddressEntry((curr) => !curr);
if (!manualAddressEntry) {
setValue('toAddress', '');
}
}}
className="text-xs underline"
>
{manualAddressEntry ? t('Select from wallet') : t('Enter manually')}
</button>
{errors.toAddress?.message && (
<InputError forInput="to-address">
{errors.toAddress.message}
</InputError>
)}
</FormGroup>
<FormGroup label="Asset" labelFor="asset">
<Controller
control={control}
name="asset"
rules={{
validate: {
required,
},
}}
render={({ field }) => (
<RichSelect
data-testid="select-asset"
id={field.name}
name={field.name}
onValueChange={(value) => {
field.onChange(value);
}}
placeholder={t('Please select')}
value={field.value}
>
{assets.map((a) => (
<Option key={a.id} value={a.id}>
<div className="text-left">
<div>{a.name}</div>
<div className="text-xs">
<span className="font-mono">
{formatNumber(a.balance, a.decimals)}
</span>{' '}
<span>{a.symbol}</span>
</div>
</div>
</Option>
))}
</RichSelect>
)}
/>
{errors.asset?.message && (
<InputError forInput="asset">{errors.asset.message}</InputError>
)}
</FormGroup>
<FormGroup label="Account type" labelFor="account-type">
<Input
id="account-type"
name="account-type"
readOnly={true}
disabled={true}
value={Schema.AccountType.ACCOUNT_TYPE_GENERAL}
/>
</FormGroup>
<FormGroup label="Amount" labelFor="amount">
<Input
type="number"
{...register('amount', {
validate: {
required,
minSafe: (value) => minSafe(new BigNumber(min))(value),
maxSafe: (v) => {
const value = new BigNumber(v);
console.log(value.toString(), max.toString());
if (value.isGreaterThan(max)) {
return t(
'You cannot transfer more than your available collateral'
);
}
return maxSafe(max)(v);
},
},
})}
/>
{errors.amount?.message && (
<InputError forInput="amount">{errors.amount.message}</InputError>
)}
</FormGroup>
<TransferFee amount={amount} feeFactor={feeFactor} />
<Button type="submit" variant="primary" fill={true}>
Confirm transfer
</Button>
</form>
);
};
const TransferFee = ({
amount,
feeFactor,
}: {
amount: string;
feeFactor: string | null;
}) => {
if (!feeFactor || !amount) return null;
const value = new BigNumber(amount).times(feeFactor).toString();
return <div>Transfer fee: {value.toString()}</div>;
};
@@ -104,6 +104,7 @@ export const NetworkParams = {
market_liquidity_stakeToCcyVolume: 'market_liquidity_stakeToCcyVolume',
market_liquidity_targetstake_triggering_ratio:
'market_liquidity_targetstake_triggering_ratio',
transfer_fee_factor: 'transfer.fee.factor',
} as const;
type Params = typeof NetworkParams;