import { Button, ExternalLink, Intent, ProgressBar, ToastsContainer, } from '@vegaprotocol/ui-toolkit'; import { useCallback, useEffect, useMemo } from 'react'; import { useEthTransactionStore, useEthWithdrawApprovalsStore, TransactionContent, EthTxStatus, isEthereumError, ApprovalStatus, } from '@vegaprotocol/web3'; import { isWithdrawTransaction, useVegaTransactionStore, VegaTxStatus, } from '@vegaprotocol/wallet'; import { VegaTransaction } from '../components/vega-transaction'; import { VerificationStatus } from '@vegaprotocol/withdraws'; import compact from 'lodash/compact'; import sortBy from 'lodash/sortBy'; import type { EthStoredTxState, EthWithdrawalApprovalState, } from '@vegaprotocol/web3'; import type { Toast } from '@vegaprotocol/ui-toolkit'; import type { VegaStoredTxState, WithdrawSubmissionBody, WithdrawalBusEventFieldsFragment, } from '@vegaprotocol/wallet'; import type { Asset } from '@vegaprotocol/assets'; import { useAssetsDataProvider } from '@vegaprotocol/assets'; import { formatNumber, t, toBigNum } from '@vegaprotocol/react-helpers'; import { DApp, ETHERSCAN_TX, EXPLORER_TX, useEtherscanLink, useLinks, } from '@vegaprotocol/environment'; import { prepend0x } from '@vegaprotocol/smart-contracts'; const intentMap = { Default: Intent.Primary, Requested: Intent.Warning, Pending: Intent.Warning, Error: Intent.Danger, Complete: Intent.Success, Confirmed: Intent.Success, Idle: Intent.None, Delayed: Intent.Warning, Ready: Intent.Success, }; const TransactionDetails = ({ label, amount, asset, }: { label: string; amount: string; asset: Pick; }) => { const num = formatNumber(toBigNum(amount, asset.decimals), asset.decimals); return (
{label} {num} {asset.symbol}
); }; const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => { const { data } = useAssetsDataProvider(); if (!data) return null; const VEGA_WITHDRAW = isWithdrawTransaction(tx.body); if (VEGA_WITHDRAW) { const transactionDetails = tx.body as WithdrawSubmissionBody; const asset = data?.find( (a) => a.id === transactionDetails.withdrawSubmission.asset ); if (asset) { return ( ); } } return null; }; const EthTransactionDetails = ({ tx }: { tx: EthStoredTxState }) => { const { data } = useAssetsDataProvider(); if (!data) return null; const ETH_WITHDRAW = tx.methodName === 'withdraw_asset' && tx.args.length > 2 && tx.asset; if (ETH_WITHDRAW) { const asset = data.find((a) => a.id === tx.asset); if (asset) { return ( <> {tx.requiresConfirmation && (
{t('Awaiting confirmations')}{' '} {`(${tx.confirmations}/${tx.requiredConfirmations})`}
)} ); } } return null; }; export const ToastsManager = () => { const vegaTransactions = useVegaTransactionStore((state) => state.transactions.filter((transaction) => transaction?.dialogOpen) ); const dismissVegaTransaction = useVegaTransactionStore( (state) => state.dismiss ); const ethTransactions = useEthTransactionStore((state) => state.transactions.filter((transaction) => transaction?.dialogOpen) ); const dismissEthTransaction = useEthTransactionStore( (state) => state.dismiss ); const { withdrawApprovals, createEthWithdrawalApproval } = useEthWithdrawApprovalsStore((state) => ({ withdrawApprovals: state.transactions.filter( (transaction) => transaction?.dialogOpen ), createEthWithdrawalApproval: state.create, })); const dismissWithdrawApproval = useEthWithdrawApprovalsStore( (state) => state.dismiss ); const explorerLink = useLinks(DApp.Explorer); const etherscanLink = useEtherscanLink(); const fromVegaTransaction = useCallback( (tx: VegaStoredTxState): Toast => { let toast: Partial = {}; const defaultValues = { id: `vega-${tx.id}`, intent: intentMap[tx.status], render: () => { return ; }, onClose: () => dismissVegaTransaction(tx.id), }; if (tx.status === VegaTxStatus.Requested) { toast = { render: () => { return (

{t('Action required')}

{t( 'Please go to your Vega wallet application and approve or reject the transaction.' )}

); }, }; } if (tx.status === VegaTxStatus.Pending) { toast = { render: () => { return (

{t('Awaiting confirmation')}

{t('Please wait for your transaction to be confirmed')}

{tx.txHash && (

{t('View in block explorer')}

)}
); }, loader: true, }; } if (tx.status === VegaTxStatus.Complete) { toast = { render: () => { if (isWithdrawTransaction(tx.body)) { const completeWithdrawalButton = tx.withdrawal && (
); return (

{t('Funds unlocked')}

{t('Your funds have been unlocked for withdrawal')}

{tx.txHash && (

{t('View in block explorer')}

)} {completeWithdrawalButton}
); } return (

{t('Confirmed')}

{t('Your transaction has been confirmed ')}

{tx.txHash && (

{t('View in block explorer')}

)}
); }, }; } if (tx.status === VegaTxStatus.Error) { toast = { render: () => { const errorMessage = `${tx.error?.message} ${ tx.error?.data ? `: ${tx.error?.data}` : '' }`; return (

{t('Error occurred')}

{errorMessage}

); }, }; } return { ...defaultValues, ...toast, }; }, [createEthWithdrawalApproval, dismissVegaTransaction, explorerLink] ); const fromEthTransaction = useCallback( (tx: EthStoredTxState): Toast => { let toast: Partial = {}; const defaultValues = { id: `eth-${tx.id}`, intent: intentMap[tx.status], render: () => { return ; }, onClose: () => dismissEthTransaction(tx.id), }; if (tx.status === EthTxStatus.Requested) { toast = { render: () => { return (

{t('Action required')}

{t( 'Please go to your wallet application and approve or reject the transaction.' )}

); }, }; } if (tx.status === EthTxStatus.Pending) { toast = { render: () => { return (

{t('Awaiting confirmation')}

{t('Please wait for your transaction to be confirmed')}

{tx.txHash && (

{t('View on Etherscan')}

)}
); }, loader: true, }; } if (tx.status === EthTxStatus.Confirmed) { toast = { render: () => { return (

{t('Transaction completed')}

{t('Your transaction has been completed')}

{tx.txHash && (

{t('View on Etherscan')}

)}
); }, }; } if (tx.status === EthTxStatus.Error) { toast = { render: () => { let errorMessage = ''; if (isEthereumError(tx.error)) { errorMessage = tx.error.reason; } else if (tx.error instanceof Error) { errorMessage = tx.error.message; } return (

{t('Error occurred')}

{errorMessage}

); }, }; } return { ...defaultValues, ...toast, }; }, [dismissEthTransaction, etherscanLink] ); const fromWithdrawalApproval = useCallback( (tx: EthWithdrawalApprovalState): Toast => ({ id: `withdrawal-${tx.id}`, intent: intentMap[tx.status], render: () => { let title = ''; if (tx.status === ApprovalStatus.Error) { title = t('Error occurred'); } if (tx.status === ApprovalStatus.Pending) { title = t('Pending approval'); } if (tx.status === ApprovalStatus.Delayed) { title = t('Delayed'); } return (
{title.length > 0 &&

{title}

}
); }, onClose: () => dismissWithdrawApproval(tx.id), loader: tx.status === ApprovalStatus.Pending, }), [dismissWithdrawApproval] ); const toasts = useMemo(() => { return sortBy( [ ...compact(vegaTransactions).map(fromVegaTransaction), ...compact(ethTransactions).map(fromEthTransaction), ...compact(withdrawApprovals).map(fromWithdrawalApproval), ], ['createdBy'] ); }, [ fromEthTransaction, fromVegaTransaction, fromWithdrawalApproval, ethTransactions, vegaTransactions, withdrawApprovals, ]); useEffect( () => console.log([ ...vegaTransactions, ...ethTransactions, ...withdrawApprovals, ]), [ethTransactions, vegaTransactions, withdrawApprovals] ); useEffect(() => console.log(toasts), [toasts]); return ; }; export default ToastsManager;