diff --git a/apps/governance/src/app.tsx b/apps/governance/src/app.tsx
index c75d5e602..fa206fe4c 100644
--- a/apps/governance/src/app.tsx
+++ b/apps/governance/src/app.tsx
@@ -16,6 +16,7 @@ import { AppStateProvider } from './contexts/app-state/app-state-provider';
import { ContractsProvider } from './contexts/contracts/contracts-provider';
import { AppRouter } from './routes';
import type { EthereumConfig } from '@vegaprotocol/web3';
+import { WithdrawalApprovalDialogContainer } from '@vegaprotocol/web3';
import {
createConnectors,
useEthTransactionManager,
@@ -43,7 +44,7 @@ import {
} from '@vegaprotocol/environment';
import { ENV } from './config';
import type { InMemoryCacheConfig } from '@apollo/client';
-import { WithdrawalDialog } from '@vegaprotocol/withdraws';
+import { CreateWithdrawalDialog } from '@vegaprotocol/withdraws';
import { SplashLoader } from './components/splash-loader';
import { ToastsManager } from './toasts-manager';
import {
@@ -158,7 +159,8 @@ const Web3Container = ({
-
+
+
>
diff --git a/apps/trading/pages/dialogs-container.tsx b/apps/trading/pages/dialogs-container.tsx
index 6f0bbeb77..8b181e142 100644
--- a/apps/trading/pages/dialogs-container.tsx
+++ b/apps/trading/pages/dialogs-container.tsx
@@ -6,7 +6,10 @@ import { VegaConnectDialog } from '@vegaprotocol/wallet';
import { Connectors } from '../lib/vega-connectors';
import { CreateWithdrawalDialog } from '@vegaprotocol/withdraws';
import { DepositDialog } from '@vegaprotocol/deposits';
-import { Web3ConnectUncontrolledDialog } from '@vegaprotocol/web3';
+import {
+ Web3ConnectUncontrolledDialog,
+ WithdrawalApprovalDialogContainer,
+} from '@vegaprotocol/web3';
import { WelcomeDialog } from '../components/welcome-dialog';
import { TransferDialog } from '@vegaprotocol/accounts';
@@ -27,6 +30,7 @@ const DialogsContainer = () => {
+
>
);
};
diff --git a/libs/ui-toolkit/src/components/toast/toast.tsx b/libs/ui-toolkit/src/components/toast/toast.tsx
index fdddac9b8..3a5033282 100644
--- a/libs/ui-toolkit/src/components/toast/toast.tsx
+++ b/libs/ui-toolkit/src/components/toast/toast.tsx
@@ -3,7 +3,8 @@ import styles from './toast.module.css';
import type { IconName } from '@blueprintjs/icons';
import { IconNames } from '@blueprintjs/icons';
import classNames from 'classnames';
-import type { HTMLAttributes, HtmlHTMLAttributes } from 'react';
+import type { HTMLAttributes, HtmlHTMLAttributes, ReactNode } from 'react';
+import { useState } from 'react';
import { forwardRef, useEffect } from 'react';
import { useCallback } from 'react';
import { useLayoutEffect } from 'react';
@@ -11,6 +12,7 @@ import { useRef } from 'react';
import { Intent } from '../../utils/intent';
import { Icon } from '../icon';
import { Loader } from '../loader';
+import { t } from '@vegaprotocol/i18n';
export type ToastContent = JSX.Element | undefined;
@@ -63,11 +65,88 @@ export const Panel = forwardRef>(
}
);
+type CollapsiblePanelProps = {
+ actions?: ReactNode;
+};
+export const CollapsiblePanel = forwardRef<
+ HTMLDivElement,
+ CollapsiblePanelProps & HTMLAttributes
+>(({ children, className, actions, ...props }, ref) => {
+ const [collapsed, setCollapsed] = useState(true);
+ return (
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
+ h4]:font-bold',
+ 'overflow-auto',
+ {
+ 'h-[64px] overflow-hidden': collapsed,
+ 'pb-4': !collapsed,
+ },
+ className
+ )}
+ aria-expanded={!collapsed}
+ onDoubleClick={(e) => {
+ e.preventDefault();
+ setCollapsed(!collapsed);
+ }}
+ {...props}
+ >
+ {children}
+ {collapsed && (
+
+ )}
+
+ {actions}
+ {
+ e.preventDefault();
+ setCollapsed(!collapsed);
+ }}
+ title={collapsed ? t('Expand') : t('Collapse')}
+ aria-label={collapsed ? t('Expand') : t('Collapse')}
+ >
+ {collapsed ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
+});
+
export const ToastHeading = forwardRef<
HTMLHeadingElement,
HtmlHTMLAttributes
->(({ children, ...props }, ref) => (
-
+>(({ children, className, ...props }, ref) => (
+
{children}
));
@@ -167,7 +246,7 @@ export const Toast = ({
},
// panel's colours
{
- '[&_[data-panel]]:bg-vega-light-150 [&_[data-panel]]:dark:bg-vega-dark-150 ':
+ '[&_[data-panel]]:bg-vega-light-150 [&_[data-panel]]:dark:bg-vega-dark-150':
intent === Intent.None,
'[&_[data-panel]]:bg-vega-blue-350 [&_[data-panel]]:dark:bg-vega-blue-650':
intent === Intent.Primary,
@@ -178,6 +257,31 @@ export const Toast = ({
'[&_[data-panel]]:bg-vega-pink-350 [&_[data-panel]]:dark:bg-vega-pink-650':
intent === Intent.Danger,
},
+ {
+ '[&_[data-panel]]:to-vega-light-150 [&_[data-panel]]:dark:to-vega-dark-150':
+ intent === Intent.None,
+ '[&_[data-panel]]:to-vega-blue-350 [&_[data-panel]]:dark:to-vega-blue-650':
+ intent === Intent.Primary,
+ '[&_[data-panel]]:to-vega-green-350 [&_[data-panel]]:dark:to-vega-green-650':
+ intent === Intent.Success,
+ '[&_[data-panel]]:to-vega-orange-350 [&_[data-panel]]:dark:to-vega-orange-650':
+ intent === Intent.Warning,
+ '[&_[data-panel]]:to-vega-pink-350 [&_[data-panel]]:dark:to-vega-pink-650':
+ intent === Intent.Danger,
+ },
+ // panel's actions
+ {
+ '[&_[data-panel-actions]]:bg-vega-light-200 [&_[data-panel-actions]]:dark:bg-vega-dark-100 ':
+ intent === Intent.None,
+ '[&_[data-panel-actions]]:bg-vega-blue-400 [&_[data-panel-actions]]:dark:bg-vega-blue-600':
+ intent === Intent.Primary,
+ '[&_[data-panel-actions]]:bg-vega-green-400 [&_[data-panel-actions]]:dark:bg-vega-green-600':
+ intent === Intent.Success,
+ '[&_[data-panel-actions]]:bg-vega-orange-400 [&_[data-panel-actions]]:dark:bg-vega-orange-600':
+ intent === Intent.Warning,
+ '[&_[data-panel-actions]]:bg-vega-pink-400 [&_[data-panel-actions]]:dark:bg-vega-pink-600':
+ intent === Intent.Danger,
+ },
// panels's progress bar colours
'[&_[data-progress-bar]]:mt-[10px] [&_[data-progress-bar]]:mb-[4px]',
{
diff --git a/libs/web3/src/index.ts b/libs/web3/src/index.ts
index beaedc770..687d7e3fc 100644
--- a/libs/web3/src/index.ts
+++ b/libs/web3/src/index.ts
@@ -9,20 +9,21 @@ export * from './lib/use-ethereum-config';
export * from './lib/use-ethereum-read-contract';
export * from './lib/use-ethereum-transaction-manager';
export * from './lib/use-ethereum-transaction-store';
+export * from './lib/use-ethereum-transaction-toasts';
export * from './lib/use-ethereum-transaction-updater';
export * from './lib/use-ethereum-transaction';
+export * from './lib/use-ethereum-withdraw-approval-toasts';
export * from './lib/use-ethereum-withdraw-approvals-manager';
export * from './lib/use-ethereum-withdraw-approvals-store';
export * from './lib/use-get-withdraw-delay';
export * from './lib/use-get-withdraw-threshold';
export * from './lib/use-token-contract';
export * from './lib/use-token-decimals';
+export * from './lib/use-vega-transaction-toasts';
export * from './lib/use-web3-disconnect';
export * from './lib/web3-connect-dialog';
export * from './lib/web3-connect-store';
export * from './lib/web3-connectors';
export * from './lib/web3-provider';
-export * from './lib/use-vega-transaction-toasts';
-export * from './lib/use-ethereum-transaction-toasts';
-export * from './lib/use-ethereum-withdraw-approval-toasts';
+export * from './lib/withdrawal-approval-dialog';
export * from './lib/withdrawal-approval-status';
diff --git a/libs/web3/src/lib/use-ethereum-withdraw-approval-toasts.tsx b/libs/web3/src/lib/use-ethereum-withdraw-approval-toasts.tsx
index d80dce460..0471eb3cc 100644
--- a/libs/web3/src/lib/use-ethereum-withdraw-approval-toasts.tsx
+++ b/libs/web3/src/lib/use-ethereum-withdraw-approval-toasts.tsx
@@ -50,11 +50,10 @@ const EthWithdrawalApprovalToastContent = ({
);
+
return (
<>
- {title.length > 0 && (
- {title}
- )}
+ {title.length > 0 && {title} }
{details}
>
diff --git a/libs/web3/src/lib/use-vega-transaction-toasts.tsx b/libs/web3/src/lib/use-vega-transaction-toasts.tsx
index 5722b8f50..568e54cc6 100644
--- a/libs/web3/src/lib/use-vega-transaction-toasts.tsx
+++ b/libs/web3/src/lib/use-vega-transaction-toasts.tsx
@@ -49,6 +49,7 @@ import { useMarketList } from '@vegaprotocol/markets';
import type { Side } from '@vegaprotocol/types';
import { OrderStatusMapping } from '@vegaprotocol/types';
import { Size } from '@vegaprotocol/datagrid';
+import { useWithdrawalApprovalDialog } from './withdrawal-approval-dialog';
const intentMap: { [s in VegaTxStatus]: Intent } = {
Default: Intent.Primary,
@@ -457,20 +458,41 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
);
+
+ const dialogTrigger = (
+ // It has to stay as due to the word breaking issue
+ // eslint-disable-next-line jsx-a11y/anchor-is-valid
+ {
+ e.preventDefault();
+ if (tx.withdrawal?.id) {
+ useWithdrawalApprovalDialog.getState().open(tx.withdrawal?.id);
+ }
+ }}
+ >
+ {t('save your withdrawal details')}
+
+ );
+
return (
<>
{t('Funds unlocked')}
- {t('Your funds have been unlocked for withdrawal')}
+ {t('Your funds have been unlocked for withdrawal.')}
{tx.txHash && (
-
-
- {t('View in block explorer')}
-
-
+
+ {t('View in block explorer')}
+
)}
+ {/* TODO: Delay message - This withdrawal is subject to a delay. Come back in 5 days to complete the withdrawal. */}
+
+ {t('You can')} {dialogTrigger} {t('for extra security.')}
+
{completeWithdrawalButton}
>
@@ -638,8 +660,9 @@ export const useVegaTransactionToasts = () => {
);
const fromVegaTransaction = (tx: VegaStoredTxState): Toast => {
- const closeAfter = isFinal(tx) ? CLOSE_AFTER : undefined;
const { intent, content } = getVegaTransactionContentIntent(tx);
+ const closeAfter =
+ isFinal(tx) && !isWithdrawTransaction(tx.body) ? CLOSE_AFTER : undefined;
return {
id: `vega-${tx.id}`,
@@ -680,10 +703,22 @@ export const getVegaTransactionContentIntent = (tx: VegaStoredTxState) => {
}
// Transaction can be successful but the order can be rejected by the network
+ const intentForRejectedOrder =
+ tx.order &&
+ !isOrderAmendmentTransaction(tx.body) &&
+ getOrderToastIntent(tx.order.status);
+
+ // Although the transaction is completed on the vega network the whole
+ // withdrawal process is not - funds are only released at this point
+ const intentForCompletedWithdrawal =
+ tx.status === VegaTxStatus.Complete &&
+ isWithdrawTransaction(tx.body) &&
+ Intent.Warning;
+
const intent =
- (tx.order &&
- !isOrderAmendmentTransaction(tx.body) &&
- getOrderToastIntent(tx.order.status)) ||
+ intentForRejectedOrder ||
+ intentForCompletedWithdrawal ||
intentMap[tx.status];
+
return { intent, content };
};
diff --git a/libs/web3/src/lib/withdrawal-approval-dialog.tsx b/libs/web3/src/lib/withdrawal-approval-dialog.tsx
new file mode 100644
index 000000000..138e3a2bd
--- /dev/null
+++ b/libs/web3/src/lib/withdrawal-approval-dialog.tsx
@@ -0,0 +1,187 @@
+import { t } from '@vegaprotocol/i18n';
+import {
+ Button,
+ CopyWithTooltip,
+ Dialog,
+ Icon,
+ KeyValueTable,
+ KeyValueTableRow,
+ Splash,
+ SyntaxHighlighter,
+ VegaIcon,
+ VegaIconNames,
+} from '@vegaprotocol/ui-toolkit';
+import { useWithdrawalApprovalQuery } from '@vegaprotocol/wallet';
+import omit from 'lodash/omit';
+import { create } from 'zustand';
+
+type WithdrawalApprovalDialogProps = {
+ withdrawalId: string | undefined;
+ trigger?: HTMLElement | null;
+ open: boolean;
+ onChange: (open: boolean) => void;
+ asJson?: boolean;
+};
+export const WithdrawalApprovalDialog = ({
+ withdrawalId,
+ trigger,
+ open,
+ onChange,
+ asJson,
+}: WithdrawalApprovalDialogProps) => {
+ return (
+ }
+ open={open}
+ onChange={(isOpen) => onChange(isOpen)}
+ onCloseAutoFocus={(e) => {
+ /**
+ * This mimics radix's default behaviour that focuses the dialog's
+ * trigger after closing itself
+ */
+ if (trigger) {
+ e.preventDefault();
+ trigger.focus();
+ }
+ }}
+ >
+
+
+ {t(
+ `If the network is reset or has an outage, records of your withdrawal
+ may be lost. We recommend you save these details in a safe place so
+ you can still complete your withdrawal.`
+ )}
+
+ {withdrawalId ? (
+
+ ) : (
+
+ )}
+
+
+ onChange(false)}
+ >
+ {t('Close')}
+
+
+
+ );
+};
+
+type WithdrawalApprovalDialogContentProps = {
+ withdrawalId: string;
+ asJson: boolean;
+};
+
+const NoDataContent = ({ msg = t('No data') }) => (
+
+ {msg}
+
+);
+
+const WithdrawalApprovalDialogContent = ({
+ withdrawalId,
+ asJson,
+}: WithdrawalApprovalDialogContentProps) => {
+ const { data, loading } = useWithdrawalApprovalQuery({
+ variables: {
+ withdrawalId,
+ },
+ });
+
+ if (loading) {
+ return ;
+ }
+
+ if (data?.erc20WithdrawalApproval) {
+ const details = omit(data.erc20WithdrawalApproval, '__typename');
+
+ if (asJson) {
+ return (
+
+
+
+ );
+ } else {
+ return (
+
+
+
+
+
+ {t('Copy')}
+
+
+
+
+ {Object.entries(details).map(([key, value]) => (
+
+ {key}
+
+ {value}
+
+
+ ))}
+
+
+ );
+ }
+ }
+
+ return ;
+};
+
+export type WithdrawalApprovalDialogStore = {
+ isOpen: boolean;
+ id: string;
+ trigger: HTMLElement | null | undefined;
+ asJson: boolean;
+ setOpen: (isOpen: boolean) => void;
+ open: (id: string, trigger?: HTMLElement | null, asJson?: boolean) => void;
+};
+
+export const useWithdrawalApprovalDialog =
+ create()((set) => ({
+ isOpen: false,
+ id: '',
+ trigger: null,
+ asJson: false,
+ setOpen: (isOpen) => {
+ set({ isOpen: isOpen });
+ },
+ open: (id, trigger?, asJson = false) => {
+ set({
+ isOpen: true,
+ id,
+ trigger,
+ asJson,
+ });
+ },
+ }));
+
+export const WithdrawalApprovalDialogContainer = () => {
+ const { isOpen, id, trigger, setOpen, asJson } =
+ useWithdrawalApprovalDialog();
+ return (
+
+ );
+};
diff --git a/libs/withdraws/src/index.ts b/libs/withdraws/src/index.ts
index 6e061ea1c..12fe6d7fc 100644
--- a/libs/withdraws/src/index.ts
+++ b/libs/withdraws/src/index.ts
@@ -1,13 +1,13 @@
+export * from './lib/__generated__/Erc20Approval';
+export * from './lib/__generated__/Withdrawal';
export * from './lib/create-withdrawal-dialog';
-export * from './lib/withdrawal-dialog';
-export * from './lib/withdraw-form';
-export * from './lib/withdraw-form-container';
-export * from './lib/withdraw-manager';
-export * from './lib/withdrawals-table';
-export * from './lib/withdrawal-feedback';
export * from './lib/use-complete-withdraw';
export * from './lib/use-create-withdraw';
export * from './lib/use-verify-withdrawal';
+export * from './lib/withdraw-form-container';
+export * from './lib/withdraw-form';
+export * from './lib/withdraw-manager';
+export * from './lib/withdrawal-dialog';
+export * from './lib/withdrawal-feedback';
export * from './lib/withdrawals-provider';
-export * from './lib/__generated__/Withdrawal';
-export * from './lib/__generated__/Erc20Approval';
+export * from './lib/withdrawals-table';
diff --git a/libs/withdraws/src/lib/withdrawals-table.tsx b/libs/withdraws/src/lib/withdrawals-table.tsx
index 802cb858d..9a080e509 100644
--- a/libs/withdraws/src/lib/withdrawals-table.tsx
+++ b/libs/withdraws/src/lib/withdrawals-table.tsx
@@ -9,7 +9,16 @@ import {
} from '@vegaprotocol/utils';
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
import { t } from '@vegaprotocol/i18n';
-import { ButtonLink } from '@vegaprotocol/ui-toolkit';
+import {
+ ButtonLink,
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+ Icon,
+ VegaIcon,
+ VegaIconNames,
+} from '@vegaprotocol/ui-toolkit';
import type {
TypedDataAgGrid,
VegaICellRendererParams,
@@ -18,7 +27,10 @@ import type {
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { EtherscanLink } from '@vegaprotocol/environment';
import type { WithdrawalFieldsFragment } from './__generated__/Withdrawal';
-import { useEthWithdrawApprovalsStore } from '@vegaprotocol/web3';
+import {
+ useEthWithdrawApprovalsStore,
+ useWithdrawalApprovalDialog,
+} from '@vegaprotocol/web3';
import * as Schema from '@vegaprotocol/types';
export const WithdrawalsTable = (
@@ -119,6 +131,7 @@ export const WithdrawalsTable = (
headerName={t('Transaction')}
field="txHash"
flex={2}
+ type="rightAligned"
cellRendererParams={{
complete: (withdrawal: WithdrawalFieldsFragment) => {
createWithdrawApproval(withdrawal);
@@ -139,18 +152,51 @@ export type CompleteCellProps = {
complete: (withdrawal: WithdrawalFieldsFragment) => void;
};
export const CompleteCell = ({ data, complete }: CompleteCellProps) => {
+ const open = useWithdrawalApprovalDialog((state) => state.open);
+ const ref = useRef(null);
+
if (!data) {
return null;
}
return data.pendingOnForeignChain ? (
'-'
) : (
- complete(data)}
- >
- {t('Complete withdrawal')}
-
+
+ complete(data)}
+ >
+ {t('Complete withdrawal')}
+
+
+
+
+
+ }
+ >
+
+ {
+ if (data.id) {
+ open(data.id, ref.current, false);
+ }
+ }}
+ >
+
+ {t('View withdrawal details')}
+
+
+
+
+
);
};