From 860af395035d64bdcb63f363c746f820c4468823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Von=C3=A1=C5=A1ek?= Date: Thu, 11 Apr 2024 14:03:39 +0200 Subject: [PATCH 1/2] Display error explanation for failed transaction toast --- src/components/Toast/Toast.styled.ts | 6 +++ src/components/Toast/Toast.tsx | 3 ++ src/components/Toast/ToastContent.tsx | 24 ++++++--- src/components/Toast/sidebar/ToastSidebar.tsx | 18 +++++++ .../trade/sections/otc/modals/PlaceOrder.tsx | 50 ++++++++----------- .../transaction/ReviewTransaction.tsx | 2 +- .../transaction/ReviewTransaction.utils.tsx | 43 +++++++++++++--- .../transaction/ReviewTransactionToast.tsx | 18 ++++++- src/state/toasts.ts | 24 ++++++++- src/utils/helpers.ts | 12 +++++ 10 files changed, 156 insertions(+), 44 deletions(-) diff --git a/src/components/Toast/Toast.styled.ts b/src/components/Toast/Toast.styled.ts index 7fb534632..e96a6f14f 100644 --- a/src/components/Toast/Toast.styled.ts +++ b/src/components/Toast/Toast.styled.ts @@ -76,6 +76,12 @@ export const STitle = styled(Title)` & .referralDesc { color: ${theme.colors.white}; } + + & strong, + & b { + font-weight: 700; + font-family: "ChakraPetchBold"; + } ` export const SClose = styled(Close)` diff --git a/src/components/Toast/Toast.tsx b/src/components/Toast/Toast.tsx index 575619330..1482ea3a0 100644 --- a/src/components/Toast/Toast.tsx +++ b/src/components/Toast/Toast.tsx @@ -17,6 +17,7 @@ import { ToastVariant } from "state/toasts" type Props = { variant?: ToastVariant title?: string | ReactNode + description?: string | ReactNode link?: string actions?: ReactNode index?: number @@ -31,6 +32,7 @@ type Props = { export const Toast: FC = ({ variant = "info", title, + description, link, actions, index, @@ -56,6 +58,7 @@ export const Toast: FC = ({ title?: string | ReactNode + description?: string | ReactNode link?: string actions?: ReactNode meta?: ReactNode @@ -45,13 +46,24 @@ export function ToastContent(props: {
- - {typeof props.title === "string" ? ( -

- ) : ( - props.title +

+ + {typeof props.title === "string" ? ( +

+ ) : ( + props.title + )} + + {props.description && ( + + {typeof props.description === "string" ? ( +

+ ) : ( + props.description + )} + )} - +

{props.actions}
diff --git a/src/components/Toast/sidebar/ToastSidebar.tsx b/src/components/Toast/sidebar/ToastSidebar.tsx index 35df6ae3a..d517276d9 100644 --- a/src/components/Toast/sidebar/ToastSidebar.tsx +++ b/src/components/Toast/sidebar/ToastSidebar.tsx @@ -89,6 +89,15 @@ export function ToastSidebar() { }} /> } + description={ + toast.description ? ( +
+ ) : undefined + } actions={toast.actions} dateCreated={ typeof toast.dateCreated === "string" @@ -115,6 +124,15 @@ export function ToastSidebar() { }} /> } + description={ + toast.description ? ( +
+ ) : undefined + } actions={toast.actions} dateCreated={ typeof toast.dateCreated === "string" diff --git a/src/sections/trade/sections/otc/modals/PlaceOrder.tsx b/src/sections/trade/sections/otc/modals/PlaceOrder.tsx index 3d4f902e1..2507d9224 100644 --- a/src/sections/trade/sections/otc/modals/PlaceOrder.tsx +++ b/src/sections/trade/sections/otc/modals/PlaceOrder.tsx @@ -13,12 +13,13 @@ import { AssetsModalContent } from "sections/assets/AssetsModal" import { getFixedPointAmount } from "utils/balance" import { BN_10 } from "utils/constants" import { FormValues } from "utils/helpers" -import { useStore } from "state/store" +import { ToastMessage, useStore } from "state/store" import { OrderAssetSelect } from "./cmp/AssetSelect" import { OrderAssetRate } from "./cmp/AssetXRate" import { PartialOrderToggle } from "./cmp/PartialOrderToggle" import { useRpcProvider } from "providers/rpcProvider" import { useAccount } from "sections/web3-connect/Web3Connect.utils" +import { TOAST_MESSAGES } from "state/toasts" type PlaceOrderProps = { assetOut?: u32 | string @@ -109,6 +110,24 @@ export const PlaceOrder = ({ assetInMeta.decimals, ).decimalPlaces(0, 1) + const toast = TOAST_MESSAGES.reduce((memo, type) => { + const msType = type === "onError" ? "onLoading" : type + memo[type] = ( + + + + + ) + return memo + }, {} as ToastMessage) + await createTransaction( { tx: api.tx.otc.placeOrder( @@ -125,34 +144,7 @@ export const PlaceOrder = ({ onClose() form.reset() }, - toast: { - onLoading: ( - - - - - ), - onSuccess: ( - - - - - ), - }, + toast, }, ) } diff --git a/src/sections/transaction/ReviewTransaction.tsx b/src/sections/transaction/ReviewTransaction.tsx index f467ae37f..b1fc05ff7 100644 --- a/src/sections/transaction/ReviewTransaction.tsx +++ b/src/sections/transaction/ReviewTransaction.tsx @@ -28,7 +28,7 @@ export const ReviewTransaction = (props: Transaction) => { data, txState, reset, - } = useSendTx() + } = useSendTx({ id: props.id }) const isError = isSendError || !!signError const error = sendError || signError diff --git a/src/sections/transaction/ReviewTransaction.utils.tsx b/src/sections/transaction/ReviewTransaction.utils.tsx index 45857bae4..9a43b05c6 100644 --- a/src/sections/transaction/ReviewTransaction.utils.tsx +++ b/src/sections/transaction/ReviewTransaction.utils.tsx @@ -18,6 +18,7 @@ import { useMountedState } from "react-use" import { useEvmAccount } from "sections/web3-connect/Web3Connect.utils" import { useToast } from "state/toasts" import { H160, getEvmTxLink, isEvmAccount } from "utils/evm" +import { defer } from "utils/helpers" type TxMethod = AnyJson & { method: string @@ -86,6 +87,14 @@ export function getTransactionJSON(tx: SubmittableExtrinsic<"promise">) { export class UnknownTransactionState extends Error {} +export class TransactionError extends Error { + docs: string = "" + method: string = "" + constructor(public error: string) { + super(error) + } +} + function evmTxReceiptToSubmittableResult(txReceipt: TransactionReceipt) { const isSuccess = txReceipt.status === 1 const submittableResult: ISubmittableResult = { @@ -169,7 +178,7 @@ export const useSendEvmTransactionMutation = ( export const useSendTransactionMutation = ( options: MutationObserverOptions< ISubmittableResult & { transactionLink?: string }, - unknown, + TransactionError, SubmittableExtrinsic<"promise"> > = {}, ) => { @@ -197,19 +206,25 @@ export const useSendTransactionMutation = ( if (result.isCompleted) { if (result.dispatchError) { + let docs = "" + let method = "" let errorMessage = result.dispatchError.toString() if (result.dispatchError.isModule) { const decoded = api.registry.findMetaError( result.dispatchError.asModule, ) - errorMessage = `${decoded.section}.${ - decoded.method - }: ${decoded.docs.join(" ")}` + docs = decoded.docs.join(" ") + method = decoded.method + errorMessage = `${decoded.section}.${decoded.method}: ${docs}` } + const error = new TransactionError(errorMessage) + error.docs = docs + error.method = method + clearTimeout(timeout) - reject(new Error(errorMessage)) + reject(error) } else { const transactionLink = await link.mutateAsync({ blockHash: result.status.asInBlock.toString(), @@ -307,9 +322,11 @@ const useBoundReferralToast = () => { } } -export const useSendTx = () => { +export const useSendTx = ({ id }: { id: string }) => { const [txType, setTxType] = useState<"default" | "evm" | null>(null) + const { edit } = useToast() + const boundReferralToast = useBoundReferralToast() const sendTx = useSendTransactionMutation({ @@ -318,6 +335,20 @@ export const useSendTx = () => { setTxType("default") }, onSuccess: boundReferralToast.onSuccess, + onError: (err) => { + if (err?.method) { + defer(() => { + edit(id, { + description: ( +

+ {err.method} + {err.docs ? ` - ${err.docs}` : ""} +

+ ), + }) + }) + } + }, }) const sendEvmTx = useSendEvmTransactionMutation({ diff --git a/src/sections/transaction/ReviewTransactionToast.tsx b/src/sections/transaction/ReviewTransactionToast.tsx index 04297a7de..ae72262f5 100644 --- a/src/sections/transaction/ReviewTransactionToast.tsx +++ b/src/sections/transaction/ReviewTransactionToast.tsx @@ -34,6 +34,7 @@ export function ReviewTransactionToast(props: { if (isSuccess) { // toast should be still present, even if ReviewTransaction is unmounted toastRef.current.success({ + id: props.id, title: props.toastMessage?.onSuccess ?? (

{t("liquidity.reviewTransaction.toast.success")}

), @@ -48,12 +49,16 @@ export function ReviewTransactionToast(props: { if (isError) { if (error instanceof UnknownTransactionState) { toastRef.current.unknown({ + id: props.id, + link: props.link, title: props.toastMessage?.onError ?? (

{t("liquidity.reviewTransaction.toast.unknown")}

), }) } else { toastRef.current.error({ + id: props.id, + link: props.link, title: props.toastMessage?.onError ?? (

{t("liquidity.reviewTransaction.toast.error")}

), @@ -63,6 +68,8 @@ export function ReviewTransactionToast(props: { if (isLoading) { toRemoveId = toastRef.current.loading({ + id: props.id, + link: props.link, title: props.toastMessage?.onLoading ?? (

{t("liquidity.reviewTransaction.toast.pending")}

), @@ -72,7 +79,16 @@ export function ReviewTransactionToast(props: { return () => { if (toRemoveId) toastRef.current.remove(toRemoveId) } - }, [t, props.toastMessage, isError, error, isSuccess, isLoading, props.link]) + }, [ + t, + props.toastMessage, + isError, + error, + isSuccess, + isLoading, + props.link, + props.id, + ]) return null } diff --git a/src/state/toasts.ts b/src/state/toasts.ts index e9eeb62b5..c54941158 100644 --- a/src/state/toasts.ts +++ b/src/state/toasts.ts @@ -21,6 +21,7 @@ type ToastParams = { id?: string link?: string title: ReactElement + description?: ReactElement actions?: ReactNode persist?: boolean hideTime?: number @@ -32,6 +33,7 @@ type ToastData = ToastParams & { hidden: boolean dateCreated: string title: string + description?: string } type PersistState = { @@ -48,7 +50,6 @@ interface ToastStore { accoutAddress: Maybe, callback: (toasts: Array) => Array, ) => void - sidebar: boolean setSidebar: (value: boolean) => void updateToastsTemp: ( @@ -292,6 +293,26 @@ export const useToast = () => { return id } + const edit = (id: string, toast: Partial) => { + const title = toast.title ? renderToString(toast.title) : undefined + const description = toast.description + ? renderToString(toast.description) + : undefined + + store.update(account?.address, (toasts) => + toasts.map((t) => + t.id === id + ? ({ + ...t, + ...toast, + title: title ?? t.title, + description: description ?? t.description, + } as ToastData) + : t, + ), + ) + } + const info = (toast: ToastParams) => add("info", toast) const success = (toast: ToastParams) => add("success", toast) const error = (toast: ToastParams) => add("error", toast) @@ -331,6 +352,7 @@ export const useToast = () => { toastsTemp: store.toastsTemp, setSidebar, add, + edit, hide, remove, info, diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index f2b1d0be9..3bb334fca 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -321,3 +321,15 @@ export function abbreviateNumber(price: BN): string { return formattedPrice } + +export function defer(callback: () => void) { + if (typeof callback === "function") { + if ("requestIdleCallback" in window) { + const id = requestIdleCallback(callback) + return () => cancelIdleCallback(id) + } else { + const id = setTimeout(callback, 1) + return () => clearTimeout(id) + } + } +} From 95175fbe0c289b826086dc84b6a18d6f2b275f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Von=C3=A1=C5=A1ek?= Date: Thu, 11 Apr 2024 14:18:09 +0200 Subject: [PATCH 2/2] Add TX errors to EVM --- .../transaction/ReviewTransaction.utils.tsx | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/sections/transaction/ReviewTransaction.utils.tsx b/src/sections/transaction/ReviewTransaction.utils.tsx index 9a43b05c6..9f34fbf55 100644 --- a/src/sections/transaction/ReviewTransaction.utils.tsx +++ b/src/sections/transaction/ReviewTransaction.utils.tsx @@ -119,7 +119,7 @@ export const useSendEvmTransactionMutation = ( ISubmittableResult & { transactionLink?: string }, - unknown, + TransactionError, { evmTx: TransactionResponse tx?: SubmittableExtrinsic<"promise"> @@ -158,7 +158,7 @@ export const useSendEvmTransactionMutation = ( }) } catch (err) { const { error } = decodeError(err) - reject(new Error(error)) + reject(new TransactionError(error)) } finally { clearTimeout(timeout) } @@ -322,12 +322,30 @@ const useBoundReferralToast = () => { } } +const useErrorToastUpdate = (id: string) => { + const { edit } = useToast() + + return (err: TransactionError) => { + if (err?.method) { + defer(() => { + edit(id, { + description: ( +

+ {err.method} + {err.docs ? ` - ${err.docs}` : ""} +

+ ), + }) + }) + } + } +} + export const useSendTx = ({ id }: { id: string }) => { const [txType, setTxType] = useState<"default" | "evm" | null>(null) - const { edit } = useToast() - const boundReferralToast = useBoundReferralToast() + const updateErrorToast = useErrorToastUpdate(id) const sendTx = useSendTransactionMutation({ onMutate: (tx) => { @@ -335,20 +353,7 @@ export const useSendTx = ({ id }: { id: string }) => { setTxType("default") }, onSuccess: boundReferralToast.onSuccess, - onError: (err) => { - if (err?.method) { - defer(() => { - edit(id, { - description: ( -

- {err.method} - {err.docs ? ` - ${err.docs}` : ""} -

- ), - }) - }) - } - }, + onError: updateErrorToast, }) const sendEvmTx = useSendEvmTransactionMutation({ @@ -357,6 +362,7 @@ export const useSendTx = ({ id }: { id: string }) => { setTxType("evm") }, onSuccess: boundReferralToast.onSuccess, + onError: updateErrorToast, }) const activeMutation = txType === "default" ? sendTx : sendEvmTx