refacotr: deposit manager (#867)
* refactor: deposit manager with a zustand store and refetching balances after contracts complete * refactor: remove assetId query string functionality * chore: remove unused import * chore: add a comment with a link to code explanation * refactor: capture errors from deposit value get functions * refactor: add error handling for async perform funcs * feat: add assets to react helpers for types and erc20 check
This commit is contained in:
parent
3498b9d54b
commit
11be7aaa8a
@ -18,6 +18,7 @@ import type {
|
|||||||
} from './__generated__/Delegations';
|
} from './__generated__/Delegations';
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import { useContracts } from '../../contexts/contracts/contracts-context';
|
import { useContracts } from '../../contexts/contracts/contracts-context';
|
||||||
|
import { isAssetTypeERC20 } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
const DELEGATIONS_QUERY = gql`
|
const DELEGATIONS_QUERY = gql`
|
||||||
query Delegations($partyId: ID!) {
|
query Delegations($partyId: ID!) {
|
||||||
@ -117,7 +118,7 @@ export const usePollForDelegations = () => {
|
|||||||
.filter((a) => a.type === AccountType.General)
|
.filter((a) => a.type === AccountType.General)
|
||||||
.map((a) => {
|
.map((a) => {
|
||||||
const isVega =
|
const isVega =
|
||||||
a.asset.source.__typename === 'ERC20' &&
|
isAssetTypeERC20(a.asset) &&
|
||||||
a.asset.source.contractAddress === vegaToken.address;
|
a.asset.source.contractAddress === vegaToken.address;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -131,8 +132,7 @@ export const usePollForDelegations = () => {
|
|||||||
),
|
),
|
||||||
image: isVega ? vegaBlack : noIcon,
|
image: isVega ? vegaBlack : noIcon,
|
||||||
border: isVega,
|
border: isVega,
|
||||||
address:
|
address: isAssetTypeERC20(a.asset)
|
||||||
a.asset.source.__typename === 'ERC20'
|
|
||||||
? a.asset.source.contractAddress
|
? a.asset.source.contractAddress
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
|
@ -16,14 +16,10 @@ const DEPOSIT_PAGE_QUERY = gql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface DepositContainerProps {
|
|
||||||
assetId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches data required for the Deposit page
|
* Fetches data required for the Deposit page
|
||||||
*/
|
*/
|
||||||
export const DepositContainer = ({ assetId }: DepositContainerProps) => {
|
export const DepositContainer = () => {
|
||||||
const { VEGA_ENV } = useEnvironment();
|
const { VEGA_ENV } = useEnvironment();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -41,7 +37,6 @@ export const DepositContainer = ({ assetId }: DepositContainerProps) => {
|
|||||||
return (
|
return (
|
||||||
<DepositManager
|
<DepositManager
|
||||||
assets={data.assets}
|
assets={data.assets}
|
||||||
initialAssetId={assetId}
|
|
||||||
isFaucetable={VEGA_ENV !== 'MAINNET'}
|
isFaucetable={VEGA_ENV !== 'MAINNET'}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -1,29 +1,12 @@
|
|||||||
import { useRouter } from 'next/router';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { Web3Container } from '../../../components/web3-container';
|
import { Web3Container } from '../../../components/web3-container';
|
||||||
import { DepositContainer } from './deposit-container';
|
import { DepositContainer } from './deposit-container';
|
||||||
|
|
||||||
const Deposit = () => {
|
const Deposit = () => {
|
||||||
const { query } = useRouter();
|
|
||||||
|
|
||||||
// AssetId can be specified in the query string to allow link to deposit a particular asset
|
|
||||||
const assetId = useMemo(() => {
|
|
||||||
if (query.assetId && Array.isArray(query.assetId)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(query.assetId)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return query.assetId;
|
|
||||||
}, [query]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Web3Container>
|
<Web3Container>
|
||||||
<div className="max-w-[420px] p-24 mx-auto">
|
<div className="max-w-[420px] p-24 mx-auto">
|
||||||
<h1 className="text-h3 mb-12">Deposit</h1>
|
<h1 className="text-h3 mb-12">Deposit</h1>
|
||||||
<DepositContainer assetId={assetId} />
|
<DepositContainer />
|
||||||
</div>
|
</div>
|
||||||
</Web3Container>
|
</Web3Container>
|
||||||
);
|
);
|
||||||
|
@ -37,10 +37,8 @@ beforeEach(() => {
|
|||||||
submitApprove: jest.fn(),
|
submitApprove: jest.fn(),
|
||||||
submitDeposit: jest.fn(),
|
submitDeposit: jest.fn(),
|
||||||
requestFaucet: jest.fn(),
|
requestFaucet: jest.fn(),
|
||||||
limits: {
|
|
||||||
max: new BigNumber(20),
|
max: new BigNumber(20),
|
||||||
deposited: new BigNumber(10),
|
deposited: new BigNumber(10),
|
||||||
},
|
|
||||||
allowance: new BigNumber(30),
|
allowance: new BigNumber(30),
|
||||||
isFaucetable: true,
|
isFaucetable: true,
|
||||||
};
|
};
|
||||||
@ -134,7 +132,8 @@ describe('Deposit form', () => {
|
|||||||
<DepositForm
|
<DepositForm
|
||||||
{...props}
|
{...props}
|
||||||
balance={new BigNumber(100)}
|
balance={new BigNumber(100)}
|
||||||
limits={{ max: new BigNumber(100), deposited: new BigNumber(10) }}
|
max={new BigNumber(100)}
|
||||||
|
deposited={new BigNumber(10)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -213,18 +212,16 @@ describe('Deposit form', () => {
|
|||||||
const mockUseWeb3React = useWeb3React as jest.Mock;
|
const mockUseWeb3React = useWeb3React as jest.Mock;
|
||||||
mockUseWeb3React.mockReturnValue({ account });
|
mockUseWeb3React.mockReturnValue({ account });
|
||||||
|
|
||||||
const limits = {
|
|
||||||
max: new BigNumber(20),
|
|
||||||
deposited: new BigNumber(10),
|
|
||||||
};
|
|
||||||
const balance = new BigNumber(50);
|
const balance = new BigNumber(50);
|
||||||
|
const max = new BigNumber(20);
|
||||||
|
const deposited = new BigNumber(10);
|
||||||
render(
|
render(
|
||||||
<DepositForm
|
<DepositForm
|
||||||
{...props}
|
{...props}
|
||||||
allowance={new BigNumber(100)}
|
allowance={new BigNumber(100)}
|
||||||
balance={balance}
|
balance={balance}
|
||||||
limits={limits}
|
max={max}
|
||||||
|
deposited={deposited}
|
||||||
selectedAsset={asset}
|
selectedAsset={asset}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -237,13 +234,13 @@ describe('Deposit form', () => {
|
|||||||
expect(
|
expect(
|
||||||
screen.getByText('Maximum total deposit amount', { selector: 'th' })
|
screen.getByText('Maximum total deposit amount', { selector: 'th' })
|
||||||
.nextElementSibling
|
.nextElementSibling
|
||||||
).toHaveTextContent(limits.max.toString());
|
).toHaveTextContent(max.toString());
|
||||||
expect(
|
expect(
|
||||||
screen.getByText('Deposited', { selector: 'th' }).nextElementSibling
|
screen.getByText('Deposited', { selector: 'th' }).nextElementSibling
|
||||||
).toHaveTextContent(limits.deposited.toString());
|
).toHaveTextContent(deposited.toString());
|
||||||
expect(
|
expect(
|
||||||
screen.getByText('Remaining', { selector: 'th' }).nextElementSibling
|
screen.getByText('Remaining', { selector: 'th' }).nextElementSibling
|
||||||
).toHaveTextContent(limits.max.minus(limits.deposited).toString());
|
).toHaveTextContent(max.minus(deposited).toString());
|
||||||
|
|
||||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||||
target: { value: '8' },
|
target: { value: '8' },
|
||||||
@ -257,7 +254,7 @@ describe('Deposit form', () => {
|
|||||||
expect(props.submitDeposit).toHaveBeenCalledWith({
|
expect(props.submitDeposit).toHaveBeenCalledWith({
|
||||||
// @ts-ignore contract address definitely defined
|
// @ts-ignore contract address definitely defined
|
||||||
assetSource: asset.source.contractAddress,
|
assetSource: asset.source.contractAddress,
|
||||||
amount: '800',
|
amount: '8',
|
||||||
vegaPublicKey: vegaKey,
|
vegaPublicKey: vegaKey,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
import type { Asset } from '@vegaprotocol/react-helpers';
|
||||||
import {
|
import {
|
||||||
removeDecimal,
|
|
||||||
ethereumAddress,
|
ethereumAddress,
|
||||||
t,
|
t,
|
||||||
required,
|
required,
|
||||||
@ -7,6 +7,7 @@ import {
|
|||||||
minSafe,
|
minSafe,
|
||||||
maxSafe,
|
maxSafe,
|
||||||
addDecimal,
|
addDecimal,
|
||||||
|
isAssetTypeERC20,
|
||||||
} from '@vegaprotocol/react-helpers';
|
} from '@vegaprotocol/react-helpers';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -21,10 +22,9 @@ import { useWeb3React } from '@web3-react/core';
|
|||||||
import { Web3WalletInput } from '@vegaprotocol/web3';
|
import { Web3WalletInput } from '@vegaprotocol/web3';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { useMemo, useEffect } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useForm, useWatch } from 'react-hook-form';
|
import { Controller, useForm, useWatch } from 'react-hook-form';
|
||||||
import { DepositLimits } from './deposit-limits';
|
import { DepositLimits } from './deposit-limits';
|
||||||
import type { Asset } from './deposit-manager';
|
|
||||||
|
|
||||||
interface FormFields {
|
interface FormFields {
|
||||||
asset: string;
|
asset: string;
|
||||||
@ -45,10 +45,8 @@ export interface DepositFormProps {
|
|||||||
vegaPublicKey: string;
|
vegaPublicKey: string;
|
||||||
}) => void;
|
}) => void;
|
||||||
requestFaucet: () => void;
|
requestFaucet: () => void;
|
||||||
limits: {
|
max: BigNumber | undefined;
|
||||||
max: BigNumber;
|
deposited: BigNumber | undefined;
|
||||||
deposited: BigNumber;
|
|
||||||
} | null;
|
|
||||||
allowance: BigNumber | undefined;
|
allowance: BigNumber | undefined;
|
||||||
isFaucetable?: boolean;
|
isFaucetable?: boolean;
|
||||||
}
|
}
|
||||||
@ -58,10 +56,11 @@ export const DepositForm = ({
|
|||||||
selectedAsset,
|
selectedAsset,
|
||||||
onSelectAsset,
|
onSelectAsset,
|
||||||
balance,
|
balance,
|
||||||
|
max,
|
||||||
|
deposited,
|
||||||
submitApprove,
|
submitApprove,
|
||||||
submitDeposit,
|
submitDeposit,
|
||||||
requestFaucet,
|
requestFaucet,
|
||||||
limits,
|
|
||||||
allowance,
|
allowance,
|
||||||
isFaucetable,
|
isFaucetable,
|
||||||
}: DepositFormProps) => {
|
}: DepositFormProps) => {
|
||||||
@ -89,15 +88,14 @@ export const DepositForm = ({
|
|||||||
|
|
||||||
submitDeposit({
|
submitDeposit({
|
||||||
assetSource: selectedAsset.source.contractAddress,
|
assetSource: selectedAsset.source.contractAddress,
|
||||||
amount: removeDecimal(fields.amount, selectedAsset.decimals),
|
amount: fields.amount,
|
||||||
vegaPublicKey: fields.to,
|
vegaPublicKey: fields.to,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const assetId = useWatch({ name: 'asset', control });
|
|
||||||
const amount = useWatch({ name: 'amount', control });
|
const amount = useWatch({ name: 'amount', control });
|
||||||
|
|
||||||
const max = useMemo(() => {
|
const maxAmount = useMemo(() => {
|
||||||
const maxApproved = allowance ? allowance : new BigNumber(0);
|
const maxApproved = allowance ? allowance : new BigNumber(0);
|
||||||
const maxAvailable = balance ? balance : new BigNumber(0);
|
const maxAvailable = balance ? balance : new BigNumber(0);
|
||||||
|
|
||||||
@ -106,8 +104,8 @@ export const DepositForm = ({
|
|||||||
let maxLimit = new BigNumber(Infinity);
|
let maxLimit = new BigNumber(Infinity);
|
||||||
|
|
||||||
// A max limit of zero indicates that there is no limit
|
// A max limit of zero indicates that there is no limit
|
||||||
if (limits && limits.max.isGreaterThan(0)) {
|
if (max && deposited && max.isGreaterThan(0)) {
|
||||||
maxLimit = limits.max.minus(limits.deposited);
|
maxLimit = max.minus(deposited);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -116,7 +114,7 @@ export const DepositForm = ({
|
|||||||
limit: maxLimit,
|
limit: maxLimit,
|
||||||
amount: BigNumber.minimum(maxLimit, maxApproved, maxAvailable),
|
amount: BigNumber.minimum(maxLimit, maxApproved, maxAvailable),
|
||||||
};
|
};
|
||||||
}, [limits, allowance, balance]);
|
}, [max, deposited, allowance, balance]);
|
||||||
|
|
||||||
const min = useMemo(() => {
|
const min = useMemo(() => {
|
||||||
// Min viable amount given asset decimals EG for WEI 0.000000000000000001
|
// Min viable amount given asset decimals EG for WEI 0.000000000000000001
|
||||||
@ -127,10 +125,6 @@ export const DepositForm = ({
|
|||||||
return minViableAmount;
|
return minViableAmount;
|
||||||
}, [selectedAsset]);
|
}, [selectedAsset]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
onSelectAsset(assetId);
|
|
||||||
}, [assetId, onSelectAsset]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit(onDeposit)}
|
onSubmit={handleSubmit(onDeposit)}
|
||||||
@ -154,16 +148,28 @@ export const DepositForm = ({
|
|||||||
)}
|
)}
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup label={t('Asset')} labelFor="asset" className="relative">
|
<FormGroup label={t('Asset')} labelFor="asset" className="relative">
|
||||||
<Select {...register('asset', { validate: { required } })} id="asset">
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="asset"
|
||||||
|
rules={{ validate: { required } }}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select
|
||||||
|
id="asset"
|
||||||
|
{...field}
|
||||||
|
onChange={(e) => {
|
||||||
|
field.onChange(e);
|
||||||
|
onSelectAsset(e.target.value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<option value="">{t('Please select')}</option>
|
<option value="">{t('Please select')}</option>
|
||||||
{assets
|
{assets.filter(isAssetTypeERC20).map((a) => (
|
||||||
.filter((a) => a.source.__typename === 'ERC20')
|
|
||||||
.map((a) => (
|
|
||||||
<option key={a.id} value={a.id}>
|
<option key={a.id} value={a.id}>
|
||||||
{a.name}
|
{a.name}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
{errors.asset?.message && (
|
{errors.asset?.message && (
|
||||||
<InputError intent="danger" className="mt-4" forInput="asset">
|
<InputError intent="danger" className="mt-4" forInput="asset">
|
||||||
{errors.asset.message}
|
{errors.asset.message}
|
||||||
@ -196,9 +202,9 @@ export const DepositForm = ({
|
|||||||
</UseButton>
|
</UseButton>
|
||||||
)}
|
)}
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
{selectedAsset && limits && (
|
{selectedAsset && max && deposited && (
|
||||||
<div className="mb-20">
|
<div className="mb-20">
|
||||||
<DepositLimits limits={limits} balance={balance} />
|
<DepositLimits max={max} deposited={deposited} balance={balance} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<FormGroup label={t('Amount')} labelFor="amount" className="relative">
|
<FormGroup label={t('Amount')} labelFor="amount" className="relative">
|
||||||
@ -212,14 +218,14 @@ export const DepositForm = ({
|
|||||||
minSafe: (value) => minSafe(new BigNumber(min))(value),
|
minSafe: (value) => minSafe(new BigNumber(min))(value),
|
||||||
maxSafe: (v) => {
|
maxSafe: (v) => {
|
||||||
const value = new BigNumber(v);
|
const value = new BigNumber(v);
|
||||||
if (value.isGreaterThan(max.available)) {
|
if (value.isGreaterThan(maxAmount.available)) {
|
||||||
return t('Insufficient amount in Ethereum wallet');
|
return t('Insufficient amount in Ethereum wallet');
|
||||||
} else if (value.isGreaterThan(max.limit)) {
|
} else if (value.isGreaterThan(maxAmount.limit)) {
|
||||||
return t('Amount is above temporary deposit limit');
|
return t('Amount is above temporary deposit limit');
|
||||||
} else if (value.isGreaterThan(max.approved)) {
|
} else if (value.isGreaterThan(maxAmount.approved)) {
|
||||||
return t('Amount is above approved amount');
|
return t('Amount is above approved amount');
|
||||||
}
|
}
|
||||||
return maxSafe(max.amount)(v);
|
return maxSafe(maxAmount.amount)(v);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
|
@ -2,28 +2,30 @@ import { t } from '@vegaprotocol/react-helpers';
|
|||||||
import type BigNumber from 'bignumber.js';
|
import type BigNumber from 'bignumber.js';
|
||||||
|
|
||||||
interface DepositLimitsProps {
|
interface DepositLimitsProps {
|
||||||
limits: {
|
|
||||||
max: BigNumber;
|
max: BigNumber;
|
||||||
deposited: BigNumber;
|
deposited: BigNumber;
|
||||||
};
|
|
||||||
balance?: BigNumber;
|
balance?: BigNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DepositLimits = ({ limits, balance }: DepositLimitsProps) => {
|
export const DepositLimits = ({
|
||||||
|
max,
|
||||||
|
deposited,
|
||||||
|
balance,
|
||||||
|
}: DepositLimitsProps) => {
|
||||||
let maxLimit = '';
|
let maxLimit = '';
|
||||||
if (limits.max.isEqualTo(Infinity)) {
|
if (max.isEqualTo(Infinity)) {
|
||||||
maxLimit = t('No limit');
|
maxLimit = t('No limit');
|
||||||
} else if (limits.max.isGreaterThan(1_000_000)) {
|
} else if (max.isGreaterThan(1_000_000)) {
|
||||||
maxLimit = t('1m+');
|
maxLimit = t('1m+');
|
||||||
} else {
|
} else {
|
||||||
maxLimit = limits.max.toString();
|
maxLimit = max.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
let remaining = '';
|
let remaining = '';
|
||||||
if (limits.deposited.isEqualTo(0)) {
|
if (deposited.isEqualTo(0)) {
|
||||||
remaining = maxLimit;
|
remaining = maxLimit;
|
||||||
} else {
|
} else {
|
||||||
const amountRemaining = limits.max.minus(limits.deposited);
|
const amountRemaining = max.minus(deposited);
|
||||||
remaining = amountRemaining.isGreaterThan(1_000_000)
|
remaining = amountRemaining.isGreaterThan(1_000_000)
|
||||||
? t('1m+')
|
? t('1m+')
|
||||||
: amountRemaining.toString();
|
: amountRemaining.toString();
|
||||||
@ -44,7 +46,7 @@ export const DepositLimits = ({ limits, balance }: DepositLimitsProps) => {
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th className="text-left font-normal">{t('Deposited')}</th>
|
<th className="text-left font-normal">{t('Deposited')}</th>
|
||||||
<td className="text-right">{limits.deposited.toString()}</td>
|
<td className="text-right">{deposited.toString()}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th className="text-left font-normal">{t('Remaining')}</th>
|
<th className="text-left font-normal">{t('Remaining')}</th>
|
||||||
|
@ -1,121 +1,56 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
|
||||||
import { DepositForm } from './deposit-form';
|
import { DepositForm } from './deposit-form';
|
||||||
import { useGetBalanceOfERC20Token } from './use-get-balance-of-erc20-token';
|
|
||||||
import { useSubmitDeposit } from './use-submit-deposit';
|
import { useSubmitDeposit } from './use-submit-deposit';
|
||||||
import sortBy from 'lodash/sortBy';
|
import sortBy from 'lodash/sortBy';
|
||||||
import { useSubmitApproval } from './use-submit-approval';
|
import { useSubmitApproval } from './use-submit-approval';
|
||||||
import { useGetDepositLimits } from './use-get-deposit-limits';
|
|
||||||
import { useGetAllowance } from './use-get-allowance';
|
|
||||||
import { useSubmitFaucet } from './use-submit-faucet';
|
import { useSubmitFaucet } from './use-submit-faucet';
|
||||||
import { EthTxStatus, useEthereumConfig } from '@vegaprotocol/web3';
|
import { useDepositStore } from './deposit-store';
|
||||||
import { useTokenContract } from '@vegaprotocol/web3';
|
import { useCallback } from 'react';
|
||||||
import { removeDecimal } from '@vegaprotocol/react-helpers';
|
import { useDepositBalances } from './use-deposit-balances';
|
||||||
|
import type { Asset } from '@vegaprotocol/react-helpers';
|
||||||
interface ERC20AssetSource {
|
|
||||||
__typename: 'ERC20';
|
|
||||||
contractAddress: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BuiltinAssetSource {
|
|
||||||
__typename: 'BuiltinAsset';
|
|
||||||
}
|
|
||||||
|
|
||||||
type AssetSource = ERC20AssetSource | BuiltinAssetSource;
|
|
||||||
export interface Asset {
|
|
||||||
__typename: 'Asset';
|
|
||||||
id: string;
|
|
||||||
symbol: string;
|
|
||||||
name: string;
|
|
||||||
decimals: number;
|
|
||||||
source: AssetSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DepositManagerProps {
|
interface DepositManagerProps {
|
||||||
assets: Asset[];
|
assets: Asset[];
|
||||||
initialAssetId?: string;
|
isFaucetable: boolean;
|
||||||
isFaucetable?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DepositManager = ({
|
export const DepositManager = ({
|
||||||
assets,
|
assets,
|
||||||
initialAssetId,
|
|
||||||
isFaucetable,
|
isFaucetable,
|
||||||
}: DepositManagerProps) => {
|
}: DepositManagerProps) => {
|
||||||
const [assetId, setAssetId] = useState<string | undefined>(initialAssetId);
|
const { asset, balance, allowance, deposited, max, update } =
|
||||||
|
useDepositStore();
|
||||||
// Find the asset object from the select box
|
useDepositBalances(isFaucetable);
|
||||||
const asset = useMemo(() => {
|
|
||||||
const asset = assets?.find((a) => a.id === assetId);
|
|
||||||
return asset;
|
|
||||||
}, [assets, assetId]);
|
|
||||||
|
|
||||||
const { config } = useEthereumConfig();
|
|
||||||
|
|
||||||
const tokenContract = useTokenContract(
|
|
||||||
asset?.source.__typename === 'ERC20'
|
|
||||||
? asset.source.contractAddress
|
|
||||||
: undefined,
|
|
||||||
isFaucetable
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get users balance of the erc20 token selected
|
|
||||||
const { balance, refetch: refetchBalance } = useGetBalanceOfERC20Token(
|
|
||||||
tokenContract,
|
|
||||||
asset?.decimals
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get temporary deposit limits
|
|
||||||
const limits = useGetDepositLimits(asset);
|
|
||||||
|
|
||||||
// Get allowance (approved spending limit of brdige contract) for the selected asset
|
|
||||||
const { allowance, refetch: refetchAllowance } = useGetAllowance(
|
|
||||||
tokenContract,
|
|
||||||
asset?.decimals
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set up approve transaction
|
// Set up approve transaction
|
||||||
const approve = useSubmitApproval(tokenContract);
|
const approve = useSubmitApproval();
|
||||||
|
|
||||||
// Set up deposit transaction
|
// Set up deposit transaction
|
||||||
const deposit = useSubmitDeposit();
|
const deposit = useSubmitDeposit();
|
||||||
|
|
||||||
// Set up faucet transaction
|
// Set up faucet transaction
|
||||||
const faucet = useSubmitFaucet(tokenContract);
|
const faucet = useSubmitFaucet();
|
||||||
|
|
||||||
// Update balance after confirmation event has been received
|
const handleSelectAsset = useCallback(
|
||||||
useEffect(() => {
|
(id) => {
|
||||||
if (
|
const asset = assets.find((a) => a.id === id);
|
||||||
faucet.transaction.status === EthTxStatus.Confirmed ||
|
if (!asset) return;
|
||||||
deposit.transaction.status === EthTxStatus.Confirmed
|
update({ asset });
|
||||||
) {
|
},
|
||||||
refetchBalance();
|
[assets, update]
|
||||||
}
|
);
|
||||||
}, [deposit.transaction.status, faucet.transaction.status, refetchBalance]);
|
|
||||||
|
|
||||||
// After an approval transaction refetch allowance
|
|
||||||
useEffect(() => {
|
|
||||||
if (approve.transaction.status === EthTxStatus.Confirmed) {
|
|
||||||
refetchAllowance();
|
|
||||||
}
|
|
||||||
}, [approve.transaction.status, refetchAllowance]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DepositForm
|
<DepositForm
|
||||||
balance={balance}
|
balance={balance}
|
||||||
selectedAsset={asset}
|
selectedAsset={asset}
|
||||||
onSelectAsset={(id) => setAssetId(id)}
|
onSelectAsset={handleSelectAsset}
|
||||||
assets={sortBy(assets, 'name')}
|
assets={sortBy(assets, 'name')}
|
||||||
submitApprove={() => {
|
submitApprove={() => approve.perform()}
|
||||||
if (!asset || !config) return;
|
submitDeposit={(args) => deposit.perform(args)}
|
||||||
const amount = removeDecimal('1000000', asset.decimals);
|
|
||||||
approve.perform(config.collateral_bridge_contract.address, amount);
|
|
||||||
}}
|
|
||||||
submitDeposit={(args) => {
|
|
||||||
deposit.perform(args.assetSource, args.amount, args.vegaPublicKey);
|
|
||||||
}}
|
|
||||||
requestFaucet={() => faucet.perform()}
|
requestFaucet={() => faucet.perform()}
|
||||||
limits={limits}
|
deposited={deposited}
|
||||||
|
max={max}
|
||||||
allowance={allowance}
|
allowance={allowance}
|
||||||
isFaucetable={isFaucetable}
|
isFaucetable={isFaucetable}
|
||||||
/>
|
/>
|
||||||
|
24
libs/deposits/src/lib/deposit-store.ts
Normal file
24
libs/deposits/src/lib/deposit-store.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import type { Asset } from '@vegaprotocol/react-helpers';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
import type { SetState } from 'zustand';
|
||||||
|
import create from 'zustand';
|
||||||
|
|
||||||
|
interface DepositStore {
|
||||||
|
balance: BigNumber;
|
||||||
|
allowance: BigNumber;
|
||||||
|
asset: Asset | undefined;
|
||||||
|
deposited: BigNumber;
|
||||||
|
max: BigNumber;
|
||||||
|
update: (state: Partial<DepositStore>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDepositStore = create((set: SetState<DepositStore>) => ({
|
||||||
|
balance: new BigNumber(0),
|
||||||
|
allowance: new BigNumber(0),
|
||||||
|
deposited: new BigNumber(0),
|
||||||
|
max: new BigNumber(0),
|
||||||
|
asset: undefined,
|
||||||
|
update: (updatedState) => {
|
||||||
|
set(updatedState);
|
||||||
|
},
|
||||||
|
}));
|
59
libs/deposits/src/lib/use-deposit-balances.ts
Normal file
59
libs/deposits/src/lib/use-deposit-balances.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { useBridgeContract, useTokenContract } from '@vegaprotocol/web3';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import * as Sentry from '@sentry/react';
|
||||||
|
import { useDepositStore } from './deposit-store';
|
||||||
|
import { useGetAllowance } from './use-get-allowance';
|
||||||
|
import { useGetBalanceOfERC20Token } from './use-get-balance-of-erc20-token';
|
||||||
|
import { useGetDepositMaximum } from './use-get-deposit-maximum';
|
||||||
|
import { useGetDepositedAmount } from './use-get-deposited-amount';
|
||||||
|
import { isAssetTypeERC20 } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook which fetches all the balances required for despoiting
|
||||||
|
* whenever the asset changes in the form
|
||||||
|
*/
|
||||||
|
export const useDepositBalances = (isFaucetable: boolean) => {
|
||||||
|
const { asset, update } = useDepositStore();
|
||||||
|
const tokenContract = useTokenContract(
|
||||||
|
isAssetTypeERC20(asset) ? asset : undefined,
|
||||||
|
isFaucetable
|
||||||
|
);
|
||||||
|
const bridgeContract = useBridgeContract(true);
|
||||||
|
const getAllowance = useGetAllowance(tokenContract, asset);
|
||||||
|
const getBalance = useGetBalanceOfERC20Token(tokenContract, asset);
|
||||||
|
const getDepositMaximum = useGetDepositMaximum(bridgeContract, asset);
|
||||||
|
const getDepositedAmount = useGetDepositedAmount(asset);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getBalances = async () => {
|
||||||
|
try {
|
||||||
|
const [max, deposited, balance, allowance] = await Promise.all([
|
||||||
|
getDepositMaximum(),
|
||||||
|
getDepositedAmount(),
|
||||||
|
getBalance(),
|
||||||
|
getAllowance(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
update({
|
||||||
|
max,
|
||||||
|
deposited,
|
||||||
|
balance,
|
||||||
|
allowance,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.captureException(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (asset) {
|
||||||
|
getBalances();
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
asset,
|
||||||
|
update,
|
||||||
|
getDepositMaximum,
|
||||||
|
getDepositedAmount,
|
||||||
|
getAllowance,
|
||||||
|
getBalance,
|
||||||
|
]);
|
||||||
|
};
|
@ -1,30 +1,35 @@
|
|||||||
import type { Token } from '@vegaprotocol/smart-contracts';
|
import type { Token } from '@vegaprotocol/smart-contracts';
|
||||||
|
import * as Sentry from '@sentry/react';
|
||||||
import { useWeb3React } from '@web3-react/core';
|
import { useWeb3React } from '@web3-react/core';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useEthereumConfig, useEthereumReadContract } from '@vegaprotocol/web3';
|
import { useEthereumConfig } from '@vegaprotocol/web3';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
|
import type { Asset } from '@vegaprotocol/react-helpers';
|
||||||
import { addDecimal } from '@vegaprotocol/react-helpers';
|
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
export const useGetAllowance = (contract: Token | null, decimals?: number) => {
|
export const useGetAllowance = (
|
||||||
|
contract: Token | null,
|
||||||
|
asset: Asset | undefined
|
||||||
|
) => {
|
||||||
const { account } = useWeb3React();
|
const { account } = useWeb3React();
|
||||||
const { config } = useEthereumConfig();
|
const { config } = useEthereumConfig();
|
||||||
|
|
||||||
const getAllowance = useCallback(() => {
|
const getAllowance = useCallback(async () => {
|
||||||
if (!contract || !account || !config) {
|
if (!contract || !account || !config || !asset) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return contract.allowance(
|
try {
|
||||||
|
const res = await contract.allowance(
|
||||||
account,
|
account,
|
||||||
config.collateral_bridge_contract.address
|
config.collateral_bridge_contract.address
|
||||||
);
|
);
|
||||||
}, [contract, account, config]);
|
|
||||||
|
|
||||||
const { state, refetch } = useEthereumReadContract(getAllowance);
|
return new BigNumber(addDecimal(res.toString(), asset.decimals));
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}, [contract, account, config, asset]);
|
||||||
|
|
||||||
const allowance =
|
return getAllowance;
|
||||||
state.data && decimals
|
|
||||||
? new BigNumber(addDecimal(state.data.toString(), decimals))
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return { allowance, refetch };
|
|
||||||
};
|
};
|
||||||
|
@ -1,30 +1,29 @@
|
|||||||
import { useEthereumReadContract } from '@vegaprotocol/web3';
|
|
||||||
import type { Token } from '@vegaprotocol/smart-contracts';
|
import type { Token } from '@vegaprotocol/smart-contracts';
|
||||||
|
import * as Sentry from '@sentry/react';
|
||||||
import { useWeb3React } from '@web3-react/core';
|
import { useWeb3React } from '@web3-react/core';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
|
import type { Asset } from '@vegaprotocol/react-helpers';
|
||||||
import { addDecimal } from '@vegaprotocol/react-helpers';
|
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
export const useGetBalanceOfERC20Token = (
|
export const useGetBalanceOfERC20Token = (
|
||||||
contract: Token | null,
|
contract: Token | null,
|
||||||
decimals: number | undefined
|
asset: Asset | undefined
|
||||||
) => {
|
) => {
|
||||||
const { account } = useWeb3React();
|
const { account } = useWeb3React();
|
||||||
|
const getBalance = useCallback(async () => {
|
||||||
const getBalance = useCallback(() => {
|
if (!contract || !asset || !account) {
|
||||||
if (!contract || !account) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return contract.balanceOf(account);
|
try {
|
||||||
}, [contract, account]);
|
const res = await contract.balanceOf(account);
|
||||||
|
return new BigNumber(addDecimal(res.toString(), asset.decimals));
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}, [contract, asset, account]);
|
||||||
|
|
||||||
const { state, refetch } = useEthereumReadContract(getBalance);
|
return getBalance;
|
||||||
|
|
||||||
const balance =
|
|
||||||
state.data && decimals
|
|
||||||
? new BigNumber(addDecimal(state.data?.toString(), decimals))
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return { balance, refetch };
|
|
||||||
};
|
};
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
|
||||||
import { ethers } from 'ethers';
|
|
||||||
import type { Asset } from './deposit-manager';
|
|
||||||
import {
|
|
||||||
useBridgeContract,
|
|
||||||
useEthereumConfig,
|
|
||||||
useEthereumReadContract,
|
|
||||||
} from '@vegaprotocol/web3';
|
|
||||||
import BigNumber from 'bignumber.js';
|
|
||||||
import { addDecimal } from '@vegaprotocol/react-helpers';
|
|
||||||
import { useWeb3React } from '@web3-react/core';
|
|
||||||
|
|
||||||
export const useGetDepositLimits = (asset?: Asset) => {
|
|
||||||
const { account, provider } = useWeb3React();
|
|
||||||
const { config } = useEthereumConfig();
|
|
||||||
const contract = useBridgeContract(true);
|
|
||||||
const [userTotal, setUserTotal] = useState<BigNumber | null>(null);
|
|
||||||
const getLimits = useCallback(async () => {
|
|
||||||
if (!contract || !asset || asset.source.__typename !== 'ERC20') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return contract.get_deposit_maximum(asset.source.contractAddress);
|
|
||||||
}, [asset, contract]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
!provider ||
|
|
||||||
!config ||
|
|
||||||
!account ||
|
|
||||||
!asset ||
|
|
||||||
asset.source.__typename !== 'ERC20'
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const abicoder = new ethers.utils.AbiCoder();
|
|
||||||
const innerHash = ethers.utils.keccak256(
|
|
||||||
abicoder.encode(['address', 'uint256'], [account, 4])
|
|
||||||
);
|
|
||||||
const storageLocation = ethers.utils.keccak256(
|
|
||||||
abicoder.encode(
|
|
||||||
['address', 'bytes32'],
|
|
||||||
[asset.source.contractAddress, innerHash]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
(async () => {
|
|
||||||
const res = await provider.getStorageAt(
|
|
||||||
config.collateral_bridge_contract.address,
|
|
||||||
storageLocation
|
|
||||||
);
|
|
||||||
const value = new BigNumber(res, 16).toString();
|
|
||||||
setUserTotal(new BigNumber(addDecimal(value, asset.decimals)));
|
|
||||||
})();
|
|
||||||
}, [provider, config, account, asset]);
|
|
||||||
|
|
||||||
const {
|
|
||||||
state: { data },
|
|
||||||
} = useEthereumReadContract(getLimits);
|
|
||||||
|
|
||||||
if (!data || !userTotal || !asset) return null;
|
|
||||||
|
|
||||||
const max = new BigNumber(addDecimal(data.toString(), asset.decimals));
|
|
||||||
|
|
||||||
return {
|
|
||||||
max: max.isEqualTo(0) ? new BigNumber(Infinity) : max,
|
|
||||||
deposited: userTotal,
|
|
||||||
};
|
|
||||||
};
|
|
32
libs/deposits/src/lib/use-get-deposit-maximum.ts
Normal file
32
libs/deposits/src/lib/use-get-deposit-maximum.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import * as Sentry from '@sentry/react';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
import type { Asset } from '@vegaprotocol/react-helpers';
|
||||||
|
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||||
|
import type {
|
||||||
|
CollateralBridge,
|
||||||
|
CollateralBridgeNew,
|
||||||
|
} from '@vegaprotocol/smart-contracts';
|
||||||
|
|
||||||
|
export const useGetDepositMaximum = (
|
||||||
|
contract: CollateralBridge | CollateralBridgeNew | null,
|
||||||
|
asset: Asset | undefined
|
||||||
|
) => {
|
||||||
|
const getDepositMaximum = useCallback(async () => {
|
||||||
|
if (!contract || !asset || asset.source.__typename !== 'ERC20') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await contract.get_deposit_maximum(
|
||||||
|
asset.source.contractAddress
|
||||||
|
);
|
||||||
|
const max = new BigNumber(addDecimal(res.toString(), asset.decimals));
|
||||||
|
return max.isEqualTo(0) ? new BigNumber(Infinity) : max;
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}, [contract, asset]);
|
||||||
|
|
||||||
|
return getDepositMaximum;
|
||||||
|
};
|
50
libs/deposits/src/lib/use-get-deposited-amount.ts
Normal file
50
libs/deposits/src/lib/use-get-deposited-amount.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import * as Sentry from '@sentry/react';
|
||||||
|
import { ethers } from 'ethers';
|
||||||
|
import { useEthereumConfig } from '@vegaprotocol/web3';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
import type { Asset } from '@vegaprotocol/react-helpers';
|
||||||
|
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||||
|
import { useWeb3React } from '@web3-react/core';
|
||||||
|
|
||||||
|
export const useGetDepositedAmount = (asset: Asset | undefined) => {
|
||||||
|
const { account, provider } = useWeb3React();
|
||||||
|
const { config } = useEthereumConfig();
|
||||||
|
|
||||||
|
// For an explaination of how this code works see here: https://gist.github.com/emilbayes/44a36f59b06b1f3edb9cf914041544ed
|
||||||
|
const getDepositedAmount = useCallback(async () => {
|
||||||
|
if (
|
||||||
|
!provider ||
|
||||||
|
!config ||
|
||||||
|
!account ||
|
||||||
|
!asset ||
|
||||||
|
asset.source.__typename !== 'ERC20'
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const abicoder = new ethers.utils.AbiCoder();
|
||||||
|
const innerHash = ethers.utils.keccak256(
|
||||||
|
abicoder.encode(['address', 'uint256'], [account, 4])
|
||||||
|
);
|
||||||
|
const storageLocation = ethers.utils.keccak256(
|
||||||
|
abicoder.encode(
|
||||||
|
['address', 'bytes32'],
|
||||||
|
[asset.source.contractAddress, innerHash]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const res = await provider.getStorageAt(
|
||||||
|
config.collateral_bridge_contract.address,
|
||||||
|
storageLocation
|
||||||
|
);
|
||||||
|
const value = new BigNumber(res, 16).toString();
|
||||||
|
return new BigNumber(addDecimal(value, asset.decimals));
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}, [provider, asset, config, account]);
|
||||||
|
|
||||||
|
return getDepositedAmount;
|
||||||
|
};
|
@ -1,10 +1,41 @@
|
|||||||
|
import { isAssetTypeERC20, removeDecimal } from '@vegaprotocol/react-helpers';
|
||||||
|
import * as Sentry from '@sentry/react';
|
||||||
import type { Token } from '@vegaprotocol/smart-contracts';
|
import type { Token } from '@vegaprotocol/smart-contracts';
|
||||||
import { useEthereumTransaction } from '@vegaprotocol/web3';
|
import {
|
||||||
|
useEthereumConfig,
|
||||||
|
useEthereumTransaction,
|
||||||
|
useTokenContract,
|
||||||
|
} from '@vegaprotocol/web3';
|
||||||
|
import { useDepositStore } from './deposit-store';
|
||||||
|
import { useGetAllowance } from './use-get-allowance';
|
||||||
|
|
||||||
export const useSubmitApproval = (contract: Token | null) => {
|
export const useSubmitApproval = () => {
|
||||||
|
const { config } = useEthereumConfig();
|
||||||
|
const { asset, update } = useDepositStore();
|
||||||
|
const contract = useTokenContract(
|
||||||
|
isAssetTypeERC20(asset) ? asset : undefined,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const getAllowance = useGetAllowance(contract, asset);
|
||||||
const transaction = useEthereumTransaction<Token, 'approve'>(
|
const transaction = useEthereumTransaction<Token, 'approve'>(
|
||||||
contract,
|
contract,
|
||||||
'approve'
|
'approve'
|
||||||
);
|
);
|
||||||
return transaction;
|
return {
|
||||||
|
...transaction,
|
||||||
|
perform: async () => {
|
||||||
|
if (!asset || !config) return;
|
||||||
|
try {
|
||||||
|
const amount = removeDecimal('1000000', asset.decimals);
|
||||||
|
await transaction.perform(
|
||||||
|
config.collateral_bridge_contract.address,
|
||||||
|
amount
|
||||||
|
);
|
||||||
|
const allowance = await getAllowance();
|
||||||
|
update({ allowance });
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.captureException(err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,21 +1,29 @@
|
|||||||
import { gql, useSubscription } from '@apollo/client';
|
import { gql, useSubscription } from '@apollo/client';
|
||||||
|
import * as Sentry from '@sentry/react';
|
||||||
import type {
|
import type {
|
||||||
DepositEvent,
|
DepositEvent,
|
||||||
DepositEventVariables,
|
DepositEventVariables,
|
||||||
} from './__generated__/DepositEvent';
|
} from './__generated__/DepositEvent';
|
||||||
import { DepositStatus } from '@vegaprotocol/types';
|
import { DepositStatus } from '@vegaprotocol/types';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { remove0x } from '@vegaprotocol/react-helpers';
|
import {
|
||||||
|
isAssetTypeERC20,
|
||||||
|
remove0x,
|
||||||
|
removeDecimal,
|
||||||
|
} from '@vegaprotocol/react-helpers';
|
||||||
import {
|
import {
|
||||||
useBridgeContract,
|
useBridgeContract,
|
||||||
useEthereumConfig,
|
useEthereumConfig,
|
||||||
useEthereumTransaction,
|
useEthereumTransaction,
|
||||||
|
useTokenContract,
|
||||||
} from '@vegaprotocol/web3';
|
} from '@vegaprotocol/web3';
|
||||||
import type {
|
import type {
|
||||||
CollateralBridge,
|
CollateralBridge,
|
||||||
CollateralBridgeNew,
|
CollateralBridgeNew,
|
||||||
} from '@vegaprotocol/smart-contracts';
|
} from '@vegaprotocol/smart-contracts';
|
||||||
import { prepend0x } from '@vegaprotocol/smart-contracts';
|
import { prepend0x } from '@vegaprotocol/smart-contracts';
|
||||||
|
import { useDepositStore } from './deposit-store';
|
||||||
|
import { useGetBalanceOfERC20Token } from './use-get-balance-of-erc20-token';
|
||||||
|
|
||||||
const DEPOSIT_EVENT_SUB = gql`
|
const DEPOSIT_EVENT_SUB = gql`
|
||||||
subscription DepositEvent($partyId: ID!) {
|
subscription DepositEvent($partyId: ID!) {
|
||||||
@ -32,17 +40,24 @@ const DEPOSIT_EVENT_SUB = gql`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const useSubmitDeposit = () => {
|
export const useSubmitDeposit = () => {
|
||||||
|
const { asset, update } = useDepositStore();
|
||||||
const { config } = useEthereumConfig();
|
const { config } = useEthereumConfig();
|
||||||
const contract = useBridgeContract(true);
|
const bridgeContract = useBridgeContract(true);
|
||||||
|
const tokenContract = useTokenContract(
|
||||||
|
isAssetTypeERC20(asset) ? asset : undefined,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
// Store public key from contract arguments for use in the subscription,
|
// Store public key from contract arguments for use in the subscription,
|
||||||
// NOTE: it may be different from the users connected key
|
// NOTE: it may be different from the users connected key
|
||||||
const [partyId, setPartyId] = useState<string | null>(null);
|
const [partyId, setPartyId] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const getBalance = useGetBalanceOfERC20Token(tokenContract, asset);
|
||||||
|
|
||||||
const transaction = useEthereumTransaction<
|
const transaction = useEthereumTransaction<
|
||||||
CollateralBridgeNew | CollateralBridge,
|
CollateralBridgeNew | CollateralBridge,
|
||||||
'deposit_asset'
|
'deposit_asset'
|
||||||
>(contract, 'deposit_asset', config?.confirmations, true);
|
>(bridgeContract, 'deposit_asset', config?.confirmations, true);
|
||||||
|
|
||||||
useSubscription<DepositEvent, DepositEventVariables>(DEPOSIT_EVENT_SUB, {
|
useSubscription<DepositEvent, DepositEventVariables>(DEPOSIT_EVENT_SUB, {
|
||||||
variables: { partyId: partyId ? remove0x(partyId) : '' },
|
variables: { partyId: partyId ? remove0x(partyId) : '' },
|
||||||
@ -78,10 +93,22 @@ export const useSubmitDeposit = () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...transaction,
|
...transaction,
|
||||||
perform: (...args: Parameters<typeof transaction.perform>) => {
|
perform: async (args: {
|
||||||
setPartyId(args[2]);
|
assetSource: string;
|
||||||
const publicKey = prepend0x(args[2]);
|
amount: string;
|
||||||
transaction.perform(args[0], args[1], publicKey);
|
vegaPublicKey: string;
|
||||||
|
}) => {
|
||||||
|
if (!asset) return;
|
||||||
|
try {
|
||||||
|
setPartyId(args.vegaPublicKey);
|
||||||
|
const publicKey = prepend0x(args.vegaPublicKey);
|
||||||
|
const amount = removeDecimal(args.amount, asset.decimals);
|
||||||
|
await transaction.perform(args.assetSource, amount, publicKey);
|
||||||
|
const balance = await getBalance();
|
||||||
|
update({ balance });
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.captureException(err);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,10 +1,31 @@
|
|||||||
import type { Token, TokenFaucetable } from '@vegaprotocol/smart-contracts';
|
import type { TokenFaucetable } from '@vegaprotocol/smart-contracts';
|
||||||
import { useEthereumTransaction } from '@vegaprotocol/web3';
|
import * as Sentry from '@sentry/react';
|
||||||
|
import { useEthereumTransaction, useTokenContract } from '@vegaprotocol/web3';
|
||||||
|
import { useDepositStore } from './deposit-store';
|
||||||
|
import { useGetBalanceOfERC20Token } from './use-get-balance-of-erc20-token';
|
||||||
|
import { isAssetTypeERC20 } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
export const useSubmitFaucet = (contract: Token | TokenFaucetable | null) => {
|
export const useSubmitFaucet = () => {
|
||||||
|
const { asset, update } = useDepositStore();
|
||||||
|
const contract = useTokenContract(
|
||||||
|
isAssetTypeERC20(asset) ? asset : undefined,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const getBalance = useGetBalanceOfERC20Token(contract, asset);
|
||||||
const transaction = useEthereumTransaction<TokenFaucetable, 'faucet'>(
|
const transaction = useEthereumTransaction<TokenFaucetable, 'faucet'>(
|
||||||
contract,
|
contract,
|
||||||
'faucet'
|
'faucet'
|
||||||
);
|
);
|
||||||
return transaction;
|
return {
|
||||||
|
...transaction,
|
||||||
|
perform: async () => {
|
||||||
|
try {
|
||||||
|
await transaction.perform();
|
||||||
|
const balance = await getBalance();
|
||||||
|
update({ balance });
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.captureException(err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
export * from './hooks';
|
export * from './hooks';
|
||||||
|
export * from './lib/assets';
|
||||||
export * from './lib/context';
|
export * from './lib/context';
|
||||||
export * from './lib/determine-id';
|
export * from './lib/determine-id';
|
||||||
export * from './lib/format';
|
export * from './lib/format';
|
||||||
|
30
libs/react-helpers/src/lib/assets.ts
Normal file
30
libs/react-helpers/src/lib/assets.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
export interface ERC20AssetSource {
|
||||||
|
__typename: 'ERC20';
|
||||||
|
contractAddress: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BuiltinAssetSource {
|
||||||
|
__typename: 'BuiltinAsset';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Asset {
|
||||||
|
__typename: 'Asset';
|
||||||
|
id: string;
|
||||||
|
symbol: string;
|
||||||
|
name: string;
|
||||||
|
decimals: number;
|
||||||
|
source: ERC20AssetSource | BuiltinAssetSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ERC20Asset = Omit<Asset, 'source'> & {
|
||||||
|
source: ERC20AssetSource;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BuiltinAsset = Omit<Asset, 'source'> & {
|
||||||
|
source: BuiltinAssetSource;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isAssetTypeERC20 = (asset?: Asset): asset is ERC20Asset => {
|
||||||
|
if (!asset?.source) return false;
|
||||||
|
return asset.source.__typename === 'ERC20';
|
||||||
|
};
|
@ -51,9 +51,11 @@ export const useEthereumReadContract = <T>(
|
|||||||
const response = await result;
|
const response = await result;
|
||||||
if (cancelRequest.current) return;
|
if (cancelRequest.current) return;
|
||||||
dispatch({ type: ActionType.FETCHED, payload: response });
|
dispatch({ type: ActionType.FETCHED, payload: response });
|
||||||
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (cancelRequest.current) return;
|
if (cancelRequest.current) return;
|
||||||
dispatch({ type: ActionType.ERROR, error: error as Error });
|
dispatch({ type: ActionType.ERROR, error: error as Error });
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}, [contractFunc]);
|
}, [contractFunc]);
|
||||||
|
|
||||||
|
@ -130,6 +130,7 @@ export const useEthereumTransaction = <
|
|||||||
error: new Error('Something went wrong'),
|
error: new Error('Something went wrong'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
@ -1,26 +1,25 @@
|
|||||||
|
import type { ERC20Asset } from '@vegaprotocol/react-helpers';
|
||||||
import { Token, TokenFaucetable } from '@vegaprotocol/smart-contracts';
|
import { Token, TokenFaucetable } from '@vegaprotocol/smart-contracts';
|
||||||
import { useWeb3React } from '@web3-react/core';
|
import { useWeb3React } from '@web3-react/core';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
export const useTokenContract = (
|
export const useTokenContract = (asset?: ERC20Asset, faucetable = false) => {
|
||||||
contractAddress?: string,
|
|
||||||
faucetable = false
|
|
||||||
) => {
|
|
||||||
const { provider } = useWeb3React();
|
const { provider } = useWeb3React();
|
||||||
|
|
||||||
const contract = useMemo(() => {
|
const contract = useMemo(() => {
|
||||||
if (!provider || !contractAddress) {
|
if (!provider || !asset) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const signer = provider.getSigner();
|
const signer = provider.getSigner();
|
||||||
|
const address = asset.source.contractAddress;
|
||||||
|
|
||||||
if (faucetable) {
|
if (faucetable) {
|
||||||
return new TokenFaucetable(contractAddress, signer || provider);
|
return new TokenFaucetable(address, signer || provider);
|
||||||
} else {
|
} else {
|
||||||
return new Token(contractAddress, signer || provider);
|
return new Token(address, signer || provider);
|
||||||
}
|
}
|
||||||
}, [provider, contractAddress, faucetable]);
|
}, [provider, asset, faucetable]);
|
||||||
|
|
||||||
return contract;
|
return contract;
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
|
import type { Asset } from '@vegaprotocol/react-helpers';
|
||||||
import { AccountType, WithdrawalStatus } from '@vegaprotocol/types';
|
import { AccountType, WithdrawalStatus } from '@vegaprotocol/types';
|
||||||
import merge from 'lodash/merge';
|
import merge from 'lodash/merge';
|
||||||
import type { PartialDeep } from 'type-fest';
|
import type { PartialDeep } from 'type-fest';
|
||||||
import type { Asset, Account } from './types';
|
import type { Account } from './types';
|
||||||
import type { Withdrawals_party_withdrawals } from './__generated__/Withdrawals';
|
import type { Withdrawals_party_withdrawals } from './__generated__/Withdrawals';
|
||||||
|
|
||||||
export const generateAsset = (override?: PartialDeep<Asset>) => {
|
export const generateAsset = (override?: PartialDeep<Asset>) => {
|
||||||
const defaultAsset: Asset = {
|
const defaultAsset: Asset = {
|
||||||
|
__typename: 'Asset',
|
||||||
id: 'asset-id',
|
id: 'asset-id',
|
||||||
symbol: 'asset-symbol',
|
symbol: 'asset-symbol',
|
||||||
name: 'asset-name',
|
name: 'asset-name',
|
||||||
|
@ -1,24 +1,5 @@
|
|||||||
import type { AccountType } from '@vegaprotocol/types';
|
import type { AccountType } from '@vegaprotocol/types';
|
||||||
|
|
||||||
interface ERC20AssetSource {
|
|
||||||
__typename: 'ERC20';
|
|
||||||
contractAddress: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BuiltinAssetSource {
|
|
||||||
__typename: 'BuiltinAsset';
|
|
||||||
}
|
|
||||||
|
|
||||||
type AssetSource = ERC20AssetSource | BuiltinAssetSource;
|
|
||||||
|
|
||||||
export interface Asset {
|
|
||||||
id: string;
|
|
||||||
symbol: string;
|
|
||||||
name: string;
|
|
||||||
decimals: number;
|
|
||||||
source: AssetSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Account {
|
export interface Account {
|
||||||
type: AccountType;
|
type: AccountType;
|
||||||
balance: string;
|
balance: string;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import type { Asset } from './types';
|
|
||||||
import { useBridgeContract, useEthereumReadContract } from '@vegaprotocol/web3';
|
import { useBridgeContract, useEthereumReadContract } from '@vegaprotocol/web3';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
|
import type { Asset } from '@vegaprotocol/react-helpers';
|
||||||
import { addDecimal } from '@vegaprotocol/react-helpers';
|
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
export const useGetWithdrawLimits = (asset?: Asset) => {
|
export const useGetWithdrawLimits = (asset?: Asset) => {
|
||||||
|
@ -3,8 +3,8 @@ import BigNumber from 'bignumber.js';
|
|||||||
import { useWeb3React } from '@web3-react/core';
|
import { useWeb3React } from '@web3-react/core';
|
||||||
import { WithdrawForm } from './withdraw-form';
|
import { WithdrawForm } from './withdraw-form';
|
||||||
import { generateAsset } from './test-helpers';
|
import { generateAsset } from './test-helpers';
|
||||||
import type { Asset } from './types';
|
|
||||||
import type { WithdrawFormProps } from './withdraw-form';
|
import type { WithdrawFormProps } from './withdraw-form';
|
||||||
|
import type { Asset } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
jest.mock('@web3-react/core');
|
jest.mock('@web3-react/core');
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import type { Asset } from '@vegaprotocol/react-helpers';
|
||||||
import {
|
import {
|
||||||
ethereumAddress,
|
ethereumAddress,
|
||||||
minSafe,
|
minSafe,
|
||||||
@ -5,6 +6,7 @@ import {
|
|||||||
removeDecimal,
|
removeDecimal,
|
||||||
required,
|
required,
|
||||||
maxSafe,
|
maxSafe,
|
||||||
|
isAssetTypeERC20,
|
||||||
} from '@vegaprotocol/react-helpers';
|
} from '@vegaprotocol/react-helpers';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -19,7 +21,6 @@ import BigNumber from 'bignumber.js';
|
|||||||
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
||||||
import { useForm, Controller } from 'react-hook-form';
|
import { useForm, Controller } from 'react-hook-form';
|
||||||
import type { WithdrawalFields } from './use-withdraw';
|
import type { WithdrawalFields } from './use-withdraw';
|
||||||
import type { Asset } from './types';
|
|
||||||
import { WithdrawLimits } from './withdraw-limits';
|
import { WithdrawLimits } from './withdraw-limits';
|
||||||
|
|
||||||
interface FormFields {
|
interface FormFields {
|
||||||
@ -99,9 +100,7 @@ export const WithdrawForm = ({
|
|||||||
id="asset"
|
id="asset"
|
||||||
>
|
>
|
||||||
<option value="">{t('Please select')}</option>
|
<option value="">{t('Please select')}</option>
|
||||||
{assets
|
{assets.filter(isAssetTypeERC20).map((a) => (
|
||||||
.filter((a) => a.source.__typename === 'ERC20')
|
|
||||||
.map((a) => (
|
|
||||||
<option key={a.id} value={a.id}>
|
<option key={a.id} value={a.id}>
|
||||||
{a.name}
|
{a.name}
|
||||||
</option>
|
</option>
|
||||||
|
@ -5,10 +5,11 @@ import type { WithdrawalFields } from './use-withdraw';
|
|||||||
import { useWithdraw } from './use-withdraw';
|
import { useWithdraw } from './use-withdraw';
|
||||||
import { WithdrawDialog } from './withdraw-dialog';
|
import { WithdrawDialog } from './withdraw-dialog';
|
||||||
import { isExpectedEthereumError, EthTxStatus } from '@vegaprotocol/web3';
|
import { isExpectedEthereumError, EthTxStatus } from '@vegaprotocol/web3';
|
||||||
|
import type { Asset } from '@vegaprotocol/react-helpers';
|
||||||
import { addDecimal } from '@vegaprotocol/react-helpers';
|
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||||
import { AccountType } from '@vegaprotocol/types';
|
import { AccountType } from '@vegaprotocol/types';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import type { Account, Asset } from './types';
|
import type { Account } from './types';
|
||||||
import { useGetWithdrawLimits } from './use-get-withdraw-limits';
|
import { useGetWithdrawLimits } from './use-get-withdraw-limits';
|
||||||
|
|
||||||
export interface WithdrawManagerProps {
|
export interface WithdrawManagerProps {
|
||||||
|
Loading…
Reference in New Issue
Block a user