Merge branch 'feature/initial-ens-support' into develop

This commit is contained in:
Willian Mitsuda 2021-07-02 16:25:25 -03:00
commit f4d74494c6
5 changed files with 121 additions and 75 deletions

View File

@ -1,5 +1,6 @@
import React, { useState, useEffect, useMemo } from "react"; import React, { useState, useEffect, useMemo } from "react";
import { useParams, useLocation, useHistory } from "react-router-dom"; import { useParams, useLocation, useHistory } from "react-router-dom";
import { ethers } from "ethers";
import queryString from "query-string"; import queryString from "query-string";
import Blockies from "react-blockies"; import Blockies from "react-blockies";
import StandardFrame from "./StandardFrame"; import StandardFrame from "./StandardFrame";
@ -12,10 +13,10 @@ import PendingResults from "./search/PendingResults";
import TransactionItem from "./search/TransactionItem"; import TransactionItem from "./search/TransactionItem";
import { SearchController } from "./search/search"; import { SearchController } from "./search/search";
import { useFeeToggler } from "./search/useFeeToggler"; import { useFeeToggler } from "./search/useFeeToggler";
import { ethers } from "ethers"; import { provider } from "./ethersconfig";
type BlockParams = { type BlockParams = {
address: string; addressOrName: string;
direction?: string; direction?: string;
}; };
@ -33,13 +34,37 @@ const AddressTransactions: React.FC = () => {
hash = qs.h as string; hash = qs.h as string;
} }
// Normalize to checksummed address const [checksummedAddress, setChecksummedAddress] = useState<string>();
const checksummedAddress = useMemo( const [isENS, setENS] = useState<boolean>();
() => ethers.utils.getAddress(params.address), const [error, setError] = useState<boolean>();
[params.address]
); // If it looks like it is an ENS name, try to resolve it
if (params.address !== checksummedAddress) { useEffect(() => {
console.log("NORMALIZE"); if (ethers.utils.isAddress(params.addressOrName)) {
// Normalize to checksummed address
setChecksummedAddress(ethers.utils.getAddress(params.addressOrName));
return;
}
const resolveName = async () => {
const resolvedAddress = await provider.resolveName(params.addressOrName);
if (resolvedAddress !== null) {
setENS(true);
setChecksummedAddress(resolvedAddress);
setError(false);
} else {
setError(true);
}
};
resolveName();
}, [params.addressOrName]);
// Request came with a non-checksummed address; fix the URL
if (
!isENS &&
checksummedAddress &&
params.addressOrName !== checksummedAddress
) {
history.replace( history.replace(
`/address/${checksummedAddress}${ `/address/${checksummedAddress}${
params.direction ? "/" + params.direction : "" params.direction ? "/" + params.direction : ""
@ -49,6 +74,10 @@ const AddressTransactions: React.FC = () => {
const [controller, setController] = useState<SearchController>(); const [controller, setController] = useState<SearchController>();
useEffect(() => { useEffect(() => {
if (!checksummedAddress) {
return;
}
const readFirstPage = async () => { const readFirstPage = async () => {
const _controller = await SearchController.firstPage(checksummedAddress); const _controller = await SearchController.firstPage(checksummedAddress);
setController(_controller); setController(_controller);
@ -100,77 +129,92 @@ const AddressTransactions: React.FC = () => {
const page = useMemo(() => controller?.getPage(), [controller]); const page = useMemo(() => controller?.getPage(), [controller]);
document.title = `Address ${params.address} | Otterscan`; document.title = `Address ${params.addressOrName} | Otterscan`;
const [feeDisplay, feeDisplayToggler] = useFeeToggler(); const [feeDisplay, feeDisplayToggler] = useFeeToggler();
return ( return (
<StandardFrame> <StandardFrame>
<StandardSubtitle> {error ? (
<div className="flex space-x-2 items-baseline"> <span className="text-base">
<Blockies "{params.addressOrName}" is not an ETH address or ENS name.
className="self-center rounded" </span>
seed={params.address.toLowerCase()} ) : (
scale={3} checksummedAddress && (
/>
<span>Address</span>
<span className="font-address text-base text-gray-500">
{params.address}
</span>
<Copy value={params.address} rounded />
</div>
</StandardSubtitle>
<ContentFrame>
<div className="flex justify-between items-baseline py-3">
<div className="text-sm text-gray-500">
{page === undefined ? (
<>Waiting for search results...</>
) : (
<>{page.length} transactions on this page</>
)}
</div>
<UndefinedPageControl
address={params.address}
isFirst={controller?.isFirst}
isLast={controller?.isLast}
prevHash={page ? page[0].hash : ""}
nextHash={page ? page[page.length - 1].hash : ""}
disabled={controller === undefined}
/>
</div>
<ResultHeader
feeDisplay={feeDisplay}
feeDisplayToggler={feeDisplayToggler}
/>
{controller ? (
<> <>
{controller.getPage().map((tx) => ( <StandardSubtitle>
<TransactionItem <div className="flex space-x-2 items-baseline">
key={tx.hash} <Blockies
tx={tx} className="self-center rounded"
selectedAddress={params.address} seed={checksummedAddress.toLowerCase()}
feeDisplay={feeDisplay} scale={3}
/> />
))} <span>Address</span>
<div className="flex justify-between items-baseline py-3"> <span className="font-address text-base text-gray-500">
<div className="text-sm text-gray-500"> {checksummedAddress}
{page !== undefined && ( </span>
<>{page.length} transactions on this page</> <Copy value={checksummedAddress} rounded />
{isENS && (
<span className="rounded-lg px-2 py-1 bg-gray-200 text-gray-500 text-xs">
ENS: {params.addressOrName}
</span>
)} )}
</div> </div>
<UndefinedPageControl </StandardSubtitle>
address={params.address} <ContentFrame>
isFirst={controller.isFirst} <div className="flex justify-between items-baseline py-3">
isLast={controller.isLast} <div className="text-sm text-gray-500">
prevHash={page ? page[0].hash : ""} {page === undefined ? (
nextHash={page ? page[page.length - 1].hash : ""} <>Waiting for search results...</>
) : (
<>{page.length} transactions on this page</>
)}
</div>
<UndefinedPageControl
address={params.addressOrName}
isFirst={controller?.isFirst}
isLast={controller?.isLast}
prevHash={page ? page[0].hash : ""}
nextHash={page ? page[page.length - 1].hash : ""}
disabled={controller === undefined}
/>
</div>
<ResultHeader
feeDisplay={feeDisplay}
feeDisplayToggler={feeDisplayToggler}
/> />
</div> {controller ? (
<>
{controller.getPage().map((tx) => (
<TransactionItem
key={tx.hash}
tx={tx}
selectedAddress={checksummedAddress}
feeDisplay={feeDisplay}
/>
))}
<div className="flex justify-between items-baseline py-3">
<div className="text-sm text-gray-500">
{page !== undefined && (
<>{page.length} transactions on this page</>
)}
</div>
<UndefinedPageControl
address={params.addressOrName}
isFirst={controller.isFirst}
isLast={controller.isLast}
prevHash={page ? page[0].hash : ""}
nextHash={page ? page[page.length - 1].hash : ""}
/>
</div>
</>
) : (
<PendingResults />
)}
</ContentFrame>
</> </>
) : ( )
<PendingResults /> )}
)}
</ContentFrame>
</StandardFrame> </StandardFrame>
); );
}; };

View File

@ -30,7 +30,7 @@ const App = () => (
<Route path="/tx/:txhash"> <Route path="/tx/:txhash">
<Transaction /> <Transaction />
</Route> </Route>
<Route path="/address/:address/:direction?"> <Route path="/address/:addressOrName/:direction?">
<AddressTransactions /> <AddressTransactions />
</Route> </Route>
</Route> </Route>

View File

@ -64,8 +64,9 @@ const Home: React.FC = () => {
className="w-full border rounded focus:outline-none px-2 py-1 mb-10" className="w-full border rounded focus:outline-none px-2 py-1 mb-10"
type="text" type="text"
size={50} size={50}
placeholder="Search by address / txn hash / block number" placeholder="Search by address / txn hash / block number / ENS name"
onChange={handleChange} onChange={handleChange}
autoFocus
></input> ></input>
<button <button
className="mx-auto px-3 py-1 mb-10 rounded bg-gray-100 hover:bg-gray-200 focus:outline-none" className="mx-auto px-3 py-1 mb-10 rounded bg-gray-100 hover:bg-gray-200 focus:outline-none"

View File

@ -27,7 +27,8 @@ const Search: React.FC = () => {
return <></>; return <></>;
} }
history.replace("/"); // Assume it is an ENS name
history.replace(`/address/${q}`);
return <></>; return <></>;
}; };

View File

@ -50,8 +50,8 @@ const Title: React.FC = () => {
<input <input
className="w-full border-t border-b border-l rounded-l focus:outline-none px-2 py-1 text-sm" className="w-full border-t border-b border-l rounded-l focus:outline-none px-2 py-1 text-sm"
type="text" type="text"
size={50} size={60}
placeholder='Type "/" to search by address / txn hash / block number' placeholder='Type "/" to search by address / txn hash / block number / ENS name'
onChange={handleChange} onChange={handleChange}
ref={searchRef} ref={searchRef}
/> />