Merge branch 'feature/reverse-ens-tx-list' into develop
This commit is contained in:
commit
689723655d
|
@ -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<SearchController>();
|
||||
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 = () => {
|
|||
<TransactionItem
|
||||
key={tx.hash}
|
||||
tx={tx}
|
||||
ensCache={reverseCache}
|
||||
selectedAddress={checksummedAddress}
|
||||
feeDisplay={feeDisplay}
|
||||
/>
|
||||
|
|
|
@ -14,6 +14,7 @@ import BlockLink from "./components/BlockLink";
|
|||
import { ProcessedTransaction } from "./types";
|
||||
import { PAGE_SIZE } from "./params";
|
||||
import { useFeeToggler } from "./search/useFeeToggler";
|
||||
import { useENSCache } from "./useReverseCache";
|
||||
|
||||
type BlockParams = {
|
||||
blockNumber: string;
|
||||
|
@ -80,6 +81,8 @@ const BlockTransactions: React.FC = () => {
|
|||
}, [txs, pageNumber]);
|
||||
const total = useMemo(() => txs?.length ?? 0, [txs]);
|
||||
|
||||
const reverseCache = useENSCache(page);
|
||||
|
||||
document.title = `Block #${blockNumber} Txns | Otterscan`;
|
||||
|
||||
const [feeDisplay, feeDisplayToggler] = useFeeToggler();
|
||||
|
@ -112,7 +115,12 @@ const BlockTransactions: React.FC = () => {
|
|||
{page ? (
|
||||
<>
|
||||
{page.map((tx) => (
|
||||
<TransactionItem key={tx.hash} tx={tx} feeDisplay={feeDisplay} />
|
||||
<TransactionItem
|
||||
key={tx.hash}
|
||||
tx={tx}
|
||||
ensCache={reverseCache}
|
||||
feeDisplay={feeDisplay}
|
||||
/>
|
||||
))}
|
||||
<div className="flex justify-between items-baseline py-3">
|
||||
<div className="text-sm text-gray-500">
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import React from "react";
|
||||
|
||||
const Address: React.FC = ({ children }) => (
|
||||
<span className="font-address text-gray-400 truncate">
|
||||
<p className="truncate">{children}</p>
|
||||
type AddressProps = {
|
||||
address: string;
|
||||
};
|
||||
|
||||
const Address: React.FC<AddressProps> = ({ address }) => (
|
||||
<span className="font-address text-gray-400 truncate" title={address}>
|
||||
<p className="truncate">{address}</p>
|
||||
</span>
|
||||
);
|
||||
|
||||
export default Address;
|
||||
export default React.memo(Address);
|
||||
|
|
|
@ -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<AddressOrENSNameProps> = ({
|
||||
address,
|
||||
ensName,
|
||||
selectedAddress,
|
||||
}) => {
|
||||
return address === selectedAddress ? (
|
||||
<>
|
||||
{ensName ? (
|
||||
<ENSName name={ensName} address={address} />
|
||||
) : (
|
||||
<Address address={address} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{ensName ? (
|
||||
<ENSNameLink name={ensName} address={address} />
|
||||
) : (
|
||||
<AddressLink address={address} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(AddressOrENSName);
|
|
@ -0,0 +1,25 @@
|
|||
import React from "react";
|
||||
import ENSLogo from "./ensLogo.svg";
|
||||
|
||||
type ENSNameProps = {
|
||||
name: string;
|
||||
address: string;
|
||||
};
|
||||
|
||||
const ENSName: React.FC<ENSNameProps> = ({ name, address }) => (
|
||||
<div
|
||||
className="flex items-baseline space-x-1 font-sans text-gray-700 truncate"
|
||||
title={`${name}: ${address}`}
|
||||
>
|
||||
<img
|
||||
className="self-center filter grayscale"
|
||||
src={ENSLogo}
|
||||
alt="ENS Logo"
|
||||
width={12}
|
||||
height={12}
|
||||
/>
|
||||
<p className="truncate">{name}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default React.memo(ENSName);
|
|
@ -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<ENSNameLinkProps> = ({ name, address }) => (
|
||||
<NavLink
|
||||
className="flex items-baseline space-x-1 font-sans text-link-blue hover:text-link-blue-hover truncate"
|
||||
to={`/address/${name}`}
|
||||
title={`${name}: ${address}`}
|
||||
>
|
||||
<img
|
||||
className="self-center"
|
||||
src={ENSLogo}
|
||||
alt="ENS Logo"
|
||||
width={12}
|
||||
height={12}
|
||||
/>
|
||||
<p className="truncate">{name}</p>
|
||||
</NavLink>
|
||||
);
|
||||
|
||||
export default React.memo(ENSNameLink);
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 72.52 80.95"><defs><style>.cls-3{fill:#a0a8d4}</style><linearGradient id="linear-gradient" x1="41.95" y1="2.57" x2="12.57" y2="34.42" gradientUnits="userSpaceOnUse"><stop offset=".58" stop-color="#a0a8d4"/><stop offset=".73" stop-color="#8791c7"/><stop offset=".91" stop-color="#6470b4"/></linearGradient><linearGradient id="linear-gradient-2" x1="42.57" y1="81.66" x2="71.96" y2="49.81" xlink:href="#linear-gradient"/><linearGradient id="linear-gradient-3" x1="42.26" y1="1.24" x2="42.26" y2="82.84" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#513eff"/><stop offset=".18" stop-color="#5157ff"/><stop offset=".57" stop-color="#5298ff"/><stop offset="1" stop-color="#52e5ff"/></linearGradient></defs><g style="isolation:isolate"><g id="Layer_1" data-name="Layer 1"><path d="M15.28 34.39c.8 1.71 2.78 5.09 2.78 5.09L40.95 1.64l-22.34 15.6a9.75 9.75 0 0 0-3.18 3.5 16.19 16.19 0 0 0-.15 13.65z" transform="translate(-6 -1.64)" fill="url(#linear-gradient)"/><path class="cls-3" d="M6.21 46.85a25.47 25.47 0 0 0 10 18.51l24.71 17.23s-15.46-22.28-28.5-44.45a22.39 22.39 0 0 1-2.62-7.56 12.1 12.1 0 0 1 0-3.63c-.34.63-1 1.92-1 1.92a29.35 29.35 0 0 0-2.67 8.55 52.28 52.28 0 0 0 .08 9.43z" transform="translate(-6 -1.64)"/><path d="M69.25 49.84c-.8-1.71-2.78-5.09-2.78-5.09L43.58 82.59 65.92 67a9.75 9.75 0 0 0 3.18-3.5 16.19 16.19 0 0 0 .15-13.66z" transform="translate(-6 -1.64)" fill="url(#linear-gradient-2)"/><path class="cls-3" d="M78.32 37.38a25.47 25.47 0 0 0-10-18.51L43.61 1.64s15.45 22.28 28.5 44.45a22.39 22.39 0 0 1 2.61 7.56 12.1 12.1 0 0 1 0 3.63c.34-.63 1-1.92 1-1.92a29.35 29.35 0 0 0 2.67-8.55 52.28 52.28 0 0 0-.07-9.43z" transform="translate(-6 -1.64)"/><path d="M15.43 20.74a9.75 9.75 0 0 1 3.18-3.5l22.34-15.6-22.89 37.85s-2-3.38-2.78-5.09a16.19 16.19 0 0 1 .15-13.66zM6.21 46.85a25.47 25.47 0 0 0 10 18.51l24.71 17.23s-15.46-22.28-28.5-44.45a22.39 22.39 0 0 1-2.62-7.56 12.1 12.1 0 0 1 0-3.63c-.34.63-1 1.92-1 1.92a29.35 29.35 0 0 0-2.67 8.55 52.28 52.28 0 0 0 .08 9.43zm63 3c-.8-1.71-2.78-5.09-2.78-5.09L43.58 82.59 65.92 67a9.75 9.75 0 0 0 3.18-3.5 16.19 16.19 0 0 0 .15-13.66zm9.07-12.46a25.47 25.47 0 0 0-10-18.51L43.61 1.64s15.45 22.28 28.5 44.45a22.39 22.39 0 0 1 2.61 7.56 12.1 12.1 0 0 1 0 3.63c.34-.63 1-1.92 1-1.92a29.35 29.35 0 0 0 2.67-8.55 52.28 52.28 0 0 0-.07-9.43z" transform="translate(-6 -1.64)" style="mix-blend-mode:color" fill="url(#linear-gradient-3)"/></g></g></svg>
|
After Width: | Height: | Size: 2.5 KiB |
|
@ -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<TransactionItemProps> = ({
|
||||
tx,
|
||||
ensCache,
|
||||
selectedAddress,
|
||||
feeDisplay,
|
||||
}) => {
|
||||
|
@ -39,6 +40,9 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
|||
}
|
||||
}
|
||||
|
||||
const ensFrom = ensCache && tx.from && ensCache[tx.from];
|
||||
const ensTo = ensCache && tx.to && ensCache[tx.to];
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-12 gap-x-1 items-baseline text-sm border-t border-gray-200 hover:bg-gray-100 px-2 py-3">
|
||||
<div className="col-span-2 flex space-x-1 items-baseline">
|
||||
|
@ -58,24 +62,26 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
|||
<TimestampAge timestamp={tx.timestamp} />
|
||||
<span className="col-span-2 flex justify-between items-baseline space-x-2 pr-2">
|
||||
<span className="truncate" title={tx.from}>
|
||||
{tx.from &&
|
||||
(tx.from === selectedAddress ? (
|
||||
<Address>{tx.from}</Address>
|
||||
) : (
|
||||
<AddressLink address={tx.from} />
|
||||
))}
|
||||
{tx.from && (
|
||||
<AddressOrENSName
|
||||
address={tx.from}
|
||||
ensName={ensFrom}
|
||||
selectedAddress={selectedAddress}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
<span>
|
||||
<TransactionDirection direction={direction} />
|
||||
</span>
|
||||
</span>
|
||||
<span className="col-span-2 truncate" title={tx.to}>
|
||||
{tx.to &&
|
||||
(tx.to === selectedAddress ? (
|
||||
<Address>{tx.to}</Address>
|
||||
) : (
|
||||
<AddressLink address={tx.to} />
|
||||
))}
|
||||
{tx.to && (
|
||||
<AddressOrENSName
|
||||
address={tx.to}
|
||||
ensName={ensTo}
|
||||
selectedAddress={selectedAddress}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
<span className="col-span-2 truncate">
|
||||
<TransactionValue value={tx.value} />
|
||||
|
|
|
@ -19,3 +19,7 @@ export type TransactionChunk = {
|
|||
firstPage: boolean;
|
||||
lastPage: boolean;
|
||||
};
|
||||
|
||||
export type ENSReverseCache = {
|
||||
[address: string]: string;
|
||||
};
|
||||
|
|
|
@ -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<ENSReverseCache>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!page) {
|
||||
return;
|
||||
}
|
||||
|
||||
const addrSet = new Set<string>();
|
||||
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<string>[] = [];
|
||||
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;
|
||||
};
|
|
@ -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"]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue