refactor: handle Ethereum dialog state from hook (#851)
* refactor: pass dialog down from hook * feat: convert dialog to be returned as component * chore: fix linting
This commit is contained in:
parent
62b6cd7580
commit
b7f08def47
@ -16,7 +16,6 @@ import { addDecimal } from '../../lib/decimals';
|
||||
import { truncateMiddle } from '../../lib/truncate-middle';
|
||||
import type { Withdrawals_party_withdrawals } from '@vegaprotocol/withdraws';
|
||||
import { useCompleteWithdraw, useWithdrawals } from '@vegaprotocol/withdraws';
|
||||
import { TransactionDialog } from '@vegaprotocol/web3';
|
||||
import { WithdrawalStatus } from '../../__generated__/globalTypes';
|
||||
import { Flags } from '../../config';
|
||||
|
||||
@ -35,9 +34,7 @@ const Withdrawals = () => {
|
||||
|
||||
const WithdrawPendingContainer = () => {
|
||||
const { t } = useTranslation();
|
||||
const { transaction, submit } = useCompleteWithdraw(
|
||||
Flags.USE_NEW_BRIDGE_CONTRACT
|
||||
);
|
||||
const { submit, Dialog } = useCompleteWithdraw(Flags.USE_NEW_BRIDGE_CONTRACT);
|
||||
const { data, loading, error } = useWithdrawals();
|
||||
|
||||
const withdrawals = React.useMemo(() => {
|
||||
@ -83,7 +80,7 @@ const WithdrawPendingContainer = () => {
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<TransactionDialog name="withdraw" {...transaction} />
|
||||
<Dialog />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -7,11 +7,7 @@ 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 {
|
||||
EthTxStatus,
|
||||
TransactionDialog,
|
||||
useEthereumConfig,
|
||||
} from '@vegaprotocol/web3';
|
||||
import { EthTxStatus, useEthereumConfig } from '@vegaprotocol/web3';
|
||||
import { useTokenContract } from '@vegaprotocol/web3';
|
||||
import { removeDecimal } from '@vegaprotocol/react-helpers';
|
||||
|
||||
@ -81,7 +77,7 @@ export const DepositManager = ({
|
||||
const approve = useSubmitApproval(tokenContract);
|
||||
|
||||
// Set up deposit transaction
|
||||
const { confirmationEvent, ...deposit } = useSubmitDeposit();
|
||||
const deposit = useSubmitDeposit();
|
||||
|
||||
// Set up faucet transaction
|
||||
const faucet = useSubmitFaucet(tokenContract);
|
||||
@ -89,16 +85,16 @@ export const DepositManager = ({
|
||||
// Update balance after confirmation event has been received
|
||||
useEffect(() => {
|
||||
if (
|
||||
faucet.transaction.status === EthTxStatus.Complete ||
|
||||
confirmationEvent !== null
|
||||
faucet.transaction.status === EthTxStatus.Confirmed ||
|
||||
deposit.transaction.status === EthTxStatus.Confirmed
|
||||
) {
|
||||
refetchBalance();
|
||||
}
|
||||
}, [confirmationEvent, refetchBalance, faucet.transaction.status]);
|
||||
}, [deposit.transaction.status, faucet.transaction.status, refetchBalance]);
|
||||
|
||||
// After an approval transaction refetch allowance
|
||||
useEffect(() => {
|
||||
if (approve.transaction.status === EthTxStatus.Complete) {
|
||||
if (approve.transaction.status === EthTxStatus.Confirmed) {
|
||||
refetchAllowance();
|
||||
}
|
||||
}, [approve.transaction.status, refetchAllowance]);
|
||||
@ -123,15 +119,9 @@ export const DepositManager = ({
|
||||
allowance={allowance}
|
||||
isFaucetable={isFaucetable}
|
||||
/>
|
||||
<TransactionDialog {...approve.transaction} name="approve" />
|
||||
<TransactionDialog {...faucet.transaction} name="faucet" />
|
||||
<TransactionDialog
|
||||
{...deposit}
|
||||
name="deposit"
|
||||
confirmed={Boolean(confirmationEvent)}
|
||||
// Must wait for additional confirmations for Vega to pick up the Ethereum transaction
|
||||
requiredConfirmations={config?.confirmations}
|
||||
/>
|
||||
<approve.Dialog />
|
||||
<faucet.Dialog />
|
||||
<deposit.Dialog />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -2,7 +2,6 @@ import { gql, useSubscription } from '@apollo/client';
|
||||
import type {
|
||||
DepositEvent,
|
||||
DepositEventVariables,
|
||||
DepositEvent_busEvents_event_Deposit,
|
||||
} from './__generated__/DepositEvent';
|
||||
import { DepositStatus } from '@vegaprotocol/types';
|
||||
import { useState } from 'react';
|
||||
@ -35,15 +34,15 @@ const DEPOSIT_EVENT_SUB = gql`
|
||||
export const useSubmitDeposit = () => {
|
||||
const { config } = useEthereumConfig();
|
||||
const contract = useBridgeContract(true);
|
||||
const [confirmationEvent, setConfirmationEvent] =
|
||||
useState<DepositEvent_busEvents_event_Deposit | null>(null);
|
||||
|
||||
// Store public key from contract arguments for use in the subscription,
|
||||
// NOTE: it may be different from the users connected key
|
||||
const [partyId, setPartyId] = useState<string | null>(null);
|
||||
const { transaction, perform } = useEthereumTransaction<
|
||||
|
||||
const transaction = useEthereumTransaction<
|
||||
CollateralBridgeNew | CollateralBridge,
|
||||
'deposit_asset'
|
||||
>(contract, 'deposit_asset', config?.confirmations);
|
||||
>(contract, 'deposit_asset', config?.confirmations, true);
|
||||
|
||||
useSubscription<DepositEvent, DepositEventVariables>(DEPOSIT_EVENT_SUB, {
|
||||
variables: { partyId: partyId ? remove0x(partyId) : '' },
|
||||
@ -59,7 +58,7 @@ export const useSubmitDeposit = () => {
|
||||
}
|
||||
|
||||
if (
|
||||
e.event.txHash === transaction.txHash &&
|
||||
e.event.txHash === transaction.transaction.txHash &&
|
||||
// Note there is a bug in data node where the subscription is not emitted when the status
|
||||
// changes from 'Open' to 'Finalized' as a result the deposit UI will hang in a pending state right now
|
||||
// https://github.com/vegaprotocol/data-node/issues/460
|
||||
@ -72,19 +71,17 @@ export const useSubmitDeposit = () => {
|
||||
});
|
||||
|
||||
if (matchingDeposit && matchingDeposit.event.__typename === 'Deposit') {
|
||||
setConfirmationEvent(matchingDeposit.event);
|
||||
transaction.setConfirmed();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
...transaction,
|
||||
perform: (...args: Parameters<typeof perform>) => {
|
||||
setConfirmationEvent(null);
|
||||
perform: (...args: Parameters<typeof transaction.perform>) => {
|
||||
setPartyId(args[2]);
|
||||
const publicKey = prepend0x(args[2]);
|
||||
perform(args[0], args[1], publicKey);
|
||||
transaction.perform(args[0], args[1], publicKey);
|
||||
},
|
||||
confirmationEvent,
|
||||
};
|
||||
};
|
@ -79,18 +79,14 @@ export const TxRow = ({
|
||||
|
||||
interface ConfirmationEventRowProps {
|
||||
status: EthTxStatus;
|
||||
confirmed: boolean;
|
||||
}
|
||||
|
||||
export const ConfirmationEventRow = ({
|
||||
status,
|
||||
confirmed,
|
||||
}: ConfirmationEventRowProps) => {
|
||||
if (status !== EthTxStatus.Complete) {
|
||||
export const ConfirmationEventRow = ({ status }: ConfirmationEventRowProps) => {
|
||||
if (status !== EthTxStatus.Complete && status !== EthTxStatus.Confirmed) {
|
||||
return <p>{t('Vega confirmation')}</p>;
|
||||
}
|
||||
|
||||
if (!confirmed) {
|
||||
if (status === EthTxStatus.Complete) {
|
||||
return (
|
||||
<p className="text-black dark:text-white">
|
||||
{t('Vega is confirming your transaction...')}
|
||||
|
@ -15,9 +15,7 @@ export const DialogWrapper = ({
|
||||
<div className="flex gap-12 max-w-full text-ui">
|
||||
<div className="pt-8 fill-current">{icon}</div>
|
||||
<div className="flex-1">
|
||||
<h1 className="text-h4 text-black dark:text-white capitalize mb-12">
|
||||
{title}
|
||||
</h1>
|
||||
<h1 className="text-h4 text-black dark:text-white mb-12">{title}</h1>
|
||||
<div className="text-black-40 dark:text-white-40">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import merge from 'lodash/merge';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import { EthereumError } from '../ethereum-error';
|
||||
import { EthTxStatus } from '../use-ethereum-transaction';
|
||||
import type { TransactionDialogProps } from './transaction-dialog';
|
||||
@ -15,15 +17,21 @@ let props: TransactionDialogProps;
|
||||
beforeEach(() => {
|
||||
props = {
|
||||
name: 'test',
|
||||
status: EthTxStatus.Default,
|
||||
txHash: null,
|
||||
error: null,
|
||||
confirmations: 1,
|
||||
onChange: jest.fn(),
|
||||
transaction: {
|
||||
status: EthTxStatus.Default,
|
||||
txHash: null,
|
||||
error: null,
|
||||
confirmations: 1,
|
||||
receipt: null,
|
||||
dialogOpen: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const generateJsx = (moreProps?: Partial<TransactionDialogProps>) => {
|
||||
return <TransactionDialog {...props} {...moreProps} />;
|
||||
const generateJsx = (moreProps?: PartialDeep<TransactionDialogProps>) => {
|
||||
const mergedProps = merge(props, moreProps);
|
||||
return <TransactionDialog {...mergedProps} />;
|
||||
};
|
||||
|
||||
it('Opens when tx starts and closes if the user rejects the tx', () => {
|
||||
@ -32,15 +40,17 @@ it('Opens when tx starts and closes if the user rejects the tx', () => {
|
||||
// Dialog closed by default
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
|
||||
rerender(generateJsx({ status: EthTxStatus.Pending }));
|
||||
rerender(generateJsx({ transaction: { status: EthTxStatus.Pending } }));
|
||||
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||
|
||||
// User rejecting the tx closes the dialog
|
||||
rerender(
|
||||
generateJsx({
|
||||
status: EthTxStatus.Error,
|
||||
error: new EthereumError('User rejected', 4001, 'reason'),
|
||||
transaction: {
|
||||
status: EthTxStatus.Error,
|
||||
error: new EthereumError('User rejected', 4001, 'reason'),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
@ -49,26 +59,34 @@ it('Opens when tx starts and closes if the user rejects the tx', () => {
|
||||
|
||||
it('Doesn\t repoen if user dismissed the dialog', () => {
|
||||
const { container, rerender } = render(
|
||||
generateJsx({ status: EthTxStatus.Pending })
|
||||
generateJsx({ transaction: { status: EthTxStatus.Pending } })
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByTestId('dialog-close'));
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
|
||||
rerender(generateJsx({ status: EthTxStatus.Complete }));
|
||||
rerender(generateJsx({ transaction: { status: EthTxStatus.Complete } }));
|
||||
|
||||
// Should still be closed even though tx updated
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Dialog states', () => {
|
||||
const { rerender } = render(generateJsx({ status: EthTxStatus.Requested }));
|
||||
// Requested
|
||||
const { rerender } = render(
|
||||
generateJsx({ transaction: { status: EthTxStatus.Requested } })
|
||||
);
|
||||
expect(screen.getByText('Confirm transaction')).toBeInTheDocument();
|
||||
expect(screen.getByText('Confirm transaction in wallet')).toBeInTheDocument();
|
||||
expect(screen.getByText('Await Ethereum transaction')).toBeInTheDocument();
|
||||
|
||||
rerender(generateJsx({ status: EthTxStatus.Pending, confirmations: 0 }));
|
||||
// Pending
|
||||
rerender(
|
||||
generateJsx({
|
||||
transaction: { status: EthTxStatus.Pending, confirmations: 0 },
|
||||
})
|
||||
);
|
||||
expect(screen.getByText(`${props.name} pending`)).toBeInTheDocument();
|
||||
expect(screen.getByText('Confirmed in wallet')).toBeInTheDocument();
|
||||
expect(
|
||||
@ -76,38 +94,40 @@ it('Dialog states', () => {
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByTestId('link')).toBeInTheDocument();
|
||||
|
||||
rerender(generateJsx({ status: EthTxStatus.Complete, confirmations: 1 }));
|
||||
expect(screen.getByText(`${props.name} complete`)).toBeInTheDocument();
|
||||
// Ethereum complete
|
||||
rerender(
|
||||
generateJsx({
|
||||
transaction: { status: EthTxStatus.Complete, confirmations: 1 },
|
||||
})
|
||||
);
|
||||
expect(screen.getByText(`${props.name} pending`)).toBeInTheDocument();
|
||||
expect(screen.getByText('Confirmed in wallet')).toBeInTheDocument();
|
||||
expect(screen.getByText('Ethereum transaction complete')).toBeInTheDocument();
|
||||
|
||||
// Ethereum confirmed (via api)
|
||||
rerender(
|
||||
generateJsx({
|
||||
transaction: {
|
||||
status: EthTxStatus.Confirmed,
|
||||
error: null,
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByText(`${props.name} complete`)).toBeInTheDocument();
|
||||
expect(screen.getByText('Confirmed in wallet')).toBeInTheDocument();
|
||||
expect(screen.getByText('Transaction confirmed')).toBeInTheDocument();
|
||||
|
||||
// Error
|
||||
const errorMsg = 'Something went wrong';
|
||||
const reason = 'Transaction failed';
|
||||
rerender(
|
||||
generateJsx({
|
||||
status: EthTxStatus.Error,
|
||||
error: new EthereumError(errorMsg, 1, reason),
|
||||
transaction: {
|
||||
status: EthTxStatus.Error,
|
||||
error: new EthereumError(errorMsg, 1, reason),
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByText(`${props.name} failed`)).toBeInTheDocument();
|
||||
expect(screen.getByText(`Error: ${reason}`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Success state waits for confirmation event if provided', () => {
|
||||
const { rerender } = render(
|
||||
generateJsx({ status: EthTxStatus.Complete, confirmed: false })
|
||||
);
|
||||
expect(screen.getByText(`${props.name} pending`)).toBeInTheDocument();
|
||||
expect(screen.getByText('Confirmed in wallet')).toBeInTheDocument();
|
||||
expect(screen.getByText('Ethereum transaction complete')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('Vega is confirming your transaction...')
|
||||
).toBeInTheDocument();
|
||||
|
||||
// @ts-ignore enforce truthy on confirmation event
|
||||
rerender(generateJsx({ confirmed: true, status: EthTxStatus.Complete }));
|
||||
expect(
|
||||
screen.queryByText('Vega is confirming your transaction...')
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByText('Transaction confirmed')).toBeInTheDocument();
|
||||
});
|
||||
|
@ -1,35 +1,27 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { Dialog, Icon, Intent, Loader } from '@vegaprotocol/ui-toolkit';
|
||||
import { isEthereumError, isExpectedEthereumError } from '../ethereum-error';
|
||||
import type { TxError } from '../use-ethereum-transaction';
|
||||
import { isEthereumError } from '../ethereum-error';
|
||||
import type { EthTxState } from '../use-ethereum-transaction';
|
||||
import { EthTxStatus } from '../use-ethereum-transaction';
|
||||
import { ConfirmRow, TxRow, ConfirmationEventRow } from './dialog-rows';
|
||||
import { DialogWrapper } from './dialog-wrapper';
|
||||
|
||||
export interface TransactionDialogProps {
|
||||
name: string;
|
||||
status: EthTxStatus;
|
||||
error: TxError | null;
|
||||
confirmations: number;
|
||||
txHash: string | null;
|
||||
requiredConfirmations?: number;
|
||||
onChange: (isOpen: boolean) => void;
|
||||
transaction: EthTxState;
|
||||
// Undefined means this dialog isn't expecting an additional event for a complete state, a boolean
|
||||
// value means it is but hasn't been received yet
|
||||
confirmed?: boolean;
|
||||
requiredConfirmations?: number;
|
||||
}
|
||||
|
||||
export const TransactionDialog = ({
|
||||
onChange,
|
||||
name,
|
||||
status,
|
||||
error,
|
||||
confirmations,
|
||||
txHash,
|
||||
transaction,
|
||||
requiredConfirmations = 1,
|
||||
confirmed,
|
||||
}: TransactionDialogProps) => {
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const dialogDismissed = useRef(false);
|
||||
const { status, error, confirmations, txHash } = transaction;
|
||||
|
||||
const renderContent = () => {
|
||||
if (status === EthTxStatus.Error) {
|
||||
@ -67,15 +59,18 @@ export const TransactionDialog = ({
|
||||
requiredConfirmations={requiredConfirmations}
|
||||
highlightComplete={false}
|
||||
/>
|
||||
{confirmed !== undefined && (
|
||||
<ConfirmationEventRow status={status} confirmed={confirmed} />
|
||||
)}
|
||||
<ConfirmationEventRow status={status} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getWrapperProps = () => {
|
||||
const propsMap = {
|
||||
[EthTxStatus.Default]: {
|
||||
title: '',
|
||||
icon: null,
|
||||
intent: undefined,
|
||||
},
|
||||
[EthTxStatus.Error]: {
|
||||
title: t(`${name} failed`),
|
||||
icon: <Icon name="warning-sign" size={20} />,
|
||||
@ -92,56 +87,24 @@ export const TransactionDialog = ({
|
||||
intent: Intent.None,
|
||||
},
|
||||
[EthTxStatus.Complete]: {
|
||||
title: t(`${name} pending`),
|
||||
icon: <Loader size="small" />,
|
||||
intent: Intent.None,
|
||||
},
|
||||
[EthTxStatus.Confirmed]: {
|
||||
title: t(`${name} complete`),
|
||||
icon: <Icon name="tick" />,
|
||||
intent: Intent.Success,
|
||||
},
|
||||
};
|
||||
|
||||
// Dialog not showing
|
||||
if (status === EthTxStatus.Default) {
|
||||
return { intent: undefined, title: '', icon: null };
|
||||
}
|
||||
|
||||
// Confirmation event bool is required so
|
||||
if (confirmed !== undefined) {
|
||||
// Vega has confirmed Tx
|
||||
if (confirmed === true) {
|
||||
return propsMap[EthTxStatus.Complete];
|
||||
}
|
||||
// Tx is complete but still awaiting for Vega to confirm
|
||||
else if (status === EthTxStatus.Complete) {
|
||||
return propsMap[EthTxStatus.Pending];
|
||||
}
|
||||
}
|
||||
|
||||
return propsMap[status];
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Close dialog if error is due to user rejecting the tx
|
||||
if (status === EthTxStatus.Error && isExpectedEthereumError(error)) {
|
||||
setDialogOpen(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (status !== EthTxStatus.Default && !dialogDismissed.current) {
|
||||
setDialogOpen(true);
|
||||
return;
|
||||
}
|
||||
}, [status, error]);
|
||||
|
||||
const { intent, ...wrapperProps } = getWrapperProps();
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={dialogOpen}
|
||||
onChange={(isOpen) => {
|
||||
setDialogOpen(isOpen);
|
||||
dialogDismissed.current = true;
|
||||
}}
|
||||
intent={intent}
|
||||
>
|
||||
<Dialog open={transaction.dialogOpen} onChange={onChange} intent={intent}>
|
||||
<DialogWrapper {...wrapperProps}>{renderContent()}</DialogWrapper>
|
||||
</Dialog>
|
||||
);
|
||||
|
@ -67,14 +67,19 @@ it('Ethereum transaction flow', async () => {
|
||||
error: null,
|
||||
confirmations: 0,
|
||||
receipt: null,
|
||||
dialogOpen: false,
|
||||
},
|
||||
Dialog: expect.any(Function),
|
||||
setConfirmed: expect.any(Function),
|
||||
perform: expect.any(Function),
|
||||
reset: expect.any(Function),
|
||||
});
|
||||
|
||||
result.current.perform('asset-source', '100', 'vega-key');
|
||||
act(() => {
|
||||
result.current.perform('asset-source', '100', 'vega-key');
|
||||
});
|
||||
|
||||
expect(result.current.transaction.status).toEqual(EthTxStatus.Default); // still default as we await result of static call
|
||||
expect(result.current.transaction.status).toEqual(EthTxStatus.Requested); // still default as we await result of static call
|
||||
expect(result.current.transaction.confirmations).toBe(0);
|
||||
|
||||
await act(async () => {
|
||||
@ -105,7 +110,7 @@ it('Ethereum transaction flow', async () => {
|
||||
expect(result.current.transaction.confirmations).toBe(3);
|
||||
|
||||
// Now complete as required confirmations have been surpassed
|
||||
expect(result.current.transaction.status).toEqual(EthTxStatus.Complete);
|
||||
expect(result.current.transaction.status).toEqual(EthTxStatus.Confirmed);
|
||||
expect(result.current.transaction.receipt).toEqual({
|
||||
from: 'foo',
|
||||
confirmations: result.current.transaction.confirmations,
|
||||
|
@ -1,13 +1,17 @@
|
||||
import { formatLabel } from '@vegaprotocol/react-helpers';
|
||||
import type { ethers } from 'ethers';
|
||||
import { useCallback, useState } from 'react';
|
||||
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',
|
||||
}
|
||||
|
||||
@ -19,6 +23,7 @@ export interface EthTxState {
|
||||
txHash: string | null;
|
||||
receipt: ethers.ContractReceipt | null;
|
||||
confirmations: number;
|
||||
dialogOpen: boolean;
|
||||
}
|
||||
|
||||
export const initialState = {
|
||||
@ -27,6 +32,7 @@ export const initialState = {
|
||||
txHash: null,
|
||||
receipt: null,
|
||||
confirmations: 0,
|
||||
dialogOpen: false,
|
||||
};
|
||||
|
||||
type DefaultContract = {
|
||||
@ -39,7 +45,8 @@ export const useEthereumTransaction = <
|
||||
>(
|
||||
contract: TContract | null,
|
||||
methodName: keyof TContract,
|
||||
requiredConfirmations = 1
|
||||
requiredConfirmations = 1,
|
||||
requiresConfirmation = false
|
||||
) => {
|
||||
const [transaction, _setTransaction] = useState<EthTxState>(initialState);
|
||||
|
||||
@ -54,6 +61,13 @@ export const useEthereumTransaction = <
|
||||
// @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 ||
|
||||
@ -72,12 +86,6 @@ export const useEthereumTransaction = <
|
||||
return;
|
||||
}
|
||||
|
||||
setTransaction({
|
||||
status: EthTxStatus.Requested,
|
||||
error: null,
|
||||
confirmations: 0,
|
||||
});
|
||||
|
||||
try {
|
||||
const method = contract[methodName];
|
||||
|
||||
@ -104,10 +112,18 @@ export const useEthereumTransaction = <
|
||||
throw new Error('no receipt after confirmations are met');
|
||||
}
|
||||
|
||||
setTransaction({ status: EthTxStatus.Complete, receipt });
|
||||
if (requiresConfirmation) {
|
||||
setTransaction({ status: EthTxStatus.Complete, receipt });
|
||||
} else {
|
||||
setTransaction({ status: EthTxStatus.Confirmed, receipt });
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error || isEthereumError(err)) {
|
||||
setTransaction({ status: EthTxStatus.Error, error: err });
|
||||
if (isExpectedEthereumError(err)) {
|
||||
setTransaction({ dialogOpen: false });
|
||||
} else {
|
||||
setTransaction({ status: EthTxStatus.Error, error: err });
|
||||
}
|
||||
} else {
|
||||
setTransaction({
|
||||
status: EthTxStatus.Error,
|
||||
@ -116,12 +132,35 @@ export const useEthereumTransaction = <
|
||||
}
|
||||
}
|
||||
},
|
||||
[contract, methodName, requiredConfirmations, setTransaction]
|
||||
[
|
||||
contract,
|
||||
methodName,
|
||||
requiredConfirmations,
|
||||
requiresConfirmation,
|
||||
setTransaction,
|
||||
]
|
||||
);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setTransaction(initialState);
|
||||
}, [setTransaction]);
|
||||
|
||||
return { perform, transaction, reset };
|
||||
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 };
|
||||
};
|
@ -25,7 +25,7 @@ export const useCompleteWithdraw = (isNewContract: boolean) => {
|
||||
const { query, cache } = useApolloClient();
|
||||
const contract = useBridgeContract(isNewContract);
|
||||
const [id, setId] = useState('');
|
||||
const { transaction, perform } = useEthereumTransaction<
|
||||
const { transaction, perform, Dialog } = useEthereumTransaction<
|
||||
CollateralBridgeNew | CollateralBridge,
|
||||
'withdraw_asset'
|
||||
>(contract, 'withdraw_asset');
|
||||
@ -91,5 +91,5 @@ export const useCompleteWithdraw = (isNewContract: boolean) => {
|
||||
}
|
||||
}, [cache, transaction.txHash, id]);
|
||||
|
||||
return { transaction, submit, withdrawalId: id };
|
||||
return { transaction, Dialog, submit, withdrawalId: id };
|
||||
};
|
||||
|
@ -120,6 +120,25 @@ const getProps = (
|
||||
},
|
||||
};
|
||||
|
||||
const completeProps = {
|
||||
title: t('Withdrawal complete'),
|
||||
icon: <Icon name="tick" />,
|
||||
intent: Intent.Success,
|
||||
children: (
|
||||
<Step>
|
||||
<span>{t('Ethereum transaction complete')}</span>
|
||||
<Link
|
||||
href={`${ethUrl}/tx/${ethTx.txHash}`}
|
||||
title={t('View transaction on Etherscan')}
|
||||
className="text-vega-pink dark:text-vega-yellow"
|
||||
target="_blank"
|
||||
>
|
||||
{t('View on Etherscan')}
|
||||
</Link>
|
||||
</Step>
|
||||
),
|
||||
};
|
||||
|
||||
const ethTxPropsMap: Record<EthTxStatus, DialogProps> = {
|
||||
[EthTxStatus.Default]: {
|
||||
title: '',
|
||||
@ -169,24 +188,8 @@ const getProps = (
|
||||
</Step>
|
||||
),
|
||||
},
|
||||
[EthTxStatus.Complete]: {
|
||||
title: t('Withdrawal complete'),
|
||||
icon: <Icon name="tick" />,
|
||||
intent: Intent.Success,
|
||||
children: (
|
||||
<Step>
|
||||
<span>{t('Ethereum transaction complete')}</span>
|
||||
<Link
|
||||
href={`${ethUrl}/tx/${ethTx.txHash}`}
|
||||
title={t('View transaction on Etherscan')}
|
||||
className="text-vega-pink dark:text-vega-yellow"
|
||||
target="_blank"
|
||||
>
|
||||
{t('View on Etherscan')}
|
||||
</Link>
|
||||
</Step>
|
||||
),
|
||||
},
|
||||
[EthTxStatus.Complete]: completeProps,
|
||||
[EthTxStatus.Confirmed]: completeProps,
|
||||
};
|
||||
|
||||
return approval ? ethTxPropsMap[ethTx.status] : vegaTxPropsMap[vegaTx.status];
|
||||
|
@ -12,7 +12,6 @@ import {
|
||||
import { WithdrawalStatus } from '@vegaprotocol/types';
|
||||
import { Link, AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { TransactionDialog } from '@vegaprotocol/web3';
|
||||
import { useCompleteWithdraw } from './use-complete-withdraw';
|
||||
import type { Withdrawals_party_withdrawals } from './__generated__/Withdrawals';
|
||||
|
||||
@ -22,7 +21,7 @@ export interface WithdrawalsTableProps {
|
||||
|
||||
export const WithdrawalsTable = ({ withdrawals }: WithdrawalsTableProps) => {
|
||||
const { ETHERSCAN_URL } = useEnvironment();
|
||||
const { transaction, submit } = useCompleteWithdraw(true);
|
||||
const { submit, Dialog } = useCompleteWithdraw(true);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -65,7 +64,7 @@ export const WithdrawalsTable = ({ withdrawals }: WithdrawalsTableProps) => {
|
||||
cellRendererParams={{ complete: submit, ethUrl: ETHERSCAN_URL }}
|
||||
/>
|
||||
</AgGrid>
|
||||
<TransactionDialog name="withdraw" {...transaction} />
|
||||
<Dialog />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user