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 {
|
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
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 { 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} />
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 { 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>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
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,
|
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>
|
||||||
|
|
|
@ -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 = (
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue