Merge branch 'feature/swr-everything' into develop

This commit is contained in:
Willian Mitsuda 2022-08-16 00:47:43 -03:00
commit 18ab4dff67
No known key found for this signature in database
21 changed files with 338 additions and 655 deletions

View File

@ -1,4 +1,4 @@
import React, { useEffect, useContext, useCallback, useMemo } from "react"; import React, { useEffect, useContext, useCallback } from "react";
import { import {
useParams, useParams,
useNavigate, useNavigate,
@ -23,9 +23,9 @@ import Contracts from "./address/Contracts";
import { RuntimeContext } from "./useRuntime"; import { RuntimeContext } from "./useRuntime";
import { useAppConfigContext } from "./useAppConfig"; import { useAppConfigContext } from "./useAppConfig";
import { useAddressOrENS } from "./useResolvedAddresses"; import { useAddressOrENS } from "./useResolvedAddresses";
import { useMultipleMetadata } from "./sourcify/useSourcify"; import { useSourcifyMetadata } from "./sourcify/useSourcify";
import { ChecksummedAddress } from "./types"; import { ChecksummedAddress } from "./types";
import { useAddressesWithCode } from "./useErigonHooks"; import { useHasCode } from "./useErigonHooks";
import { useChainInfo } from "./useChainInfo"; import { useChainInfo } from "./useChainInfo";
const AddressTransactionByNonce = React.lazy( const AddressTransactionByNonce = React.lazy(
@ -65,25 +65,13 @@ const Address: React.FC = () => {
} }
}, [addressOrName, checksummedAddress, isENS]); }, [addressOrName, checksummedAddress, isENS]);
const hasCode = useHasCode(provider, checksummedAddress, "latest");
const { sourcifySource } = useAppConfigContext(); const { sourcifySource } = useAppConfigContext();
const checksummedAddressAsArray = useMemo( const addressMetadata = useSourcifyMetadata(
() => (checksummedAddress !== undefined ? [checksummedAddress] : []), hasCode ? checksummedAddress : undefined,
[checksummedAddress]
);
const contractAddresses = useAddressesWithCode(
provider,
checksummedAddressAsArray
);
const metadatas = useMultipleMetadata(
undefined,
contractAddresses,
provider?.network.chainId, provider?.network.chainId,
sourcifySource sourcifySource
); );
const addressMetadata =
checksummedAddress !== undefined
? metadatas[checksummedAddress]
: undefined;
const { network, faucets } = useChainInfo(); const { network, faucets } = useChainInfo();
@ -134,7 +122,7 @@ const Address: React.FC = () => {
<Tab.Group> <Tab.Group>
<Tab.List className="flex space-x-2 border-l border-r border-t rounded-t-lg bg-white"> <Tab.List className="flex space-x-2 border-l border-r border-t rounded-t-lg bg-white">
<NavTab href={`/address/${addressOrName}`}>Overview</NavTab> <NavTab href={`/address/${addressOrName}`}>Overview</NavTab>
{(contractAddresses?.length ?? 0) > 0 && ( {hasCode && (
<NavTab href={`/address/${addressOrName}/contract`}> <NavTab href={`/address/${addressOrName}/contract`}>
<span <span
className={`flex items-baseline space-x-2 ${ className={`flex items-baseline space-x-2 ${
@ -181,12 +169,7 @@ const Address: React.FC = () => {
element={ element={
<Contracts <Contracts
checksummedAddress={checksummedAddress} checksummedAddress={checksummedAddress}
rawMetadata={ rawMetadata={addressMetadata}
contractAddresses !== undefined &&
contractAddresses.length === 0
? null
: addressMetadata
}
/> />
} }
/> />

View File

@ -6,28 +6,20 @@ import TransactionAddress from "./components/TransactionAddress";
import ValueHighlighter from "./components/ValueHighlighter"; import ValueHighlighter from "./components/ValueHighlighter";
import FormattedBalance from "./components/FormattedBalance"; import FormattedBalance from "./components/FormattedBalance";
import USDAmount from "./components/USDAmount"; import USDAmount from "./components/USDAmount";
import { import { AddressContext, TokenMeta, TokenTransfer } from "./types";
AddressContext,
ChecksummedAddress,
TokenMeta,
TokenTransfer,
} from "./types";
import { RuntimeContext } from "./useRuntime"; import { RuntimeContext } from "./useRuntime";
import { useBlockNumberContext } from "./useBlockTagContext"; import { useBlockNumberContext } from "./useBlockTagContext";
import { Metadata } from "./sourcify/useSourcify";
import { useTokenUSDOracle } from "./usePriceOracle"; import { useTokenUSDOracle } from "./usePriceOracle";
type TokenTransferItemProps = { type TokenTransferItemProps = {
t: TokenTransfer; t: TokenTransfer;
tokenMeta?: TokenMeta | null | undefined; tokenMeta?: TokenMeta | null | undefined;
metadatas: Record<ChecksummedAddress, Metadata | null | undefined>;
}; };
// TODO: handle partial // TODO: handle partial
const TokenTransferItem: React.FC<TokenTransferItemProps> = ({ const TokenTransferItem: React.FC<TokenTransferItemProps> = ({
t, t,
tokenMeta, tokenMeta,
metadatas,
}) => { }) => {
const { provider } = useContext(RuntimeContext); const { provider } = useContext(RuntimeContext);
const blockNumber = useBlockNumberContext(); const blockNumber = useBlockNumberContext();
@ -40,7 +32,6 @@ const TokenTransferItem: React.FC<TokenTransferItemProps> = ({
<TransactionAddress <TransactionAddress
address={t.from} address={t.from}
addressCtx={AddressContext.FROM} addressCtx={AddressContext.FROM}
metadata={metadatas[t.from]}
showCodeIndicator showCodeIndicator
/> />
</div> </div>
@ -51,7 +42,6 @@ const TokenTransferItem: React.FC<TokenTransferItemProps> = ({
<TransactionAddress <TransactionAddress
address={t.to} address={t.to}
addressCtx={AddressContext.TO} addressCtx={AddressContext.TO}
metadata={metadatas[t.to]}
showCodeIndicator showCodeIndicator
/> />
</div> </div>
@ -67,7 +57,7 @@ const TokenTransferItem: React.FC<TokenTransferItemProps> = ({
/> />
</ValueHighlighter> </ValueHighlighter>
</span> </span>
<TransactionAddress address={t.token} metadata={metadatas[t.token]} /> <TransactionAddress address={t.token} />
{tokenMeta && quote !== undefined && decimals !== undefined && ( {tokenMeta && quote !== undefined && decimals !== undefined && (
<span className="px-2 border-gray-200 border rounded-lg bg-gray-100 text-gray-600"> <span className="px-2 border-gray-200 border rounded-lg bg-gray-100 text-gray-600">
<USDAmount <USDAmount

View File

@ -1,13 +1,71 @@
import React from "react"; import React, { useContext } from "react";
import { useParams } from "react-router-dom"; import { useParams, Route, Routes } from "react-router-dom";
import TransactionPageContent from "./TransactionPageContent"; 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 Transaction: React.FC = () => {
const { txhash } = useParams(); const { txhash: txHash } = useParams();
if (txhash === undefined) { if (txHash === undefined) {
throw new Error("txhash couldn't be undefined here"); 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; export default Transaction;

View File

@ -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;

View File

@ -18,7 +18,6 @@ import { useMultipleETHUSDOracle } from "../usePriceOracle";
import { RuntimeContext } from "../useRuntime"; import { RuntimeContext } from "../useRuntime";
import { useParams, useSearchParams } from "react-router-dom"; import { useParams, useSearchParams } from "react-router-dom";
import { ChecksummedAddress, ProcessedTransaction } from "../types"; import { ChecksummedAddress, ProcessedTransaction } from "../types";
import { useContractsMetadata } from "../hooks";
import { useAddressBalance, useContractCreator } from "../useErigonHooks"; import { useAddressBalance, useContractCreator } from "../useErigonHooks";
import { BlockNumberContext } from "../useBlockTagContext"; import { BlockNumberContext } from "../useBlockTagContext";
@ -112,22 +111,6 @@ const AddressTransactionResults: React.FC<AddressTransactionResultsProps> = ({
}, [page]); }, [page]);
const priceMap = useMultipleETHUSDOracle(provider, blockTags); 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 balance = useAddressBalance(provider, address);
const creator = useContractCreator(provider, address); const creator = useContractCreator(provider, address);
@ -181,7 +164,6 @@ const AddressTransactionResults: React.FC<AddressTransactionResultsProps> = ({
selectedAddress={address} selectedAddress={address}
feeDisplay={feeDisplay} feeDisplay={feeDisplay}
priceMap={priceMap} priceMap={priceMap}
metadatas={metadatas}
/> />
))} ))}
<NavBar address={address} page={page} controller={controller} /> <NavBar address={address} page={page} controller={controller} />

View File

@ -1,31 +1,11 @@
import React from "react"; import React from "react";
import { SyntaxHighlighter, docco } from "../highlight-init"; import { SyntaxHighlighter, docco } from "../highlight-init";
import { useContract } from "../sourcify/useSourcify";
import { useAppConfigContext } from "../useAppConfig";
type ContractProps = { type ContractProps = {
checksummedAddress: string; content: any;
networkId: number;
filename: string;
source: any;
}; };
const Contract: React.FC<ContractProps> = ({ const Contract: React.FC<ContractProps> = ({ content }) => (
checksummedAddress,
networkId,
filename,
source,
}) => {
const { sourcifySource } = useAppConfigContext();
const content = useContract(
checksummedAddress,
networkId,
filename,
source,
sourcifySource
);
return (
<SyntaxHighlighter <SyntaxHighlighter
className="w-full h-full border font-code text-base" className="w-full h-full border font-code text-base"
language="solidity" language="solidity"
@ -35,6 +15,5 @@ const Contract: React.FC<ContractProps> = ({
{content ?? ""} {content ?? ""}
</SyntaxHighlighter> </SyntaxHighlighter>
); );
};
export default React.memo(Contract); export default React.memo(Contract);

View File

@ -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);

View File

@ -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 { commify } from "@ethersproject/units";
import { Menu } from "@headlessui/react"; import { Menu } from "@headlessui/react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -6,6 +6,7 @@ import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
import ContentFrame from "../ContentFrame"; import ContentFrame from "../ContentFrame";
import InfoRow from "../components/InfoRow"; import InfoRow from "../components/InfoRow";
import Contract from "./Contract"; import Contract from "./Contract";
import ContractFromRepo from "./ContractFromRepo";
import { RuntimeContext } from "../useRuntime"; import { RuntimeContext } from "../useRuntime";
import { Metadata } from "../sourcify/useSourcify"; import { Metadata } from "../sourcify/useSourcify";
import ExternalLink from "../components/ExternalLink"; import ExternalLink from "../components/ExternalLink";
@ -101,7 +102,7 @@ const Contracts: React.FC<ContractsProps> = ({
className={`flex text-sm px-2 py-1 ${ className={`flex text-sm px-2 py-1 ${
selected === k selected === k
? "font-bold bg-gray-200 text-gray-500" ? "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)} onClick={() => setSelected(k)}
> >
@ -113,13 +114,18 @@ const Contracts: React.FC<ContractsProps> = ({
</div> </div>
</Menu> </Menu>
{selected && ( {selected && (
<Contract <>
{rawMetadata.sources[selected].content ? (
<Contract content={rawMetadata.sources[selected].content} />
) : (
<ContractFromRepo
checksummedAddress={checksummedAddress} checksummedAddress={checksummedAddress}
networkId={provider!.network.chainId} networkId={provider!.network.chainId}
filename={selected} filename={selected}
source={rawMetadata.sources[selected]}
/> />
)} )}
</>
)}
</div> </div>
</> </>
)} )}

View File

@ -8,10 +8,9 @@ import TransactionItem from "../search/TransactionItem";
import { useFeeToggler } from "../search/useFeeToggler"; import { useFeeToggler } from "../search/useFeeToggler";
import { RuntimeContext } from "../useRuntime"; import { RuntimeContext } from "../useRuntime";
import { SelectionContext, useSelection } from "../useSelection"; import { SelectionContext, useSelection } from "../useSelection";
import { ChecksummedAddress, ProcessedTransaction } from "../types"; import { ProcessedTransaction } from "../types";
import { PAGE_SIZE } from "../params"; import { PAGE_SIZE } from "../params";
import { useMultipleETHUSDOracle } from "../usePriceOracle"; import { useMultipleETHUSDOracle } from "../usePriceOracle";
import { useContractsMetadata } from "../hooks";
type BlockTransactionResultsProps = { type BlockTransactionResultsProps = {
blockTag: BlockTag; blockTag: BlockTag;
@ -32,24 +31,6 @@ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({
const blockTags = useMemo(() => [blockTag], [blockTag]); const blockTags = useMemo(() => [blockTag], [blockTag]);
const priceMap = useMultipleETHUSDOracle(provider, blockTags); 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 ( return (
<ContentFrame> <ContentFrame>
<div className="flex justify-between items-baseline py-3"> <div className="flex justify-between items-baseline py-3">
@ -78,7 +59,6 @@ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({
tx={tx} tx={tx}
feeDisplay={feeDisplay} feeDisplay={feeDisplay}
priceMap={priceMap} priceMap={priceMap}
metadatas={metadatas}
/> />
))} ))}
<div className="flex justify-between items-baseline py-3"> <div className="flex justify-between items-baseline py-3">

View File

@ -8,8 +8,9 @@ import { faBurn } from "@fortawesome/free-solid-svg-icons/faBurn";
import { faCoins } from "@fortawesome/free-solid-svg-icons/faCoins"; import { faCoins } from "@fortawesome/free-solid-svg-icons/faCoins";
import SourcifyLogo from "../sourcify/SourcifyLogo"; import SourcifyLogo from "../sourcify/SourcifyLogo";
import PlainAddress from "./PlainAddress"; import PlainAddress from "./PlainAddress";
import { Metadata } from "../sourcify/useSourcify";
import { RuntimeContext } from "../useRuntime"; import { RuntimeContext } from "../useRuntime";
import { useAppConfigContext } from "../useAppConfig";
import { useSourcifyMetadata } from "../sourcify/useSourcify";
import { useResolvedAddress } from "../useResolvedAddresses"; import { useResolvedAddress } from "../useResolvedAddresses";
import { AddressContext, ChecksummedAddress, ZERO_ADDRESS } from "../types"; import { AddressContext, ChecksummedAddress, ZERO_ADDRESS } from "../types";
import { resolverRendererRegistry } from "../api/address-resolver"; import { resolverRendererRegistry } from "../api/address-resolver";
@ -23,7 +24,6 @@ type DecoratedAddressLinkProps = {
selfDestruct?: boolean | undefined; selfDestruct?: boolean | undefined;
txFrom?: boolean | undefined; txFrom?: boolean | undefined;
txTo?: boolean | undefined; txTo?: boolean | undefined;
metadata?: Metadata | null | undefined;
eoa?: boolean | undefined; eoa?: boolean | undefined;
}; };
@ -36,9 +36,16 @@ const DecoratedAddressLink: React.FC<DecoratedAddressLinkProps> = ({
selfDestruct, selfDestruct,
txFrom, txFrom,
txTo, txTo,
metadata,
eoa, 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 mint = addressCtx === AddressContext.FROM && address === ZERO_ADDRESS;
const burn = addressCtx === AddressContext.TO && address === ZERO_ADDRESS; const burn = addressCtx === AddressContext.TO && address === ZERO_ADDRESS;

View File

@ -5,20 +5,17 @@ import { useSelectedTransaction } from "../useSelectedTransaction";
import { useBlockNumberContext } from "../useBlockTagContext"; import { useBlockNumberContext } from "../useBlockTagContext";
import { RuntimeContext } from "../useRuntime"; import { RuntimeContext } from "../useRuntime";
import { useHasCode } from "../useErigonHooks"; import { useHasCode } from "../useErigonHooks";
import { Metadata } from "../sourcify/useSourcify";
import { AddressContext, ChecksummedAddress } from "../types"; import { AddressContext, ChecksummedAddress } from "../types";
type TransactionAddressProps = { type TransactionAddressProps = {
address: ChecksummedAddress; address: ChecksummedAddress;
addressCtx?: AddressContext | undefined; addressCtx?: AddressContext | undefined;
metadata?: Metadata | null | undefined;
showCodeIndicator?: boolean; showCodeIndicator?: boolean;
}; };
const TransactionAddress: React.FC<TransactionAddressProps> = ({ const TransactionAddress: React.FC<TransactionAddressProps> = ({
address, address,
addressCtx, addressCtx,
metadata,
showCodeIndicator = false, showCodeIndicator = false,
}) => { }) => {
const txData = useSelectedTransaction(); const txData = useSelectedTransaction();
@ -46,7 +43,6 @@ const TransactionAddress: React.FC<TransactionAddressProps> = ({
txFrom={address === txData?.from} txFrom={address === txData?.from}
txTo={address === txData?.to || creation} txTo={address === txData?.to || creation}
creation={creation} creation={creation}
metadata={metadata}
eoa={ eoa={
showCodeIndicator && blockNumber !== undefined showCodeIndicator && blockNumber !== undefined
? !toHasCode ? !toHasCode

View File

@ -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;
};

View File

@ -14,20 +14,18 @@ import TransactionDirection, {
Flags, Flags,
} from "../components/TransactionDirection"; } from "../components/TransactionDirection";
import TransactionValue from "../components/TransactionValue"; import TransactionValue from "../components/TransactionValue";
import { ChecksummedAddress, ProcessedTransaction } from "../types"; import { ProcessedTransaction } from "../types";
import { FeeDisplay } from "./useFeeToggler"; import { FeeDisplay } from "./useFeeToggler";
import { RuntimeContext } from "../useRuntime"; import { RuntimeContext } from "../useRuntime";
import { useHasCode } from "../useErigonHooks"; import { useHasCode } from "../useErigonHooks";
import { formatValue } from "../components/formatter"; import { formatValue } from "../components/formatter";
import ETH2USDValue from "../components/ETH2USDValue"; import ETH2USDValue from "../components/ETH2USDValue";
import { Metadata } from "../sourcify/useSourcify";
type TransactionItemProps = { type TransactionItemProps = {
tx: ProcessedTransaction; tx: ProcessedTransaction;
selectedAddress?: string; selectedAddress?: string;
feeDisplay: FeeDisplay; feeDisplay: FeeDisplay;
priceMap: Record<BlockTag, BigNumber>; priceMap: Record<BlockTag, BigNumber>;
metadatas: Record<ChecksummedAddress, Metadata | null | undefined>;
}; };
const TransactionItem: React.FC<TransactionItemProps> = ({ const TransactionItem: React.FC<TransactionItemProps> = ({
@ -35,7 +33,6 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
selectedAddress, selectedAddress,
feeDisplay, feeDisplay,
priceMap, priceMap,
metadatas,
}) => { }) => {
const { provider } = useContext(RuntimeContext); const { provider } = useContext(RuntimeContext);
const toHasCode = useHasCode( const toHasCode = useHasCode(
@ -113,7 +110,6 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
address={tx.to} address={tx.to}
selectedAddress={selectedAddress} selectedAddress={selectedAddress}
miner={tx.miner === tx.to} miner={tx.miner === tx.to}
metadata={metadatas[tx.to]}
eoa={toHasCode === undefined ? undefined : !toHasCode} eoa={toHasCode === undefined ? undefined : !toHasCode}
/> />
</AddressHighlighter> </AddressHighlighter>
@ -123,7 +119,6 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
address={tx.createdContractAddress!} address={tx.createdContractAddress!}
selectedAddress={selectedAddress} selectedAddress={selectedAddress}
creation creation
metadata={metadatas[tx.createdContractAddress!]}
eoa={false} eoa={false}
/> />
</AddressHighlighter> </AddressHighlighter>

View File

@ -1,6 +1,7 @@
import { useState, useEffect, useMemo } from "react"; import { useMemo } from "react";
import { Interface } from "@ethersproject/abi"; import { Interface } from "@ethersproject/abi";
import { ErrorDescription } from "@ethersproject/abi/lib/interface"; import { ErrorDescription } from "@ethersproject/abi/lib/interface";
import useSWRImmutable from "swr/immutable";
import { ChecksummedAddress, TransactionData } from "../types"; import { ChecksummedAddress, TransactionData } from "../types";
import { sourcifyMetadata, SourcifySource, sourcifySourceFile } from "../url"; import { sourcifyMetadata, SourcifySource, sourcifySourceFile } from "../url";
@ -80,127 +81,54 @@ export type Metadata = {
}; };
}; };
const fetchSourcifyMetadata = async ( const sourcifyFetcher = async (url: string) => {
address: ChecksummedAddress,
chainId: number,
source: SourcifySource,
abortController: AbortController
): Promise<Metadata | null> => {
try { try {
const metadataURL = sourcifyMetadata(address, chainId, source); const res = await fetch(url);
const result = await fetch(metadataURL, { if (res.ok) {
signal: abortController.signal, return res.json();
});
if (result.ok) {
return await result.json();
} }
return null; return null;
} catch (err) { } catch (err) {
console.error(err); console.warn(
`error while getting Sourcify metadata: url=${url} err=${err}`
);
return null; return null;
} }
}; };
// TODO: replace every occurrence with the multiple version one export const useSourcifyMetadata = (
export const useSourcify = (
address: ChecksummedAddress | undefined, address: ChecksummedAddress | undefined,
chainId: number | undefined, chainId: number | undefined,
source: SourcifySource source: SourcifySource
): Metadata | null | undefined => { ): Metadata | null | undefined => {
const [rawMetadata, setRawMetadata] = useState<Metadata | null | undefined>(); const metadataURL = () =>
address === undefined || chainId === undefined
useEffect(() => { ? null
if (!address || chainId === undefined) { : sourcifyMetadata(address, chainId, source);
return; const { data, error } = useSWRImmutable<Metadata>(
} metadataURL,
setRawMetadata(undefined); sourcifyFetcher
const abortController = new AbortController();
const fetchMetadata = async () => {
const _metadata = await fetchSourcifyMetadata(
address,
chainId,
source,
abortController
); );
setRawMetadata(_metadata); if (error) {
}; return null;
fetchMetadata(); }
return data;
return () => {
abortController.abort();
};
}, [address, chainId, source]);
return rawMetadata;
}; };
export const useMultipleMetadata = ( const contractFetcher = async (url: string): Promise<string | null> => {
baseMetadatas: Record<string, Metadata | null> | undefined, const res = await fetch(url);
addresses: ChecksummedAddress[] | undefined, if (res.ok) {
chainId: number | undefined, return await res.text();
source: SourcifySource
): Record<ChecksummedAddress, Metadata | null | undefined> => {
const [rawMetadata, setRawMetadata] = useState<
Record<string, Metadata | null | undefined>
>({});
useEffect(() => {
if (addresses === undefined || chainId === undefined) {
return;
} }
setRawMetadata({}); return null;
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;
}; };
export const useContract = ( export const useContract = (
checksummedAddress: string, checksummedAddress: string,
networkId: number, networkId: number,
filename: string, filename: string,
source: any,
sourcifySource: SourcifySource sourcifySource: SourcifySource
) => { ) => {
const [content, setContent] = useState<string>(source.content);
useEffect(() => {
if (source.content) {
return;
}
const abortController = new AbortController();
const readContent = async () => {
const normalizedFilename = filename.replaceAll(/[@:]/g, "_"); const normalizedFilename = filename.replaceAll(/[@:]/g, "_");
const url = sourcifySourceFile( const url = sourcifySourceFile(
checksummedAddress, checksummedAddress,
@ -208,20 +136,12 @@ export const useContract = (
normalizedFilename, normalizedFilename,
sourcifySource sourcifySource
); );
const res = await fetch(url, { signal: abortController.signal });
if (res.ok) { const { data, error } = useSWRImmutable(url, contractFetcher);
const _content = await res.text(); if (error) {
setContent(_content); return undefined;
} }
}; return data;
readContent();
return () => {
abortController.abort();
};
}, [checksummedAddress, networkId, filename, source.content, sourcifySource]);
return content;
}; };
export const useTransactionDescription = ( export const useTransactionDescription = (

View File

@ -1,6 +1,5 @@
import React, { useContext, useMemo, useState } from "react"; import React, { useContext, useMemo, useState } from "react";
import { Tab } from "@headlessui/react"; import { Tab } from "@headlessui/react";
import { TransactionDescription } from "@ethersproject/abi";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheckCircle } from "@fortawesome/free-solid-svg-icons/faCheckCircle"; import { faCheckCircle } from "@fortawesome/free-solid-svg-icons/faCheckCircle";
import { faCube } from "@fortawesome/free-solid-svg-icons/faCube"; import { faCube } from "@fortawesome/free-solid-svg-icons/faCube";
@ -25,11 +24,7 @@ import USDValue from "../components/USDValue";
import FormattedBalance from "../components/FormattedBalance"; import FormattedBalance from "../components/FormattedBalance";
import ETH2USDValue from "../components/ETH2USDValue"; import ETH2USDValue from "../components/ETH2USDValue";
import TokenTransferItem from "../TokenTransferItem"; import TokenTransferItem from "../TokenTransferItem";
import { import { TransactionData } from "../types";
TransactionData,
InternalOperation,
ChecksummedAddress,
} from "../types";
import PercentageBar from "../components/PercentageBar"; import PercentageBar from "../components/PercentageBar";
import ExternalLink from "../components/ExternalLink"; import ExternalLink from "../components/ExternalLink";
import RelativePosition from "../components/RelativePosition"; import RelativePosition from "../components/RelativePosition";
@ -41,32 +36,24 @@ import {
use4Bytes, use4Bytes,
useTransactionDescription, useTransactionDescription,
} from "../use4Bytes"; } 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 { RuntimeContext } from "../useRuntime";
import { useContractsMetadata } from "../hooks"; import { useInternalOperations, useTransactionError } from "../useErigonHooks";
import { useTransactionError } from "../useErigonHooks";
import { useChainInfo } from "../useChainInfo"; import { useChainInfo } from "../useChainInfo";
import { useETHUSDOracle } from "../usePriceOracle"; import { useETHUSDOracle } from "../usePriceOracle";
type DetailsProps = { type DetailsProps = {
txData: TransactionData; 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> = ({ const Details: React.FC<DetailsProps> = ({ txData }) => {
txData, const { provider } = useContext(RuntimeContext);
txDesc,
toMetadata,
userDoc,
devDoc,
internalOps,
sendsEthToMiner,
}) => {
const hasEIP1559 = const hasEIP1559 =
txData.confirmedData?.blockBaseFeePerGas !== undefined && txData.confirmedData?.blockBaseFeePerGas !== undefined &&
txData.confirmedData?.blockBaseFeePerGas !== null; txData.confirmedData?.blockBaseFeePerGas !== null;
@ -80,11 +67,34 @@ const Details: React.FC<DetailsProps> = ({
txData.value 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 resolvedTxDesc = txDesc ?? fourBytesTxDesc;
const userMethod = txDesc ? userDoc?.methods[txDesc.signature] : undefined; const userMethod = txDesc ? userDoc?.methods[txDesc.signature] : undefined;
const devMethod = txDesc ? devDoc?.methods[txDesc.signature] : undefined; const devMethod = txDesc ? devDoc?.methods[txDesc.signature] : undefined;
const { provider } = useContext(RuntimeContext);
const { const {
nativeCurrency: { name, symbol }, nativeCurrency: { name, symbol },
} = useChainInfo(); } = useChainInfo();
@ -94,28 +104,12 @@ const Details: React.FC<DetailsProps> = ({
txData?.confirmedData?.blockNumber 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( const [errorMsg, outputData, isCustomError] = useTransactionError(
provider, provider,
txData.transactionHash txData.transactionHash
); );
const errorDescription = useError( const errorDescription = useError(
toMetadata, metadata,
isCustomError ? outputData : undefined isCustomError ? outputData : undefined
); );
const userError = errorDescription const userError = errorDescription
@ -269,11 +263,7 @@ const Details: React.FC<DetailsProps> = ({
<InfoRow title={txData.to ? "Interacted With (To)" : "Contract Created"}> <InfoRow title={txData.to ? "Interacted With (To)" : "Contract Created"}>
{txData.to ? ( {txData.to ? (
<div className="flex items-baseline space-x-2 -ml-1"> <div className="flex items-baseline space-x-2 -ml-1">
<TransactionAddress <TransactionAddress address={txData.to} showCodeIndicator />
address={txData.to}
metadata={metadatas?.[txData.to]}
showCodeIndicator
/>
<Copy value={txData.to} /> <Copy value={txData.to} />
</div> </div>
) : txData.confirmedData === undefined ? ( ) : txData.confirmedData === undefined ? (
@ -284,9 +274,6 @@ const Details: React.FC<DetailsProps> = ({
<div className="flex items-baseline space-x-2 -ml-1"> <div className="flex items-baseline space-x-2 -ml-1">
<TransactionAddress <TransactionAddress
address={txData.confirmedData?.createdContractAddress!} address={txData.confirmedData?.createdContractAddress!}
metadata={
metadatas?.[txData.confirmedData?.createdContractAddress!]
}
/> />
<Copy value={txData.confirmedData.createdContractAddress!} /> <Copy value={txData.confirmedData.createdContractAddress!} />
</div> </div>
@ -316,7 +303,6 @@ const Details: React.FC<DetailsProps> = ({
key={i} key={i}
t={t} t={t}
tokenMeta={txData.tokenMetas[t.token]} tokenMeta={txData.tokenMetas[t.token]}
metadatas={metadatas}
/> />
))} ))}
</InfoRow> </InfoRow>

View File

@ -1,6 +1,6 @@
import React, { useMemo } from "react"; import React, { useContext, useMemo } from "react";
import { Log } from "@ethersproject/abstract-provider"; 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 { Tab } from "@headlessui/react";
import TransactionAddress from "../components/TransactionAddress"; import TransactionAddress from "../components/TransactionAddress";
import Copy from "../components/Copy"; import Copy from "../components/Copy";
@ -8,16 +8,41 @@ import ModeTab from "../components/ModeTab";
import DecodedParamsTable from "./decoder/DecodedParamsTable"; import DecodedParamsTable from "./decoder/DecodedParamsTable";
import DecodedLogSignature from "./decoder/DecodedLogSignature"; import DecodedLogSignature from "./decoder/DecodedLogSignature";
import { useTopic0 } from "../useTopic0"; import { useTopic0 } from "../useTopic0";
import { ChecksummedAddress } from "../types"; import { RuntimeContext } from "../useRuntime";
import { Metadata } from "../sourcify/useSourcify"; import { useAppConfigContext } from "../useAppConfig";
import { useSourcifyMetadata } from "../sourcify/useSourcify";
type LogEntryProps = { type LogEntryProps = {
log: Log; 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 rawTopic0 = log.topics[0];
const topic0 = useTopic0(rawTopic0); 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="font-bold text-right">Address</div>
<div className="col-span-11 mr-auto"> <div className="col-span-11 mr-auto">
<div className="flex items-baseline space-x-2 -ml-1 mr-3"> <div className="flex items-baseline space-x-2 -ml-1 mr-3">
<TransactionAddress <TransactionAddress address={log.address} />
address={log.address}
metadata={metadatas[log.address]}
/>
<Copy value={log.address} /> <Copy value={log.address} />
</div> </div>
</div> </div>

View File

@ -1,73 +1,20 @@
import React, { useContext, useMemo } from "react"; import React from "react";
import { Interface } from "@ethersproject/abi";
import ContentFrame from "../ContentFrame"; import ContentFrame from "../ContentFrame";
import LogEntry from "./LogEntry"; import LogEntry from "./LogEntry";
import { TransactionData } from "../types"; import { TransactionData } from "../types";
import { Metadata } from "../sourcify/useSourcify";
import { RuntimeContext } from "../useRuntime";
import { useContractsMetadata } from "../hooks";
type LogsProps = { type LogsProps = {
txData: TransactionData; txData: TransactionData;
metadata: Metadata | null | undefined;
}; };
const Logs: React.FC<LogsProps> = ({ txData, metadata }) => { const Logs: React.FC<LogsProps> = ({ txData }) => (
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> <ContentFrame tabs>
{txData.confirmedData && ( {txData.confirmedData && (
<> <>
{txData.confirmedData.logs.length > 0 ? ( {txData.confirmedData.logs.length > 0 ? (
<> <>
{txData.confirmedData.logs.map((l, i) => ( {txData.confirmedData.logs.map((l, i) => (
<LogEntry <LogEntry key={i} log={l} />
key={i}
log={l}
logDesc={logDescs?.[i]}
metadatas={metadatas}
/>
))} ))}
</> </>
) : ( ) : (
@ -77,6 +24,5 @@ const Logs: React.FC<LogsProps> = ({ txData, metadata }) => {
)} )}
</ContentFrame> </ContentFrame>
); );
};
export default React.memo(Logs); export default React.memo(Logs);

View File

@ -53,6 +53,10 @@ const resolveSourcifySource = (source: SourcifySource) => {
throw new Error(`Unknown Sourcify integration source code: ${source}`); 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 = ( export const sourcifyMetadata = (
address: ChecksummedAddress, address: ChecksummedAddress,
chainId: number, chainId: number,

View File

@ -5,6 +5,7 @@ import {
TransactionDescription, TransactionDescription,
} from "@ethersproject/abi"; } from "@ethersproject/abi";
import { BigNumberish } from "@ethersproject/bignumber"; import { BigNumberish } from "@ethersproject/bignumber";
import { Fetcher } from "swr";
import useSWRImmutable from "swr/immutable"; import useSWRImmutable from "swr/immutable";
import { RuntimeContext } from "./useRuntime"; import { RuntimeContext } from "./useRuntime";
import { fourBytesURL } from "./url"; import { fourBytesURL } from "./url";
@ -29,16 +30,32 @@ export const extract4Bytes = (rawInput: string): string | null => {
return rawInput.slice(0, 10); return rawInput.slice(0, 10);
}; };
const fetch4Bytes = async ( type FourBytesKey = [id: "4bytes", fourBytes: string];
assetsURLPrefix: string, type FourBytesFetcher = Fetcher<
fourBytes: string FourBytesEntry | null | undefined,
): Promise<FourBytesEntry | null> => { 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); const signatureURL = fourBytesURL(assetsURLPrefix, fourBytes);
try {
const res = await fetch(signatureURL); const res = await fetch(signatureURL);
if (!res.ok) { 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; return null;
} }
@ -53,10 +70,6 @@ const fetch4Bytes = async (
signature: sig, signature: sig,
}; };
return entry; return entry;
} catch (err) {
console.error(`Couldn't fetch signature URL ${signatureURL}`, err);
return null;
}
}; };
/** /**
@ -75,26 +88,10 @@ export const use4Bytes = (
const { config } = useContext(RuntimeContext); const { config } = useContext(RuntimeContext);
const assetsURLPrefix = config?.assetsURLPrefix; const assetsURLPrefix = config?.assetsURLPrefix;
const fourBytesKey = assetsURLPrefix !== undefined ? rawFourBytes : null;
const fourBytesFetcher = (key: string | null) => { const fetcher = fourBytesFetcher(assetsURLPrefix!);
if (key === null || key === "0x") { const { data, error } = useSWRImmutable(["4bytes", fourBytesKey], fetcher);
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
);
return error ? undefined : data; return error ? undefined : data;
}; };

View File

@ -427,44 +427,6 @@ export const useTraceTransaction = (
return traceGroups; 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) // Error(string)
const ERROR_MESSAGE_SELECTOR = "0x08c379a0"; const ERROR_MESSAGE_SELECTOR = "0x08c379a0";
@ -582,7 +544,6 @@ export const prefetchTransactionBySenderAndNonce = (
} }
return getTransactionBySenderAndNonceFetcher(provider)(key); return getTransactionBySenderAndNonceFetcher(provider)(key);
}); });
// }
}; };
export const useTransactionBySenderAndNonce = ( export const useTransactionBySenderAndNonce = (
@ -705,7 +666,6 @@ export const providerFetcher =
const method = key[0]; const method = key[0];
const args = key.slice(1); const args = key.slice(1);
const result = await provider.send(method, args); const result = await provider.send(method, args);
// console.log(`providerFetcher: ${method} ${args} === ${result}`);
return result; return result;
}; };

View File

@ -1,4 +1,5 @@
import { useState, useEffect, useContext } from "react"; import { useContext } from "react";
import useSWRImmutable from "swr/immutable";
import { RuntimeContext } from "./useRuntime"; import { RuntimeContext } from "./useRuntime";
import { topic0URL } from "./url"; import { topic0URL } from "./url";
@ -6,7 +7,28 @@ export type Topic0Entry = {
signatures: string[] | undefined; 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 * Extract topic0 DB info
@ -26,52 +48,11 @@ export const useTopic0 = (
const assetsURLPrefix = runtime.config?.assetsURLPrefix; const assetsURLPrefix = runtime.config?.assetsURLPrefix;
const topic0 = rawTopic0.slice(2); const topic0 = rawTopic0.slice(2);
const [entry, setEntry] = useState<Topic0Entry | null | undefined>( const signatureURL = () =>
fullCache.get(topic0) assetsURLPrefix === undefined ? null : topic0URL(assetsURLPrefix, topic0);
); const { data, error } = useSWRImmutable(signatureURL, topic0Fetcher);
useEffect(() => { if (error) {
if (assetsURLPrefix === undefined) { return null;
return;
} }
return data;
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;
}
// 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;
}; };