vega-frontend-monorepo/libs/web3/src/lib/use-ethereum-transaction.tsx
Matthew Russell 11be7aaa8a
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
2022-07-28 13:23:59 +01:00

168 lines
4.5 KiB
TypeScript

import { formatLabel } from '@vegaprotocol/react-helpers';
import type { ethers } from 'ethers';
import { useCallback, useMemo, useState } from 'react';
import type { EthereumError } from './ethereum-error';
import { isExpectedEthereumError } from './ethereum-error';
import { isEthereumError } from './ethereum-error';
import { TransactionDialog } from './transaction-dialog';
export enum EthTxStatus {
Default = 'Default',
Requested = 'Requested',
Pending = 'Pending',
Complete = 'Complete',
Confirmed = 'Confirmed',
Error = 'Error',
}
export type TxError = Error | EthereumError;
export interface EthTxState {
status: EthTxStatus;
error: TxError | null;
txHash: string | null;
receipt: ethers.ContractReceipt | null;
confirmations: number;
dialogOpen: boolean;
}
export const initialState = {
status: EthTxStatus.Default,
error: null,
txHash: null,
receipt: null,
confirmations: 0,
dialogOpen: false,
};
type DefaultContract = {
contract: ethers.Contract;
};
export const useEthereumTransaction = <
TContract extends DefaultContract,
TMethod extends string
>(
contract: TContract | null,
methodName: keyof TContract,
requiredConfirmations = 1,
requiresConfirmation = false
) => {
const [transaction, _setTransaction] = useState<EthTxState>(initialState);
const setTransaction = useCallback((update: Partial<EthTxState>) => {
_setTransaction((curr) => ({
...curr,
...update,
}));
}, []);
const perform = useCallback(
// @ts-ignore TS errors here as TMethod doesn't satisfy the constraints on TContract
// its a tricky one to fix but does enforce the correct types when calling perform
async (...args: Parameters<TContract[TMethod]>) => {
setTransaction({
status: EthTxStatus.Requested,
error: null,
confirmations: 0,
dialogOpen: true,
});
try {
if (
!contract ||
typeof contract[methodName] !== 'function' ||
typeof contract.contract.callStatic[methodName as string] !==
'function'
) {
throw new Error('method not found on contract');
}
await contract.contract.callStatic[methodName as string](...args);
} catch (err) {
setTransaction({
status: EthTxStatus.Error,
error: err as EthereumError,
});
return;
}
try {
const method = contract[methodName];
if (!method || typeof method !== 'function') {
throw new Error('method not found on contract');
}
const tx = await method.call(contract, ...args);
let receipt: ethers.ContractReceipt | null = null;
setTransaction({ status: EthTxStatus.Pending, txHash: tx.hash });
for (let i = 1; i <= requiredConfirmations; i++) {
receipt = await tx.wait(i);
setTransaction({
confirmations: receipt
? receipt.confirmations
: requiredConfirmations,
});
}
if (!receipt) {
throw new Error('no receipt after confirmations are met');
}
if (requiresConfirmation) {
setTransaction({ status: EthTxStatus.Complete, receipt });
} else {
setTransaction({ status: EthTxStatus.Confirmed, receipt });
}
} catch (err) {
if (err instanceof Error || isEthereumError(err)) {
if (isExpectedEthereumError(err)) {
setTransaction({ dialogOpen: false });
} else {
setTransaction({ status: EthTxStatus.Error, error: err });
}
} else {
setTransaction({
status: EthTxStatus.Error,
error: new Error('Something went wrong'),
});
}
return;
}
},
[
contract,
methodName,
requiredConfirmations,
requiresConfirmation,
setTransaction,
]
);
const reset = useCallback(() => {
setTransaction(initialState);
}, [setTransaction]);
const setConfirmed = useCallback(() => {
setTransaction({ status: EthTxStatus.Confirmed });
}, [setTransaction]);
const Dialog = useMemo(() => {
return () => (
<TransactionDialog
name={formatLabel(methodName as string)}
onChange={() => {
reset();
}}
transaction={transaction}
requiredConfirmations={requiredConfirmations}
/>
);
}, [methodName, transaction, requiredConfirmations, reset]);
return { perform, transaction, reset, setConfirmed, Dialog };
};