From 46edf8ae929a5bdfbec5eb1e9a39792bf6f039d4 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Fri, 2 Jul 2021 21:40:02 -0300 Subject: [PATCH 1/3] Add reverse ENS resolution on block transactions list --- src/BlockTransactions.tsx | 47 +++++++++++++++++++++++++++-- src/components/Address.tsx | 12 +++++--- src/components/AddressOrENSName.tsx | 37 +++++++++++++++++++++++ src/components/ENSName.tsx | 25 +++++++++++++++ src/components/ENSNameLink.tsx | 27 +++++++++++++++++ src/components/ensLogo.svg | 1 + src/search/TransactionItem.tsx | 36 +++++++++++++--------- src/types.ts | 4 +++ tsconfig.json | 10 ++---- 9 files changed, 170 insertions(+), 29 deletions(-) create mode 100644 src/components/AddressOrENSName.tsx create mode 100644 src/components/ENSName.tsx create mode 100644 src/components/ENSNameLink.tsx create mode 100644 src/components/ensLogo.svg diff --git a/src/BlockTransactions.tsx b/src/BlockTransactions.tsx index 060b937..7370db6 100644 --- a/src/BlockTransactions.tsx +++ b/src/BlockTransactions.tsx @@ -11,7 +11,7 @@ import ResultHeader from "./search/ResultHeader"; import PendingResults from "./search/PendingResults"; import TransactionItem from "./search/TransactionItem"; import BlockLink from "./components/BlockLink"; -import { ProcessedTransaction } from "./types"; +import { ProcessedTransaction, ENSReverseCache } from "./types"; import { PAGE_SIZE } from "./params"; import { useFeeToggler } from "./search/useFeeToggler"; @@ -80,6 +80,44 @@ const BlockTransactions: React.FC = () => { }, [txs, pageNumber]); const total = useMemo(() => txs?.length ?? 0, [txs]); + const [reverseCache, setReverseCache] = useState(); + useEffect(() => { + if (!page) { + return; + } + + const addrSet = new Set(); + for (const tx of page) { + if (tx.from) { + addrSet.add(tx.from); + } + if (tx.to) { + addrSet.add(tx.to); + } + } + const addresses = Array.from(addrSet); + + const reverseResolve = async () => { + const solvers: Promise[] = []; + for (const a of addresses) { + solvers.push(provider.lookupAddress(a)); + } + + const results = await Promise.all(solvers); + const cache: ENSReverseCache = {}; + for (let i = 0; i < results.length; i++) { + if (results[i] === null) { + continue; + } + cache[addresses[i]] = results[i]; + } + console.log("RESOLVED"); + console.log(cache); + setReverseCache(cache); + }; + reverseResolve(); + }, [page]); + document.title = `Block #${blockNumber} Txns | Otterscan`; const [feeDisplay, feeDisplayToggler] = useFeeToggler(); @@ -112,7 +150,12 @@ const BlockTransactions: React.FC = () => { {page ? ( <> {page.map((tx) => ( - + ))}
diff --git a/src/components/Address.tsx b/src/components/Address.tsx index fb9f6c1..d757ed3 100644 --- a/src/components/Address.tsx +++ b/src/components/Address.tsx @@ -1,9 +1,13 @@ import React from "react"; -const Address: React.FC = ({ children }) => ( - -

{children}

+type AddressProps = { + address: string; +}; + +const Address: React.FC = ({ address }) => ( + +

{address}

); -export default Address; +export default React.memo(Address); diff --git a/src/components/AddressOrENSName.tsx b/src/components/AddressOrENSName.tsx new file mode 100644 index 0000000..e3cfdb9 --- /dev/null +++ b/src/components/AddressOrENSName.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import Address from "./Address"; +import AddressLink from "./AddressLink"; +import ENSName from "./ENSName"; +import ENSNameLink from "./ENSNameLink"; + +type AddressOrENSNameProps = { + address: string; + ensName?: string; + selectedAddress?: string; +}; + +const AddressOrENSName: React.FC = ({ + address, + ensName, + selectedAddress, +}) => { + return address === selectedAddress ? ( + <> + {ensName ? ( + + ) : ( +
+ )} + + ) : ( + <> + {ensName ? ( + + ) : ( + + )} + + ); +}; + +export default React.memo(AddressOrENSName); diff --git a/src/components/ENSName.tsx b/src/components/ENSName.tsx new file mode 100644 index 0000000..f5750fa --- /dev/null +++ b/src/components/ENSName.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import ENSLogo from "./ensLogo.svg"; + +type ENSNameProps = { + name: string; + address: string; +}; + +const ENSName: React.FC = ({ name, address }) => ( +
+ ENS Logo +

{name}

+
+); + +export default React.memo(ENSName); diff --git a/src/components/ENSNameLink.tsx b/src/components/ENSNameLink.tsx new file mode 100644 index 0000000..a111139 --- /dev/null +++ b/src/components/ENSNameLink.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import { NavLink } from "react-router-dom"; +import ENSLogo from "./ensLogo.svg"; + +type ENSNameLinkProps = { + name: string; + address: string; +}; + +const ENSNameLink: React.FC = ({ name, address }) => ( + + ENS Logo +

{name}

+
+); + +export default React.memo(ENSNameLink); diff --git a/src/components/ensLogo.svg b/src/components/ensLogo.svg new file mode 100644 index 0000000..20d94c0 --- /dev/null +++ b/src/components/ensLogo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/search/TransactionItem.tsx b/src/search/TransactionItem.tsx index abe9e8c..4e74b35 100644 --- a/src/search/TransactionItem.tsx +++ b/src/search/TransactionItem.tsx @@ -4,25 +4,26 @@ import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons"; import MethodName from "../components/MethodName"; import BlockLink from "../components/BlockLink"; import TransactionLink from "../components/TransactionLink"; -import Address from "../components/Address"; -import AddressLink from "../components/AddressLink"; +import AddressOrENSName from "../components/AddressOrENSName"; import TimestampAge from "../components/TimestampAge"; import TransactionDirection, { Direction, } from "../components/TransactionDirection"; import TransactionValue from "../components/TransactionValue"; -import { ProcessedTransaction } from "../types"; +import { ENSReverseCache, ProcessedTransaction } from "../types"; import { FeeDisplay } from "./useFeeToggler"; import { formatValue } from "../components/formatter"; type TransactionItemProps = { tx: ProcessedTransaction; + ensCache?: ENSReverseCache; selectedAddress?: string; feeDisplay: FeeDisplay; }; const TransactionItem: React.FC = ({ tx, + ensCache, selectedAddress, feeDisplay, }) => { @@ -39,6 +40,9 @@ const TransactionItem: React.FC = ({ } } + const ensFrom = ensCache && tx.from && ensCache[tx.from]; + const ensTo = ensCache && tx.to && ensCache[tx.to]; + return (
@@ -58,24 +62,26 @@ const TransactionItem: React.FC = ({ - {tx.from && - (tx.from === selectedAddress ? ( -
{tx.from}
- ) : ( - - ))} + {tx.from && ( + + )}
- {tx.to && - (tx.to === selectedAddress ? ( -
{tx.to}
- ) : ( - - ))} + {tx.to && ( + + )}
diff --git a/src/types.ts b/src/types.ts index 9677652..170fe58 100644 --- a/src/types.ts +++ b/src/types.ts @@ -19,3 +19,7 @@ export type TransactionChunk = { firstPage: boolean; lastPage: boolean; }; + +export type ENSReverseCache = { + [address: string]: string; +}; diff --git a/tsconfig.json b/tsconfig.json index a273b0c..9d379a3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, @@ -20,7 +16,5 @@ "noEmit": true, "jsx": "react-jsx" }, - "include": [ - "src" - ] + "include": ["src"] } From a697c8eb831dbe8188587be6c9edddf3ed175858 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Sat, 3 Jul 2021 01:45:36 -0300 Subject: [PATCH 2/3] Make ENS icon grayscale when not a link --- src/components/ENSName.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ENSName.tsx b/src/components/ENSName.tsx index f5750fa..035c4ed 100644 --- a/src/components/ENSName.tsx +++ b/src/components/ENSName.tsx @@ -12,7 +12,7 @@ const ENSName: React.FC = ({ name, address }) => ( title={`${name}: ${address}`} > ENS Logo Date: Sat, 3 Jul 2021 01:46:15 -0300 Subject: [PATCH 3/3] Extract ens cache hook; apply on block/address transactions page --- src/AddressTransactions.tsx | 36 ++++++++++++++++-------------- src/BlockTransactions.tsx | 41 +++------------------------------- src/useReverseCache.ts | 44 +++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 54 deletions(-) create mode 100644 src/useReverseCache.ts diff --git a/src/AddressTransactions.tsx b/src/AddressTransactions.tsx index b7985fa..03011cf 100644 --- a/src/AddressTransactions.tsx +++ b/src/AddressTransactions.tsx @@ -12,6 +12,7 @@ import ResultHeader from "./search/ResultHeader"; import PendingResults from "./search/PendingResults"; import TransactionItem from "./search/TransactionItem"; import { SearchController } from "./search/search"; +import { useENSCache } from "./useReverseCache"; import { useFeeToggler } from "./search/useFeeToggler"; import { provider } from "./ethersconfig"; @@ -41,8 +42,20 @@ const AddressTransactions: React.FC = () => { // If it looks like it is an ENS name, try to resolve it useEffect(() => { if (ethers.utils.isAddress(params.addressOrName)) { + setENS(false); + setError(false); + // Normalize to checksummed address - setChecksummedAddress(ethers.utils.getAddress(params.addressOrName)); + const _checksummedAddress = ethers.utils.getAddress(params.addressOrName); + if (_checksummedAddress !== params.addressOrName) { + // Request came with a non-checksummed address; fix the URL + history.replace( + `/address/${_checksummedAddress}${ + params.direction ? "/" + params.direction : "" + }${location.search}` + ); + } + setChecksummedAddress(_checksummedAddress); return; } @@ -50,27 +63,16 @@ const AddressTransactions: React.FC = () => { const resolvedAddress = await provider.resolveName(params.addressOrName); if (resolvedAddress !== null) { setENS(true); - setChecksummedAddress(resolvedAddress); setError(false); + setChecksummedAddress(resolvedAddress); } else { + setENS(false); setError(true); + setChecksummedAddress(undefined); } }; resolveName(); - }, [params.addressOrName]); - - // Request came with a non-checksummed address; fix the URL - if ( - !isENS && - checksummedAddress && - params.addressOrName !== checksummedAddress - ) { - history.replace( - `/address/${checksummedAddress}${ - params.direction ? "/" + params.direction : "" - }${location.search}` - ); - } + }, [params.addressOrName, history, params.direction, location.search]); const [controller, setController] = useState(); useEffect(() => { @@ -128,6 +130,7 @@ const AddressTransactions: React.FC = () => { }, [checksummedAddress, params.direction, hash, controller]); const page = useMemo(() => controller?.getPage(), [controller]); + const reverseCache = useENSCache(page); document.title = `Address ${params.addressOrName} | Otterscan`; @@ -189,6 +192,7 @@ const AddressTransactions: React.FC = () => { diff --git a/src/BlockTransactions.tsx b/src/BlockTransactions.tsx index 7370db6..ef81025 100644 --- a/src/BlockTransactions.tsx +++ b/src/BlockTransactions.tsx @@ -11,9 +11,10 @@ import ResultHeader from "./search/ResultHeader"; import PendingResults from "./search/PendingResults"; import TransactionItem from "./search/TransactionItem"; import BlockLink from "./components/BlockLink"; -import { ProcessedTransaction, ENSReverseCache } from "./types"; +import { ProcessedTransaction } from "./types"; import { PAGE_SIZE } from "./params"; import { useFeeToggler } from "./search/useFeeToggler"; +import { useENSCache } from "./useReverseCache"; type BlockParams = { blockNumber: string; @@ -80,43 +81,7 @@ const BlockTransactions: React.FC = () => { }, [txs, pageNumber]); const total = useMemo(() => txs?.length ?? 0, [txs]); - const [reverseCache, setReverseCache] = useState(); - useEffect(() => { - if (!page) { - return; - } - - const addrSet = new Set(); - for (const tx of page) { - if (tx.from) { - addrSet.add(tx.from); - } - if (tx.to) { - addrSet.add(tx.to); - } - } - const addresses = Array.from(addrSet); - - const reverseResolve = async () => { - const solvers: Promise[] = []; - for (const a of addresses) { - solvers.push(provider.lookupAddress(a)); - } - - const results = await Promise.all(solvers); - const cache: ENSReverseCache = {}; - for (let i = 0; i < results.length; i++) { - if (results[i] === null) { - continue; - } - cache[addresses[i]] = results[i]; - } - console.log("RESOLVED"); - console.log(cache); - setReverseCache(cache); - }; - reverseResolve(); - }, [page]); + const reverseCache = useENSCache(page); document.title = `Block #${blockNumber} Txns | Otterscan`; diff --git a/src/useReverseCache.ts b/src/useReverseCache.ts new file mode 100644 index 0000000..9451d88 --- /dev/null +++ b/src/useReverseCache.ts @@ -0,0 +1,44 @@ +import { useState, useEffect } from "react"; +import { ENSReverseCache, ProcessedTransaction } from "./types"; +import { provider } from "./ethersconfig"; + +export const useENSCache = (page?: ProcessedTransaction[]) => { + const [reverseCache, setReverseCache] = useState(); + + useEffect(() => { + if (!page) { + return; + } + + const addrSet = new Set(); + for (const tx of page) { + if (tx.from) { + addrSet.add(tx.from); + } + if (tx.to) { + addrSet.add(tx.to); + } + } + const addresses = Array.from(addrSet); + + const reverseResolve = async () => { + const solvers: Promise[] = []; + for (const a of addresses) { + solvers.push(provider.lookupAddress(a)); + } + + const results = await Promise.all(solvers); + const cache: ENSReverseCache = {}; + for (let i = 0; i < results.length; i++) { + if (results[i] === null) { + continue; + } + cache[addresses[i]] = results[i]; + } + setReverseCache(cache); + }; + reverseResolve(); + }, [page]); + + return reverseCache; +};