diff --git a/src/Address.tsx b/src/Address.tsx index f739e7b..24260c3 100644 --- a/src/Address.tsx +++ b/src/Address.tsx @@ -15,6 +15,7 @@ import StandardFrame from "./StandardFrame"; import StandardSubtitle from "./StandardSubtitle"; import AddressOrENSNameNotFound from "./components/AddressOrENSNameNotFound"; import Copy from "./components/Copy"; +import Faucet from "./components/Faucet"; import NavTab from "./components/NavTab"; import SourcifyLogo from "./sourcify/SourcifyLogo"; import AddressTransactionResults from "./address/AddressTransactionResults"; @@ -25,6 +26,7 @@ import { useAddressOrENS } from "./useResolvedAddresses"; import { useMultipleMetadata } from "./sourcify/useSourcify"; import { ChecksummedAddress } from "./types"; import { useAddressesWithCode } from "./useErigonHooks"; +import { useChainInfo } from "./useChainInfo"; const AddressTransactionByNonce = React.lazy( () => @@ -86,6 +88,8 @@ const Address: React.FC = () => { ? metadatas[checksummedAddress] : undefined; + const { faucets } = useChainInfo(); + // Search address by nonce === transaction @ nonce const rawNonce = searchParams.get("nonce"); if (rawNonce !== null) { @@ -119,6 +123,10 @@ const Address: React.FC = () => { {checksummedAddress} + {/* Only display faucets for testnets who actually have any */} + {faucets && faucets.length > 0 && ( + + )} {isENS && ( ENS: {addressOrName} diff --git a/src/App.tsx b/src/App.tsx index 81116d7..523dc5e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -27,10 +27,10 @@ const Transaction = React.lazy( import(/* webpackChunkName: "tx", webpackPrefetch: true */ "./Transaction") ); const London = React.lazy( - () => - import( - /* webpackChunkName: "london", webpackPrefetch: true */ "./special/london/London" - ) + () => import(/* webpackChunkName: "london" */ "./special/london/London") +); +const Faucets = React.lazy( + () => import(/* webpackChunkName: "faucets" */ "./Faucets") ); const PageNotFound = React.lazy( () => @@ -74,6 +74,7 @@ const App = () => { path="address/:addressOrName/*" element={
} /> + } /> } /> diff --git a/src/Faucets.tsx b/src/Faucets.tsx new file mode 100644 index 0000000..7e26bd1 --- /dev/null +++ b/src/Faucets.tsx @@ -0,0 +1,85 @@ +import React, { useMemo } from "react"; +import { useLocation } from "react-router-dom"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons/faTriangleExclamation"; +import { faFaucetDrip } from "@fortawesome/free-solid-svg-icons/faFaucetDrip"; +import ExternalLink from "./components/ExternalLink"; +import ContentFrame from "./ContentFrame"; +import StandardFrame from "./StandardFrame"; +import StandardSubtitle from "./StandardSubtitle"; +import { useChainInfo } from "./useChainInfo"; +const Faucets: React.FC = () => { + const { faucets } = useChainInfo(); + const loc = useLocation(); + const urls = useMemo(() => { + const s = new URLSearchParams(loc.search); + const address = s.get("address"); + + const _urls = faucets.map((u) => + address !== null ? u.replaceAll("${ADDRESS}", address) : u + ); + + // Shuffle faucets to avoid UI bias + for (let i = _urls.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [_urls[i], _urls[j]] = [_urls[j], _urls[i]]; + } + + return _urls; + }, [faucets, loc]); + + return ( + + Faucets + +
+ {urls.length > 0 && ( +
+ + + The following external links come from + https://github.com/ethereum-lists/chains and are *NOT* endorsed + by us. Use at your own risk. + +
+ )} + {/* Display the shuffling notice only if there are 1+ faucets */} + {urls.length > 1 && ( +
+ + The faucet links below are shuffled on page load. +
+ )} + {urls.length > 0 ? ( +
+ {urls.map((url) => ( +
+ + + {url} + +
+ ))} +
+ ) : ( +
There are no registered faucets.
+ )} +
+
+
+ ); +}; + +export default Faucets; diff --git a/src/components/Faucet.tsx b/src/components/Faucet.tsx new file mode 100644 index 0000000..a0480df --- /dev/null +++ b/src/components/Faucet.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { NavLink } from "react-router-dom"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faFaucetDrip } from "@fortawesome/free-solid-svg-icons/faFaucetDrip"; +import { ChecksummedAddress } from "../types"; + +type FaucetProps = { + address: ChecksummedAddress; + rounded?: boolean; +}; + +const Faucet: React.FC = ({ address, rounded }) => ( + + + +); + +export default React.memo(Faucet); diff --git a/src/useChainInfo.ts b/src/useChainInfo.ts index 1d62904..c737c68 100644 --- a/src/useChainInfo.ts +++ b/src/useChainInfo.ts @@ -3,6 +3,7 @@ import { chainInfoURL } from "./url"; import { OtterscanRuntime } from "./useRuntime"; export type ChainInfo = { + faucets: string[]; nativeCurrency: { name: string; symbol: string; @@ -11,6 +12,7 @@ export type ChainInfo = { }; export const defaultChainInfo: ChainInfo = { + faucets: [], nativeCurrency: { name: "Ether", symbol: "ETH", @@ -41,15 +43,9 @@ export const useChainInfoFromMetadataFile = ( setChainInfo(defaultChainInfo); return; } - const info = await res.json(); - setChainInfo({ - nativeCurrency: { - name: info.nativeCurrency.name, - decimals: info.nativeCurrency.decimals, - symbol: info.nativeCurrency.symbol, - }, - }); + const info: ChainInfo = await res.json(); + setChainInfo(info); } catch (err) { // ignore setChainInfo(defaultChainInfo);