feat: add transfer dialog and form
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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 />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user