Merge branch 'feature/swr-everything' into develop
This commit is contained in:
commit
18ab4dff67
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useContext, useCallback, useMemo } from "react";
|
||||
import React, { useEffect, useContext, useCallback } from "react";
|
||||
import {
|
||||
useParams,
|
||||
useNavigate,
|
||||
|
@ -23,9 +23,9 @@ import Contracts from "./address/Contracts";
|
|||
import { RuntimeContext } from "./useRuntime";
|
||||
import { useAppConfigContext } from "./useAppConfig";
|
||||
import { useAddressOrENS } from "./useResolvedAddresses";
|
||||
import { useMultipleMetadata } from "./sourcify/useSourcify";
|
||||
import { useSourcifyMetadata } from "./sourcify/useSourcify";
|
||||
import { ChecksummedAddress } from "./types";
|
||||
import { useAddressesWithCode } from "./useErigonHooks";
|
||||
import { useHasCode } from "./useErigonHooks";
|
||||
import { useChainInfo } from "./useChainInfo";
|
||||
|
||||
const AddressTransactionByNonce = React.lazy(
|
||||
|
@ -65,25 +65,13 @@ const Address: React.FC = () => {
|
|||
}
|
||||
}, [addressOrName, checksummedAddress, isENS]);
|
||||
|
||||
const hasCode = useHasCode(provider, checksummedAddress, "latest");
|
||||
const { sourcifySource } = useAppConfigContext();
|
||||
const checksummedAddressAsArray = useMemo(
|
||||
() => (checksummedAddress !== undefined ? [checksummedAddress] : []),
|
||||
[checksummedAddress]
|
||||
);
|
||||
const contractAddresses = useAddressesWithCode(
|
||||
provider,
|
||||
checksummedAddressAsArray
|
||||
);
|
||||
const metadatas = useMultipleMetadata(
|
||||
undefined,
|
||||
contractAddresses,
|
||||
const addressMetadata = useSourcifyMetadata(
|
||||
hasCode ? checksummedAddress : undefined,
|
||||
provider?.network.chainId,
|
||||
sourcifySource
|
||||
);
|
||||
const addressMetadata =
|
||||
checksummedAddress !== undefined
|
||||
? metadatas[checksummedAddress]
|
||||
: undefined;
|
||||
|
||||
const { network, faucets } = useChainInfo();
|
||||
|
||||
|
@ -134,7 +122,7 @@ const Address: React.FC = () => {
|
|||
<Tab.Group>
|
||||
<Tab.List className="flex space-x-2 border-l border-r border-t rounded-t-lg bg-white">
|
||||
<NavTab href={`/address/${addressOrName}`}>Overview</NavTab>
|
||||
{(contractAddresses?.length ?? 0) > 0 && (
|
||||
{hasCode && (
|
||||
<NavTab href={`/address/${addressOrName}/contract`}>
|
||||
<span
|
||||
className={`flex items-baseline space-x-2 ${
|
||||
|
@ -181,12 +169,7 @@ const Address: React.FC = () => {
|
|||
element={
|
||||
<Contracts
|
||||
checksummedAddress={checksummedAddress}
|
||||
rawMetadata={
|
||||
contractAddresses !== undefined &&
|
||||
contractAddresses.length === 0
|
||||
? null
|
||||
: addressMetadata
|
||||
}
|
||||
rawMetadata={addressMetadata}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -6,28 +6,20 @@ import TransactionAddress from "./components/TransactionAddress";
|
|||
import ValueHighlighter from "./components/ValueHighlighter";
|
||||
import FormattedBalance from "./components/FormattedBalance";
|
||||
import USDAmount from "./components/USDAmount";
|
||||
import {
|
||||
AddressContext,
|
||||
ChecksummedAddress,
|
||||
TokenMeta,
|
||||
TokenTransfer,
|
||||
} from "./types";
|
||||
import { AddressContext, TokenMeta, TokenTransfer } from "./types";
|
||||
import { RuntimeContext } from "./useRuntime";
|
||||
import { useBlockNumberContext } from "./useBlockTagContext";
|
||||
import { Metadata } from "./sourcify/useSourcify";
|
||||
import { useTokenUSDOracle } from "./usePriceOracle";
|
||||
|
||||
type TokenTransferItemProps = {
|
||||
t: TokenTransfer;
|
||||
tokenMeta?: TokenMeta | null | undefined;
|
||||
metadatas: Record<ChecksummedAddress, Metadata | null | undefined>;
|
||||
};
|
||||
|
||||
// TODO: handle partial
|
||||
const TokenTransferItem: React.FC<TokenTransferItemProps> = ({
|
||||
t,
|
||||
tokenMeta,
|
||||
metadatas,
|
||||
}) => {
|
||||
const { provider } = useContext(RuntimeContext);
|
||||
const blockNumber = useBlockNumberContext();
|
||||
|
@ -40,7 +32,6 @@ const TokenTransferItem: React.FC<TokenTransferItemProps> = ({
|
|||
<TransactionAddress
|
||||
address={t.from}
|
||||
addressCtx={AddressContext.FROM}
|
||||
metadata={metadatas[t.from]}
|
||||
showCodeIndicator
|
||||
/>
|
||||
</div>
|
||||
|
@ -51,7 +42,6 @@ const TokenTransferItem: React.FC<TokenTransferItemProps> = ({
|
|||
<TransactionAddress
|
||||
address={t.to}
|
||||
addressCtx={AddressContext.TO}
|
||||
metadata={metadatas[t.to]}
|
||||
showCodeIndicator
|
||||
/>
|
||||
</div>
|
||||
|
@ -67,7 +57,7 @@ const TokenTransferItem: React.FC<TokenTransferItemProps> = ({
|
|||
/>
|
||||
</ValueHighlighter>
|
||||
</span>
|
||||
<TransactionAddress address={t.token} metadata={metadatas[t.token]} />
|
||||
<TransactionAddress address={t.token} />
|
||||
{tokenMeta && quote !== undefined && decimals !== undefined && (
|
||||
<span className="px-2 border-gray-200 border rounded-lg bg-gray-100 text-gray-600">
|
||||
<USDAmount
|
||||
|
|
|
@ -1,13 +1,71 @@
|
|||
import React from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import TransactionPageContent from "./TransactionPageContent";
|
||||
import React, { useContext } from "react";
|
||||
import { useParams, Route, Routes } from "react-router-dom";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import StandardFrame from "./StandardFrame";
|
||||
import StandardSubtitle from "./StandardSubtitle";
|
||||
import ContentFrame from "./ContentFrame";
|
||||
import NavTab from "./components/NavTab";
|
||||
import { RuntimeContext } from "./useRuntime";
|
||||
import { useTxData } from "./useErigonHooks";
|
||||
import { SelectionContext, useSelection } from "./useSelection";
|
||||
import { SelectedTransactionContext } from "./useSelectedTransaction";
|
||||
import { BlockNumberContext } from "./useBlockTagContext";
|
||||
|
||||
const Details = React.lazy(() => import("./transaction/Details"));
|
||||
const Logs = React.lazy(() => import("./transaction/Logs"));
|
||||
const Trace = React.lazy(() => import("./transaction/Trace"));
|
||||
|
||||
const Transaction: React.FC = () => {
|
||||
const { txhash } = useParams();
|
||||
if (txhash === undefined) {
|
||||
const { txhash: txHash } = useParams();
|
||||
if (txHash === undefined) {
|
||||
throw new Error("txhash couldn't be undefined here");
|
||||
}
|
||||
return <TransactionPageContent txHash={txhash} />;
|
||||
|
||||
const { provider } = useContext(RuntimeContext);
|
||||
const txData = useTxData(provider, txHash);
|
||||
const selectionCtx = useSelection();
|
||||
|
||||
return (
|
||||
<SelectedTransactionContext.Provider value={txData}>
|
||||
<BlockNumberContext.Provider value={txData?.confirmedData?.blockNumber}>
|
||||
<StandardFrame>
|
||||
<StandardSubtitle>Transaction Details</StandardSubtitle>
|
||||
{txData === null && (
|
||||
<ContentFrame>
|
||||
<div className="py-4 text-sm">
|
||||
Transaction <span className="font-hash">{txHash}</span> not
|
||||
found.
|
||||
</div>
|
||||
</ContentFrame>
|
||||
)}
|
||||
{txData && (
|
||||
<SelectionContext.Provider value={selectionCtx}>
|
||||
<Tab.Group>
|
||||
<Tab.List className="flex space-x-2 border-l border-r border-t rounded-t-lg bg-white">
|
||||
<NavTab href=".">Overview</NavTab>
|
||||
{txData.confirmedData?.blockNumber !== undefined && (
|
||||
<NavTab href="logs">
|
||||
Logs
|
||||
{txData &&
|
||||
` (${txData.confirmedData?.logs?.length ?? 0})`}
|
||||
</NavTab>
|
||||
)}
|
||||
<NavTab href="trace">Trace</NavTab>
|
||||
</Tab.List>
|
||||
</Tab.Group>
|
||||
<React.Suspense fallback={null}>
|
||||
<Routes>
|
||||
<Route index element={<Details txData={txData} />} />
|
||||
<Route path="logs" element={<Logs txData={txData} />} />
|
||||
<Route path="trace" element={<Trace txData={txData} />} />
|
||||
</Routes>
|
||||
</React.Suspense>
|
||||
</SelectionContext.Provider>
|
||||
)}
|
||||
</StandardFrame>
|
||||
</BlockNumberContext.Provider>
|
||||
</SelectedTransactionContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default Transaction;
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
import React, { useContext, useMemo } from "react";
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import StandardFrame from "./StandardFrame";
|
||||
import StandardSubtitle from "./StandardSubtitle";
|
||||
import ContentFrame from "./ContentFrame";
|
||||
import NavTab from "./components/NavTab";
|
||||
import { RuntimeContext } from "./useRuntime";
|
||||
import { useInternalOperations, useTxData } from "./useErigonHooks";
|
||||
import { SelectionContext, useSelection } from "./useSelection";
|
||||
import { SelectedTransactionContext } from "./useSelectedTransaction";
|
||||
import { BlockNumberContext } from "./useBlockTagContext";
|
||||
import { useAppConfigContext } from "./useAppConfig";
|
||||
import { useSourcify, useTransactionDescription } from "./sourcify/useSourcify";
|
||||
|
||||
const Details = React.lazy(() => import("./transaction/Details"));
|
||||
const Logs = React.lazy(() => import("./transaction/Logs"));
|
||||
const Trace = React.lazy(() => import("./transaction/Trace"));
|
||||
|
||||
type TransactionPageContentProps = {
|
||||
txHash: string;
|
||||
};
|
||||
|
||||
const TransactionPageContent: React.FC<TransactionPageContentProps> = ({
|
||||
txHash,
|
||||
}) => {
|
||||
const { provider } = useContext(RuntimeContext);
|
||||
|
||||
const txData = useTxData(provider, txHash);
|
||||
const internalOps = useInternalOperations(provider, txData);
|
||||
const sendsEthToMiner = useMemo(() => {
|
||||
if (!txData || !internalOps) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const t of internalOps) {
|
||||
if (t.to === txData.confirmedData?.miner) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, [txData, internalOps]);
|
||||
|
||||
const selectionCtx = useSelection();
|
||||
|
||||
const { sourcifySource } = useAppConfigContext();
|
||||
const metadata = useSourcify(
|
||||
txData?.to,
|
||||
provider?.network.chainId,
|
||||
sourcifySource
|
||||
);
|
||||
const txDesc = useTransactionDescription(metadata, txData);
|
||||
|
||||
return (
|
||||
<SelectedTransactionContext.Provider value={txData}>
|
||||
<BlockNumberContext.Provider value={txData?.confirmedData?.blockNumber}>
|
||||
<StandardFrame>
|
||||
<StandardSubtitle>Transaction Details</StandardSubtitle>
|
||||
{txData === null && (
|
||||
<ContentFrame>
|
||||
<div className="py-4 text-sm">
|
||||
Transaction <span className="font-hash">{txHash}</span> not
|
||||
found.
|
||||
</div>
|
||||
</ContentFrame>
|
||||
)}
|
||||
{txData && (
|
||||
<SelectionContext.Provider value={selectionCtx}>
|
||||
<Tab.Group>
|
||||
<Tab.List className="flex space-x-2 border-l border-r border-t rounded-t-lg bg-white">
|
||||
<NavTab href=".">Overview</NavTab>
|
||||
{txData.confirmedData?.blockNumber !== undefined && (
|
||||
<NavTab href="logs">
|
||||
Logs
|
||||
{txData &&
|
||||
` (${txData.confirmedData?.logs?.length ?? 0})`}
|
||||
</NavTab>
|
||||
)}
|
||||
<NavTab href="trace">Trace</NavTab>
|
||||
</Tab.List>
|
||||
</Tab.Group>
|
||||
<React.Suspense fallback={null}>
|
||||
<Routes>
|
||||
<Route
|
||||
index
|
||||
element={
|
||||
<Details
|
||||
txData={txData}
|
||||
txDesc={txDesc}
|
||||
toMetadata={metadata}
|
||||
userDoc={metadata?.output.userdoc}
|
||||
devDoc={metadata?.output.devdoc}
|
||||
internalOps={internalOps}
|
||||
sendsEthToMiner={sendsEthToMiner}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="logs"
|
||||
element={<Logs txData={txData} metadata={metadata} />}
|
||||
/>
|
||||
<Route path="trace" element={<Trace txData={txData} />} />
|
||||
</Routes>
|
||||
</React.Suspense>
|
||||
</SelectionContext.Provider>
|
||||
)}
|
||||
</StandardFrame>
|
||||
</BlockNumberContext.Provider>
|
||||
</SelectedTransactionContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default TransactionPageContent;
|
|
@ -18,7 +18,6 @@ import { useMultipleETHUSDOracle } from "../usePriceOracle";
|
|||
import { RuntimeContext } from "../useRuntime";
|
||||
import { useParams, useSearchParams } from "react-router-dom";
|
||||
import { ChecksummedAddress, ProcessedTransaction } from "../types";
|
||||
import { useContractsMetadata } from "../hooks";
|
||||
import { useAddressBalance, useContractCreator } from "../useErigonHooks";
|
||||
import { BlockNumberContext } from "../useBlockTagContext";
|
||||
|
||||
|
@ -112,22 +111,6 @@ const AddressTransactionResults: React.FC<AddressTransactionResultsProps> = ({
|
|||
}, [page]);
|
||||
const priceMap = useMultipleETHUSDOracle(provider, blockTags);
|
||||
|
||||
// Calculate Sourcify metadata for all addresses that appear on this page results
|
||||
const addresses = useMemo(() => {
|
||||
const _addresses = [address];
|
||||
if (page) {
|
||||
for (const t of page) {
|
||||
if (t.to) {
|
||||
_addresses.push(t.to);
|
||||
}
|
||||
if (t.createdContractAddress) {
|
||||
_addresses.push(t.createdContractAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _addresses;
|
||||
}, [address, page]);
|
||||
const metadatas = useContractsMetadata(addresses, provider);
|
||||
const balance = useAddressBalance(provider, address);
|
||||
const creator = useContractCreator(provider, address);
|
||||
|
||||
|
@ -181,7 +164,6 @@ const AddressTransactionResults: React.FC<AddressTransactionResultsProps> = ({
|
|||
selectedAddress={address}
|
||||
feeDisplay={feeDisplay}
|
||||
priceMap={priceMap}
|
||||
metadatas={metadatas}
|
||||
/>
|
||||
))}
|
||||
<NavBar address={address} page={page} controller={controller} />
|
||||
|
|
|
@ -1,40 +1,19 @@
|
|||
import React from "react";
|
||||
import { SyntaxHighlighter, docco } from "../highlight-init";
|
||||
import { useContract } from "../sourcify/useSourcify";
|
||||
import { useAppConfigContext } from "../useAppConfig";
|
||||
|
||||
type ContractProps = {
|
||||
checksummedAddress: string;
|
||||
networkId: number;
|
||||
filename: string;
|
||||
source: any;
|
||||
content: any;
|
||||
};
|
||||
|
||||
const Contract: React.FC<ContractProps> = ({
|
||||
checksummedAddress,
|
||||
networkId,
|
||||
filename,
|
||||
source,
|
||||
}) => {
|
||||
const { sourcifySource } = useAppConfigContext();
|
||||
const content = useContract(
|
||||
checksummedAddress,
|
||||
networkId,
|
||||
filename,
|
||||
source,
|
||||
sourcifySource
|
||||
);
|
||||
|
||||
return (
|
||||
<SyntaxHighlighter
|
||||
className="w-full h-full border font-code text-base"
|
||||
language="solidity"
|
||||
style={docco}
|
||||
showLineNumbers
|
||||
>
|
||||
{content ?? ""}
|
||||
</SyntaxHighlighter>
|
||||
);
|
||||
};
|
||||
const Contract: React.FC<ContractProps> = ({ content }) => (
|
||||
<SyntaxHighlighter
|
||||
className="w-full h-full border font-code text-base"
|
||||
language="solidity"
|
||||
style={docco}
|
||||
showLineNumbers
|
||||
>
|
||||
{content ?? ""}
|
||||
</SyntaxHighlighter>
|
||||
);
|
||||
|
||||
export default React.memo(Contract);
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import React from "react";
|
||||
import { SyntaxHighlighter, docco } from "../highlight-init";
|
||||
import { useContract } from "../sourcify/useSourcify";
|
||||
import { useAppConfigContext } from "../useAppConfig";
|
||||
|
||||
type ContractFromRepoProps = {
|
||||
checksummedAddress: string;
|
||||
networkId: number;
|
||||
filename: string;
|
||||
};
|
||||
|
||||
const ContractFromRepo: React.FC<ContractFromRepoProps> = ({
|
||||
checksummedAddress,
|
||||
networkId,
|
||||
filename,
|
||||
}) => {
|
||||
const { sourcifySource } = useAppConfigContext();
|
||||
const content = useContract(
|
||||
checksummedAddress,
|
||||
networkId,
|
||||
filename,
|
||||
sourcifySource
|
||||
);
|
||||
|
||||
return (
|
||||
<SyntaxHighlighter
|
||||
className="w-full h-full border font-code text-base"
|
||||
language="solidity"
|
||||
style={docco}
|
||||
showLineNumbers
|
||||
>
|
||||
{content ?? ""}
|
||||
</SyntaxHighlighter>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ContractFromRepo);
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect, useContext, Fragment } from "react";
|
||||
import React, { useState, useEffect, useContext } from "react";
|
||||
import { commify } from "@ethersproject/units";
|
||||
import { Menu } from "@headlessui/react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
@ -6,6 +6,7 @@ import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
|
|||
import ContentFrame from "../ContentFrame";
|
||||
import InfoRow from "../components/InfoRow";
|
||||
import Contract from "./Contract";
|
||||
import ContractFromRepo from "./ContractFromRepo";
|
||||
import { RuntimeContext } from "../useRuntime";
|
||||
import { Metadata } from "../sourcify/useSourcify";
|
||||
import ExternalLink from "../components/ExternalLink";
|
||||
|
@ -101,7 +102,7 @@ const Contracts: React.FC<ContractsProps> = ({
|
|||
className={`flex text-sm px-2 py-1 ${
|
||||
selected === k
|
||||
? "font-bold bg-gray-200 text-gray-500"
|
||||
: "hover:border-orange-200 hover:text-gray-500 text-gray-400 transition-transform transition-colors duration-75"
|
||||
: "hover:text-gray-500 text-gray-400 transition-colors duration-75"
|
||||
}`}
|
||||
onClick={() => setSelected(k)}
|
||||
>
|
||||
|
@ -113,12 +114,17 @@ const Contracts: React.FC<ContractsProps> = ({
|
|||
</div>
|
||||
</Menu>
|
||||
{selected && (
|
||||
<Contract
|
||||
checksummedAddress={checksummedAddress}
|
||||
networkId={provider!.network.chainId}
|
||||
filename={selected}
|
||||
source={rawMetadata.sources[selected]}
|
||||
/>
|
||||
<>
|
||||
{rawMetadata.sources[selected].content ? (
|
||||
<Contract content={rawMetadata.sources[selected].content} />
|
||||
) : (
|
||||
<ContractFromRepo
|
||||
checksummedAddress={checksummedAddress}
|
||||
networkId={provider!.network.chainId}
|
||||
filename={selected}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -8,10 +8,9 @@ import TransactionItem from "../search/TransactionItem";
|
|||
import { useFeeToggler } from "../search/useFeeToggler";
|
||||
import { RuntimeContext } from "../useRuntime";
|
||||
import { SelectionContext, useSelection } from "../useSelection";
|
||||
import { ChecksummedAddress, ProcessedTransaction } from "../types";
|
||||
import { ProcessedTransaction } from "../types";
|
||||
import { PAGE_SIZE } from "../params";
|
||||
import { useMultipleETHUSDOracle } from "../usePriceOracle";
|
||||
import { useContractsMetadata } from "../hooks";
|
||||
|
||||
type BlockTransactionResultsProps = {
|
||||
blockTag: BlockTag;
|
||||
|
@ -32,24 +31,6 @@ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({
|
|||
const blockTags = useMemo(() => [blockTag], [blockTag]);
|
||||
const priceMap = useMultipleETHUSDOracle(provider, blockTags);
|
||||
|
||||
const addresses = useMemo((): ChecksummedAddress[] => {
|
||||
if (!page) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const _addresses: ChecksummedAddress[] = [];
|
||||
for (const t of page) {
|
||||
if (t.to) {
|
||||
_addresses.push(t.to);
|
||||
}
|
||||
if (t.createdContractAddress) {
|
||||
_addresses.push(t.createdContractAddress);
|
||||
}
|
||||
}
|
||||
return _addresses;
|
||||
}, [page]);
|
||||
const metadatas = useContractsMetadata(addresses, provider);
|
||||
|
||||
return (
|
||||
<ContentFrame>
|
||||
<div className="flex justify-between items-baseline py-3">
|
||||
|
@ -78,7 +59,6 @@ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({
|
|||
tx={tx}
|
||||
feeDisplay={feeDisplay}
|
||||
priceMap={priceMap}
|
||||
metadatas={metadatas}
|
||||
/>
|
||||
))}
|
||||
<div className="flex justify-between items-baseline py-3">
|
||||
|
|
|
@ -8,8 +8,9 @@ import { faBurn } from "@fortawesome/free-solid-svg-icons/faBurn";
|
|||
import { faCoins } from "@fortawesome/free-solid-svg-icons/faCoins";
|
||||
import SourcifyLogo from "../sourcify/SourcifyLogo";
|
||||
import PlainAddress from "./PlainAddress";
|
||||
import { Metadata } from "../sourcify/useSourcify";
|
||||
import { RuntimeContext } from "../useRuntime";
|
||||
import { useAppConfigContext } from "../useAppConfig";
|
||||
import { useSourcifyMetadata } from "../sourcify/useSourcify";
|
||||
import { useResolvedAddress } from "../useResolvedAddresses";
|
||||
import { AddressContext, ChecksummedAddress, ZERO_ADDRESS } from "../types";
|
||||
import { resolverRendererRegistry } from "../api/address-resolver";
|
||||
|
@ -23,7 +24,6 @@ type DecoratedAddressLinkProps = {
|
|||
selfDestruct?: boolean | undefined;
|
||||
txFrom?: boolean | undefined;
|
||||
txTo?: boolean | undefined;
|
||||
metadata?: Metadata | null | undefined;
|
||||
eoa?: boolean | undefined;
|
||||
};
|
||||
|
||||
|
@ -36,9 +36,16 @@ const DecoratedAddressLink: React.FC<DecoratedAddressLinkProps> = ({
|
|||
selfDestruct,
|
||||
txFrom,
|
||||
txTo,
|
||||
metadata,
|
||||
eoa,
|
||||
}) => {
|
||||
const { provider } = useContext(RuntimeContext);
|
||||
const { sourcifySource } = useAppConfigContext();
|
||||
const metadata = useSourcifyMetadata(
|
||||
address,
|
||||
provider?.network.chainId,
|
||||
sourcifySource
|
||||
);
|
||||
|
||||
const mint = addressCtx === AddressContext.FROM && address === ZERO_ADDRESS;
|
||||
const burn = addressCtx === AddressContext.TO && address === ZERO_ADDRESS;
|
||||
|
||||
|
|
|
@ -5,20 +5,17 @@ import { useSelectedTransaction } from "../useSelectedTransaction";
|
|||
import { useBlockNumberContext } from "../useBlockTagContext";
|
||||
import { RuntimeContext } from "../useRuntime";
|
||||
import { useHasCode } from "../useErigonHooks";
|
||||
import { Metadata } from "../sourcify/useSourcify";
|
||||
import { AddressContext, ChecksummedAddress } from "../types";
|
||||
|
||||
type TransactionAddressProps = {
|
||||
address: ChecksummedAddress;
|
||||
addressCtx?: AddressContext | undefined;
|
||||
metadata?: Metadata | null | undefined;
|
||||
showCodeIndicator?: boolean;
|
||||
};
|
||||
|
||||
const TransactionAddress: React.FC<TransactionAddressProps> = ({
|
||||
address,
|
||||
addressCtx,
|
||||
metadata,
|
||||
showCodeIndicator = false,
|
||||
}) => {
|
||||
const txData = useSelectedTransaction();
|
||||
|
@ -46,7 +43,6 @@ const TransactionAddress: React.FC<TransactionAddressProps> = ({
|
|||
txFrom={address === txData?.from}
|
||||
txTo={address === txData?.to || creation}
|
||||
creation={creation}
|
||||
metadata={metadata}
|
||||
eoa={
|
||||
showCodeIndicator && blockNumber !== undefined
|
||||
? !toHasCode
|
||||
|
|
33
src/hooks.ts
33
src/hooks.ts
|
@ -1,33 +0,0 @@
|
|||
import { useMemo } from "react";
|
||||
import { JsonRpcProvider } from "@ethersproject/providers";
|
||||
import { ChecksummedAddress } from "./types";
|
||||
import { Metadata, useMultipleMetadata } from "./sourcify/useSourcify";
|
||||
import { useAppConfigContext } from "./useAppConfig";
|
||||
import { useAddressesWithCode } from "./useErigonHooks";
|
||||
|
||||
export const useDedupedAddresses = (
|
||||
addresses: ChecksummedAddress[]
|
||||
): ChecksummedAddress[] => {
|
||||
return useMemo(() => {
|
||||
const deduped = new Set(addresses);
|
||||
return [...deduped];
|
||||
}, [addresses]);
|
||||
};
|
||||
|
||||
export const useContractsMetadata = (
|
||||
addresses: ChecksummedAddress[],
|
||||
provider: JsonRpcProvider | undefined,
|
||||
baseMetadatas?: Record<string, Metadata | null>
|
||||
) => {
|
||||
const deduped = useDedupedAddresses(addresses);
|
||||
const contracts = useAddressesWithCode(provider, deduped);
|
||||
const { sourcifySource } = useAppConfigContext();
|
||||
const metadatas = useMultipleMetadata(
|
||||
baseMetadatas,
|
||||
contracts,
|
||||
provider?.network.chainId,
|
||||
sourcifySource
|
||||
);
|
||||
|
||||
return metadatas;
|
||||
};
|
|
@ -14,20 +14,18 @@ import TransactionDirection, {
|
|||
Flags,
|
||||
} from "../components/TransactionDirection";
|
||||
import TransactionValue from "../components/TransactionValue";
|
||||
import { ChecksummedAddress, ProcessedTransaction } from "../types";
|
||||
import { ProcessedTransaction } from "../types";
|
||||
import { FeeDisplay } from "./useFeeToggler";
|
||||
import { RuntimeContext } from "../useRuntime";
|
||||
import { useHasCode } from "../useErigonHooks";
|
||||
import { formatValue } from "../components/formatter";
|
||||
import ETH2USDValue from "../components/ETH2USDValue";
|
||||
import { Metadata } from "../sourcify/useSourcify";
|
||||
|
||||
type TransactionItemProps = {
|
||||
tx: ProcessedTransaction;
|
||||
selectedAddress?: string;
|
||||
feeDisplay: FeeDisplay;
|
||||
priceMap: Record<BlockTag, BigNumber>;
|
||||
metadatas: Record<ChecksummedAddress, Metadata | null | undefined>;
|
||||
};
|
||||
|
||||
const TransactionItem: React.FC<TransactionItemProps> = ({
|
||||
|
@ -35,7 +33,6 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
|||
selectedAddress,
|
||||
feeDisplay,
|
||||
priceMap,
|
||||
metadatas,
|
||||
}) => {
|
||||
const { provider } = useContext(RuntimeContext);
|
||||
const toHasCode = useHasCode(
|
||||
|
@ -113,7 +110,6 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
|||
address={tx.to}
|
||||
selectedAddress={selectedAddress}
|
||||
miner={tx.miner === tx.to}
|
||||
metadata={metadatas[tx.to]}
|
||||
eoa={toHasCode === undefined ? undefined : !toHasCode}
|
||||
/>
|
||||
</AddressHighlighter>
|
||||
|
@ -123,7 +119,6 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
|||
address={tx.createdContractAddress!}
|
||||
selectedAddress={selectedAddress}
|
||||
creation
|
||||
metadata={metadatas[tx.createdContractAddress!]}
|
||||
eoa={false}
|
||||
/>
|
||||
</AddressHighlighter>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { useState, useEffect, useMemo } from "react";
|
||||
import { useMemo } from "react";
|
||||
import { Interface } from "@ethersproject/abi";
|
||||
import { ErrorDescription } from "@ethersproject/abi/lib/interface";
|
||||
import useSWRImmutable from "swr/immutable";
|
||||
import { ChecksummedAddress, TransactionData } from "../types";
|
||||
import { sourcifyMetadata, SourcifySource, sourcifySourceFile } from "../url";
|
||||
|
||||
|
@ -80,148 +81,67 @@ export type Metadata = {
|
|||
};
|
||||
};
|
||||
|
||||
const fetchSourcifyMetadata = async (
|
||||
address: ChecksummedAddress,
|
||||
chainId: number,
|
||||
source: SourcifySource,
|
||||
abortController: AbortController
|
||||
): Promise<Metadata | null> => {
|
||||
const sourcifyFetcher = async (url: string) => {
|
||||
try {
|
||||
const metadataURL = sourcifyMetadata(address, chainId, source);
|
||||
const result = await fetch(metadataURL, {
|
||||
signal: abortController.signal,
|
||||
});
|
||||
if (result.ok) {
|
||||
return await result.json();
|
||||
const res = await fetch(url);
|
||||
if (res.ok) {
|
||||
return res.json();
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
console.warn(
|
||||
`error while getting Sourcify metadata: url=${url} err=${err}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: replace every occurrence with the multiple version one
|
||||
export const useSourcify = (
|
||||
export const useSourcifyMetadata = (
|
||||
address: ChecksummedAddress | undefined,
|
||||
chainId: number | undefined,
|
||||
source: SourcifySource
|
||||
): Metadata | null | undefined => {
|
||||
const [rawMetadata, setRawMetadata] = useState<Metadata | null | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!address || chainId === undefined) {
|
||||
return;
|
||||
}
|
||||
setRawMetadata(undefined);
|
||||
|
||||
const abortController = new AbortController();
|
||||
const fetchMetadata = async () => {
|
||||
const _metadata = await fetchSourcifyMetadata(
|
||||
address,
|
||||
chainId,
|
||||
source,
|
||||
abortController
|
||||
);
|
||||
setRawMetadata(_metadata);
|
||||
};
|
||||
fetchMetadata();
|
||||
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, [address, chainId, source]);
|
||||
|
||||
return rawMetadata;
|
||||
const metadataURL = () =>
|
||||
address === undefined || chainId === undefined
|
||||
? null
|
||||
: sourcifyMetadata(address, chainId, source);
|
||||
const { data, error } = useSWRImmutable<Metadata>(
|
||||
metadataURL,
|
||||
sourcifyFetcher
|
||||
);
|
||||
if (error) {
|
||||
return null;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
export const useMultipleMetadata = (
|
||||
baseMetadatas: Record<string, Metadata | null> | undefined,
|
||||
addresses: ChecksummedAddress[] | undefined,
|
||||
chainId: number | undefined,
|
||||
source: SourcifySource
|
||||
): Record<ChecksummedAddress, Metadata | null | undefined> => {
|
||||
const [rawMetadata, setRawMetadata] = useState<
|
||||
Record<string, Metadata | null | undefined>
|
||||
>({});
|
||||
useEffect(() => {
|
||||
if (addresses === undefined || chainId === undefined) {
|
||||
return;
|
||||
}
|
||||
setRawMetadata({});
|
||||
|
||||
const abortController = new AbortController();
|
||||
const fetchMetadata = async (_addresses: string[]) => {
|
||||
const fetchers: Promise<Metadata | null>[] = [];
|
||||
for (const address of _addresses) {
|
||||
fetchers.push(
|
||||
fetchSourcifyMetadata(address, chainId, source, abortController)
|
||||
);
|
||||
}
|
||||
|
||||
const results = await Promise.all(fetchers);
|
||||
if (abortController.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
let metadatas: Record<string, Metadata | null> = {};
|
||||
if (baseMetadatas) {
|
||||
metadatas = { ...baseMetadatas };
|
||||
}
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
metadatas[_addresses[i]] = results[i];
|
||||
}
|
||||
setRawMetadata(metadatas);
|
||||
};
|
||||
|
||||
const filtered = addresses.filter((a) => baseMetadatas?.[a] === undefined);
|
||||
fetchMetadata(filtered);
|
||||
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, [baseMetadatas, addresses, chainId, source]);
|
||||
|
||||
return rawMetadata;
|
||||
const contractFetcher = async (url: string): Promise<string | null> => {
|
||||
const res = await fetch(url);
|
||||
if (res.ok) {
|
||||
return await res.text();
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const useContract = (
|
||||
checksummedAddress: string,
|
||||
networkId: number,
|
||||
filename: string,
|
||||
source: any,
|
||||
sourcifySource: SourcifySource
|
||||
) => {
|
||||
const [content, setContent] = useState<string>(source.content);
|
||||
const normalizedFilename = filename.replaceAll(/[@:]/g, "_");
|
||||
const url = sourcifySourceFile(
|
||||
checksummedAddress,
|
||||
networkId,
|
||||
normalizedFilename,
|
||||
sourcifySource
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (source.content) {
|
||||
return;
|
||||
}
|
||||
|
||||
const abortController = new AbortController();
|
||||
const readContent = async () => {
|
||||
const normalizedFilename = filename.replaceAll(/[@:]/g, "_");
|
||||
const url = sourcifySourceFile(
|
||||
checksummedAddress,
|
||||
networkId,
|
||||
normalizedFilename,
|
||||
sourcifySource
|
||||
);
|
||||
const res = await fetch(url, { signal: abortController.signal });
|
||||
if (res.ok) {
|
||||
const _content = await res.text();
|
||||
setContent(_content);
|
||||
}
|
||||
};
|
||||
readContent();
|
||||
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, [checksummedAddress, networkId, filename, source.content, sourcifySource]);
|
||||
|
||||
return content;
|
||||
const { data, error } = useSWRImmutable(url, contractFetcher);
|
||||
if (error) {
|
||||
return undefined;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
export const useTransactionDescription = (
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useContext, useMemo, useState } from "react";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import { TransactionDescription } from "@ethersproject/abi";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCheckCircle } from "@fortawesome/free-solid-svg-icons/faCheckCircle";
|
||||
import { faCube } from "@fortawesome/free-solid-svg-icons/faCube";
|
||||
|
@ -25,11 +24,7 @@ import USDValue from "../components/USDValue";
|
|||
import FormattedBalance from "../components/FormattedBalance";
|
||||
import ETH2USDValue from "../components/ETH2USDValue";
|
||||
import TokenTransferItem from "../TokenTransferItem";
|
||||
import {
|
||||
TransactionData,
|
||||
InternalOperation,
|
||||
ChecksummedAddress,
|
||||
} from "../types";
|
||||
import { TransactionData } from "../types";
|
||||
import PercentageBar from "../components/PercentageBar";
|
||||
import ExternalLink from "../components/ExternalLink";
|
||||
import RelativePosition from "../components/RelativePosition";
|
||||
|
@ -41,32 +36,24 @@ import {
|
|||
use4Bytes,
|
||||
useTransactionDescription,
|
||||
} from "../use4Bytes";
|
||||
import { DevDoc, Metadata, useError, UserDoc } from "../sourcify/useSourcify";
|
||||
import { useAppConfigContext } from "../useAppConfig";
|
||||
import {
|
||||
useError,
|
||||
useSourcifyMetadata,
|
||||
useTransactionDescription as useSourcifyTransactionDescription,
|
||||
} from "../sourcify/useSourcify";
|
||||
import { RuntimeContext } from "../useRuntime";
|
||||
import { useContractsMetadata } from "../hooks";
|
||||
import { useTransactionError } from "../useErigonHooks";
|
||||
import { useInternalOperations, useTransactionError } from "../useErigonHooks";
|
||||
import { useChainInfo } from "../useChainInfo";
|
||||
import { useETHUSDOracle } from "../usePriceOracle";
|
||||
|
||||
type DetailsProps = {
|
||||
txData: TransactionData;
|
||||
txDesc: TransactionDescription | null | undefined;
|
||||
toMetadata: Metadata | null | undefined;
|
||||
userDoc?: UserDoc | undefined;
|
||||
devDoc?: DevDoc | undefined;
|
||||
internalOps?: InternalOperation[];
|
||||
sendsEthToMiner: boolean;
|
||||
};
|
||||
|
||||
const Details: React.FC<DetailsProps> = ({
|
||||
txData,
|
||||
txDesc,
|
||||
toMetadata,
|
||||
userDoc,
|
||||
devDoc,
|
||||
internalOps,
|
||||
sendsEthToMiner,
|
||||
}) => {
|
||||
const Details: React.FC<DetailsProps> = ({ txData }) => {
|
||||
const { provider } = useContext(RuntimeContext);
|
||||
|
||||
const hasEIP1559 =
|
||||
txData.confirmedData?.blockBaseFeePerGas !== undefined &&
|
||||
txData.confirmedData?.blockBaseFeePerGas !== null;
|
||||
|
@ -80,11 +67,34 @@ const Details: React.FC<DetailsProps> = ({
|
|||
txData.value
|
||||
);
|
||||
|
||||
const internalOps = useInternalOperations(provider, txData);
|
||||
const sendsEthToMiner = useMemo(() => {
|
||||
if (!txData || !internalOps) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const t of internalOps) {
|
||||
if (t.to === txData.confirmedData?.miner) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, [txData, internalOps]);
|
||||
|
||||
const { sourcifySource } = useAppConfigContext();
|
||||
const metadata = useSourcifyMetadata(
|
||||
txData?.to,
|
||||
provider?.network.chainId,
|
||||
sourcifySource
|
||||
);
|
||||
|
||||
const txDesc = useSourcifyTransactionDescription(metadata, txData);
|
||||
const userDoc = metadata?.output.userdoc;
|
||||
const devDoc = metadata?.output.devdoc;
|
||||
const resolvedTxDesc = txDesc ?? fourBytesTxDesc;
|
||||
const userMethod = txDesc ? userDoc?.methods[txDesc.signature] : undefined;
|
||||
const devMethod = txDesc ? devDoc?.methods[txDesc.signature] : undefined;
|
||||
|
||||
const { provider } = useContext(RuntimeContext);
|
||||
const {
|
||||
nativeCurrency: { name, symbol },
|
||||
} = useChainInfo();
|
||||
|
@ -94,28 +104,12 @@ const Details: React.FC<DetailsProps> = ({
|
|||
txData?.confirmedData?.blockNumber
|
||||
);
|
||||
|
||||
const addresses = useMemo(() => {
|
||||
const _addresses: ChecksummedAddress[] = [];
|
||||
if (txData.to) {
|
||||
_addresses.push(txData.to);
|
||||
}
|
||||
if (txData.confirmedData?.createdContractAddress) {
|
||||
_addresses.push(txData.confirmedData.createdContractAddress);
|
||||
}
|
||||
for (const t of txData.tokenTransfers) {
|
||||
_addresses.push(t.from);
|
||||
_addresses.push(t.to);
|
||||
_addresses.push(t.token);
|
||||
}
|
||||
return _addresses;
|
||||
}, [txData]);
|
||||
const metadatas = useContractsMetadata(addresses, provider);
|
||||
const [errorMsg, outputData, isCustomError] = useTransactionError(
|
||||
provider,
|
||||
txData.transactionHash
|
||||
);
|
||||
const errorDescription = useError(
|
||||
toMetadata,
|
||||
metadata,
|
||||
isCustomError ? outputData : undefined
|
||||
);
|
||||
const userError = errorDescription
|
||||
|
@ -269,11 +263,7 @@ const Details: React.FC<DetailsProps> = ({
|
|||
<InfoRow title={txData.to ? "Interacted With (To)" : "Contract Created"}>
|
||||
{txData.to ? (
|
||||
<div className="flex items-baseline space-x-2 -ml-1">
|
||||
<TransactionAddress
|
||||
address={txData.to}
|
||||
metadata={metadatas?.[txData.to]}
|
||||
showCodeIndicator
|
||||
/>
|
||||
<TransactionAddress address={txData.to} showCodeIndicator />
|
||||
<Copy value={txData.to} />
|
||||
</div>
|
||||
) : txData.confirmedData === undefined ? (
|
||||
|
@ -284,9 +274,6 @@ const Details: React.FC<DetailsProps> = ({
|
|||
<div className="flex items-baseline space-x-2 -ml-1">
|
||||
<TransactionAddress
|
||||
address={txData.confirmedData?.createdContractAddress!}
|
||||
metadata={
|
||||
metadatas?.[txData.confirmedData?.createdContractAddress!]
|
||||
}
|
||||
/>
|
||||
<Copy value={txData.confirmedData.createdContractAddress!} />
|
||||
</div>
|
||||
|
@ -316,7 +303,6 @@ const Details: React.FC<DetailsProps> = ({
|
|||
key={i}
|
||||
t={t}
|
||||
tokenMeta={txData.tokenMetas[t.token]}
|
||||
metadatas={metadatas}
|
||||
/>
|
||||
))}
|
||||
</InfoRow>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useMemo } from "react";
|
||||
import React, { useContext, useMemo } from "react";
|
||||
import { Log } from "@ethersproject/abstract-provider";
|
||||
import { Fragment, Interface, LogDescription } from "@ethersproject/abi";
|
||||
import { Fragment, Interface } from "@ethersproject/abi";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import TransactionAddress from "../components/TransactionAddress";
|
||||
import Copy from "../components/Copy";
|
||||
|
@ -8,16 +8,41 @@ import ModeTab from "../components/ModeTab";
|
|||
import DecodedParamsTable from "./decoder/DecodedParamsTable";
|
||||
import DecodedLogSignature from "./decoder/DecodedLogSignature";
|
||||
import { useTopic0 } from "../useTopic0";
|
||||
import { ChecksummedAddress } from "../types";
|
||||
import { Metadata } from "../sourcify/useSourcify";
|
||||
import { RuntimeContext } from "../useRuntime";
|
||||
import { useAppConfigContext } from "../useAppConfig";
|
||||
import { useSourcifyMetadata } from "../sourcify/useSourcify";
|
||||
|
||||
type LogEntryProps = {
|
||||
log: Log;
|
||||
logDesc: LogDescription | null | undefined;
|
||||
metadatas: Record<ChecksummedAddress, Metadata | null | undefined>;
|
||||
};
|
||||
|
||||
const LogEntry: React.FC<LogEntryProps> = ({ log, logDesc, metadatas }) => {
|
||||
const LogEntry: React.FC<LogEntryProps> = ({ log }) => {
|
||||
const { provider } = useContext(RuntimeContext);
|
||||
const { sourcifySource } = useAppConfigContext();
|
||||
const metadata = useSourcifyMetadata(
|
||||
log.address,
|
||||
provider?.network.chainId,
|
||||
sourcifySource
|
||||
);
|
||||
|
||||
const logDesc = useMemo(() => {
|
||||
if (!metadata) {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
const abi = metadata.output.abi;
|
||||
const intf = new Interface(abi as any);
|
||||
try {
|
||||
return intf.parseLog({
|
||||
topics: log.topics,
|
||||
data: log.data,
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn("Couldn't find function signature", err);
|
||||
return null;
|
||||
}
|
||||
}, [log, metadata]);
|
||||
|
||||
const rawTopic0 = log.topics[0];
|
||||
const topic0 = useTopic0(rawTopic0);
|
||||
|
||||
|
@ -56,10 +81,7 @@ const LogEntry: React.FC<LogEntryProps> = ({ log, logDesc, metadatas }) => {
|
|||
<div className="font-bold text-right">Address</div>
|
||||
<div className="col-span-11 mr-auto">
|
||||
<div className="flex items-baseline space-x-2 -ml-1 mr-3">
|
||||
<TransactionAddress
|
||||
address={log.address}
|
||||
metadata={metadatas[log.address]}
|
||||
/>
|
||||
<TransactionAddress address={log.address} />
|
||||
<Copy value={log.address} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,82 +1,28 @@
|
|||
import React, { useContext, useMemo } from "react";
|
||||
import { Interface } from "@ethersproject/abi";
|
||||
import React from "react";
|
||||
import ContentFrame from "../ContentFrame";
|
||||
import LogEntry from "./LogEntry";
|
||||
import { TransactionData } from "../types";
|
||||
import { Metadata } from "../sourcify/useSourcify";
|
||||
import { RuntimeContext } from "../useRuntime";
|
||||
import { useContractsMetadata } from "../hooks";
|
||||
|
||||
type LogsProps = {
|
||||
txData: TransactionData;
|
||||
metadata: Metadata | null | undefined;
|
||||
};
|
||||
|
||||
const Logs: React.FC<LogsProps> = ({ txData, metadata }) => {
|
||||
const baseMetadatas = useMemo((): Record<string, Metadata | null> => {
|
||||
if (!txData.to || metadata === undefined) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const md: Record<string, Metadata | null> = {};
|
||||
md[txData.to] = metadata;
|
||||
return md;
|
||||
}, [txData.to, metadata]);
|
||||
|
||||
const logAddresses = useMemo(
|
||||
() => txData.confirmedData?.logs.map((l) => l.address) ?? [],
|
||||
[txData]
|
||||
);
|
||||
const { provider } = useContext(RuntimeContext);
|
||||
const metadatas = useContractsMetadata(logAddresses, provider, baseMetadatas);
|
||||
|
||||
const logDescs = useMemo(() => {
|
||||
if (!txData) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return txData.confirmedData?.logs.map((l) => {
|
||||
const mt = metadatas[l.address];
|
||||
if (!mt) {
|
||||
return mt;
|
||||
}
|
||||
|
||||
const abi = mt.output.abi;
|
||||
const intf = new Interface(abi as any);
|
||||
try {
|
||||
return intf.parseLog({
|
||||
topics: l.topics,
|
||||
data: l.data,
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn("Couldn't find function signature", err);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}, [metadatas, txData]);
|
||||
|
||||
return (
|
||||
<ContentFrame tabs>
|
||||
{txData.confirmedData && (
|
||||
<>
|
||||
{txData.confirmedData.logs.length > 0 ? (
|
||||
<>
|
||||
{txData.confirmedData.logs.map((l, i) => (
|
||||
<LogEntry
|
||||
key={i}
|
||||
log={l}
|
||||
logDesc={logDescs?.[i]}
|
||||
metadatas={metadatas}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<div className="text-sm py-4">Transaction didn't emit any logs</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</ContentFrame>
|
||||
);
|
||||
};
|
||||
const Logs: React.FC<LogsProps> = ({ txData }) => (
|
||||
<ContentFrame tabs>
|
||||
{txData.confirmedData && (
|
||||
<>
|
||||
{txData.confirmedData.logs.length > 0 ? (
|
||||
<>
|
||||
{txData.confirmedData.logs.map((l, i) => (
|
||||
<LogEntry key={i} log={l} />
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<div className="text-sm py-4">Transaction didn't emit any logs</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</ContentFrame>
|
||||
);
|
||||
|
||||
export default React.memo(Logs);
|
||||
|
|
|
@ -53,6 +53,10 @@ const resolveSourcifySource = (source: SourcifySource) => {
|
|||
throw new Error(`Unknown Sourcify integration source code: ${source}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds a complete Sourcify metadata.json URL given the contract address
|
||||
* and chain.
|
||||
*/
|
||||
export const sourcifyMetadata = (
|
||||
address: ChecksummedAddress,
|
||||
chainId: number,
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
TransactionDescription,
|
||||
} from "@ethersproject/abi";
|
||||
import { BigNumberish } from "@ethersproject/bignumber";
|
||||
import { Fetcher } from "swr";
|
||||
import useSWRImmutable from "swr/immutable";
|
||||
import { RuntimeContext } from "./useRuntime";
|
||||
import { fourBytesURL } from "./url";
|
||||
|
@ -29,16 +30,32 @@ export const extract4Bytes = (rawInput: string): string | null => {
|
|||
return rawInput.slice(0, 10);
|
||||
};
|
||||
|
||||
const fetch4Bytes = async (
|
||||
assetsURLPrefix: string,
|
||||
fourBytes: string
|
||||
): Promise<FourBytesEntry | null> => {
|
||||
const signatureURL = fourBytesURL(assetsURLPrefix, fourBytes);
|
||||
type FourBytesKey = [id: "4bytes", fourBytes: string];
|
||||
type FourBytesFetcher = Fetcher<
|
||||
FourBytesEntry | null | undefined,
|
||||
FourBytesKey
|
||||
>;
|
||||
|
||||
const fourBytesFetcher =
|
||||
(assetsURLPrefix: string): FourBytesFetcher =>
|
||||
async (_, key) => {
|
||||
if (key === null || key === "0x") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Handle simple transfers with invalid selector like tx:
|
||||
// 0x8bcbdcc1589b5c34c1e55909c8269a411f0267a4fed59a73dd4348cc71addbb9,
|
||||
// which contains 0x00 as data
|
||||
if (key.length !== 10) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const fourBytes = key.slice(2);
|
||||
const signatureURL = fourBytesURL(assetsURLPrefix, fourBytes);
|
||||
|
||||
try {
|
||||
const res = await fetch(signatureURL);
|
||||
if (!res.ok) {
|
||||
console.error(`Signature does not exist in 4bytes DB: ${fourBytes}`);
|
||||
console.warn(`Signature does not exist in 4bytes DB: ${fourBytes}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -53,11 +70,7 @@ const fetch4Bytes = async (
|
|||
signature: sig,
|
||||
};
|
||||
return entry;
|
||||
} catch (err) {
|
||||
console.error(`Couldn't fetch signature URL ${signatureURL}`, err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract 4bytes DB info
|
||||
|
@ -75,26 +88,10 @@ export const use4Bytes = (
|
|||
|
||||
const { config } = useContext(RuntimeContext);
|
||||
const assetsURLPrefix = config?.assetsURLPrefix;
|
||||
const fourBytesKey = assetsURLPrefix !== undefined ? rawFourBytes : null;
|
||||
|
||||
const fourBytesFetcher = (key: string | null) => {
|
||||
if (key === null || key === "0x") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Handle simple transfers with invalid selector like tx:
|
||||
// 0x8bcbdcc1589b5c34c1e55909c8269a411f0267a4fed59a73dd4348cc71addbb9,
|
||||
// which contains 0x00 as data
|
||||
if (key.length !== 10) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return fetch4Bytes(assetsURLPrefix!, key.slice(2));
|
||||
};
|
||||
|
||||
const { data, error } = useSWRImmutable<FourBytesEntry | null | undefined>(
|
||||
assetsURLPrefix !== undefined ? rawFourBytes : null,
|
||||
fourBytesFetcher
|
||||
);
|
||||
const fetcher = fourBytesFetcher(assetsURLPrefix!);
|
||||
const { data, error } = useSWRImmutable(["4bytes", fourBytesKey], fetcher);
|
||||
return error ? undefined : data;
|
||||
};
|
||||
|
||||
|
|
|
@ -427,44 +427,6 @@ export const useTraceTransaction = (
|
|||
return traceGroups;
|
||||
};
|
||||
|
||||
const hasCode = async (
|
||||
provider: JsonRpcProvider,
|
||||
address: ChecksummedAddress
|
||||
): Promise<boolean> => {
|
||||
const result = await provider.send("ots_hasCode", [address, "latest"]);
|
||||
return result as boolean;
|
||||
};
|
||||
|
||||
export const useAddressesWithCode = (
|
||||
provider: JsonRpcProvider | undefined,
|
||||
addresses: ChecksummedAddress[]
|
||||
): ChecksummedAddress[] | undefined => {
|
||||
const [results, setResults] = useState<ChecksummedAddress[] | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
// Reset
|
||||
setResults(undefined);
|
||||
|
||||
if (provider === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const readCodes = async () => {
|
||||
const checkers: Promise<boolean>[] = [];
|
||||
for (const a of addresses) {
|
||||
checkers.push(hasCode(provider, a));
|
||||
}
|
||||
|
||||
const result = await Promise.all(checkers);
|
||||
const filtered = addresses.filter((_, i) => result[i]);
|
||||
setResults(filtered);
|
||||
};
|
||||
readCodes();
|
||||
}, [provider, addresses]);
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
// Error(string)
|
||||
const ERROR_MESSAGE_SELECTOR = "0x08c379a0";
|
||||
|
||||
|
@ -582,7 +544,6 @@ export const prefetchTransactionBySenderAndNonce = (
|
|||
}
|
||||
return getTransactionBySenderAndNonceFetcher(provider)(key);
|
||||
});
|
||||
// }
|
||||
};
|
||||
|
||||
export const useTransactionBySenderAndNonce = (
|
||||
|
@ -705,7 +666,6 @@ export const providerFetcher =
|
|||
const method = key[0];
|
||||
const args = key.slice(1);
|
||||
const result = await provider.send(method, args);
|
||||
// console.log(`providerFetcher: ${method} ${args} === ${result}`);
|
||||
return result;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { useState, useEffect, useContext } from "react";
|
||||
import { useContext } from "react";
|
||||
import useSWRImmutable from "swr/immutable";
|
||||
import { RuntimeContext } from "./useRuntime";
|
||||
import { topic0URL } from "./url";
|
||||
|
||||
|
@ -6,7 +7,28 @@ export type Topic0Entry = {
|
|||
signatures: string[] | undefined;
|
||||
};
|
||||
|
||||
const fullCache = new Map<string, Topic0Entry | null>();
|
||||
const topic0Fetcher = async (
|
||||
signatureURL: string
|
||||
): Promise<Topic0Entry | null | undefined> => {
|
||||
try {
|
||||
const res = await fetch(signatureURL);
|
||||
if (!res.ok) {
|
||||
console.error(`Signature does not exist in topic0 DB: ${signatureURL}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get only the first occurrence, for now ignore alternative param names
|
||||
const sig = await res.text();
|
||||
const sigs = sig.split(";");
|
||||
const entry: Topic0Entry = {
|
||||
signatures: sigs,
|
||||
};
|
||||
return entry;
|
||||
} catch (err) {
|
||||
console.error(`Couldn't fetch signature URL ${signatureURL}`, err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract topic0 DB info
|
||||
|
@ -26,52 +48,11 @@ export const useTopic0 = (
|
|||
const assetsURLPrefix = runtime.config?.assetsURLPrefix;
|
||||
|
||||
const topic0 = rawTopic0.slice(2);
|
||||
const [entry, setEntry] = useState<Topic0Entry | null | undefined>(
|
||||
fullCache.get(topic0)
|
||||
);
|
||||
useEffect(() => {
|
||||
if (assetsURLPrefix === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const signatureURL = topic0URL(assetsURLPrefix, topic0);
|
||||
fetch(signatureURL)
|
||||
.then(async (res) => {
|
||||
if (!res.ok) {
|
||||
console.error(`Signature does not exist in topic0 DB: ${topic0}`);
|
||||
fullCache.set(topic0, null);
|
||||
setEntry(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get only the first occurrence, for now ignore alternative param names
|
||||
const sig = await res.text();
|
||||
const sigs = sig.split(";");
|
||||
const entry: Topic0Entry = {
|
||||
signatures: sigs,
|
||||
};
|
||||
setEntry(entry);
|
||||
fullCache.set(topic0, entry);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(`Couldn't fetch signature URL ${signatureURL}`, err);
|
||||
setEntry(null);
|
||||
fullCache.set(topic0, null);
|
||||
});
|
||||
}, [topic0, assetsURLPrefix]);
|
||||
|
||||
if (assetsURLPrefix === undefined) {
|
||||
return undefined;
|
||||
const signatureURL = () =>
|
||||
assetsURLPrefix === undefined ? null : topic0URL(assetsURLPrefix, topic0);
|
||||
const { data, error } = useSWRImmutable(signatureURL, topic0Fetcher);
|
||||
if (error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try to resolve topic0 name
|
||||
if (entry === null || entry === undefined) {
|
||||
return entry;
|
||||
}
|
||||
|
||||
// Simulates LRU
|
||||
// TODO: implement LRU purging
|
||||
fullCache.delete(topic0);
|
||||
fullCache.set(topic0, entry);
|
||||
return entry;
|
||||
return data;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue