Skip to content

Commit

Permalink
feat: add multi-network functionality
Browse files Browse the repository at this point in the history
closes #599
  • Loading branch information
sverps committed Nov 20, 2023
1 parent 9680b07 commit 0dc82ec
Show file tree
Hide file tree
Showing 27 changed files with 210 additions and 93 deletions.
5 changes: 3 additions & 2 deletions packages/nextjs/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import React from "react";
import Link from "next/link";
import { hardhat } from "viem/chains";
import { useNetwork } from "wagmi";
import { CurrencyDollarIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { HeartIcon } from "@heroicons/react/24/outline";
import { SwitchTheme } from "~~/components/SwitchTheme";
import { BuidlGuidlLogo } from "~~/components/assets/BuidlGuidlLogo";
import { Faucet } from "~~/components/scaffold-eth";
import { useGlobalState } from "~~/services/store/store";
import { getTargetNetwork } from "~~/utils/scaffold-eth";

/**
* Site footer
*/
export const Footer = () => {
const nativeCurrencyPrice = useGlobalState(state => state.nativeCurrencyPrice);
const isLocalNetwork = getTargetNetwork().id === hardhat.id;
const { chain } = useNetwork();
const isLocalNetwork = chain?.id === hardhat.id;

return (
<div className="min-h-0 py-5 px-1 mb-11 lg:mb-0">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { formatEther } from "viem";
import { TransactionHash } from "~~/components/blockexplorer/TransactionHash";
import { Address } from "~~/components/scaffold-eth";
import { TransactionWithFunction, getTargetNetwork } from "~~/utils/scaffold-eth";
import { TransactionWithFunction, useTargetNetwork } from "~~/utils/scaffold-eth";
import { TransactionsTableProps } from "~~/utils/scaffold-eth/";

export const TransactionsTable = ({ blocks, transactionReceipts }: TransactionsTableProps) => {
const targetNetwork = getTargetNetwork();
const targetNetwork = useTargetNetwork();

return (
<div className="flex justify-center px-4 md:px-0">
Expand Down
8 changes: 5 additions & 3 deletions packages/nextjs/components/scaffold-eth/Address.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { hardhat } from "viem/chains";
import { useEnsAvatar, useEnsName } from "wagmi";
import { CheckCircleIcon, DocumentDuplicateIcon } from "@heroicons/react/24/outline";
import { BlockieAvatar } from "~~/components/scaffold-eth";
import { getBlockExplorerAddressLink, getTargetNetwork } from "~~/utils/scaffold-eth";
import { getBlockExplorerAddressLink, useTargetNetwork } from "~~/utils/scaffold-eth";

type TAddressProps = {
address?: string;
Expand All @@ -33,6 +33,8 @@ export const Address = ({ address, disableAddressLink, format, size = "base" }:
const [ensAvatar, setEnsAvatar] = useState<string | null>();
const [addressCopied, setAddressCopied] = useState(false);

const targetNetwork = useTargetNetwork();

const { data: fetchedEns } = useEnsName({ address, enabled: isAddress(address ?? ""), chainId: 1 });
const { data: fetchedEnsAvatar } = useEnsAvatar({
name: fetchedEns,
Expand Down Expand Up @@ -66,7 +68,7 @@ export const Address = ({ address, disableAddressLink, format, size = "base" }:
return <span className="text-error">Wrong address</span>;
}

const blockExplorerAddressLink = getBlockExplorerAddressLink(getTargetNetwork(), address);
const blockExplorerAddressLink = getBlockExplorerAddressLink(targetNetwork, address);
let displayAddress = address?.slice(0, 5) + "..." + address?.slice(-4);

if (ens) {
Expand All @@ -86,7 +88,7 @@ export const Address = ({ address, disableAddressLink, format, size = "base" }:
</div>
{disableAddressLink ? (
<span className={`ml-1.5 text-${size} font-normal`}>{displayAddress}</span>
) : getTargetNetwork().id === hardhat.id ? (
) : targetNetwork.id === hardhat.id ? (
<span className={`ml-1.5 text-${size} font-normal`}>
<Link href={blockExplorerAddressLink}>{displayAddress}</Link>
</span>
Expand Down
4 changes: 2 additions & 2 deletions packages/nextjs/components/scaffold-eth/Balance.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useAccountBalance } from "~~/hooks/scaffold-eth";
import { getTargetNetwork } from "~~/utils/scaffold-eth";
import { useTargetNetwork } from "~~/utils/scaffold-eth";

type TBalanceProps = {
address?: string;
Expand All @@ -10,7 +10,7 @@ type TBalanceProps = {
* Display (ETH & USD) balance of an ETH address.
*/
export const Balance = ({ address, className = "" }: TBalanceProps) => {
const configuredNetwork = getTargetNetwork();
const configuredNetwork = useTargetNetwork();
const { balance, price, isError, isLoading, onToggleBalance, isEthBalance } = useAccountBalance(address);

if (!address || isLoading || balance === null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ContractWriteMethods } from "./ContractWriteMethods";
import { Spinner } from "~~/components/assets/Spinner";
import { Address, Balance } from "~~/components/scaffold-eth";
import { useDeployedContractInfo, useNetworkColor } from "~~/hooks/scaffold-eth";
import { getTargetNetwork } from "~~/utils/scaffold-eth";
import { useTargetNetwork } from "~~/utils/scaffold-eth";
import { ContractName } from "~~/utils/scaffold-eth/contract";

type ContractUIProps = {
Expand All @@ -18,7 +18,7 @@ type ContractUIProps = {
**/
export const ContractUI = ({ contractName, className = "" }: ContractUIProps) => {
const [refreshDisplayVariables, triggerRefreshDisplayVariables] = useReducer(value => !value, false);
const configuredNetwork = getTargetNetwork();
const configuredNetwork = useTargetNetwork();

const { data: deployedContractData, isLoading: deployedContractLoading } = useDeployedContractInfo(contractName);
const networkColor = useNetworkColor();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
getParsedError,
} from "~~/components/scaffold-eth";
import { useTransactor } from "~~/hooks/scaffold-eth";
import { getTargetNetwork, notification } from "~~/utils/scaffold-eth";
import { notification, useTargetNetwork } from "~~/utils/scaffold-eth";

type WriteOnlyFunctionFormProps = {
abiFunction: AbiFunction;
Expand All @@ -32,7 +32,8 @@ export const WriteOnlyFunctionForm = ({
const [txValue, setTxValue] = useState<string | bigint>("");
const { chain } = useNetwork();
const writeTxn = useTransactor();
const writeDisabled = !chain || chain?.id !== getTargetNetwork().id;
const targetNetwork = useTargetNetwork();
const writeDisabled = !chain || chain?.id !== targetNetwork.id;

const {
data: result,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useRef, useState } from "react";
import { ConnectButton } from "@rainbow-me/rainbowkit";
import { QRCodeSVG } from "qrcode.react";
import CopyToClipboard from "react-copy-to-clipboard";
Expand All @@ -13,26 +13,35 @@ import {
QrCodeIcon,
} from "@heroicons/react/24/outline";
import { Address, Balance, BlockieAvatar } from "~~/components/scaffold-eth";
import { useAutoConnect, useNetworkColor } from "~~/hooks/scaffold-eth";
import { getBlockExplorerAddressLink, getTargetNetwork } from "~~/utils/scaffold-eth";
import { useAutoConnect, useNetworkColor, useOutsideClick } from "~~/hooks/scaffold-eth";
import { getBlockExplorerAddressLink, getTargetNetworks, useTargetNetwork } from "~~/utils/scaffold-eth";

const allowedNetworks = getTargetNetworks();

/**
* Custom Wagmi Connect Button (watch balance + custom design)
*/
export const RainbowKitCustomConnectButton = () => {
useAutoConnect();
const networkColor = useNetworkColor();
const configuredNetwork = getTargetNetwork();
const configuredNetwork = useTargetNetwork();
const { disconnect } = useDisconnect();
const { switchNetwork } = useSwitchNetwork();
const [selectingNetwork, setSelectingNetwork] = useState(false);
const [addressCopied, setAddressCopied] = useState(false);
const dropdownRef = useRef<HTMLDetailsElement>(null);
const closeDropdown = () => {
setSelectingNetwork(false);
dropdownRef.current?.removeAttribute("open");
};
useOutsideClick(dropdownRef, closeDropdown);

return (
<ConnectButton.Custom>
{({ account, chain, openConnectModal, mounted }) => {
const connected = mounted && account && chain;
const blockExplorerAddressLink = account
? getBlockExplorerAddressLink(getTargetNetwork(), account.address)
? getBlockExplorerAddressLink(configuredNetwork, account.address)
: undefined;

return (
Expand Down Expand Up @@ -91,20 +100,42 @@ export const RainbowKitCustomConnectButton = () => {
{chain.name}
</span>
</div>
<div className="dropdown dropdown-end leading-3">
<label
<details ref={dropdownRef} className="dropdown dropdown-end leading-3">
<summary
tabIndex={0}
className="btn btn-secondary btn-sm pl-0 pr-2 shadow-md dropdown-toggle gap-0 !h-auto"
>
<BlockieAvatar address={account.address} size={30} ensImage={account.ensAvatar} />
<span className="ml-2 mr-1">{account.displayName}</span>
<ChevronDownIcon className="h-6 w-4 ml-2 sm:ml-0" />
</label>
</summary>
<ul
tabIndex={0}
className="dropdown-content menu z-[2] p-2 mt-2 shadow-center shadow-accent bg-base-200 rounded-box gap-1"
>
<li>
{allowedNetworks.map(network => (
<li key={network.id} className={selectingNetwork ? "" : "hidden"}>
<button
className="menu-item btn-sm !rounded-xl flex gap-3 py-3 whitespace-nowrap"
type="button"
onClick={() => {
closeDropdown();
setSelectingNetwork(false);
switchNetwork?.(network.id);
}}
>
<ArrowsRightLeftIcon className="h-6 w-4 ml-2 sm:ml-0" /> Switch to{" "}
<span
style={{
color: Array.isArray(network.color) ? network.color.slice(-1).pop() : network.color,
}}
>
{network.name}
</span>
</button>
</li>
))}
<li className={selectingNetwork ? "hidden" : ""}>
{addressCopied ? (
<div className="btn-sm !rounded-xl flex gap-3 py-3">
<CheckCircleIcon
Expand All @@ -120,6 +151,7 @@ export const RainbowKitCustomConnectButton = () => {
setAddressCopied(true);
setTimeout(() => {
setAddressCopied(false);
closeDropdown();
}, 800);
}}
>
Expand All @@ -133,13 +165,17 @@ export const RainbowKitCustomConnectButton = () => {
</CopyToClipboard>
)}
</li>
<li>
<label htmlFor="qrcode-modal" className="btn-sm !rounded-xl flex gap-3 py-3">
<li className={selectingNetwork ? "hidden" : ""}>
<label
htmlFor="qrcode-modal"
className="btn-sm !rounded-xl flex gap-3 py-3"
onClick={closeDropdown}
>
<QrCodeIcon className="h-6 w-4 ml-2 sm:ml-0" />
<span className="whitespace-nowrap">View QR Code</span>
</label>
</li>
<li>
<li className={selectingNetwork ? "hidden" : ""}>
<button className="menu-item btn-sm !rounded-xl flex gap-3 py-3" type="button">
<ArrowTopRightOnSquareIcon className="h-6 w-4 ml-2 sm:ml-0" />
<a
Expand All @@ -152,7 +188,20 @@ export const RainbowKitCustomConnectButton = () => {
</a>
</button>
</li>
<li>
{allowedNetworks.length > 1 ? (
<li className={selectingNetwork ? "hidden" : ""}>
<button
className="btn-sm !rounded-xl flex gap-3 py-3"
type="button"
onClick={() => {
setSelectingNetwork(true);
}}
>
<ArrowsRightLeftIcon className="h-6 w-4 ml-2 sm:ml-0" /> <span>Switch Network</span>
</button>
</li>
) : null}
<li className={selectingNetwork ? "hidden" : ""}>
<button
className="menu-item text-error btn-sm !rounded-xl flex gap-3 py-3"
type="button"
Expand All @@ -162,7 +211,7 @@ export const RainbowKitCustomConnectButton = () => {
</button>
</li>
</ul>
</div>
</details>
<div>
<input type="checkbox" id="qrcode-modal" className="modal-toggle" />
<label htmlFor="qrcode-modal" className="modal cursor-pointer">
Expand Down
5 changes: 3 additions & 2 deletions packages/nextjs/hooks/scaffold-eth/useAccountBalance.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { useCallback, useEffect, useState } from "react";
import { useBalance } from "wagmi";
import { useGlobalState } from "~~/services/store/store";
import { getTargetNetwork } from "~~/utils/scaffold-eth";
import { useTargetNetwork } from "~~/utils/scaffold-eth";

export function useAccountBalance(address?: string) {
const [isEthBalance, setIsEthBalance] = useState(true);
const [balance, setBalance] = useState<number | null>(null);
const price = useGlobalState(state => state.nativeCurrencyPrice);
const targetNetwork = useTargetNetwork();

const {
data: fetchedBalanceData,
Expand All @@ -15,7 +16,7 @@ export function useAccountBalance(address?: string) {
} = useBalance({
address,
watch: true,
chainId: getTargetNetwork().id,
chainId: targetNetwork.id,
});

const onToggleBalance = useCallback(() => {
Expand Down
10 changes: 6 additions & 4 deletions packages/nextjs/hooks/scaffold-eth/useAutoConnect.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useEffect } from "react";
import { useEffectOnce, useLocalStorage, useReadLocalStorage } from "usehooks-ts";
import { hardhat } from "viem/chains";
import { Chain, hardhat } from "viem/chains";
import { Connector, useAccount, useConnect } from "wagmi";
import scaffoldConfig from "~~/scaffold.config";
import { burnerWalletId, defaultBurnerChainId } from "~~/services/web3/wagmi-burner/BurnerConnector";
import { getTargetNetwork } from "~~/utils/scaffold-eth";
import { useTargetNetwork } from "~~/utils/scaffold-eth";

const SCAFFOLD_WALLET_STROAGE_KEY = "scaffoldEth2.wallet";
const WAGMI_WALLET_STORAGE_KEY = "wagmi.wallet";
Expand All @@ -14,11 +14,13 @@ const SAFE_ID = "safe";

/**
* This function will get the initial wallet connector (if any), the app will connect to
* @param targetNetwork
* @param previousWalletId
* @param connectors
* @returns
*/
const getInitialConnector = (
targetNetwork: Chain,
previousWalletId: string,
connectors: Connector[],
): { connector: Connector | undefined; chainId?: number } | undefined => {
Expand All @@ -29,7 +31,6 @@ const getInitialConnector = (
return { connector: safeConnectorInstance };
}

const targetNetwork = getTargetNetwork();
const allowBurner = scaffoldConfig.onlyLocalBurnerWallet ? targetNetwork.id === hardhat.id : true;

if (!previousWalletId) {
Expand Down Expand Up @@ -61,6 +62,7 @@ export const useAutoConnect = (): void => {
const [walletId, setWalletId] = useLocalStorage<string>(SCAFFOLD_WALLET_STROAGE_KEY, wagmiWalletValue ?? "");
const connectState = useConnect();
const accountState = useAccount();
const targetNetwork = useTargetNetwork();

useEffect(() => {
if (accountState.isConnected) {
Expand All @@ -75,7 +77,7 @@ export const useAutoConnect = (): void => {
}, [accountState.isConnected, accountState.connector?.name]);

useEffectOnce(() => {
const initialConnector = getInitialConnector(walletId, connectState.connectors);
const initialConnector = getInitialConnector(targetNetwork, walletId, connectState.connectors);

if (initialConnector?.connector) {
connectState.connect({ connector: initialConnector.connector, chainId: initialConnector.chainId });
Expand Down
9 changes: 4 additions & 5 deletions packages/nextjs/hooks/scaffold-eth/useDeployedContractInfo.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
import { useIsMounted } from "usehooks-ts";
import { usePublicClient } from "wagmi";
import scaffoldConfig from "~~/scaffold.config";
import { useTargetNetwork } from "~~/utils/scaffold-eth";
import { Contract, ContractCodeStatus, ContractName, contracts } from "~~/utils/scaffold-eth/contract";

/**
Expand All @@ -10,11 +10,10 @@ import { Contract, ContractCodeStatus, ContractName, contracts } from "~~/utils/
*/
export const useDeployedContractInfo = <TContractName extends ContractName>(contractName: TContractName) => {
const isMounted = useIsMounted();
const deployedContract = contracts?.[scaffoldConfig.targetNetwork.id]?.[
contractName as ContractName
] as Contract<TContractName>;
const targetNetwork = useTargetNetwork();
const deployedContract = contracts?.[targetNetwork.id]?.[contractName as ContractName] as Contract<TContractName>;
const [status, setStatus] = useState<ContractCodeStatus>(ContractCodeStatus.LOADING);
const publicClient = usePublicClient({ chainId: scaffoldConfig.targetNetwork.id });
const publicClient = usePublicClient({ chainId: targetNetwork.id });

useEffect(() => {
const checkContractDeployment = async () => {
Expand Down
Loading

0 comments on commit 0dc82ec

Please sign in to comment.