diff --git a/src/Transaction.tsx b/src/Transaction.tsx index 9f9fbc6..5101193 100644 --- a/src/Transaction.tsx +++ b/src/Transaction.tsx @@ -113,6 +113,7 @@ const Transaction: React.FC = () => {
>; +}; + +const ExpanderSwitch: React.FC = ({ + expanded, + setExpanded, +}) => ( + + {expanded ? [-] : <>[...]} + +); + +export default ExpanderSwitch; diff --git a/src/params.ts b/src/params.ts index 7ac916a..d1464c6 100644 --- a/src/params.ts +++ b/src/params.ts @@ -1,3 +1,3 @@ -export const MIN_API_LEVEL = 4; +export const MIN_API_LEVEL = 5; export const PAGE_SIZE = 25; diff --git a/src/sourcify/useSourcify.ts b/src/sourcify/useSourcify.ts index f5fae67..4f8f31f 100644 --- a/src/sourcify/useSourcify.ts +++ b/src/sourcify/useSourcify.ts @@ -1,5 +1,6 @@ import { useState, useEffect, useMemo } from "react"; import { Interface } from "@ethersproject/abi"; +import { ErrorDescription } from "@ethersproject/abi/lib/interface"; import { ChecksummedAddress, TransactionData } from "../types"; import { sourcifyMetadata, SourcifySource, sourcifySourceFile } from "../url"; @@ -11,12 +12,19 @@ export type UserEvent = { notice?: string | undefined; }; +export type UserError = [ + { + notice?: string | undefined; + } +]; + export type UserDoc = { kind: "user"; version?: number | undefined; notice?: string | undefined; methods: Record; events: Record; + errors?: Record | undefined; }; export type DevMethod = { @@ -24,10 +32,17 @@ export type DevMethod = { returns?: Record; }; +export type DevError = [ + { + params?: Record; + } +]; + export type DevDoc = { kind: "dev"; version?: number | undefined; methods: Record; + errors?: Record | undefined; }; export type Metadata = { @@ -236,3 +251,25 @@ export const useTransactionDescription = ( return txDesc; }; + +export const useError = ( + metadata: Metadata | null | undefined, + output: string | null | undefined +): ErrorDescription | null | undefined => { + const err = useMemo(() => { + if (!metadata || !output) { + return undefined; + } + + const abi = metadata.output.abi; + const intf = new Interface(abi as any); + try { + return intf.parseError(output); + } catch (err) { + console.warn("Couldn't find error signature", err); + return null; + } + }, [metadata, output]); + + return err; +}; diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index e4daf2f..66c34e9 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -1,4 +1,5 @@ -import React, { useContext, useMemo } from "react"; +import React, { useContext, useMemo, useState } from "react"; +import { Tab } from "@headlessui/react"; import { TransactionDescription } from "@ethersproject/abi"; import { BigNumber } from "@ethersproject/bignumber"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -8,6 +9,8 @@ import { faTimesCircle } from "@fortawesome/free-solid-svg-icons/faTimesCircle"; import ContentFrame from "../ContentFrame"; import InfoRow from "../components/InfoRow"; import BlockLink from "../components/BlockLink"; +import ModeTab from "../components/ModeTab"; +import ExpanderSwitch from "../components/ExpanderSwitch"; import BlockConfirmations from "../components/BlockConfirmations"; import TransactionAddress from "../components/TransactionAddress"; import Copy from "../components/Copy"; @@ -31,20 +34,23 @@ import PercentageBar from "../components/PercentageBar"; import ExternalLink from "../components/ExternalLink"; import RelativePosition from "../components/RelativePosition"; import PercentagePosition from "../components/PercentagePosition"; +import DecodedParamsTable from "./decoder/DecodedParamsTable"; import InputDecoder from "./decoder/InputDecoder"; import { rawInputTo4Bytes, use4Bytes, useTransactionDescription, } from "../use4Bytes"; -import { DevDoc, UserDoc } from "../sourcify/useSourcify"; +import { DevDoc, Metadata, useError, UserDoc } from "../sourcify/useSourcify"; import { ResolvedAddresses } from "../api/address-resolver"; import { RuntimeContext } from "../useRuntime"; import { useContractsMetadata } from "../hooks"; +import { useTransactionError } from "../useErigonHooks"; type DetailsProps = { txData: TransactionData; txDesc: TransactionDescription | null | undefined; + toMetadata: Metadata | null | undefined; userDoc?: UserDoc | undefined; devDoc?: DevDoc | undefined; internalOps?: InternalOperation[]; @@ -56,6 +62,7 @@ type DetailsProps = { const Details: React.FC = ({ txData, txDesc, + toMetadata, userDoc, devDoc, internalOps, @@ -96,6 +103,21 @@ const Details: React.FC = ({ return _addresses; }, [txData]); const metadatas = useContractsMetadata(addresses, provider); + const [errorMsg, outputData, isCustomError] = useTransactionError( + provider, + txData.transactionHash + ); + const errorDescription = useError( + toMetadata, + isCustomError ? outputData : undefined + ); + const userError = errorDescription + ? userDoc?.errors?.[errorDescription.signature]?.[0] + : undefined; + const devError = errorDescription + ? devDoc?.errors?.[errorDescription.signature]?.[0] + : undefined; + const [expanded, setExpanded] = useState(false); return ( @@ -109,15 +131,89 @@ const Details: React.FC = ({ {txData.confirmedData === undefined ? ( Pending ) : txData.confirmedData.status ? ( - - + + Success ) : ( - - - Fail - + <> +
+
+ + + Fail + {errorMsg && ( + <> + {" "} + with revert message: ' + {errorMsg}' + + )} + {isCustomError && ( + <> + {" "} + with custom error + {errorDescription && ( + <> + {" '"} + + {errorDescription.name} + + {"'"} + + )} + + )} + +
+ {isCustomError && ( + + )} +
+ {expanded && ( + + + Decoded + Raw + + + + {errorDescription === undefined ? ( + <>Waiting for data... + ) : errorDescription === null ? ( + <>Can't decode data + ) : errorDescription.args.length === 0 ? ( + <>No parameters + ) : ( + + )} + + +