From 47cbe36c8f81b8aa504c2a5db39958b0d8459cfb Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 27 Dec 2021 15:48:46 -0300 Subject: [PATCH 01/29] First working iteration of sender/nonce navigation --- src/components/TransactionLink.tsx | 3 +- src/params.ts | 2 +- src/transaction/Details.tsx | 6 ++++ src/transaction/NavButton.tsx | 33 ++++++++++++++++++ src/transaction/NavNonce.tsx | 56 ++++++++++++++++++++++++++++++ src/url.ts | 2 ++ src/useErigonHooks.ts | 40 +++++++++++++++++++++ 7 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 src/transaction/NavButton.tsx create mode 100644 src/transaction/NavNonce.tsx diff --git a/src/components/TransactionLink.tsx b/src/components/TransactionLink.tsx index 8db23f3..06fde64 100644 --- a/src/components/TransactionLink.tsx +++ b/src/components/TransactionLink.tsx @@ -1,5 +1,6 @@ import React from "react"; import { NavLink } from "react-router-dom"; +import { transactionURL } from "../url"; type TransactionLinkProps = { txHash: string; @@ -8,7 +9,7 @@ type TransactionLinkProps = { const TransactionLink: React.FC = ({ txHash }) => (

{txHash}

diff --git a/src/params.ts b/src/params.ts index d1464c6..b1ef13b 100644 --- a/src/params.ts +++ b/src/params.ts @@ -1,3 +1,3 @@ -export const MIN_API_LEVEL = 5; +export const MIN_API_LEVEL = 6; export const PAGE_SIZE = 25; diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index 695a686..8c0d779 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -15,6 +15,7 @@ import BlockConfirmations from "../components/BlockConfirmations"; import TransactionAddress from "../components/TransactionAddress"; import Copy from "../components/Copy"; import Nonce from "../components/Nonce"; +import NavNonce from "./NavNonce"; import Timestamp from "../components/Timestamp"; import InternalTransactionOperation from "../components/InternalTransactionOperation"; import MethodName from "../components/MethodName"; @@ -253,6 +254,11 @@ const Details: React.FC = ({
+
diff --git a/src/transaction/NavButton.tsx b/src/transaction/NavButton.tsx new file mode 100644 index 0000000..34f847d --- /dev/null +++ b/src/transaction/NavButton.tsx @@ -0,0 +1,33 @@ +import { NavLink } from "react-router-dom"; +import { transactionURL } from "../url"; + +// TODO: extract common component with block/NavButton +type NavButtonProps = { + txHash: string | undefined; + disabled?: boolean; +}; + +const NavButton: React.FC = ({ + txHash, + disabled, + children, +}) => { + if (disabled || txHash === undefined) { + return ( + + {children} + + ); + } + + return ( + + {children} + + ); +}; + +export default NavButton; diff --git a/src/transaction/NavNonce.tsx b/src/transaction/NavNonce.tsx new file mode 100644 index 0000000..f17c5cc --- /dev/null +++ b/src/transaction/NavNonce.tsx @@ -0,0 +1,56 @@ +import React, { useContext } from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faChevronLeft } from "@fortawesome/free-solid-svg-icons/faChevronLeft"; +import { faChevronRight } from "@fortawesome/free-solid-svg-icons/faChevronRight"; +import NavButton from "./NavButton"; +import { ChecksummedAddress } from "../types"; +import { RuntimeContext } from "../useRuntime"; +import { useTransactionBySenderAndNonce } from "../useErigonHooks"; + +type NavNonceProps = { + sender: ChecksummedAddress; + nonce: number; + latestBlockNumber: number | undefined; +}; + +const NavNonce: React.FC = ({ + sender, + nonce, + latestBlockNumber, +}) => { + const { provider } = useContext(RuntimeContext); + const prevTxHash = useTransactionBySenderAndNonce( + provider, + sender, + nonce - 1 + ); + const nextTxHash = useTransactionBySenderAndNonce( + provider, + sender, + nonce + 1 + ); + const lastTxHash = nextTxHash; + + return ( +
+ + + + = latestBlockNumber} + > + + + = latestBlockNumber} + > + + + +
+ ); +}; + +export default React.memo(NavNonce); diff --git a/src/url.ts b/src/url.ts index 471c9d8..2216bf6 100644 --- a/src/url.ts +++ b/src/url.ts @@ -18,6 +18,8 @@ export const blockURL = (blockNum: BlockTag) => `/block/${blockNum}`; export const blockTxsURL = (blockNum: BlockTag) => `/block/${blockNum}/txs`; +export const transactionURL = (txHash: string) => `/tx/${txHash}`; + export enum SourcifySource { // Resolve trusted IPNS for root IPFS IPFS_IPNS, diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index e63a718..d26fcd0 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -502,3 +502,43 @@ export const useTransactionError = ( return [errorMsg, data, isCustomError]; }; + +export const useTransactionBySenderAndNonce = ( + provider: JsonRpcProvider | undefined, + sender: ChecksummedAddress | undefined, + nonce: number | undefined +): string | undefined => { + const [txHash, setTxHash] = useState(); + + useEffect(() => { + // Reset + setTxHash(undefined); + + if ( + provider === undefined || + sender === undefined || + nonce === undefined || + nonce < 0 + ) { + return; + } + + const readTxHash = async () => { + const result = (await provider.send( + "ots_getTransactionBySenderAndNonce", + [sender, nonce] + )) as string; + + // Empty or success + if (result === "0x") { + setTxHash(undefined); + return; + } + + setTxHash(result); + }; + readTxHash(); + }, [provider, sender, nonce]); + + return txHash; +}; From e012165696805c87ef5fa3c3f32a75ba5bac9b36 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 27 Dec 2021 15:51:31 -0300 Subject: [PATCH 02/29] Improve readability of nonce --- src/components/Nonce.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Nonce.tsx b/src/components/Nonce.tsx index f03eaad..2a59258 100644 --- a/src/components/Nonce.tsx +++ b/src/components/Nonce.tsx @@ -1,4 +1,5 @@ import React from "react"; +import { commify } from "@ethersproject/units"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faArrowUp } from "@fortawesome/free-solid-svg-icons/faArrowUp"; @@ -14,7 +15,7 @@ const Nonce: React.FC = ({ value }) => ( - {value} + {commify(value)} ); From 0d2a1a593d268662e6b3d47ed355818443e53f95 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 27 Dec 2021 19:20:13 -0300 Subject: [PATCH 03/29] Add next/last nonce navigation support --- src/transaction/Details.tsx | 6 +----- src/transaction/NavNonce.tsx | 23 +++++++++++++---------- src/useErigonHooks.ts | 24 ++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index 8c0d779..261e651 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -254,11 +254,7 @@ const Details: React.FC = ({
- +
diff --git a/src/transaction/NavNonce.tsx b/src/transaction/NavNonce.tsx index f17c5cc..236b30f 100644 --- a/src/transaction/NavNonce.tsx +++ b/src/transaction/NavNonce.tsx @@ -5,19 +5,17 @@ import { faChevronRight } from "@fortawesome/free-solid-svg-icons/faChevronRight import NavButton from "./NavButton"; import { ChecksummedAddress } from "../types"; import { RuntimeContext } from "../useRuntime"; -import { useTransactionBySenderAndNonce } from "../useErigonHooks"; +import { + useTransactionBySenderAndNonce, + useTransactionCount, +} from "../useErigonHooks"; type NavNonceProps = { sender: ChecksummedAddress; nonce: number; - latestBlockNumber: number | undefined; }; -const NavNonce: React.FC = ({ - sender, - nonce, - latestBlockNumber, -}) => { +const NavNonce: React.FC = ({ sender, nonce }) => { const { provider } = useContext(RuntimeContext); const prevTxHash = useTransactionBySenderAndNonce( provider, @@ -29,7 +27,12 @@ const NavNonce: React.FC = ({ sender, nonce + 1 ); - const lastTxHash = nextTxHash; + const count = useTransactionCount(provider, sender); + const lastTxHash = useTransactionBySenderAndNonce( + provider, + sender, + count !== undefined ? count - 1 : undefined + ); return (
@@ -38,13 +41,13 @@ const NavNonce: React.FC = ({ = latestBlockNumber} + disabled={count === undefined || nonce >= count - 1} > = latestBlockNumber} + disabled={count === undefined || nonce >= count - 1} > diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index d26fcd0..71b95cc 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -503,6 +503,30 @@ export const useTransactionError = ( return [errorMsg, data, isCustomError]; }; +export const useTransactionCount = ( + provider: JsonRpcProvider | undefined, + sender: ChecksummedAddress | undefined +): number | undefined => { + const [count, setCount] = useState(); + + useEffect(() => { + // Reset + setCount(undefined); + + if (provider === undefined || sender === undefined) { + return; + } + + const readCount = async () => { + const _count = await provider.getTransactionCount(sender); + setCount(_count); + }; + readCount(); + }, [provider, sender]); + + return count; +}; + export const useTransactionBySenderAndNonce = ( provider: JsonRpcProvider | undefined, sender: ChecksummedAddress | undefined, From f1db3f23485842f6a6d83d1db1ddd240caf26e52 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 27 Dec 2021 21:16:33 -0300 Subject: [PATCH 04/29] Add prefetch to nonce +-2 --- src/transaction/NavButton.tsx | 4 +- src/transaction/NavNonce.tsx | 23 +++++++- src/useErigonHooks.ts | 103 +++++++++++++++++++--------------- 3 files changed, 81 insertions(+), 49 deletions(-) diff --git a/src/transaction/NavButton.tsx b/src/transaction/NavButton.tsx index 34f847d..8eb9e16 100644 --- a/src/transaction/NavButton.tsx +++ b/src/transaction/NavButton.tsx @@ -14,7 +14,7 @@ const NavButton: React.FC = ({ }) => { if (disabled || txHash === undefined) { return ( - + {children} ); @@ -22,7 +22,7 @@ const NavButton: React.FC = ({ return ( {children} diff --git a/src/transaction/NavNonce.tsx b/src/transaction/NavNonce.tsx index 236b30f..787deac 100644 --- a/src/transaction/NavNonce.tsx +++ b/src/transaction/NavNonce.tsx @@ -6,9 +6,11 @@ import NavButton from "./NavButton"; import { ChecksummedAddress } from "../types"; import { RuntimeContext } from "../useRuntime"; import { + prefetchTransactionBySenderAndNonce, useTransactionBySenderAndNonce, useTransactionCount, } from "../useErigonHooks"; +import { useSWRConfig } from "swr"; type NavNonceProps = { sender: ChecksummedAddress; @@ -34,8 +36,27 @@ const NavNonce: React.FC = ({ sender, nonce }) => { count !== undefined ? count - 1 : undefined ); + // Prefetch + const swrConfig = useSWRConfig(); + const prefetch = () => { + if (provider && sender && nonce !== undefined) { + prefetchTransactionBySenderAndNonce( + swrConfig, + provider, + sender, + nonce - 2 + ); + prefetchTransactionBySenderAndNonce( + swrConfig, + provider, + sender, + nonce + 2 + ); + } + }; + return ( -
+
diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index 71b95cc..737c6d7 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -6,6 +6,7 @@ import { Contract } from "@ethersproject/contracts"; import { defaultAbiCoder } from "@ethersproject/abi"; import { BigNumber } from "@ethersproject/bignumber"; import { arrayify, hexDataSlice, isHexString } from "@ethersproject/bytes"; +import useSWR, { useSWRConfig } from "swr"; import { getInternalOperations } from "./nodeFunctions"; import { TokenMetas, @@ -507,24 +508,52 @@ export const useTransactionCount = ( provider: JsonRpcProvider | undefined, sender: ChecksummedAddress | undefined ): number | undefined => { - const [count, setCount] = useState(); + const { data, error } = useSWR( + provider && sender ? { provider, sender } : null, + async ({ provider, sender }): Promise => + provider.getTransactionCount(sender) + ); - useEffect(() => { - // Reset - setCount(undefined); + if (error) { + return undefined; + } + return data; +}; - if (provider === undefined || sender === undefined) { - return; - } +type TransactionBySenderAndNonceKey = { + provider: JsonRpcProvider; + sender: ChecksummedAddress; + nonce: number; +}; - const readCount = async () => { - const _count = await provider.getTransactionCount(sender); - setCount(_count); - }; - readCount(); - }, [provider, sender]); +const getTransactionBySenderAndNonceFetcher = async ({ + provider, + sender, + nonce, +}: TransactionBySenderAndNonceKey): Promise => { + const result = (await provider.send("ots_getTransactionBySenderAndNonce", [ + sender, + nonce, + ])) as string; - return count; + // Empty or success + if (result === "0x") { + return undefined; + } + + return result; +}; + +export const prefetchTransactionBySenderAndNonce = ( + { cache, mutate }: ReturnType, + provider: JsonRpcProvider, + sender: ChecksummedAddress, + nonce: number +) => { + const key: TransactionBySenderAndNonceKey = { provider, sender, nonce }; + if (cache.get(key)) { + mutate(key, getTransactionBySenderAndNonceFetcher(key)); + } }; export const useTransactionBySenderAndNonce = ( @@ -532,37 +561,19 @@ export const useTransactionBySenderAndNonce = ( sender: ChecksummedAddress | undefined, nonce: number | undefined ): string | undefined => { - const [txHash, setTxHash] = useState(); + const { data, error } = useSWR< + string | undefined, + any, + TransactionBySenderAndNonceKey | null + >( + provider && sender && nonce !== undefined + ? { provider, sender, nonce } + : null, + getTransactionBySenderAndNonceFetcher + ); - useEffect(() => { - // Reset - setTxHash(undefined); - - if ( - provider === undefined || - sender === undefined || - nonce === undefined || - nonce < 0 - ) { - return; - } - - const readTxHash = async () => { - const result = (await provider.send( - "ots_getTransactionBySenderAndNonce", - [sender, nonce] - )) as string; - - // Empty or success - if (result === "0x") { - setTxHash(undefined); - return; - } - - setTxHash(result); - }; - readTxHash(); - }, [provider, sender, nonce]); - - return txHash; + if (error) { + return undefined; + } + return data; }; From 0c6137656fea770cce29be3b09405a4d59b08fd6 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Tue, 28 Dec 2021 02:45:42 -0300 Subject: [PATCH 05/29] Always prefetch --- src/transaction/NavNonce.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/transaction/NavNonce.tsx b/src/transaction/NavNonce.tsx index 787deac..ee30a4b 100644 --- a/src/transaction/NavNonce.tsx +++ b/src/transaction/NavNonce.tsx @@ -55,8 +55,11 @@ const NavNonce: React.FC = ({ sender, nonce }) => { } }; + // Always prefetch + prefetch(); + return ( -
+
From 7d87345e9806d27194fd348f3b97d9662837898f Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Tue, 28 Dec 2021 02:46:09 -0300 Subject: [PATCH 06/29] Prevent passing negative nonce because of prefetch --- src/useErigonHooks.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index 737c6d7..2a38e09 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -531,6 +531,10 @@ const getTransactionBySenderAndNonceFetcher = async ({ sender, nonce, }: TransactionBySenderAndNonceKey): Promise => { + if (nonce < 0) { + return undefined; + } + const result = (await provider.send("ots_getTransactionBySenderAndNonce", [ sender, nonce, From fe673bd0f87d2cc56c8ace630901b456c38c9f39 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Wed, 29 Dec 2021 22:00:41 -0300 Subject: [PATCH 07/29] Small fixes --- src/transaction/NavButton.tsx | 4 ++-- src/useErigonHooks.ts | 10 +++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/transaction/NavButton.tsx b/src/transaction/NavButton.tsx index 8eb9e16..04d421c 100644 --- a/src/transaction/NavButton.tsx +++ b/src/transaction/NavButton.tsx @@ -3,7 +3,7 @@ import { transactionURL } from "../url"; // TODO: extract common component with block/NavButton type NavButtonProps = { - txHash: string | undefined; + txHash: string | null | undefined; disabled?: boolean; }; @@ -12,7 +12,7 @@ const NavButton: React.FC = ({ disabled, children, }) => { - if (disabled || txHash === undefined) { + if (disabled || txHash === null || txHash === undefined) { return ( {children} diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index 2a38e09..dbf9e8f 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -530,7 +530,7 @@ const getTransactionBySenderAndNonceFetcher = async ({ provider, sender, nonce, -}: TransactionBySenderAndNonceKey): Promise => { +}: TransactionBySenderAndNonceKey): Promise => { if (nonce < 0) { return undefined; } @@ -541,10 +541,6 @@ const getTransactionBySenderAndNonceFetcher = async ({ ])) as string; // Empty or success - if (result === "0x") { - return undefined; - } - return result; }; @@ -564,9 +560,9 @@ export const useTransactionBySenderAndNonce = ( provider: JsonRpcProvider | undefined, sender: ChecksummedAddress | undefined, nonce: number | undefined -): string | undefined => { +): string | null | undefined => { const { data, error } = useSWR< - string | undefined, + string | null | undefined, any, TransactionBySenderAndNonceKey | null >( From 80ef1cf82067ea755afe75203bc29b723f9040e4 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Sat, 22 Jan 2022 15:02:00 -0300 Subject: [PATCH 08/29] Add initial support for address:nonce search --- src/search/search.ts | 77 +++++++++++++++++++++++++++++++++++++++---- src/useErigonHooks.ts | 2 +- 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/src/search/search.ts b/src/search/search.ts index 57a5958..6994444 100644 --- a/src/search/search.ts +++ b/src/search/search.ts @@ -2,6 +2,7 @@ import { ChangeEventHandler, FormEventHandler, RefObject, + useContext, useRef, useState, } from "react"; @@ -12,6 +13,8 @@ import { isHexString } from "@ethersproject/bytes"; import useKeyboardShortcut from "use-keyboard-shortcut"; import { PAGE_SIZE } from "../params"; import { ProcessedTransaction, TransactionChunk } from "../types"; +import { RuntimeContext } from "../useRuntime"; +import { getTransactionBySenderAndNonceFetcher } from "../useErigonHooks"; export class SearchController { private txs: ProcessedTransaction[]; @@ -206,9 +209,30 @@ export class SearchController { } } -const doSearch = (q: string, navigate: NavigateFunction) => { - if (isAddress(q)) { - navigate(`/address/${q}`, { replace: true }); +const doSearch = async ( + provider: JsonRpcProvider, + q: string, + navigate: NavigateFunction +) => { + // Cleanup + q = q.trim(); + + let maybeAddress = q; + let maybeIndex: string | undefined; + const sepIndex = q.lastIndexOf(":"); + if (sepIndex !== -1) { + maybeAddress = q.substring(0, sepIndex); + maybeIndex = q.substring(sepIndex + 1); + } + + if (isAddress(maybeAddress)) { + // Plain address + nonce? + if (await navigateToTx(provider, maybeAddress, maybeIndex, navigate)) { + return; + } + + // Plain address + navigate(`/address/${maybeAddress}`, { replace: true }); return; } @@ -224,7 +248,47 @@ const doSearch = (q: string, navigate: NavigateFunction) => { } // Assume it is an ENS name - navigate(`/address/${q}`); + const resolvedName = await provider.resolveName(maybeAddress); + if (resolvedName !== null) { + await navigateToTx(provider, resolvedName, maybeIndex, navigate); + } + + // TODO: handle default +}; + +const navigateToTx = async ( + provider: JsonRpcProvider, + maybeAddress: string, + maybeIndex: string | undefined, + navigate: NavigateFunction +): Promise => { + if (maybeIndex !== undefined) { + try { + let nonce = 0; + if (maybeIndex === "latest") { + const count = await provider.getTransactionCount(maybeAddress); + if (count > 0) { + nonce = count - 1; + } + } else { + nonce = parseInt(maybeIndex); + } + + const txHash = await getTransactionBySenderAndNonceFetcher({ + provider, + sender: maybeAddress, + nonce, + }); + if (txHash) { + navigate(`/tx/${txHash}`, { replace: true }); + } + return true; + } catch (err) { + // ignore + } + } + + return false; }; export const useGenericSearch = (): [ @@ -232,6 +296,7 @@ export const useGenericSearch = (): [ ChangeEventHandler, FormEventHandler ] => { + const { provider } = useContext(RuntimeContext); const [searchString, setSearchString] = useState(""); const [canSubmit, setCanSubmit] = useState(false); const navigate = useNavigate(); @@ -244,14 +309,14 @@ export const useGenericSearch = (): [ const handleSubmit: React.FormEventHandler = (e) => { e.preventDefault(); - if (!canSubmit) { + if (!canSubmit || !provider) { return; } if (searchRef.current) { searchRef.current.value = ""; } - doSearch(searchString, navigate); + doSearch(provider, searchString, navigate); }; const searchRef = useRef(null); diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index dbf9e8f..e2916b3 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -526,7 +526,7 @@ type TransactionBySenderAndNonceKey = { nonce: number; }; -const getTransactionBySenderAndNonceFetcher = async ({ +export const getTransactionBySenderAndNonceFetcher = async ({ provider, sender, nonce, From af15aaba0c9d18bbc6077d0c7ffea43eaeea0575 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 24 Jan 2022 16:18:28 -0300 Subject: [PATCH 09/29] Rename, since the /:addressorName now may not route to just address search results --- src/{AddressTransactions.tsx => Address.tsx} | 4 ++-- src/App.tsx | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) rename src/{AddressTransactions.tsx => Address.tsx} (98%) diff --git a/src/AddressTransactions.tsx b/src/Address.tsx similarity index 98% rename from src/AddressTransactions.tsx rename to src/Address.tsx index d7222c0..eb0213d 100644 --- a/src/AddressTransactions.tsx +++ b/src/Address.tsx @@ -25,7 +25,7 @@ import { useMultipleMetadata } from "./sourcify/useSourcify"; import { ChecksummedAddress } from "./types"; import { useAddressesWithCode } from "./useErigonHooks"; -const AddressTransactions: React.FC = () => { +const Address: React.FC = () => { const { provider } = useContext(RuntimeContext); const { addressOrName, direction } = useParams(); if (addressOrName === undefined) { @@ -175,4 +175,4 @@ const AddressTransactions: React.FC = () => { ); }; -export default AddressTransactions; +export default Address; diff --git a/src/App.tsx b/src/App.tsx index f31fff6..aae4ab8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,11 +17,9 @@ const BlockTransactions = React.lazy( /* webpackChunkName: "blocktxs", webpackPrefetch: true */ "./BlockTransactions" ) ); -const AddressTransactions = React.lazy( +const Address = React.lazy( () => - import( - /* webpackChunkName: "address", webpackPrefetch: true */ "./AddressTransactions" - ) + import(/* webpackChunkName: "address", webpackPrefetch: true */ "./Address") ); const Transaction = React.lazy( () => @@ -61,7 +59,7 @@ const App = () => { } /> } + element={
} /> } /> From e62068c65a4d3c4b1a6ea28cb3721b351d90f8df Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 24 Jan 2022 16:23:16 -0300 Subject: [PATCH 10/29] Fix ens name without nonce search --- src/search/search.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/search/search.ts b/src/search/search.ts index 6994444..e1e6730 100644 --- a/src/search/search.ts +++ b/src/search/search.ts @@ -250,7 +250,14 @@ const doSearch = async ( // Assume it is an ENS name const resolvedName = await provider.resolveName(maybeAddress); if (resolvedName !== null) { - await navigateToTx(provider, resolvedName, maybeIndex, navigate); + // ENS name + nonce? + if (await navigateToTx(provider, resolvedName, maybeIndex, navigate)) { + return; + } + + // ENS name + navigate(`/address/${maybeAddress}`, { replace: true }); + return; } // TODO: handle default From f138444eee1fa08f7623c44614ebe84300ff2282 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 24 Jan 2022 16:36:16 -0300 Subject: [PATCH 11/29] Fix signalling tx not found on network error --- src/useErigonHooks.ts | 187 ++++++++++++++++++++++-------------------- 1 file changed, 98 insertions(+), 89 deletions(-) diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index e2916b3..7eb847c 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -191,98 +191,107 @@ export const useTxData = ( } const readTxData = async () => { - const [_response, _receipt] = await Promise.all([ - provider.getTransaction(txhash), - provider.getTransactionReceipt(txhash), - ]); - if (_response === null) { + try { + const [_response, _receipt] = await Promise.all([ + provider.getTransaction(txhash), + provider.getTransactionReceipt(txhash), + ]); + if (_response === null) { + setTxData(null); + return; + } + + let _block: ExtendedBlock | undefined; + if (_response.blockNumber) { + _block = await readBlock(provider, _response.blockNumber.toString()); + } + + document.title = `Transaction ${_response.hash} | Otterscan`; + + // Extract token transfers + const tokenTransfers: TokenTransfer[] = []; + if (_receipt) { + for (const l of _receipt.logs) { + if (l.topics.length !== 3) { + continue; + } + if (l.topics[0] !== TRANSFER_TOPIC) { + continue; + } + tokenTransfers.push({ + token: l.address, + from: getAddress(hexDataSlice(arrayify(l.topics[1]), 12)), + to: getAddress(hexDataSlice(arrayify(l.topics[2]), 12)), + value: BigNumber.from(l.data), + }); + } + } + + // Extract token meta + const tokenMetas: TokenMetas = {}; + for (const t of tokenTransfers) { + if (tokenMetas[t.token] !== undefined) { + continue; + } + const erc20Contract = new Contract(t.token, erc20, provider); + try { + const [name, symbol, decimals] = await Promise.all([ + erc20Contract.name(), + erc20Contract.symbol(), + erc20Contract.decimals(), + ]); + tokenMetas[t.token] = { + name, + symbol, + decimals, + }; + } catch (err) { + tokenMetas[t.token] = null; + console.warn( + `Couldn't get token ${t.token} metadata; ignoring`, + err + ); + } + } + + setTxData({ + transactionHash: _response.hash, + from: _response.from, + to: _response.to, + value: _response.value, + tokenTransfers, + tokenMetas, + type: _response.type ?? 0, + maxFeePerGas: _response.maxFeePerGas, + maxPriorityFeePerGas: _response.maxPriorityFeePerGas, + gasPrice: _response.gasPrice!, + gasLimit: _response.gasLimit, + nonce: _response.nonce, + data: _response.data, + confirmedData: + _receipt === null + ? undefined + : { + status: _receipt.status === 1, + blockNumber: _receipt.blockNumber, + transactionIndex: _receipt.transactionIndex, + blockBaseFeePerGas: _block!.baseFeePerGas, + blockTransactionCount: _block!.transactionCount, + confirmations: _receipt.confirmations, + timestamp: _block!.timestamp, + miner: _block!.miner, + createdContractAddress: _receipt.contractAddress, + fee: _response.gasPrice!.mul(_receipt.gasUsed), + gasUsed: _receipt.gasUsed, + logs: _receipt.logs, + }, + }); + } catch (err) { + console.error(err); setTxData(null); - return; } - - let _block: ExtendedBlock | undefined; - if (_response.blockNumber) { - _block = await readBlock(provider, _response.blockNumber.toString()); - } - - document.title = `Transaction ${_response.hash} | Otterscan`; - - // Extract token transfers - const tokenTransfers: TokenTransfer[] = []; - if (_receipt) { - for (const l of _receipt.logs) { - if (l.topics.length !== 3) { - continue; - } - if (l.topics[0] !== TRANSFER_TOPIC) { - continue; - } - tokenTransfers.push({ - token: l.address, - from: getAddress(hexDataSlice(arrayify(l.topics[1]), 12)), - to: getAddress(hexDataSlice(arrayify(l.topics[2]), 12)), - value: BigNumber.from(l.data), - }); - } - } - - // Extract token meta - const tokenMetas: TokenMetas = {}; - for (const t of tokenTransfers) { - if (tokenMetas[t.token] !== undefined) { - continue; - } - const erc20Contract = new Contract(t.token, erc20, provider); - try { - const [name, symbol, decimals] = await Promise.all([ - erc20Contract.name(), - erc20Contract.symbol(), - erc20Contract.decimals(), - ]); - tokenMetas[t.token] = { - name, - symbol, - decimals, - }; - } catch (err) { - tokenMetas[t.token] = null; - console.warn(`Couldn't get token ${t.token} metadata; ignoring`, err); - } - } - - setTxData({ - transactionHash: _response.hash, - from: _response.from, - to: _response.to, - value: _response.value, - tokenTransfers, - tokenMetas, - type: _response.type ?? 0, - maxFeePerGas: _response.maxFeePerGas, - maxPriorityFeePerGas: _response.maxPriorityFeePerGas, - gasPrice: _response.gasPrice!, - gasLimit: _response.gasLimit, - nonce: _response.nonce, - data: _response.data, - confirmedData: - _receipt === null - ? undefined - : { - status: _receipt.status === 1, - blockNumber: _receipt.blockNumber, - transactionIndex: _receipt.transactionIndex, - blockBaseFeePerGas: _block!.baseFeePerGas, - blockTransactionCount: _block!.transactionCount, - confirmations: _receipt.confirmations, - timestamp: _block!.timestamp, - miner: _block!.miner, - createdContractAddress: _receipt.contractAddress, - fee: _response.gasPrice!.mul(_receipt.gasUsed), - gasUsed: _receipt.gasUsed, - logs: _receipt.logs, - }, - }); }; + readTxData(); }, [provider, txhash]); From 2c47f04384436cdccdbbda543635757a0a6dc9ff Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Tue, 25 Jan 2022 22:34:09 -0300 Subject: [PATCH 12/29] Improve git status performance by not checking submodules --- .gitmodules | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitmodules b/.gitmodules index 3ee2444..e55ad32 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,12 @@ [submodule "4bytes"] path = 4bytes url = https://github.com/ethereum-lists/4bytes.git + ignore = dirty [submodule "trustwallet"] path = trustwallet url = https://github.com/trustwallet/assets.git + ignore = dirty [submodule "topic0"] path = topic0 url = https://github.com/wmitsuda/topic0.git + ignore = dirty From cb6d1f3bcd37eaf9736ca0c904ecac06d250ad88 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Sat, 29 Jan 2022 19:04:52 -0300 Subject: [PATCH 13/29] Extract tx page content component --- src/Transaction.tsx | 124 +------------------------------ src/TransactionPageContent.tsx | 131 +++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 120 deletions(-) create mode 100644 src/TransactionPageContent.tsx diff --git a/src/Transaction.tsx b/src/Transaction.tsx index 22873d0..988c5d2 100644 --- a/src/Transaction.tsx +++ b/src/Transaction.tsx @@ -1,129 +1,13 @@ -import React, { useMemo, useContext } from "react"; -import { useParams, Routes, Route } 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 { SelectionContext, useSelection } from "./useSelection"; -import { useInternalOperations, useTxData } from "./useErigonHooks"; -import { useETHUSDOracle } from "./usePriceOracle"; -import { useAppConfigContext } from "./useAppConfig"; -import { useSourcify, useTransactionDescription } from "./sourcify/useSourcify"; -import { SelectedTransactionContext } from "./useSelectedTransaction"; - -const Details = React.lazy( - () => - import( - /* webpackChunkName: "txdetails", webpackPrefetch: true */ - "./transaction/Details" - ) -); -const Logs = React.lazy( - () => - import( - /* webpackChunkName: "txlogs", webpackPrefetch: true */ "./transaction/Logs" - ) -); -const Trace = React.lazy( - () => - import( - /* webpackChunkName: "txtrace", webpackPrefetch: true */ "./transaction/Trace" - ) -); +import React from "react"; +import { useParams } from "react-router-dom"; +import TransactionPageContent from "./TransactionPageContent"; const Transaction: React.FC = () => { - const { provider } = useContext(RuntimeContext); const { txhash } = useParams(); if (txhash === undefined) { throw new Error("txhash couldn't be undefined here"); } - - 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 blockETHUSDPrice = useETHUSDOracle( - provider, - txData?.confirmedData?.blockNumber - ); - - const { sourcifySource } = useAppConfigContext(); - const metadata = useSourcify( - txData?.to, - provider?.network.chainId, - sourcifySource - ); - const txDesc = useTransactionDescription(metadata, txData); - - return ( - - - Transaction Details - {txData === null && ( - -
- Transaction {txhash} not found. -
-
- )} - {txData && ( - - - - Overview - {txData.confirmedData?.blockNumber !== undefined && ( - - Logs - {txData && ` (${txData.confirmedData?.logs?.length ?? 0})`} - - )} - Trace - - - - - - } - /> - } - /> - } /> - - - - )} -
-
- ); + return ; }; export default Transaction; diff --git a/src/TransactionPageContent.tsx b/src/TransactionPageContent.tsx new file mode 100644 index 0000000..ab88d46 --- /dev/null +++ b/src/TransactionPageContent.tsx @@ -0,0 +1,131 @@ +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 { useETHUSDOracle } from "./usePriceOracle"; +import { useAppConfigContext } from "./useAppConfig"; +import { useSourcify, useTransactionDescription } from "./sourcify/useSourcify"; + +const Details = React.lazy( + () => + import( + /* webpackChunkName: "txdetails", webpackPrefetch: true */ + "./transaction/Details" + ) +); +const Logs = React.lazy( + () => + import( + /* webpackChunkName: "txlogs", webpackPrefetch: true */ "./transaction/Logs" + ) +); +const Trace = React.lazy( + () => + import( + /* webpackChunkName: "txtrace", webpackPrefetch: true */ "./transaction/Trace" + ) +); + +type TransactionPageContentProps = { + txhash: string; +}; + +const TransactionPageContent: React.FC = ({ + 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 blockETHUSDPrice = useETHUSDOracle( + provider, + txData?.confirmedData?.blockNumber + ); + + const { sourcifySource } = useAppConfigContext(); + const metadata = useSourcify( + txData?.to, + provider?.network.chainId, + sourcifySource + ); + const txDesc = useTransactionDescription(metadata, txData); + + return ( + + + Transaction Details + {txData === null && ( + +
+ Transaction {txhash} not found. +
+
+ )} + {txData && ( + + + + Overview + {txData.confirmedData?.blockNumber !== undefined && ( + + Logs + {txData && ` (${txData.confirmedData?.logs?.length ?? 0})`} + + )} + Trace + + + + + + } + /> + } + /> + } /> + + + + )} +
+
+ ); +}; + +export default TransactionPageContent; From 17b6c03a33400084b6ad1bcd2898d9aa9292480b Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Sat, 29 Jan 2022 19:40:26 -0300 Subject: [PATCH 14/29] Basic nonce locator URL --- src/AddressTransaction.tsx | 35 ++++++++++++++++++++++++++++++++++ src/App.tsx | 10 ++++++++++ src/Transaction.tsx | 2 +- src/TransactionPageContent.tsx | 8 ++++---- 4 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 src/AddressTransaction.tsx diff --git a/src/AddressTransaction.tsx b/src/AddressTransaction.tsx new file mode 100644 index 0000000..a6b1a52 --- /dev/null +++ b/src/AddressTransaction.tsx @@ -0,0 +1,35 @@ +import React, { useContext } from "react"; +import { useNavigate, useParams, useSearchParams } from "react-router-dom"; +import StandardFrame from "./StandardFrame"; +import { transactionURL } from "./url"; +import { useTransactionBySenderAndNonce } from "./useErigonHooks"; +import { RuntimeContext } from "./useRuntime"; + +const AddressTransaction: React.FC = () => { + const { provider } = useContext(RuntimeContext); + const navigate = useNavigate(); + const { addressOrName } = useParams(); + if (addressOrName === undefined) { + throw new Error("addressOrName couldn't be undefined here"); + } + + const [searchParams] = useSearchParams(); + const rawNonce = searchParams.get("nonce"); + if (rawNonce === null) { + throw new Error("rawNonce couldn't be undefined here"); + } + let nonce: number | undefined = undefined; + try { + nonce = parseInt(rawNonce); + } catch (err) { + // ignore + } + + const txHash = useTransactionBySenderAndNonce(provider, addressOrName, nonce); + if (txHash) { + navigate(transactionURL(txHash)); + } + return ; +}; + +export default AddressTransaction; diff --git a/src/App.tsx b/src/App.tsx index aae4ab8..c75c474 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,6 +21,12 @@ const Address = React.lazy( () => import(/* webpackChunkName: "address", webpackPrefetch: true */ "./Address") ); +const AddressTransaction = React.lazy( + () => + import( + /* webpackChunkName: "addresstx", webpackPrefetch: true */ "./AddressTransaction" + ) +); const Transaction = React.lazy( () => import(/* webpackChunkName: "tx", webpackPrefetch: true */ "./Transaction") @@ -61,6 +67,10 @@ const App = () => { path="address/:addressOrName/*" element={
} /> + } + /> } /> diff --git a/src/Transaction.tsx b/src/Transaction.tsx index 988c5d2..94b2e43 100644 --- a/src/Transaction.tsx +++ b/src/Transaction.tsx @@ -7,7 +7,7 @@ const Transaction: React.FC = () => { if (txhash === undefined) { throw new Error("txhash couldn't be undefined here"); } - return ; + return ; }; export default Transaction; diff --git a/src/TransactionPageContent.tsx b/src/TransactionPageContent.tsx index ab88d46..377c345 100644 --- a/src/TransactionPageContent.tsx +++ b/src/TransactionPageContent.tsx @@ -34,15 +34,15 @@ const Trace = React.lazy( ); type TransactionPageContentProps = { - txhash: string; + txHash: string; }; const TransactionPageContent: React.FC = ({ - txhash, + txHash, }) => { const { provider } = useContext(RuntimeContext); - const txData = useTxData(provider, txhash); + const txData = useTxData(provider, txHash); const internalOps = useInternalOperations(provider, txData); const sendsEthToMiner = useMemo(() => { if (!txData || !internalOps) { @@ -79,7 +79,7 @@ const TransactionPageContent: React.FC = ({ {txData === null && (
- Transaction {txhash} not found. + Transaction {txHash} not found.
)} From aeda92e41c2e4f5f95a222e185e39107e8017854 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Sat, 29 Jan 2022 19:54:21 -0300 Subject: [PATCH 15/29] Add ENS support to search by nonce url --- src/Address.tsx | 4 ++-- src/AddressTransaction.tsx | 31 +++++++++++++++++++++++++++++-- src/useResolvedAddresses.ts | 2 +- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/Address.tsx b/src/Address.tsx index eb0213d..950b04e 100644 --- a/src/Address.tsx +++ b/src/Address.tsx @@ -20,7 +20,7 @@ import AddressTransactionResults from "./address/AddressTransactionResults"; import Contracts from "./address/Contracts"; import { RuntimeContext } from "./useRuntime"; import { useAppConfigContext } from "./useAppConfig"; -import { useAddressOrENSFromURL } from "./useResolvedAddresses"; +import { useAddressOrENS } from "./useResolvedAddresses"; import { useMultipleMetadata } from "./sourcify/useSourcify"; import { ChecksummedAddress } from "./types"; import { useAddressesWithCode } from "./useErigonHooks"; @@ -45,7 +45,7 @@ const Address: React.FC = () => { }, [navigate, direction, searchParams] ); - const [checksummedAddress, isENS, error] = useAddressOrENSFromURL( + const [checksummedAddress, isENS, error] = useAddressOrENS( addressOrName, urlFixer ); diff --git a/src/AddressTransaction.tsx b/src/AddressTransaction.tsx index a6b1a52..84d5394 100644 --- a/src/AddressTransaction.tsx +++ b/src/AddressTransaction.tsx @@ -1,8 +1,10 @@ -import React, { useContext } from "react"; +import React, { useCallback, useContext } from "react"; import { useNavigate, useParams, useSearchParams } from "react-router-dom"; import StandardFrame from "./StandardFrame"; +import { ChecksummedAddress } from "./types"; import { transactionURL } from "./url"; import { useTransactionBySenderAndNonce } from "./useErigonHooks"; +import { useAddressOrENS } from "./useResolvedAddresses"; import { RuntimeContext } from "./useRuntime"; const AddressTransaction: React.FC = () => { @@ -14,6 +16,19 @@ const AddressTransaction: React.FC = () => { } const [searchParams] = useSearchParams(); + const urlFixer = useCallback( + (address: ChecksummedAddress) => { + navigate(`/address/${address}/tx?${searchParams.toString()}`, { + replace: true, + }); + }, + [navigate, searchParams] + ); + const [checksummedAddress, isENS, error] = useAddressOrENS( + addressOrName, + urlFixer + ); + const rawNonce = searchParams.get("nonce"); if (rawNonce === null) { throw new Error("rawNonce couldn't be undefined here"); @@ -25,7 +40,19 @@ const AddressTransaction: React.FC = () => { // ignore } - const txHash = useTransactionBySenderAndNonce(provider, addressOrName, nonce); + const txHash = useTransactionBySenderAndNonce( + provider, + checksummedAddress, + nonce + ); + + if (error) { + return ( + + "{addressOrName}" is not an ETH address or ENS name. + + ); + } if (txHash) { navigate(transactionURL(txHash)); } diff --git a/src/useResolvedAddresses.ts b/src/useResolvedAddresses.ts index adc3819..056fa98 100644 --- a/src/useResolvedAddresses.ts +++ b/src/useResolvedAddresses.ts @@ -7,7 +7,7 @@ import { SelectedResolvedName } from "./api/address-resolver/CompositeAddressRes import { RuntimeContext } from "./useRuntime"; import { ChecksummedAddress } from "./types"; -export const useAddressOrENSFromURL = ( +export const useAddressOrENS = ( addressOrName: string, urlFixer: (address: ChecksummedAddress) => void ): [ From d70c22b5a02e258b8dd67335af26cd75ec85d8cd Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Sat, 29 Jan 2022 20:00:01 -0300 Subject: [PATCH 16/29] Extract common component for address or ens name not found --- src/Address.tsx | 5 ++--- src/AddressTransaction.tsx | 7 ++----- src/components/AddressOrENSNameNotFound.tsx | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 src/components/AddressOrENSNameNotFound.tsx diff --git a/src/Address.tsx b/src/Address.tsx index 950b04e..3bfbd41 100644 --- a/src/Address.tsx +++ b/src/Address.tsx @@ -13,6 +13,7 @@ import { faCircleNotch } from "@fortawesome/free-solid-svg-icons/faCircleNotch"; import { faQuestionCircle } from "@fortawesome/free-regular-svg-icons/faQuestionCircle"; import StandardFrame from "./StandardFrame"; import StandardSubtitle from "./StandardSubtitle"; +import AddressOrENSNameNotFound from "./components/AddressOrENSNameNotFound"; import Copy from "./components/Copy"; import NavTab from "./components/NavTab"; import SourcifyLogo from "./sourcify/SourcifyLogo"; @@ -81,9 +82,7 @@ const Address: React.FC = () => { return ( {error ? ( - - "{addressOrName}" is not an ETH address or ENS name. - + ) : ( checksummedAddress && ( <> diff --git a/src/AddressTransaction.tsx b/src/AddressTransaction.tsx index 84d5394..da709ed 100644 --- a/src/AddressTransaction.tsx +++ b/src/AddressTransaction.tsx @@ -1,6 +1,7 @@ import React, { useCallback, useContext } from "react"; import { useNavigate, useParams, useSearchParams } from "react-router-dom"; import StandardFrame from "./StandardFrame"; +import AddressOrENSNameNotFound from "./components/AddressOrENSNameNotFound"; import { ChecksummedAddress } from "./types"; import { transactionURL } from "./url"; import { useTransactionBySenderAndNonce } from "./useErigonHooks"; @@ -47,11 +48,7 @@ const AddressTransaction: React.FC = () => { ); if (error) { - return ( - - "{addressOrName}" is not an ETH address or ENS name. - - ); + return ; } if (txHash) { navigate(transactionURL(txHash)); diff --git a/src/components/AddressOrENSNameNotFound.tsx b/src/components/AddressOrENSNameNotFound.tsx new file mode 100644 index 0000000..6798124 --- /dev/null +++ b/src/components/AddressOrENSNameNotFound.tsx @@ -0,0 +1,15 @@ +import React from "react"; + +type AddressOrENSNameNotFoundProps = { + addressOrENSName: string; +}; + +const AddressOrENSNameNotFound: React.FC = ({ + addressOrENSName, +}) => ( + + "{addressOrENSName}" is not an ETH address or ENS name. + +); + +export default React.memo(AddressOrENSNameNotFound); From 20fc443009b4fdb45afa8bc4473a4d03d905f27e Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Sat, 29 Jan 2022 20:44:26 -0300 Subject: [PATCH 17/29] Error handling UI improvements --- src/AddressTransaction.tsx | 38 ++++++++++++++++--- src/components/AddressOrENSName.tsx | 5 ++- .../AddressOrENSNameInvalidNonce.tsx | 25 ++++++++++++ src/components/AddressOrENSNameNotFound.tsx | 13 +++++-- 4 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 src/components/AddressOrENSNameInvalidNonce.tsx diff --git a/src/AddressTransaction.tsx b/src/AddressTransaction.tsx index da709ed..7c4ee76 100644 --- a/src/AddressTransaction.tsx +++ b/src/AddressTransaction.tsx @@ -2,6 +2,7 @@ import React, { useCallback, useContext } from "react"; import { useNavigate, useParams, useSearchParams } from "react-router-dom"; import StandardFrame from "./StandardFrame"; import AddressOrENSNameNotFound from "./components/AddressOrENSNameNotFound"; +import AddressOrENSNameInvalidNonce from "./components/AddressOrENSNameInvalidNonce"; import { ChecksummedAddress } from "./types"; import { transactionURL } from "./url"; import { useTransactionBySenderAndNonce } from "./useErigonHooks"; @@ -25,18 +26,15 @@ const AddressTransaction: React.FC = () => { }, [navigate, searchParams] ); - const [checksummedAddress, isENS, error] = useAddressOrENS( + const [checksummedAddress, , error] = useAddressOrENS( addressOrName, urlFixer ); const rawNonce = searchParams.get("nonce"); - if (rawNonce === null) { - throw new Error("rawNonce couldn't be undefined here"); - } let nonce: number | undefined = undefined; try { - nonce = parseInt(rawNonce); + nonce = rawNonce === null ? undefined : parseInt(rawNonce); } catch (err) { // ignore } @@ -48,7 +46,35 @@ const AddressTransaction: React.FC = () => { ); if (error) { - return ; + return ( + + + + ); + } + if (checksummedAddress !== undefined && rawNonce === null) { + return ( + + + + ); + } + if ( + checksummedAddress !== undefined && + nonce !== undefined && + txHash === null + ) { + return ( + + + + ); } if (txHash) { navigate(transactionURL(txHash)); diff --git a/src/components/AddressOrENSName.tsx b/src/components/AddressOrENSName.tsx index 9f43662..b884c48 100644 --- a/src/components/AddressOrENSName.tsx +++ b/src/components/AddressOrENSName.tsx @@ -1,11 +1,12 @@ import React, { useContext } from "react"; +import PlainAddress from "./PlainAddress"; import { resolverRendererRegistry } from "../api/address-resolver"; import { useResolvedAddress } from "../useResolvedAddresses"; import { RuntimeContext } from "../useRuntime"; -import PlainAddress from "./PlainAddress"; +import { ChecksummedAddress } from "../types"; type AddressOrENSNameProps = { - address: string; + address: ChecksummedAddress; selectedAddress?: string; dontOverrideColors?: boolean; }; diff --git a/src/components/AddressOrENSNameInvalidNonce.tsx b/src/components/AddressOrENSNameInvalidNonce.tsx new file mode 100644 index 0000000..a92496a --- /dev/null +++ b/src/components/AddressOrENSNameInvalidNonce.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import StandardSubtitle from "../StandardSubtitle"; +import ContentFrame from "../ContentFrame"; +import AddressOrENSName from "./AddressOrENSName"; + +type AddressOrENSNameInvalidNonceProps = { + addressOrENSName: string; + nonce: string; +}; + +const AddressOrENSNameInvalidNonce: React.FC< + AddressOrENSNameInvalidNonceProps +> = ({ addressOrENSName, nonce }) => ( + <> + Transaction Details + +
+ + : no transaction found for nonce="{nonce}". +
+
+ +); + +export default React.memo(AddressOrENSNameInvalidNonce); diff --git a/src/components/AddressOrENSNameNotFound.tsx b/src/components/AddressOrENSNameNotFound.tsx index 6798124..437aa6c 100644 --- a/src/components/AddressOrENSNameNotFound.tsx +++ b/src/components/AddressOrENSNameNotFound.tsx @@ -1,4 +1,6 @@ import React from "react"; +import StandardSubtitle from "../StandardSubtitle"; +import ContentFrame from "../ContentFrame"; type AddressOrENSNameNotFoundProps = { addressOrENSName: string; @@ -7,9 +9,14 @@ type AddressOrENSNameNotFoundProps = { const AddressOrENSNameNotFound: React.FC = ({ addressOrENSName, }) => ( - - "{addressOrENSName}" is not an ETH address or ENS name. - + <> + Transaction Details + +
+ "{addressOrENSName}" is not an ETH address or ENS name. +
+
+ ); export default React.memo(AddressOrENSNameNotFound); From e6d60f1fcd6f68929b529a4a203f137acd4379ea Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 31 Jan 2022 15:37:29 -0300 Subject: [PATCH 18/29] =?UTF-8?q?Change=20address=20tx=20by=20nonce=20url?= =?UTF-8?q?=20to=20addressOrName=3Fnonce=3D=3F=20format?= --- src/Address.tsx | 24 +++++++++ src/AddressTransaction.tsx | 85 ------------------------------- src/AddressTransactionByNonce.tsx | 57 +++++++++++++++++++++ src/App.tsx | 10 ---- 4 files changed, 81 insertions(+), 95 deletions(-) delete mode 100644 src/AddressTransaction.tsx create mode 100644 src/AddressTransactionByNonce.tsx diff --git a/src/Address.tsx b/src/Address.tsx index 3bfbd41..da1f959 100644 --- a/src/Address.tsx +++ b/src/Address.tsx @@ -26,6 +26,13 @@ import { useMultipleMetadata } from "./sourcify/useSourcify"; import { ChecksummedAddress } from "./types"; import { useAddressesWithCode } from "./useErigonHooks"; +const AddressTransactionByNonce = React.lazy( + () => + import( + /* webpackChunkName: "addresstxbynonce", webpackPrefetch: true */ "./AddressTransactionByNonce" + ) +); + const Address: React.FC = () => { const { provider } = useContext(RuntimeContext); const { addressOrName, direction } = useParams(); @@ -79,6 +86,23 @@ const Address: React.FC = () => { ? metadatas[checksummedAddress] : undefined; + // Search address by nonce === transaction @ nonce + const rawNonce = searchParams.get("nonce"); + if (rawNonce !== null) { + let nonce: number | undefined = undefined; + try { + nonce = parseInt(rawNonce); + } catch (err) { + // ignore + } + return ( + + ); + } + return ( {error ? ( diff --git a/src/AddressTransaction.tsx b/src/AddressTransaction.tsx deleted file mode 100644 index 7c4ee76..0000000 --- a/src/AddressTransaction.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import React, { useCallback, useContext } from "react"; -import { useNavigate, useParams, useSearchParams } from "react-router-dom"; -import StandardFrame from "./StandardFrame"; -import AddressOrENSNameNotFound from "./components/AddressOrENSNameNotFound"; -import AddressOrENSNameInvalidNonce from "./components/AddressOrENSNameInvalidNonce"; -import { ChecksummedAddress } from "./types"; -import { transactionURL } from "./url"; -import { useTransactionBySenderAndNonce } from "./useErigonHooks"; -import { useAddressOrENS } from "./useResolvedAddresses"; -import { RuntimeContext } from "./useRuntime"; - -const AddressTransaction: React.FC = () => { - const { provider } = useContext(RuntimeContext); - const navigate = useNavigate(); - const { addressOrName } = useParams(); - if (addressOrName === undefined) { - throw new Error("addressOrName couldn't be undefined here"); - } - - const [searchParams] = useSearchParams(); - const urlFixer = useCallback( - (address: ChecksummedAddress) => { - navigate(`/address/${address}/tx?${searchParams.toString()}`, { - replace: true, - }); - }, - [navigate, searchParams] - ); - const [checksummedAddress, , error] = useAddressOrENS( - addressOrName, - urlFixer - ); - - const rawNonce = searchParams.get("nonce"); - let nonce: number | undefined = undefined; - try { - nonce = rawNonce === null ? undefined : parseInt(rawNonce); - } catch (err) { - // ignore - } - - const txHash = useTransactionBySenderAndNonce( - provider, - checksummedAddress, - nonce - ); - - if (error) { - return ( - - - - ); - } - if (checksummedAddress !== undefined && rawNonce === null) { - return ( - - - - ); - } - if ( - checksummedAddress !== undefined && - nonce !== undefined && - txHash === null - ) { - return ( - - - - ); - } - if (txHash) { - navigate(transactionURL(txHash)); - } - return ; -}; - -export default AddressTransaction; diff --git a/src/AddressTransactionByNonce.tsx b/src/AddressTransactionByNonce.tsx new file mode 100644 index 0000000..c720bd9 --- /dev/null +++ b/src/AddressTransactionByNonce.tsx @@ -0,0 +1,57 @@ +import React, { useContext } from "react"; +import { useNavigate } from "react-router-dom"; +import StandardFrame from "./StandardFrame"; +import AddressOrENSNameInvalidNonce from "./components/AddressOrENSNameInvalidNonce"; +import { ChecksummedAddress } from "./types"; +import { transactionURL } from "./url"; +import { useTransactionBySenderAndNonce } from "./useErigonHooks"; +import { RuntimeContext } from "./useRuntime"; + +type AddressTransactionByNonceProps = { + checksummedAddress: ChecksummedAddress | undefined; + nonce: number | undefined; +}; + +const AddressTransactionByNonce: React.FC = ({ + checksummedAddress, + nonce, +}) => { + const { provider } = useContext(RuntimeContext); + const txHash = useTransactionBySenderAndNonce( + provider, + checksummedAddress, + nonce + ); + const navigate = useNavigate(); + + if (checksummedAddress !== undefined && nonce === undefined) { + return ( + + + + ); + } + if ( + checksummedAddress !== undefined && + nonce !== undefined && + txHash === null + ) { + return ( + + + + ); + } + if (txHash) { + navigate(transactionURL(txHash)); + } + return ; +}; + +export default AddressTransactionByNonce; diff --git a/src/App.tsx b/src/App.tsx index c75c474..aae4ab8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,12 +21,6 @@ const Address = React.lazy( () => import(/* webpackChunkName: "address", webpackPrefetch: true */ "./Address") ); -const AddressTransaction = React.lazy( - () => - import( - /* webpackChunkName: "addresstx", webpackPrefetch: true */ "./AddressTransaction" - ) -); const Transaction = React.lazy( () => import(/* webpackChunkName: "tx", webpackPrefetch: true */ "./Transaction") @@ -67,10 +61,6 @@ const App = () => { path="address/:addressOrName/*" element={
} /> - } - /> } /> From 17194c089a431a71c5b250a7e950cedeab7b89e7 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 31 Jan 2022 15:53:25 -0300 Subject: [PATCH 19/29] Add 404 page --- src/App.tsx | 8 +++++++- src/PageNotFound.tsx | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 src/PageNotFound.tsx diff --git a/src/App.tsx b/src/App.tsx index aae4ab8..62d3b2c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -31,6 +31,12 @@ const London = React.lazy( /* webpackChunkName: "london", webpackPrefetch: true */ "./special/london/London" ) ); +const PageNotFound = React.lazy( + () => + import( + /* webpackChunkName: "notfound", webpackPrefetch: true */ "./PageNotFound" + ) +); const App = () => { const runtime = useRuntime(); @@ -61,7 +67,7 @@ const App = () => { path="address/:addressOrName/*" element={
} /> - } /> + } /> diff --git a/src/PageNotFound.tsx b/src/PageNotFound.tsx new file mode 100644 index 0000000..690f0a0 --- /dev/null +++ b/src/PageNotFound.tsx @@ -0,0 +1,16 @@ +import React from "react"; +import { NavLink } from "react-router-dom"; +import StandardFrame from "./StandardFrame"; + +const PageNotFound: React.FC = () => ( + +
+ Page not found! + + Click here to go to home + +
+
+); + +export default PageNotFound; From 5abf171be96bb39d0690884e41a890be85931fb8 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 31 Jan 2022 16:26:27 -0300 Subject: [PATCH 20/29] Fix invalid nonce handling --- src/Address.tsx | 8 +------- src/AddressTransactionByNonce.tsx | 18 ++++++++---------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/Address.tsx b/src/Address.tsx index da1f959..3b9b62d 100644 --- a/src/Address.tsx +++ b/src/Address.tsx @@ -89,16 +89,10 @@ const Address: React.FC = () => { // Search address by nonce === transaction @ nonce const rawNonce = searchParams.get("nonce"); if (rawNonce !== null) { - let nonce: number | undefined = undefined; - try { - nonce = parseInt(rawNonce); - } catch (err) { - // ignore - } return ( ); } diff --git a/src/AddressTransactionByNonce.tsx b/src/AddressTransactionByNonce.tsx index c720bd9..1478092 100644 --- a/src/AddressTransactionByNonce.tsx +++ b/src/AddressTransactionByNonce.tsx @@ -9,36 +9,34 @@ import { RuntimeContext } from "./useRuntime"; type AddressTransactionByNonceProps = { checksummedAddress: ChecksummedAddress | undefined; - nonce: number | undefined; + rawNonce: string; }; const AddressTransactionByNonce: React.FC = ({ checksummedAddress, - nonce, + rawNonce, }) => { const { provider } = useContext(RuntimeContext); + + const nonce = parseInt(rawNonce, 10); const txHash = useTransactionBySenderAndNonce( provider, checksummedAddress, - nonce + isNaN(nonce) ? undefined : nonce ); const navigate = useNavigate(); - if (checksummedAddress !== undefined && nonce === undefined) { + if (checksummedAddress !== undefined && isNaN(nonce)) { return ( ); } - if ( - checksummedAddress !== undefined && - nonce !== undefined && - txHash === null - ) { + if (checksummedAddress !== undefined && !isNaN(nonce) && txHash === null) { return ( Date: Mon, 31 Jan 2022 16:28:08 -0300 Subject: [PATCH 21/29] Fix extra redirect --- src/AddressTransactionByNonce.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AddressTransactionByNonce.tsx b/src/AddressTransactionByNonce.tsx index 1478092..9e1763b 100644 --- a/src/AddressTransactionByNonce.tsx +++ b/src/AddressTransactionByNonce.tsx @@ -47,7 +47,7 @@ const AddressTransactionByNonce: React.FC = ({ ); } if (txHash) { - navigate(transactionURL(txHash)); + navigate(transactionURL(txHash), { replace: true }); } return ; }; From b0c31441c45da9da63a72044474b321d11f08a30 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 31 Jan 2022 16:35:34 -0300 Subject: [PATCH 22/29] Simplify logic --- src/AddressTransactionByNonce.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/AddressTransactionByNonce.tsx b/src/AddressTransactionByNonce.tsx index 9e1763b..d752138 100644 --- a/src/AddressTransactionByNonce.tsx +++ b/src/AddressTransactionByNonce.tsx @@ -26,7 +26,12 @@ const AddressTransactionByNonce: React.FC = ({ ); const navigate = useNavigate(); - if (checksummedAddress !== undefined && isNaN(nonce)) { + if (checksummedAddress === undefined) { + return ; + } + + // Garbage nonce + if (isNaN(nonce)) { return ( = ({ ); } - if (checksummedAddress !== undefined && !isNaN(nonce) && txHash === null) { + + // Valid nonce, but no tx found + if (!txHash) { return ( = ({ ); } - if (txHash) { - navigate(transactionURL(txHash), { replace: true }); - } + + // Success; replace and render filler + navigate(transactionURL(txHash), { replace: true }); return ; }; From 9d7709ca635580939eeadae1a39088a4b1b474b9 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 31 Jan 2022 16:49:58 -0300 Subject: [PATCH 23/29] Add "latest" support for nonce param --- src/AddressTransactionByNonce.tsx | 34 +++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/AddressTransactionByNonce.tsx b/src/AddressTransactionByNonce.tsx index d752138..a5bb17e 100644 --- a/src/AddressTransactionByNonce.tsx +++ b/src/AddressTransactionByNonce.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from "react"; +import React, { useContext, useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import StandardFrame from "./StandardFrame"; import AddressOrENSNameInvalidNonce from "./components/AddressOrENSNameInvalidNonce"; @@ -18,15 +18,41 @@ const AddressTransactionByNonce: React.FC = ({ }) => { const { provider } = useContext(RuntimeContext); - const nonce = parseInt(rawNonce, 10); + // Calculate txCount ONLY when asked for latest nonce + const [txCount, setTxCount] = useState(); + useEffect(() => { + if (!provider || !checksummedAddress || rawNonce !== "latest") { + setTxCount(undefined); + return; + } + + const readTxCount = async () => { + const count = await provider.getTransactionCount(checksummedAddress); + setTxCount(count); + }; + readTxCount(); + }, [provider, checksummedAddress, rawNonce]); + + // Determine desired nonce from parse int query param or txCount - 1 nonce + // in case of latest + let nonce: number | undefined; + if (rawNonce === "latest") { + if (txCount !== undefined && txCount > 0) { + nonce = txCount - 1; + } + } else { + nonce = parseInt(rawNonce, 10); + } + + // Given all base params are determined, get the corresponding tx const txHash = useTransactionBySenderAndNonce( provider, checksummedAddress, - isNaN(nonce) ? undefined : nonce + nonce !== undefined && isNaN(nonce) ? undefined : nonce ); const navigate = useNavigate(); - if (checksummedAddress === undefined) { + if (checksummedAddress === undefined || nonce === undefined) { return ; } From c728dd881d16c60724acfb67e7873218f8c9707f Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 31 Jan 2022 17:06:49 -0300 Subject: [PATCH 24/29] Handle search for latest nonce on uninitialized addresses --- src/AddressTransactionByNonce.tsx | 13 ++++++++++++- src/components/AddressOrENSNameNoTx.tsx | 24 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 src/components/AddressOrENSNameNoTx.tsx diff --git a/src/AddressTransactionByNonce.tsx b/src/AddressTransactionByNonce.tsx index a5bb17e..f7e1a8a 100644 --- a/src/AddressTransactionByNonce.tsx +++ b/src/AddressTransactionByNonce.tsx @@ -2,6 +2,7 @@ import React, { useContext, useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import StandardFrame from "./StandardFrame"; import AddressOrENSNameInvalidNonce from "./components/AddressOrENSNameInvalidNonce"; +import AddressOrENSNameNoTx from "./components/AddressOrENSNameNoTx"; import { ChecksummedAddress } from "./types"; import { transactionURL } from "./url"; import { useTransactionBySenderAndNonce } from "./useErigonHooks"; @@ -37,7 +38,7 @@ const AddressTransactionByNonce: React.FC = ({ // in case of latest let nonce: number | undefined; if (rawNonce === "latest") { - if (txCount !== undefined && txCount > 0) { + if (txCount !== undefined) { nonce = txCount - 1; } } else { @@ -52,10 +53,20 @@ const AddressTransactionByNonce: React.FC = ({ ); const navigate = useNavigate(); + // Loading... if (checksummedAddress === undefined || nonce === undefined) { return ; } + // Address hasn't made the first outbound tx yet + if (nonce < 0) { + return ( + + + + ); + } + // Garbage nonce if (isNaN(nonce)) { return ( diff --git a/src/components/AddressOrENSNameNoTx.tsx b/src/components/AddressOrENSNameNoTx.tsx new file mode 100644 index 0000000..c4f6178 --- /dev/null +++ b/src/components/AddressOrENSNameNoTx.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import StandardSubtitle from "../StandardSubtitle"; +import ContentFrame from "../ContentFrame"; +import AddressOrENSName from "./AddressOrENSName"; + +type AddressOrENSNameNoTxProps = { + addressOrENSName: string; +}; + +const AddressOrENSNameNoTx: React.FC = ({ + addressOrENSName, +}) => ( + <> + Transaction Details + +
+ + : no outbound transactions found. +
+
+ +); + +export default React.memo(AddressOrENSNameNoTx); From 3695db552bb1c6ef90dd81d6a7e35d0f24e75e6c Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Tue, 1 Feb 2022 15:19:42 -0300 Subject: [PATCH 25/29] Handle negative nonce --- src/AddressTransactionByNonce.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/AddressTransactionByNonce.tsx b/src/AddressTransactionByNonce.tsx index f7e1a8a..21324f6 100644 --- a/src/AddressTransactionByNonce.tsx +++ b/src/AddressTransactionByNonce.tsx @@ -43,6 +43,9 @@ const AddressTransactionByNonce: React.FC = ({ } } else { nonce = parseInt(rawNonce, 10); + if (nonce < 0) { + nonce = NaN; + } } // Given all base params are determined, get the corresponding tx From 6a6e2cdb149786d54bd7b362718b010e9fa369ea Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Tue, 1 Feb 2022 15:31:07 -0300 Subject: [PATCH 26/29] Retrofit search urls into search --- src/search/search.ts | 86 ++++++++++---------------------------------- 1 file changed, 19 insertions(+), 67 deletions(-) diff --git a/src/search/search.ts b/src/search/search.ts index e1e6730..95ebdce 100644 --- a/src/search/search.ts +++ b/src/search/search.ts @@ -2,7 +2,6 @@ import { ChangeEventHandler, FormEventHandler, RefObject, - useContext, useRef, useState, } from "react"; @@ -13,8 +12,6 @@ import { isHexString } from "@ethersproject/bytes"; import useKeyboardShortcut from "use-keyboard-shortcut"; import { PAGE_SIZE } from "../params"; import { ProcessedTransaction, TransactionChunk } from "../types"; -import { RuntimeContext } from "../useRuntime"; -import { getTransactionBySenderAndNonceFetcher } from "../useErigonHooks"; export class SearchController { private txs: ProcessedTransaction[]; @@ -209,38 +206,36 @@ export class SearchController { } } -const doSearch = async ( - provider: JsonRpcProvider, - q: string, - navigate: NavigateFunction -) => { +const doSearch = async (q: string, navigate: NavigateFunction) => { // Cleanup q = q.trim(); let maybeAddress = q; - let maybeIndex: string | undefined; + let maybeIndex = ""; const sepIndex = q.lastIndexOf(":"); if (sepIndex !== -1) { maybeAddress = q.substring(0, sepIndex); maybeIndex = q.substring(sepIndex + 1); } + // Plain address? if (isAddress(maybeAddress)) { - // Plain address + nonce? - if (await navigateToTx(provider, maybeAddress, maybeIndex, navigate)) { - return; - } - - // Plain address - navigate(`/address/${maybeAddress}`, { replace: true }); + navigate( + `/address/${maybeAddress}${ + maybeIndex !== "" ? `?nonce=${maybeIndex}` : "" + }`, + { replace: true } + ); return; } + // Tx hash? if (isHexString(q, 32)) { navigate(`/tx/${q}`, { replace: true }); return; } + // Block number? const blockNumber = parseInt(q); if (!isNaN(blockNumber)) { navigate(`/block/${blockNumber}`, { replace: true }); @@ -248,54 +243,12 @@ const doSearch = async ( } // Assume it is an ENS name - const resolvedName = await provider.resolveName(maybeAddress); - if (resolvedName !== null) { - // ENS name + nonce? - if (await navigateToTx(provider, resolvedName, maybeIndex, navigate)) { - return; - } - - // ENS name - navigate(`/address/${maybeAddress}`, { replace: true }); - return; - } - - // TODO: handle default -}; - -const navigateToTx = async ( - provider: JsonRpcProvider, - maybeAddress: string, - maybeIndex: string | undefined, - navigate: NavigateFunction -): Promise => { - if (maybeIndex !== undefined) { - try { - let nonce = 0; - if (maybeIndex === "latest") { - const count = await provider.getTransactionCount(maybeAddress); - if (count > 0) { - nonce = count - 1; - } - } else { - nonce = parseInt(maybeIndex); - } - - const txHash = await getTransactionBySenderAndNonceFetcher({ - provider, - sender: maybeAddress, - nonce, - }); - if (txHash) { - navigate(`/tx/${txHash}`, { replace: true }); - } - return true; - } catch (err) { - // ignore - } - } - - return false; + navigate( + `/address/${maybeAddress}${ + maybeIndex !== "" ? `?nonce=${maybeIndex}` : "" + }`, + { replace: true } + ); }; export const useGenericSearch = (): [ @@ -303,7 +256,6 @@ export const useGenericSearch = (): [ ChangeEventHandler, FormEventHandler ] => { - const { provider } = useContext(RuntimeContext); const [searchString, setSearchString] = useState(""); const [canSubmit, setCanSubmit] = useState(false); const navigate = useNavigate(); @@ -316,14 +268,14 @@ export const useGenericSearch = (): [ const handleSubmit: React.FormEventHandler = (e) => { e.preventDefault(); - if (!canSubmit || !provider) { + if (!canSubmit) { return; } if (searchRef.current) { searchRef.current.value = ""; } - doSearch(provider, searchString, navigate); + doSearch(searchString, navigate); }; const searchRef = useRef(null); From f9f7a569e2ad52928d1c9f922f798c9e1126e3a1 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Tue, 1 Feb 2022 15:53:45 -0300 Subject: [PATCH 27/29] Make nav nonce control dumb; use just urls for navigation; speed up with prefetch --- src/transaction/NavButton.tsx | 13 ++++++++----- src/transaction/NavNonce.tsx | 24 +++++------------------- src/url.ts | 3 +++ 3 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/transaction/NavButton.tsx b/src/transaction/NavButton.tsx index 04d421c..cad49d2 100644 --- a/src/transaction/NavButton.tsx +++ b/src/transaction/NavButton.tsx @@ -1,18 +1,21 @@ import { NavLink } from "react-router-dom"; -import { transactionURL } from "../url"; +import { ChecksummedAddress } from "../types"; +import { addressByNonceURL } from "../url"; // TODO: extract common component with block/NavButton type NavButtonProps = { - txHash: string | null | undefined; + sender: ChecksummedAddress; + nonce: number; disabled?: boolean; }; const NavButton: React.FC = ({ - txHash, + sender, + nonce, disabled, children, }) => { - if (disabled || txHash === null || txHash === undefined) { + if (disabled) { return ( {children} @@ -23,7 +26,7 @@ const NavButton: React.FC = ({ return ( {children} diff --git a/src/transaction/NavNonce.tsx b/src/transaction/NavNonce.tsx index ee30a4b..fb94b66 100644 --- a/src/transaction/NavNonce.tsx +++ b/src/transaction/NavNonce.tsx @@ -7,7 +7,6 @@ import { ChecksummedAddress } from "../types"; import { RuntimeContext } from "../useRuntime"; import { prefetchTransactionBySenderAndNonce, - useTransactionBySenderAndNonce, useTransactionCount, } from "../useErigonHooks"; import { useSWRConfig } from "swr"; @@ -19,22 +18,7 @@ type NavNonceProps = { const NavNonce: React.FC = ({ sender, nonce }) => { const { provider } = useContext(RuntimeContext); - const prevTxHash = useTransactionBySenderAndNonce( - provider, - sender, - nonce - 1 - ); - const nextTxHash = useTransactionBySenderAndNonce( - provider, - sender, - nonce + 1 - ); const count = useTransactionCount(provider, sender); - const lastTxHash = useTransactionBySenderAndNonce( - provider, - sender, - count !== undefined ? count - 1 : undefined - ); // Prefetch const swrConfig = useSWRConfig(); @@ -60,17 +44,19 @@ const NavNonce: React.FC = ({ sender, nonce }) => { return (
- + = count - 1} > = count - 1} > diff --git a/src/url.ts b/src/url.ts index 2216bf6..cf44ab6 100644 --- a/src/url.ts +++ b/src/url.ts @@ -20,6 +20,9 @@ export const blockTxsURL = (blockNum: BlockTag) => `/block/${blockNum}/txs`; export const transactionURL = (txHash: string) => `/tx/${txHash}`; +export const addressByNonceURL = (address: ChecksummedAddress, nonce: number) => + `/address/${address}?nonce=${nonce}`; + export enum SourcifySource { // Resolve trusted IPNS for root IPFS IPFS_IPNS, From 97f6ae89fe5ea641e28e2846eaa656786c08ae8e Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Tue, 1 Feb 2022 16:01:23 -0300 Subject: [PATCH 28/29] Fix display of error message while loading tx by nonce --- src/AddressTransactionByNonce.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/AddressTransactionByNonce.tsx b/src/AddressTransactionByNonce.tsx index 21324f6..e804f84 100644 --- a/src/AddressTransactionByNonce.tsx +++ b/src/AddressTransactionByNonce.tsx @@ -57,7 +57,11 @@ const AddressTransactionByNonce: React.FC = ({ const navigate = useNavigate(); // Loading... - if (checksummedAddress === undefined || nonce === undefined) { + if ( + checksummedAddress === undefined || + nonce === undefined || + txHash === undefined + ) { return ; } @@ -83,7 +87,7 @@ const AddressTransactionByNonce: React.FC = ({ } // Valid nonce, but no tx found - if (!txHash) { + if (txHash === null) { return ( Date: Tue, 1 Feb 2022 19:14:05 -0300 Subject: [PATCH 29/29] Fix prefetch --- src/transaction/NavNonce.tsx | 25 +++++++--------- src/useErigonHooks.ts | 58 ++++++++++++++++++++++-------------- 2 files changed, 47 insertions(+), 36 deletions(-) diff --git a/src/transaction/NavNonce.tsx b/src/transaction/NavNonce.tsx index fb94b66..9efd47f 100644 --- a/src/transaction/NavNonce.tsx +++ b/src/transaction/NavNonce.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from "react"; +import React, { useContext, useEffect } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faChevronLeft } from "@fortawesome/free-solid-svg-icons/faChevronLeft"; import { faChevronRight } from "@fortawesome/free-solid-svg-icons/faChevronRight"; @@ -22,25 +22,22 @@ const NavNonce: React.FC = ({ sender, nonce }) => { // Prefetch const swrConfig = useSWRConfig(); - const prefetch = () => { - if (provider && sender && nonce !== undefined) { + useEffect(() => { + if (!provider || !sender || nonce === undefined || count === undefined) { + return; + } + + prefetchTransactionBySenderAndNonce(swrConfig, provider, sender, nonce - 1); + prefetchTransactionBySenderAndNonce(swrConfig, provider, sender, nonce + 1); + if (count > 0) { prefetchTransactionBySenderAndNonce( swrConfig, provider, sender, - nonce - 2 - ); - prefetchTransactionBySenderAndNonce( - swrConfig, - provider, - sender, - nonce + 2 + count - 1 ); } - }; - - // Always prefetch - prefetch(); + }, [swrConfig, provider, sender, nonce, count]); return (
diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index 7eb847c..45378f5 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -530,39 +530,49 @@ export const useTransactionCount = ( }; type TransactionBySenderAndNonceKey = { - provider: JsonRpcProvider; + network: number; sender: ChecksummedAddress; nonce: number; }; -export const getTransactionBySenderAndNonceFetcher = async ({ - provider, - sender, - nonce, -}: TransactionBySenderAndNonceKey): Promise => { - if (nonce < 0) { - return undefined; - } - - const result = (await provider.send("ots_getTransactionBySenderAndNonce", [ +const getTransactionBySenderAndNonceFetcher = + (provider: JsonRpcProvider) => + async ({ + network, sender, nonce, - ])) as string; + }: TransactionBySenderAndNonceKey): Promise => { + if (nonce < 0) { + return undefined; + } - // Empty or success - return result; -}; + const result = (await provider.send("ots_getTransactionBySenderAndNonce", [ + sender, + nonce, + ])) as string; + + // Empty or success + return result; + }; export const prefetchTransactionBySenderAndNonce = ( - { cache, mutate }: ReturnType, + { mutate }: ReturnType, provider: JsonRpcProvider, sender: ChecksummedAddress, nonce: number ) => { - const key: TransactionBySenderAndNonceKey = { provider, sender, nonce }; - if (cache.get(key)) { - mutate(key, getTransactionBySenderAndNonceFetcher(key)); - } + const key: TransactionBySenderAndNonceKey = { + network: provider.network.chainId, + sender, + nonce, + }; + mutate(key, (curr: any) => { + if (curr) { + return curr; + } + return getTransactionBySenderAndNonceFetcher(provider)(key); + }); + // } }; export const useTransactionBySenderAndNonce = ( @@ -576,9 +586,13 @@ export const useTransactionBySenderAndNonce = ( TransactionBySenderAndNonceKey | null >( provider && sender && nonce !== undefined - ? { provider, sender, nonce } + ? { + network: provider.network.chainId, + sender, + nonce, + } : null, - getTransactionBySenderAndNonceFetcher + getTransactionBySenderAndNonceFetcher(provider!) ); if (error) {