fix(withdraws): clear eth network errors (#4044)

This commit is contained in:
Maciek 2023-06-09 08:58:16 +02:00 committed by GitHub
parent b6286cd0f3
commit e47b868c53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 162 additions and 29 deletions

View File

@ -13,3 +13,4 @@ export * from './lib/remove-0x';
export * from './lib/remove-pagination-wrapper';
export * from './lib/time';
export * from './lib/validate';
export * from './lib/resolve-network-name';

View File

@ -0,0 +1,6 @@
const NETWORK_NAME_MAP: Readonly<Record<string, string>> = {
'1': 'Ethereum Mainnet',
'11155111': 'Sepolia test network',
};
export const resolveNetworkName = (chainId?: string): string =>
NETWORK_NAME_MAP[chainId || ''] || `(chainID: ${chainId})`;

View File

@ -9,7 +9,10 @@ import { Intent } from '@vegaprotocol/ui-toolkit';
import { useCallback } from 'react';
import compact from 'lodash/compact';
import type { EthWithdrawalApprovalState } from './use-ethereum-withdraw-approvals-store';
import { useEthWithdrawApprovalsStore } from './use-ethereum-withdraw-approvals-store';
import {
useEthWithdrawApprovalsStore,
WithdrawalFailure,
} from './use-ethereum-withdraw-approvals-store';
import { ApprovalStatus } from './use-ethereum-withdraw-approvals-store';
import { VerificationStatus } from './withdrawal-approval-status';
@ -26,12 +29,23 @@ const EthWithdrawalApprovalToastContent = ({
}: {
tx: EthWithdrawalApprovalState;
}) => {
const isConnectionFailure =
tx.failureReason &&
[
WithdrawalFailure.WrongConnection,
WithdrawalFailure.NoConnection,
].includes(tx.failureReason);
let title = '';
if (tx.status === ApprovalStatus.Error) {
title = t('Error occurred');
}
if (tx.status === ApprovalStatus.Pending) {
title = t('Pending approval');
if (tx.failureReason && isConnectionFailure) {
title = tx.message || t('Withdraw failure');
} else {
title = t('Pending approval');
}
}
if (tx.status === ApprovalStatus.Delayed) {
title = t('Delayed');
@ -39,11 +53,12 @@ const EthWithdrawalApprovalToastContent = ({
if (tx.status === ApprovalStatus.Ready) {
title = t('Approved');
}
const num = formatNumber(
toBigNum(tx.withdrawal.amount, tx.withdrawal.asset.decimals),
tx.withdrawal.asset.decimals
);
const details = (
const details = isConnectionFailure ? null : (
<Panel>
<strong>
{t('Withdraw')} {num} {tx.withdrawal.asset.symbol}
@ -61,7 +76,8 @@ const EthWithdrawalApprovalToastContent = ({
};
const isFinal = (tx: EthWithdrawalApprovalState) =>
[ApprovalStatus.Ready, ApprovalStatus.Error].includes(tx.status);
[ApprovalStatus.Ready, ApprovalStatus.Error].includes(tx.status) &&
!tx.failureReason;
export const useEthereumWithdrawApprovalsToasts = () => {
const [setToast, remove] = useToasts((state) => [
@ -74,21 +90,34 @@ export const useEthereumWithdrawApprovalsToasts = () => {
]);
const fromWithdrawalApproval = useCallback(
(tx: EthWithdrawalApprovalState): Toast => ({
id: `withdrawal-${tx.id}`,
intent: intentMap[tx.status],
onClose: () => {
if ([ApprovalStatus.Error, ApprovalStatus.Ready].includes(tx.status)) {
deleteTx(tx.id);
} else {
dismissTx(tx.id);
}
remove(`withdrawal-${tx.id}`);
},
loader: tx.status === ApprovalStatus.Pending,
content: <EthWithdrawalApprovalToastContent tx={tx} />,
closeAfter: isFinal(tx) ? CLOSE_AFTER : undefined,
}),
(tx: EthWithdrawalApprovalState): Toast => {
const loader =
tx.status === ApprovalStatus.Pending &&
!(
tx.failureReason &&
[
WithdrawalFailure.WrongConnection,
WithdrawalFailure.NoConnection,
].includes(tx.failureReason)
);
return {
id: `withdrawal-${tx.id}`,
intent: intentMap[tx.status],
onClose: () => {
if (
[ApprovalStatus.Error, ApprovalStatus.Ready].includes(tx.status)
) {
deleteTx(tx.id);
} else {
dismissTx(tx.id);
}
remove(`withdrawal-${tx.id}`);
},
loader,
content: <EthWithdrawalApprovalToastContent tx={tx} />,
closeAfter: isFinal(tx) ? CLOSE_AFTER : undefined,
};
},
[deleteTx, dismissTx, remove]
);

View File

@ -5,7 +5,10 @@ import type { ReactNode } from 'react';
import { MockedProvider } from '@apollo/client/testing';
import waitForNextTick from 'flush-promises';
import * as Schema from '@vegaprotocol/types';
import { ApprovalStatus } from './use-ethereum-withdraw-approvals-store';
import {
ApprovalStatus,
WithdrawalFailure,
} from './use-ethereum-withdraw-approvals-store';
import BigNumber from 'bignumber.js';
import type {
EthWithdrawApprovalStore,
@ -21,7 +24,7 @@ import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
const mockWeb3Provider = jest.fn();
let mockChainId = 111111;
let mockChainId: number | undefined = 111111;
jest.mock('@web3-react/core', () => ({
useWeb3React: () => ({
provider: mockWeb3Provider(),
@ -314,9 +317,29 @@ describe('useEthWithdrawApprovalsManager', () => {
update,
});
render();
expect(update.mock.calls[0][1].status).toEqual(ApprovalStatus.Error);
expect(update.mock.calls[0][1].status).toEqual(ApprovalStatus.Pending);
expect(update.mock.calls[0][1].message).toEqual('Change network');
expect(update.mock.calls[0][1].failureReason).toEqual(
WithdrawalFailure.WrongConnection
);
mockChainId = 111111;
});
it('detect no chainId', () => {
mockChainId = undefined;
const transaction = createWithdrawTransaction();
mockEthTransactionStoreState.mockReturnValue({ create });
mockEthWithdrawApprovalsStoreState.mockReturnValue({
transactions: [transaction],
update,
});
render();
expect(update.mock.calls[0][1].status).toEqual(ApprovalStatus.Pending);
expect(update.mock.calls[0][1].message).toEqual(
'You are on the wrong network'
'Connect wallet to withdraw'
);
expect(update.mock.calls[0][1].failureReason).toEqual(
WithdrawalFailure.NoConnection
);
mockChainId = 111111;
});

View File

@ -1,6 +1,6 @@
import { useApolloClient } from '@apollo/client';
import BigNumber from 'bignumber.js';
import { useRef, useEffect } from 'react';
import { useEffect, useRef } from 'react';
import { addDecimal } from '@vegaprotocol/utils';
import { useGetWithdrawThreshold } from './use-get-withdraw-threshold';
import { useGetWithdrawDelay } from './use-get-withdraw-delay';
@ -21,8 +21,9 @@ import { WithdrawalApprovalDocument } from '@vegaprotocol/wallet';
import { useEthTransactionStore } from './use-ethereum-transaction-store';
import {
useEthWithdrawApprovalsStore,
ApprovalStatus,
useEthWithdrawApprovalsStore,
WithdrawalFailure,
} from './use-ethereum-withdraw-approvals-store';
export const useEthWithdrawApprovalsManager = () => {
@ -55,16 +56,27 @@ export const useEthWithdrawApprovalsManager = () => {
message: t(
`Invalid asset source: ${withdrawal.asset.source.__typename}`
),
failureReason: WithdrawalFailure.InvalidAsset,
});
return;
}
if (!chainId) {
update(transaction.id, {
status: ApprovalStatus.Pending,
message: t(`Connect wallet to withdraw`),
failureReason: WithdrawalFailure.NoConnection,
});
return;
}
if (chainId?.toString() !== config?.chain_id) {
update(transaction.id, {
status: ApprovalStatus.Error,
message: t(`You are on the wrong network`),
status: ApprovalStatus.Pending,
message: t(`Change network`),
failureReason: WithdrawalFailure.WrongConnection,
});
return;
}
update(transaction.id, {
status: ApprovalStatus.Pending,
message: t('Verifying withdrawal approval'),

View File

@ -14,6 +14,13 @@ export enum ApprovalStatus {
Error = 'Error',
Ready = 'Ready',
}
export enum WithdrawalFailure {
InvalidAsset,
NoConnection,
WrongConnection,
}
export interface EthWithdrawalApprovalState {
id: number;
createdAt: Date;
@ -24,6 +31,7 @@ export interface EthWithdrawalApprovalState {
dialogOpen?: boolean;
withdrawal: WithdrawalBusEventFieldsFragment;
approval?: WithdrawalApprovalQuery['erc20WithdrawalApproval'];
failureReason?: WithdrawalFailure;
}
export interface EthWithdrawApprovalStore {
transactions: (EthWithdrawalApprovalState | undefined)[];
@ -42,6 +50,7 @@ export interface EthWithdrawApprovalStore {
| 'threshold'
| 'completeTimestamp'
| 'dialogOpen'
| 'failureReason'
>
>
) => void;
@ -86,6 +95,7 @@ export const useEthWithdrawApprovalsStore = create<EthWithdrawApprovalStore>()(
| 'threshold'
| 'completeTimestamp'
| 'dialogOpen'
| 'failureReason'
>
>
) =>

View File

@ -1,17 +1,69 @@
import { t } from '@vegaprotocol/i18n';
import { getDateTimeFormat } from '@vegaprotocol/utils';
import {
getDateTimeFormat,
resolveNetworkName,
truncateByChars,
} from '@vegaprotocol/utils';
import type { EthWithdrawalApprovalState } from './use-ethereum-withdraw-approvals-store';
import { ApprovalStatus } from './use-ethereum-withdraw-approvals-store';
import {
ApprovalStatus,
useEthWithdrawApprovalsStore,
WithdrawalFailure,
} from './use-ethereum-withdraw-approvals-store';
import { useEthereumConfig } from './use-ethereum-config';
import { Button, useToasts } from '@vegaprotocol/ui-toolkit';
import { useWeb3ConnectStore } from './web3-connect-store';
export const VerificationStatus = ({
state,
}: {
state: EthWithdrawalApprovalState;
}) => {
const { config } = useEthereumConfig();
const openDialog = useWeb3ConnectStore((state) => state.open);
const remove = useToasts((state) => state.remove);
const deleteTx = useEthWithdrawApprovalsStore((state) => state.delete);
if (state.status === ApprovalStatus.Error) {
return <p>{state.message || t('Something went wrong')}</p>;
}
if (
state.failureReason &&
[
WithdrawalFailure.WrongConnection,
WithdrawalFailure.NoConnection,
].includes(state.failureReason)
) {
return state.failureReason === WithdrawalFailure.NoConnection ? (
<>
<p>
{t('To complete this withdrawal, connect the Ethereum wallet %s', [
truncateByChars(state.withdrawal.details?.receiverAddress || ' '),
])}
</p>
<Button
onClick={() => {
openDialog();
deleteTx(state.id);
remove(`withdrawal-${state.id}`);
}}
>
{t('Connect wallet')}
</Button>
</>
) : (
<>
<p>{t('Your Ethereum wallet is connected to the wrong network.')}</p>
<p className="mt-2">
{t('Go to your Ethereum wallet and connect to the network %s', [
resolveNetworkName(config?.chain_id),
])}
</p>
</>
);
}
if (state.status === ApprovalStatus.Pending) {
return <p>{t('Verifying...')}</p>;
}