diff --git a/.changeset/modern-dolls-tickle.md b/.changeset/modern-dolls-tickle.md new file mode 100644 index 000000000..5de8d29a2 --- /dev/null +++ b/.changeset/modern-dolls-tickle.md @@ -0,0 +1,10 @@ +--- +"create-eth": patch +--- + +- Feat/multi chain (#615) +- Add links to the docs for components and hooks (#620) +- Fix useScaffoldEventHistory hook new events order (#622) +- Add requiredFilters param to useScaffoldEventHistory hook (#621) +- update wagmi, viem and rainbowkit versions (#626) +- Refactor: types/interfaces (#627) diff --git a/README.md b/README.md index 4bf6ecf19..eab5a1184 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ ⚙️ Built using NextJS, RainbowKit, Hardhat, Foundry, Wagmi, and Typescript. - ✅ **Contract Hot Reload**: Your frontend auto-adapts to your smart contract as you edit it. +- 🪝 **[Custom hooks](https://docs.scaffoldeth.io/hooks/)**: Collection of React hooks wrapper around [wagmi](https://wagmi.sh/) to simplify interactions with smart contracts with typescript autocompletion. +- 🧱 [**Components**](https://docs.scaffoldeth.io/components/): Collection of common web3 components to quickly build your frontend. - 🔥 **Burner Wallet & Local Faucet**: Quickly test your application with a burner wallet and local faucet. - 🔐 **Integration with Wallet Providers**: Connect to different wallet providers and interact with the Ethereum network. diff --git a/templates/base/packages/nextjs/components/Footer.tsx b/templates/base/packages/nextjs/components/Footer.tsx index b0bd90484..92981c8b9 100644 --- a/templates/base/packages/nextjs/components/Footer.tsx +++ b/templates/base/packages/nextjs/components/Footer.tsx @@ -6,15 +6,16 @@ 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 { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork"; 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 { targetNetwork } = useTargetNetwork(); + const isLocalNetwork = targetNetwork.id === hardhat.id; return (
diff --git a/templates/base/packages/nextjs/components/Header.tsx b/templates/base/packages/nextjs/components/Header.tsx index 13fa47a04..4a5d86b15 100644 --- a/templates/base/packages/nextjs/components/Header.tsx +++ b/templates/base/packages/nextjs/components/Header.tsx @@ -6,11 +6,11 @@ import { Bars3Icon, BugAntIcon } from "@heroicons/react/24/outline"; import { FaucetButton, RainbowKitCustomConnectButton } from "~~/components/scaffold-eth"; import { useOutsideClick } from "~~/hooks/scaffold-eth"; -interface HeaderMenuLink { +type HeaderMenuLink = { label: string; href: string; icon?: React.ReactNode; -} +}; export const menuLinks: HeaderMenuLink[] = [ { diff --git a/templates/base/packages/nextjs/components/blockexplorer/PaginationButton.tsx b/templates/base/packages/nextjs/components/blockexplorer/PaginationButton.tsx index 97e4cb986..77aefbc14 100644 --- a/templates/base/packages/nextjs/components/blockexplorer/PaginationButton.tsx +++ b/templates/base/packages/nextjs/components/blockexplorer/PaginationButton.tsx @@ -1,10 +1,10 @@ import { ArrowLeftIcon, ArrowRightIcon } from "@heroicons/react/24/outline"; -interface PaginationButtonProps { +type PaginationButtonProps = { currentPage: number; totalItems: number; setCurrentPage: (page: number) => void; -} +}; const ITEMS_PER_PAGE = 20; diff --git a/templates/base/packages/nextjs/components/blockexplorer/TransactionsTable.tsx b/templates/base/packages/nextjs/components/blockexplorer/TransactionsTable.tsx index 5f9d4611c..e462dc946 100644 --- a/templates/base/packages/nextjs/components/blockexplorer/TransactionsTable.tsx +++ b/templates/base/packages/nextjs/components/blockexplorer/TransactionsTable.tsx @@ -1,11 +1,12 @@ import { formatEther } from "viem"; import { TransactionHash } from "~~/components/blockexplorer/TransactionHash"; import { Address } from "~~/components/scaffold-eth"; -import { TransactionWithFunction, getTargetNetwork } from "~~/utils/scaffold-eth"; +import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork"; +import { TransactionWithFunction } from "~~/utils/scaffold-eth"; import { TransactionsTableProps } from "~~/utils/scaffold-eth/"; export const TransactionsTable = ({ blocks, transactionReceipts }: TransactionsTableProps) => { - const targetNetwork = getTargetNetwork(); + const { targetNetwork } = useTargetNetwork(); return (
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Address.tsx b/templates/base/packages/nextjs/components/scaffold-eth/Address.tsx index 67eb512a1..057d706bc 100644 --- a/templates/base/packages/nextjs/components/scaffold-eth/Address.tsx +++ b/templates/base/packages/nextjs/components/scaffold-eth/Address.tsx @@ -6,9 +6,10 @@ 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 { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork"; +import { getBlockExplorerAddressLink } from "~~/utils/scaffold-eth"; -type TAddressProps = { +type AddressProps = { address?: string; disableAddressLink?: boolean; format?: "short" | "long"; @@ -28,11 +29,13 @@ const blockieSizeMap = { /** * Displays an address (or ENS) with a Blockie image and option to copy address. */ -export const Address = ({ address, disableAddressLink, format, size = "base" }: TAddressProps) => { +export const Address = ({ address, disableAddressLink, format, size = "base" }: AddressProps) => { const [ens, setEns] = useState(); const [ensAvatar, setEnsAvatar] = useState(); const [addressCopied, setAddressCopied] = useState(false); + const { targetNetwork } = useTargetNetwork(); + const { data: fetchedEns } = useEnsName({ address, enabled: isAddress(address ?? ""), chainId: 1 }); const { data: fetchedEnsAvatar } = useEnsAvatar({ name: fetchedEns, @@ -66,7 +69,7 @@ export const Address = ({ address, disableAddressLink, format, size = "base" }: return Wrong address; } - const blockExplorerAddressLink = getBlockExplorerAddressLink(getTargetNetwork(), address); + const blockExplorerAddressLink = getBlockExplorerAddressLink(targetNetwork, address); let displayAddress = address?.slice(0, 5) + "..." + address?.slice(-4); if (ens) { @@ -86,7 +89,7 @@ export const Address = ({ address, disableAddressLink, format, size = "base" }:
{disableAddressLink ? ( {displayAddress} - ) : getTargetNetwork().id === hardhat.id ? ( + ) : targetNetwork.id === hardhat.id ? ( {displayAddress} diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Balance.tsx b/templates/base/packages/nextjs/components/scaffold-eth/Balance.tsx index 7de5e843c..66bc172e7 100644 --- a/templates/base/packages/nextjs/components/scaffold-eth/Balance.tsx +++ b/templates/base/packages/nextjs/components/scaffold-eth/Balance.tsx @@ -1,7 +1,7 @@ import { useAccountBalance } from "~~/hooks/scaffold-eth"; -import { getTargetNetwork } from "~~/utils/scaffold-eth"; +import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork"; -type TBalanceProps = { +type BalanceProps = { address?: string; className?: string; }; @@ -9,8 +9,8 @@ type TBalanceProps = { /** * Display (ETH & USD) balance of an ETH address. */ -export const Balance = ({ address, className = "" }: TBalanceProps) => { - const configuredNetwork = getTargetNetwork(); +export const Balance = ({ address, className = "" }: BalanceProps) => { + const { targetNetwork } = useTargetNetwork(); const { balance, price, isError, isLoading, onToggleBalance, isEthBalance } = useAccountBalance(address); if (!address || isLoading || balance === null) { @@ -41,7 +41,7 @@ export const Balance = ({ address, className = "" }: TBalanceProps) => { {isEthBalance ? ( <> {balance?.toFixed(4)} - {configuredNetwork.nativeCurrency.symbol} + {targetNetwork.nativeCurrency.symbol} ) : ( <> diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractUI.tsx b/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractUI.tsx index bcacf9c08..84869deca 100644 --- a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractUI.tsx +++ b/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractUI.tsx @@ -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 "~~/hooks/scaffold-eth/useTargetNetwork"; import { ContractName } from "~~/utils/scaffold-eth/contract"; type ContractUIProps = { @@ -18,8 +18,7 @@ type ContractUIProps = { **/ export const ContractUI = ({ contractName, className = "" }: ContractUIProps) => { const [refreshDisplayVariables, triggerRefreshDisplayVariables] = useReducer(value => !value, false); - const configuredNetwork = getTargetNetwork(); - + const { targetNetwork } = useTargetNetwork(); const { data: deployedContractData, isLoading: deployedContractLoading } = useDeployedContractInfo(contractName); const networkColor = useNetworkColor(); @@ -34,7 +33,7 @@ export const ContractUI = ({ contractName, className = "" }: ContractUIProps) => if (!deployedContractData) { return (

- {`No contract found by the name of "${contractName}" on chain "${configuredNetwork.name}"!`} + {`No contract found by the name of "${contractName}" on chain "${targetNetwork.name}"!`}

); } @@ -54,10 +53,10 @@ export const ContractUI = ({ contractName, className = "" }: ContractUIProps) =>
- {configuredNetwork && ( + {targetNetwork && (

Network:{" "} - {configuredNetwork.name} + {targetNetwork.name}

)} diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ReadOnlyFunctionForm.tsx b/templates/base/packages/nextjs/components/scaffold-eth/Contract/ReadOnlyFunctionForm.tsx index bc8cfbc3c..2965b6e9f 100644 --- a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ReadOnlyFunctionForm.tsx +++ b/templates/base/packages/nextjs/components/scaffold-eth/Contract/ReadOnlyFunctionForm.tsx @@ -12,13 +12,13 @@ import { } from "~~/components/scaffold-eth"; import { notification } from "~~/utils/scaffold-eth"; -type TReadOnlyFunctionFormProps = { +type ReadOnlyFunctionFormProps = { contractAddress: Address; abiFunction: AbiFunction; inheritedFrom?: string; }; -export const ReadOnlyFunctionForm = ({ contractAddress, abiFunction, inheritedFrom }: TReadOnlyFunctionFormProps) => { +export const ReadOnlyFunctionForm = ({ contractAddress, abiFunction, inheritedFrom }: ReadOnlyFunctionFormProps) => { const [form, setForm] = useState>(() => getInitialFormState(abiFunction)); const [result, setResult] = useState(); diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/WriteOnlyFunctionForm.tsx b/templates/base/packages/nextjs/components/scaffold-eth/Contract/WriteOnlyFunctionForm.tsx index 01080885b..b86ce0f98 100644 --- a/templates/base/packages/nextjs/components/scaffold-eth/Contract/WriteOnlyFunctionForm.tsx +++ b/templates/base/packages/nextjs/components/scaffold-eth/Contract/WriteOnlyFunctionForm.tsx @@ -13,7 +13,8 @@ import { getParsedError, } from "~~/components/scaffold-eth"; import { useTransactor } from "~~/hooks/scaffold-eth"; -import { getTargetNetwork, notification } from "~~/utils/scaffold-eth"; +import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork"; +import { notification } from "~~/utils/scaffold-eth"; type WriteOnlyFunctionFormProps = { abiFunction: AbiFunction; @@ -32,7 +33,8 @@ export const WriteOnlyFunctionForm = ({ const [txValue, setTxValue] = useState(""); 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, diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/utilsContract.tsx b/templates/base/packages/nextjs/components/scaffold-eth/Contract/utilsContract.tsx index 286fa3b27..e31941ef3 100644 --- a/templates/base/packages/nextjs/components/scaffold-eth/Contract/utilsContract.tsx +++ b/templates/base/packages/nextjs/components/scaffold-eth/Contract/utilsContract.tsx @@ -2,9 +2,9 @@ import { AbiFunction, AbiParameter } from "abitype"; import { BaseError as BaseViemError } from "viem"; /** - * @dev utility function to generate key corresponding to function metaData - * @param {AbiFunction} functionName - * @param {utils.ParamType} input - object containing function name and input type corresponding to index + * Generates a key based on function metadata + * @param {string} functionName + * @param {AbiParameter} input - object containing function name and input type corresponding to index * @param {number} inputIndex * @returns {string} key */ @@ -14,7 +14,7 @@ const getFunctionInputKey = (functionName: string, input: AbiParameter, inputInd }; /** - * @dev utility function to parse error + * Parses an error to get a displayable string * @param e - error object * @returns {string} parsed error string */ @@ -38,8 +38,9 @@ const getParsedError = (e: any | BaseViemError): string => { // This regex is used to identify array types in the form of `type[size]` const ARRAY_TYPE_REGEX = /\[.*\]$/; + /** - * @dev Parse form input with array support + * Parses form input with array support * @param {Record} form - form object containing key value pairs * @returns parsed error string */ diff --git a/templates/base/packages/nextjs/components/scaffold-eth/FaucetButton.tsx b/templates/base/packages/nextjs/components/scaffold-eth/FaucetButton.tsx index 93837c171..b9538bbe0 100644 --- a/templates/base/packages/nextjs/components/scaffold-eth/FaucetButton.tsx +++ b/templates/base/packages/nextjs/components/scaffold-eth/FaucetButton.tsx @@ -52,8 +52,8 @@ export const FaucetButton = () => {
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Input/utils.ts b/templates/base/packages/nextjs/components/scaffold-eth/Input/utils.ts index 764ec34b8..481b5d165 100644 --- a/templates/base/packages/nextjs/components/scaffold-eth/Input/utils.ts +++ b/templates/base/packages/nextjs/components/scaffold-eth/Input/utils.ts @@ -1,10 +1,10 @@ -export interface CommonInputProps { +export type CommonInputProps = { value: T; onChange: (newValue: T) => void; name?: string; placeholder?: string; disabled?: boolean; -} +}; export enum IntegerVariant { UINT8 = "uint8", diff --git a/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton.tsx b/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton.tsx deleted file mode 100644 index 803bb8c73..000000000 --- a/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import { useState } from "react"; -import { ConnectButton } from "@rainbow-me/rainbowkit"; -import { QRCodeSVG } from "qrcode.react"; -import CopyToClipboard from "react-copy-to-clipboard"; -import { useDisconnect, useSwitchNetwork } from "wagmi"; -import { - ArrowLeftOnRectangleIcon, - ArrowTopRightOnSquareIcon, - ArrowsRightLeftIcon, - CheckCircleIcon, - ChevronDownIcon, - DocumentDuplicateIcon, - 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"; - -/** - * Custom Wagmi Connect Button (watch balance + custom design) - */ -export const RainbowKitCustomConnectButton = () => { - useAutoConnect(); - const networkColor = useNetworkColor(); - const configuredNetwork = getTargetNetwork(); - const { disconnect } = useDisconnect(); - const { switchNetwork } = useSwitchNetwork(); - const [addressCopied, setAddressCopied] = useState(false); - - return ( - - {({ account, chain, openConnectModal, mounted }) => { - const connected = mounted && account && chain; - const blockExplorerAddressLink = account - ? getBlockExplorerAddressLink(getTargetNetwork(), account.address) - : undefined; - - return ( - <> - {(() => { - if (!connected) { - return ( - - ); - } - - if (chain.unsupported || chain.id !== configuredNetwork.id) { - return ( -
- -
    -
  • - -
  • -
  • - -
  • -
-
- ); - } - - return ( -
-
- - - {chain.name} - -
-
- -
    -
  • - {addressCopied ? ( -
    -
    - ) : ( - { - setAddressCopied(true); - setTimeout(() => { - setAddressCopied(false); - }, 800); - }} - > -
    -
    -
    - )} -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
- - -
-
- ); - })()} - - ); - }} -
- ); -}; diff --git a/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/AddressInfoDropdown.tsx b/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/AddressInfoDropdown.tsx new file mode 100644 index 000000000..101c60b6f --- /dev/null +++ b/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/AddressInfoDropdown.tsx @@ -0,0 +1,132 @@ +import { useRef, useState } from "react"; +import { NetworkOptions } from "./NetworkOptions"; +import CopyToClipboard from "react-copy-to-clipboard"; +import { useDisconnect } from "wagmi"; +import { + ArrowLeftOnRectangleIcon, + ArrowTopRightOnSquareIcon, + ArrowsRightLeftIcon, + CheckCircleIcon, + ChevronDownIcon, + DocumentDuplicateIcon, + QrCodeIcon, +} from "@heroicons/react/24/outline"; +import { BlockieAvatar } from "~~/components/scaffold-eth"; +import { useOutsideClick } from "~~/hooks/scaffold-eth"; +import { getTargetNetworks } from "~~/utils/scaffold-eth"; + +const allowedNetworks = getTargetNetworks(); + +type AddressInfoDropdownProps = { + address: string; + blockExplorerAddressLink: string | undefined; + displayName: string; + ensAvatar?: string; +}; + +export const AddressInfoDropdown = ({ + address, + ensAvatar, + displayName, + blockExplorerAddressLink, +}: AddressInfoDropdownProps) => { + const { disconnect } = useDisconnect(); + + const [addressCopied, setAddressCopied] = useState(false); + + const [selectingNetwork, setSelectingNetwork] = useState(false); + const dropdownRef = useRef(null); + const closeDropdown = () => { + setSelectingNetwork(false); + dropdownRef.current?.removeAttribute("open"); + }; + useOutsideClick(dropdownRef, closeDropdown); + + return ( + <> +
+ + + {displayName} + + +
    +
+
+ + ); +}; diff --git a/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/AddressQRCodeModal.tsx b/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/AddressQRCodeModal.tsx new file mode 100644 index 000000000..c6e00b719 --- /dev/null +++ b/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/AddressQRCodeModal.tsx @@ -0,0 +1,32 @@ +import { QRCodeSVG } from "qrcode.react"; +import { Address } from "~~/components/scaffold-eth"; + +interface IAddressQRCodeModal { + address: string; + modalId: string; +} + +export const AddressQRCodeModal: React.FC = ({ address, modalId }) => { + return ( + <> +
+ + +
+ + ); +}; diff --git a/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/NetworkOptions.tsx b/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/NetworkOptions.tsx new file mode 100644 index 000000000..bd0e1bcc4 --- /dev/null +++ b/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/NetworkOptions.tsx @@ -0,0 +1,47 @@ +import { useDarkMode } from "usehooks-ts"; +import { useNetwork, useSwitchNetwork } from "wagmi"; +import { ArrowsRightLeftIcon } from "@heroicons/react/24/solid"; +import { getNetworkColor } from "~~/hooks/scaffold-eth"; +import { getTargetNetworks } from "~~/utils/scaffold-eth"; + +const allowedNetworks = getTargetNetworks(); + +type NetworkOptionsProps = { + hidden?: boolean; +}; + +export const NetworkOptions = ({ hidden = false }: NetworkOptionsProps) => { + const { isDarkMode } = useDarkMode(); + const { switchNetwork } = useSwitchNetwork(); + const { chain } = useNetwork(); + + return ( + <> + {allowedNetworks + .filter(allowedNetwork => allowedNetwork.id !== chain?.id) + .map(allowedNetwork => ( +
  • + +
  • + ))} + + ); +}; diff --git a/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/WrongNetworkDropdown.tsx b/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/WrongNetworkDropdown.tsx new file mode 100644 index 000000000..f9f0fd885 --- /dev/null +++ b/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/WrongNetworkDropdown.tsx @@ -0,0 +1,32 @@ +import { NetworkOptions } from "./NetworkOptions"; +import { useDisconnect } from "wagmi"; +import { ArrowLeftOnRectangleIcon, ChevronDownIcon } from "@heroicons/react/24/outline"; + +export const WrongNetworkDropdown = () => { + const { disconnect } = useDisconnect(); + + return ( +
    + +
      + +
    • + +
    • +
    +
    + ); +}; diff --git a/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/index.tsx b/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/index.tsx new file mode 100644 index 000000000..02efcc803 --- /dev/null +++ b/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/index.tsx @@ -0,0 +1,64 @@ +import { Balance } from "../Balance"; +import { AddressInfoDropdown } from "./AddressInfoDropdown"; +import { AddressQRCodeModal } from "./AddressQRCodeModal"; +import { WrongNetworkDropdown } from "./WrongNetworkDropdown"; +import { ConnectButton } from "@rainbow-me/rainbowkit"; +import { useAutoConnect, useNetworkColor } from "~~/hooks/scaffold-eth"; +import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork"; +import { getBlockExplorerAddressLink } from "~~/utils/scaffold-eth"; + +/** + * Custom Wagmi Connect Button (watch balance + custom design) + */ +export const RainbowKitCustomConnectButton = () => { + useAutoConnect(); + const networkColor = useNetworkColor(); + const { targetNetwork } = useTargetNetwork(); + + return ( + + {({ account, chain, openConnectModal, mounted }) => { + const connected = mounted && account && chain; + const blockExplorerAddressLink = account + ? getBlockExplorerAddressLink(targetNetwork, account.address) + : undefined; + + return ( + <> + {(() => { + if (!connected) { + return ( + + ); + } + + if (chain.unsupported || chain.id !== targetNetwork.id) { + return ; + } + + return ( + <> +
    + + + {chain.name} + +
    + + + + ); + })()} + + ); + }} +
    + ); +}; diff --git a/templates/base/packages/nextjs/hooks/scaffold-eth/useAccountBalance.ts b/templates/base/packages/nextjs/hooks/scaffold-eth/useAccountBalance.ts index 01cc07639..61946f606 100644 --- a/templates/base/packages/nextjs/hooks/scaffold-eth/useAccountBalance.ts +++ b/templates/base/packages/nextjs/hooks/scaffold-eth/useAccountBalance.ts @@ -1,12 +1,13 @@ import { useCallback, useEffect, useState } from "react"; +import { useTargetNetwork } from "./useTargetNetwork"; import { useBalance } from "wagmi"; import { useGlobalState } from "~~/services/store/store"; -import { getTargetNetwork } from "~~/utils/scaffold-eth"; export function useAccountBalance(address?: string) { const [isEthBalance, setIsEthBalance] = useState(true); const [balance, setBalance] = useState(null); const price = useGlobalState(state => state.nativeCurrencyPrice); + const { targetNetwork } = useTargetNetwork(); const { data: fetchedBalanceData, @@ -15,7 +16,7 @@ export function useAccountBalance(address?: string) { } = useBalance({ address, watch: true, - chainId: getTargetNetwork().id, + chainId: targetNetwork.id, }); const onToggleBalance = useCallback(() => { @@ -28,7 +29,7 @@ export function useAccountBalance(address?: string) { if (fetchedBalanceData?.formatted) { setBalance(Number(fetchedBalanceData.formatted)); } - }, [fetchedBalanceData]); + }, [fetchedBalanceData, targetNetwork]); return { balance, price, isError, isLoading, onToggleBalance, isEthBalance }; } diff --git a/templates/base/packages/nextjs/hooks/scaffold-eth/useAutoConnect.ts b/templates/base/packages/nextjs/hooks/scaffold-eth/useAutoConnect.ts index baf2b1104..36260f215 100644 --- a/templates/base/packages/nextjs/hooks/scaffold-eth/useAutoConnect.ts +++ b/templates/base/packages/nextjs/hooks/scaffold-eth/useAutoConnect.ts @@ -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 { burnerWalletId } from "~~/services/web3/wagmi-burner/BurnerConnector"; +import { getTargetNetworks } from "~~/utils/scaffold-eth"; const SCAFFOLD_WALLET_STROAGE_KEY = "scaffoldEth2.wallet"; const WAGMI_WALLET_STORAGE_KEY = "wagmi.wallet"; @@ -14,11 +14,13 @@ const SAFE_ID = "safe"; /** * This function will get the initial wallet connector (if any), the app will connect to + * @param initialNetwork * @param previousWalletId * @param connectors * @returns */ const getInitialConnector = ( + initialNetwork: Chain, previousWalletId: string, connectors: Connector[], ): { connector: Connector | undefined; chainId?: number } | undefined => { @@ -29,14 +31,13 @@ const getInitialConnector = ( return { connector: safeConnectorInstance }; } - const targetNetwork = getTargetNetwork(); - const allowBurner = scaffoldConfig.onlyLocalBurnerWallet ? targetNetwork.id === hardhat.id : true; + const allowBurner = scaffoldConfig.onlyLocalBurnerWallet ? initialNetwork.id === hardhat.id : true; if (!previousWalletId) { // The user was not connected to a wallet if (allowBurner && scaffoldConfig.walletAutoConnect) { const connector = connectors.find(f => f.id === burnerWalletId); - return { connector, chainId: defaultBurnerChainId }; + return { connector, chainId: initialNetwork.id }; } } else { // the user was connected to wallet @@ -75,7 +76,7 @@ export const useAutoConnect = (): void => { }, [accountState.isConnected, accountState.connector?.name]); useEffectOnce(() => { - const initialConnector = getInitialConnector(walletId, connectState.connectors); + const initialConnector = getInitialConnector(getTargetNetworks()[0], walletId, connectState.connectors); if (initialConnector?.connector) { connectState.connect({ connector: initialConnector.connector, chainId: initialConnector.chainId }); diff --git a/templates/base/packages/nextjs/hooks/scaffold-eth/useBurnerWallet.ts b/templates/base/packages/nextjs/hooks/scaffold-eth/useBurnerWallet.ts index d9ca40265..bb757061d 100644 --- a/templates/base/packages/nextjs/hooks/scaffold-eth/useBurnerWallet.ts +++ b/templates/base/packages/nextjs/hooks/scaffold-eth/useBurnerWallet.ts @@ -7,10 +7,7 @@ import { WalletClient, usePublicClient } from "wagmi"; const burnerStorageKey = "scaffoldEth2.burnerWallet.sk"; /** - * Is the private key valid - * @internal - * @param pk - * @returns + * Checks if the private key is valid */ const isValidSk = (pk: Hex | string | undefined | null): boolean => { return pk?.length === 64 || pk?.length === 66; @@ -19,13 +16,10 @@ const isValidSk = (pk: Hex | string | undefined | null): boolean => { /** * If no burner is found in localstorage, we will generate a random private key */ -const newDefaultPriaveKey = generatePrivateKey(); +const newDefaultPrivateKey = generatePrivateKey(); /** - * Save the current burner private key from storage - * Can be used outside of react. Used by the burnerConnector. - * @internal - * @returns + * Save the current burner private key to local storage */ export const saveBurnerSK = (privateKey: Hex): void => { if (typeof window != "undefined" && window != null) { @@ -34,10 +28,7 @@ export const saveBurnerSK = (privateKey: Hex): void => { }; /** - * Gets the current burner private key from storage - * Can be used outside of react. Used by the burnerConnector. - * @internal - * @returns + * Gets the current burner private key from local storage */ export const loadBurnerSK = (): Hex => { let currentSk: Hex = "0x"; @@ -48,47 +39,25 @@ export const loadBurnerSK = (): Hex => { if (!!currentSk && isValidSk(currentSk)) { return currentSk; } else { - saveBurnerSK(newDefaultPriaveKey); - return newDefaultPriaveKey; + saveBurnerSK(newDefaultPrivateKey); + return newDefaultPrivateKey; } }; -/** - * #### Summary - * Return type of useBurnerSigner: - * - * ##### ✏️ Notes - * - provides signer - * - methods of interacting with burner signer - * - methods to save and loadd signer from local storage - * - * @category Hooks - */ -export type TBurnerSigner = { +type BurnerAccount = { walletClient: WalletClient | undefined; account: PrivateKeyAccount | undefined; - /** - * create a new burner signer - */ + // creates a new burner account generateNewBurner: () => void; - /** - * explicitly save burner to storage - */ + // explicitly save burner to storage saveBurner: () => void; }; /** - * #### Summary - * A hook that creates a burner signer/address and provides ways of interacting with - * and updating the signer - * - * @category Hooks - * - * @param localProvider localhost provider - * @returns IBurnerSigner + * Creates a burner wallet */ -export const useBurnerWallet = (): TBurnerSigner => { - const [burnerSk, setBurnerSk] = useLocalStorage(burnerStorageKey, newDefaultPriaveKey); +export const useBurnerWallet = (): BurnerAccount => { + const [burnerSk, setBurnerSk] = useLocalStorage(burnerStorageKey, newDefaultPrivateKey); const publicClient = usePublicClient(); const [walletClient, setWalletClient] = useState>(); @@ -96,16 +65,10 @@ export const useBurnerWallet = (): TBurnerSigner => { const [account, setAccount] = useState(); const isCreatingNewBurnerRef = useRef(false); - /** - * callback to save current wallet sk - */ const saveBurner = useCallback(() => { setBurnerSk(generatedPrivateKey); }, [setBurnerSk, generatedPrivateKey]); - /** - * create a new burnerkey - */ const generateNewBurner = useCallback(() => { if (publicClient && !isCreatingNewBurnerRef.current) { console.log("🔑 Create new burner wallet..."); @@ -125,7 +88,7 @@ export const useBurnerWallet = (): TBurnerSigner => { setAccount(randomAccount); setBurnerSk(() => { - console.log("🔥 ...Save new burner wallet"); + console.log("🔥 Saving new burner wallet"); isCreatingNewBurnerRef.current = false; return randomPrivateKey; }); diff --git a/templates/base/packages/nextjs/hooks/scaffold-eth/useDeployedContractInfo.ts b/templates/base/packages/nextjs/hooks/scaffold-eth/useDeployedContractInfo.ts index ed9c00a8b..69b961fff 100644 --- a/templates/base/packages/nextjs/hooks/scaffold-eth/useDeployedContractInfo.ts +++ b/templates/base/packages/nextjs/hooks/scaffold-eth/useDeployedContractInfo.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; +import { useTargetNetwork } from "./useTargetNetwork"; import { useIsMounted } from "usehooks-ts"; import { usePublicClient } from "wagmi"; -import scaffoldConfig from "~~/scaffold.config"; import { Contract, ContractCodeStatus, ContractName, contracts } from "~~/utils/scaffold-eth/contract"; /** @@ -10,11 +10,10 @@ import { Contract, ContractCodeStatus, ContractName, contracts } from "~~/utils/ */ export const useDeployedContractInfo = (contractName: TContractName) => { const isMounted = useIsMounted(); - const deployedContract = contracts?.[scaffoldConfig.targetNetwork.id]?.[ - contractName as ContractName - ] as Contract; + const { targetNetwork } = useTargetNetwork(); + const deployedContract = contracts?.[targetNetwork.id]?.[contractName as ContractName] as Contract; const [status, setStatus] = useState(ContractCodeStatus.LOADING); - const publicClient = usePublicClient({ chainId: scaffoldConfig.targetNetwork.id }); + const publicClient = usePublicClient({ chainId: targetNetwork.id }); useEffect(() => { const checkContractDeployment = async () => { diff --git a/templates/base/packages/nextjs/hooks/scaffold-eth/useNativeCurrencyPrice.ts b/templates/base/packages/nextjs/hooks/scaffold-eth/useNativeCurrencyPrice.ts index 6af4895fb..b81e781e6 100644 --- a/templates/base/packages/nextjs/hooks/scaffold-eth/useNativeCurrencyPrice.ts +++ b/templates/base/packages/nextjs/hooks/scaffold-eth/useNativeCurrencyPrice.ts @@ -1,4 +1,5 @@ import { useEffect, useState } from "react"; +import { useTargetNetwork } from "./useTargetNetwork"; import { useInterval } from "usehooks-ts"; import scaffoldConfig from "~~/scaffold.config"; import { fetchPriceFromUniswap } from "~~/utils/scaffold-eth"; @@ -10,20 +11,21 @@ const enablePolling = false; * @returns nativeCurrencyPrice: number */ export const useNativeCurrencyPrice = () => { + const { targetNetwork } = useTargetNetwork(); const [nativeCurrencyPrice, setNativeCurrencyPrice] = useState(0); // Get the price of ETH from Uniswap on mount useEffect(() => { (async () => { - const price = await fetchPriceFromUniswap(); + const price = await fetchPriceFromUniswap(targetNetwork); setNativeCurrencyPrice(price); })(); - }, []); + }, [targetNetwork]); // Get the price of ETH from Uniswap at a given interval useInterval( async () => { - const price = await fetchPriceFromUniswap(); + const price = await fetchPriceFromUniswap(targetNetwork); setNativeCurrencyPrice(price); }, enablePolling ? scaffoldConfig.pollingInterval : null, diff --git a/templates/base/packages/nextjs/hooks/scaffold-eth/useNetworkColor.ts b/templates/base/packages/nextjs/hooks/scaffold-eth/useNetworkColor.ts index b4f593148..3e7e4fbba 100644 --- a/templates/base/packages/nextjs/hooks/scaffold-eth/useNetworkColor.ts +++ b/templates/base/packages/nextjs/hooks/scaffold-eth/useNetworkColor.ts @@ -1,14 +1,20 @@ +import { useTargetNetwork } from "./useTargetNetwork"; import { useDarkMode } from "usehooks-ts"; -import { getTargetNetwork } from "~~/utils/scaffold-eth"; +import { ChainWithAttributes } from "~~/utils/scaffold-eth"; -const DEFAULT_NETWORK_COLOR: [string, string] = ["#666666", "#bbbbbb"]; +export const DEFAULT_NETWORK_COLOR: [string, string] = ["#666666", "#bbbbbb"]; + +export function getNetworkColor(network: ChainWithAttributes, isDarkMode: boolean) { + const colorConfig = network.color ?? DEFAULT_NETWORK_COLOR; + return Array.isArray(colorConfig) ? (isDarkMode ? colorConfig[1] : colorConfig[0]) : colorConfig; +} /** * Gets the color of the target network */ export const useNetworkColor = () => { const { isDarkMode } = useDarkMode(); - const colorConfig = getTargetNetwork().color ?? DEFAULT_NETWORK_COLOR; + const { targetNetwork } = useTargetNetwork(); - return Array.isArray(colorConfig) ? (isDarkMode ? colorConfig[1] : colorConfig[0]) : colorConfig; + return getNetworkColor(targetNetwork, isDarkMode); }; diff --git a/templates/base/packages/nextjs/hooks/scaffold-eth/useOutsideClick.ts b/templates/base/packages/nextjs/hooks/scaffold-eth/useOutsideClick.ts index a74a55809..28a44b5d2 100644 --- a/templates/base/packages/nextjs/hooks/scaffold-eth/useOutsideClick.ts +++ b/templates/base/packages/nextjs/hooks/scaffold-eth/useOutsideClick.ts @@ -3,7 +3,7 @@ import React, { useEffect } from "react"; /** * Check if a click was made outside the passed ref */ -export const useOutsideClick = (ref: React.RefObject, callback: { (): void }) => { +export const useOutsideClick = (ref: React.RefObject, callback: { (): void }) => { useEffect(() => { function handleOutsideClick(event: MouseEvent) { if (!(event.target instanceof Element)) { diff --git a/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldContractRead.ts b/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldContractRead.ts index e44b78447..55cb76dd0 100644 --- a/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldContractRead.ts +++ b/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldContractRead.ts @@ -1,7 +1,7 @@ +import { useTargetNetwork } from "./useTargetNetwork"; import type { ExtractAbiFunctionNames } from "abitype"; import { useContractRead } from "wagmi"; import { useDeployedContractInfo } from "~~/hooks/scaffold-eth"; -import { getTargetNetwork } from "~~/utils/scaffold-eth"; import { AbiFunctionReturnType, ContractAbi, @@ -10,7 +10,8 @@ import { } from "~~/utils/scaffold-eth/contract"; /** - * @dev wrapper for wagmi's useContractRead hook which loads in deployed contract contract abi, address automatically + * Wrapper for wagmi's useContractRead hook which automatically loads (by name) + * the contract ABI and address from the deployed contracts * @param config - The config settings, including extra wagmi configuration * @param config.contractName - deployed contract name * @param config.functionName - name of the function to be called @@ -26,9 +27,10 @@ export const useScaffoldContractRead = < ...readConfig }: UseScaffoldReadConfig) => { const { data: deployedContract } = useDeployedContractInfo(contractName); + const { targetNetwork } = useTargetNetwork(); return useContractRead({ - chainId: getTargetNetwork().id, + chainId: targetNetwork.id, functionName, address: deployedContract?.address, abi: deployedContract?.abi, diff --git a/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldContractWrite.ts b/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldContractWrite.ts index 7efe10fbb..8c09582e3 100644 --- a/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldContractWrite.ts +++ b/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldContractWrite.ts @@ -1,15 +1,17 @@ import { useState } from "react"; +import { useTargetNetwork } from "./useTargetNetwork"; import { Abi, ExtractAbiFunctionNames } from "abitype"; import { useContractWrite, useNetwork } from "wagmi"; import { getParsedError } from "~~/components/scaffold-eth"; import { useDeployedContractInfo, useTransactor } from "~~/hooks/scaffold-eth"; -import { getTargetNetwork, notification } from "~~/utils/scaffold-eth"; +import { notification } from "~~/utils/scaffold-eth"; import { ContractAbi, ContractName, UseScaffoldWriteConfig } from "~~/utils/scaffold-eth/contract"; type UpdatedArgs = Parameters>["writeAsync"]>[0]; /** - * @dev wrapper for wagmi's useContractWrite hook(with config prepared by usePrepareContractWrite hook) which loads in deployed contract abi and address automatically + * Wrapper for wagmi's useContractWrite hook (with config prepared by usePrepareContractWrite hook) + * which automatically loads (by name) the contract ABI and address from the deployed contracts * @param config - The config settings, including extra wagmi configuration * @param config.contractName - deployed contract name * @param config.functionName - name of the function to be called @@ -32,9 +34,10 @@ export const useScaffoldContractWrite = < const { chain } = useNetwork(); const writeTx = useTransactor(); const [isMining, setIsMining] = useState(false); - const configuredNetwork = getTargetNetwork(); + const { targetNetwork } = useTargetNetwork(); const wagmiContractWrite = useContractWrite({ + chainId: targetNetwork.id, address: deployedContractData?.address, abi: deployedContractData?.abi as Abi, functionName: functionName as any, @@ -59,7 +62,7 @@ export const useScaffoldContractWrite = < notification.error("Please connect your wallet"); return; } - if (chain?.id !== configuredNetwork.id) { + if (chain?.id !== targetNetwork.id) { notification.error("You are on the wrong network"); return; } @@ -67,7 +70,7 @@ export const useScaffoldContractWrite = < if (wagmiContractWrite.writeAsync) { try { setIsMining(true); - await writeTx( + const writeTxResult = await writeTx( () => wagmiContractWrite.writeAsync({ args: newArgs ?? args, @@ -76,6 +79,8 @@ export const useScaffoldContractWrite = < }), { onBlockConfirmation, blockConfirmations }, ); + + return writeTxResult; } catch (e: any) { const message = getParsedError(e); notification.error(message); diff --git a/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldEventHistory.ts b/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldEventHistory.ts index 5368aba94..a8c2b3a89 100644 --- a/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldEventHistory.ts +++ b/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldEventHistory.ts @@ -1,4 +1,5 @@ import { useEffect, useMemo, useState } from "react"; +import { useTargetNetwork } from "./useTargetNetwork"; import { Abi, AbiEvent, ExtractAbiEventNames } from "abitype"; import { useInterval } from "usehooks-ts"; import { Hash } from "viem"; @@ -6,7 +7,6 @@ import * as chains from "viem/chains"; import { usePublicClient } from "wagmi"; import { useDeployedContractInfo } from "~~/hooks/scaffold-eth"; import scaffoldConfig from "~~/scaffold.config"; -import { getTargetNetwork } from "~~/utils/scaffold-eth"; import { replacer } from "~~/utils/scaffold-eth/common"; import { ContractAbi, @@ -16,7 +16,7 @@ import { } from "~~/utils/scaffold-eth/contract"; /** - * @dev reads events from a deployed contract + * Reads events from a deployed contract * @param config - The config settings * @param config.contractName - deployed contract name * @param config.eventName - name of the event to listen for @@ -26,6 +26,7 @@ import { * @param config.transactionData - if set to true it will return the transaction data for each event (default: false) * @param config.receiptData - if set to true it will return the receipt data for each event (default: false) * @param config.watch - if set to true, the events will be updated every pollingInterval milliseconds set at scaffoldConfig (default: false) + * @param config.enabled - set this to false to disable the hook from running (default: true) */ export const useScaffoldEventHistory = < TContractName extends ContractName, @@ -42,6 +43,7 @@ export const useScaffoldEventHistory = < transactionData, receiptData, watch, + enabled = true, }: UseScaffoldEventHistoryConfig) => { const [events, setEvents] = useState(); const [isLoading, setIsLoading] = useState(false); @@ -50,7 +52,7 @@ export const useScaffoldEventHistory = < const { data: deployedContractData, isLoading: deployedContractLoading } = useDeployedContractInfo(contractName); const publicClient = usePublicClient(); - const configuredNetwork = getTargetNetwork(); + const { targetNetwork } = useTargetNetwork(); const readEvents = async (fromBlock?: bigint) => { setIsLoading(true); @@ -59,6 +61,10 @@ export const useScaffoldEventHistory = < throw new Error("Contract not found"); } + if (!enabled) { + throw new Error("Hook disabled"); + } + const event = (deployedContractData.abi as Abi).find( part => part.type === "event" && part.name === eventName, ) as AbiEvent; @@ -95,7 +101,7 @@ export const useScaffoldEventHistory = < }); } if (events && typeof fromBlock === "undefined") { - setEvents([...events, ...newEvents]); + setEvents([...newEvents, ...events]); } else { setEvents(newEvents); } @@ -113,7 +119,7 @@ export const useScaffoldEventHistory = < useEffect(() => { readEvents(fromBlock); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fromBlock]); + }, [fromBlock, enabled]); useEffect(() => { if (!deployedContractLoading) { @@ -134,13 +140,20 @@ export const useScaffoldEventHistory = < receiptData, ]); + useEffect(() => { + // Reset the internal state when target network or fromBlock changed + setEvents([]); + setFromBlockUpdated(fromBlock); + setError(undefined); + }, [fromBlock, targetNetwork.id]); + useInterval( async () => { if (!deployedContractLoading) { readEvents(); } }, - watch ? (configuredNetwork.id !== chains.hardhat.id ? scaffoldConfig.pollingInterval : 4_000) : null, + watch ? (targetNetwork.id !== chains.hardhat.id ? scaffoldConfig.pollingInterval : 4_000) : null, ); const eventHistoryData = useMemo( diff --git a/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldEventSubscriber.ts b/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldEventSubscriber.ts index 96c1dbacd..66fb3e412 100644 --- a/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldEventSubscriber.ts +++ b/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldEventSubscriber.ts @@ -1,12 +1,13 @@ +import { useTargetNetwork } from "./useTargetNetwork"; import { Abi, ExtractAbiEventNames } from "abitype"; import { Log } from "viem"; import { useContractEvent } from "wagmi"; import { addIndexedArgsToEvent, useDeployedContractInfo } from "~~/hooks/scaffold-eth"; -import { getTargetNetwork } from "~~/utils/scaffold-eth"; import { ContractAbi, ContractName, UseScaffoldEventConfig } from "~~/utils/scaffold-eth/contract"; /** - * @dev wrapper for wagmi's useContractEvent + * Wrapper for wagmi's useContractEvent which automatically loads (by name) + * the contract ABI and address from the deployed contracts. * @param config - The config settings * @param config.contractName - deployed contract name * @param config.eventName - name of the event to listen for @@ -21,6 +22,7 @@ export const useScaffoldEventSubscriber = < listener, }: UseScaffoldEventConfig) => { const { data: deployedContractData } = useDeployedContractInfo(contractName); + const { targetNetwork } = useTargetNetwork(); const addInexedArgsToLogs = (logs: Log[]) => logs.map(addIndexedArgsToEvent); const listenerWithIndexedArgs = (logs: Log[]) => @@ -29,7 +31,7 @@ export const useScaffoldEventSubscriber = < return useContractEvent({ address: deployedContractData?.address, abi: deployedContractData?.abi as Abi, - chainId: getTargetNetwork().id, + chainId: targetNetwork.id, listener: listenerWithIndexedArgs, eventName, }); diff --git a/templates/base/packages/nextjs/hooks/scaffold-eth/useTargetNetwork.ts b/templates/base/packages/nextjs/hooks/scaffold-eth/useTargetNetwork.ts new file mode 100644 index 000000000..2ce765375 --- /dev/null +++ b/templates/base/packages/nextjs/hooks/scaffold-eth/useTargetNetwork.ts @@ -0,0 +1,26 @@ +import { useEffect } from "react"; +import { useNetwork } from "wagmi"; +import scaffoldConfig from "~~/scaffold.config"; +import { useGlobalState } from "~~/services/store/store"; +import { ChainWithAttributes } from "~~/utils/scaffold-eth"; +import { NETWORKS_EXTRA_DATA } from "~~/utils/scaffold-eth"; + +export function useTargetNetwork(): { targetNetwork: ChainWithAttributes } { + const { chain } = useNetwork(); + const targetNetwork = useGlobalState(({ targetNetwork }) => targetNetwork); + const setTargetNetwork = useGlobalState(({ setTargetNetwork }) => setTargetNetwork); + + useEffect(() => { + const newSelectedNetwork = scaffoldConfig.targetNetworks.find(targetNetwork => targetNetwork.id === chain?.id); + if (newSelectedNetwork && newSelectedNetwork.id !== targetNetwork.id) { + setTargetNetwork(newSelectedNetwork); + } + }, [chain?.id, setTargetNetwork, targetNetwork.id]); + + return { + targetNetwork: { + ...targetNetwork, + ...NETWORKS_EXTRA_DATA[targetNetwork.id], + }, + }; +} diff --git a/templates/base/packages/nextjs/hooks/scaffold-eth/useTransactor.tsx b/templates/base/packages/nextjs/hooks/scaffold-eth/useTransactor.tsx index 634120ec1..efd74440a 100644 --- a/templates/base/packages/nextjs/hooks/scaffold-eth/useTransactor.tsx +++ b/templates/base/packages/nextjs/hooks/scaffold-eth/useTransactor.tsx @@ -5,7 +5,7 @@ import { getParsedError } from "~~/components/scaffold-eth"; import { getBlockExplorerTxLink, notification } from "~~/utils/scaffold-eth"; type TransactionFunc = ( - tx: (() => Promise) | SendTransactionParameters, + tx: (() => Promise) | (() => Promise) | SendTransactionParameters, options?: { onBlockConfirmation?: (txnReceipt: TransactionReceipt) => void; blockConfirmations?: number; @@ -57,7 +57,12 @@ export const useTransactor = (_walletClient?: WalletClient): TransactionFunc => notificationId = notification.loading(); if (typeof tx === "function") { // Tx is already prepared by the caller - transactionHash = (await tx()).hash; + const result = await tx(); + if (typeof result === "string") { + transactionHash = result; + } else { + transactionHash = result.hash; + } } else if (tx != null) { transactionHash = await walletClient.sendTransaction(tx); } else { diff --git a/templates/base/packages/nextjs/package.json b/templates/base/packages/nextjs/package.json index db9e72f12..1ec849205 100644 --- a/templates/base/packages/nextjs/package.json +++ b/templates/base/packages/nextjs/package.json @@ -16,7 +16,7 @@ "dependencies": { "@ethersproject/providers": "~5.7.2", "@heroicons/react": "~2.0.11", - "@rainbow-me/rainbowkit": "1.1.2", + "@rainbow-me/rainbowkit": "1.3.0", "@uniswap/sdk-core": "~4.0.1", "@uniswap/v2-sdk": "~3.0.1", "blo": "~1.0.1", @@ -30,8 +30,8 @@ "react-hot-toast": "~2.4.0", "use-debounce": "~8.0.4", "usehooks-ts": "~2.9.1", - "viem": "~1.16.6", - "wagmi": "1.4.4", + "viem": "1.19.9", + "wagmi": "1.4.7", "zustand": "~4.1.2" }, "devDependencies": { diff --git a/templates/base/packages/nextjs/pages/_app.tsx b/templates/base/packages/nextjs/pages/_app.tsx index 70b88958b..86e4fd08f 100644 --- a/templates/base/packages/nextjs/pages/_app.tsx +++ b/templates/base/packages/nextjs/pages/_app.tsx @@ -18,9 +18,6 @@ import "~~/styles/globals.css"; const ScaffoldEthApp = ({ Component, pageProps }: AppProps) => { const price = useNativeCurrencyPrice(); const setNativeCurrencyPrice = useGlobalState(state => state.setNativeCurrencyPrice); - // This variable is required for initial client side rendering of correct theme for RainbowKit - const [isDarkTheme, setIsDarkTheme] = useState(true); - const { isDarkMode } = useDarkMode(); useEffect(() => { if (price > 0) { @@ -28,6 +25,24 @@ const ScaffoldEthApp = ({ Component, pageProps }: AppProps) => { } }, [setNativeCurrencyPrice, price]); + return ( + <> +
    +
    +
    + +
    +
    +
    + + + ); +}; + +const ScaffoldEthAppWithProviders = (props: AppProps) => { + // This variable is required for initial client side rendering of correct theme for RainbowKit + const [isDarkTheme, setIsDarkTheme] = useState(true); + const { isDarkMode } = useDarkMode(); useEffect(() => { setIsDarkTheme(isDarkMode); }, [isDarkMode]); @@ -40,17 +55,10 @@ const ScaffoldEthApp = ({ Component, pageProps }: AppProps) => { avatar={BlockieAvatar} theme={isDarkTheme ? darkTheme() : lightTheme()} > -
    -
    -
    - -
    -
    -
    - + ); }; -export default ScaffoldEthApp; +export default ScaffoldEthAppWithProviders; diff --git a/templates/base/packages/nextjs/pages/blockexplorer/address/[address].tsx b/templates/base/packages/nextjs/pages/blockexplorer/address/[address].tsx.template.mjs similarity index 86% rename from templates/base/packages/nextjs/pages/blockexplorer/address/[address].tsx rename to templates/base/packages/nextjs/pages/blockexplorer/address/[address].tsx.template.mjs index 465714bd2..0c8d62c52 100644 --- a/templates/base/packages/nextjs/pages/blockexplorer/address/[address].tsx +++ b/templates/base/packages/nextjs/pages/blockexplorer/address/[address].tsx.template.mjs @@ -1,10 +1,13 @@ -import { useEffect, useState } from "react"; +import { withDefaults } from "../../../../../../utils.js"; + +const contents = ({ chainName, artifactsDirName }) => + `import { useEffect, useState } from "react"; import { useRouter } from "next/router"; import fs from "fs"; import { GetServerSideProps } from "next"; import path from "path"; import { createPublicClient, http } from "viem"; -import { hardhat } from "viem/chains"; +import { ${chainName[0]} } from "viem/chains"; import { AddressCodeTab, AddressLogsTab, @@ -15,7 +18,6 @@ import { import { Address, Balance } from "~~/components/scaffold-eth"; import deployedContracts from "~~/contracts/deployedContracts"; import { useFetchBlocks } from "~~/hooks/scaffold-eth"; -import { getTargetNetwork } from "~~/utils/scaffold-eth"; import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract"; type AddressCodeTabProps = { @@ -29,7 +31,7 @@ type PageProps = { }; const publicClient = createPublicClient({ - chain: hardhat, + chain: ${chainName[0]}, transport: http(), }); @@ -82,25 +84,25 @@ const AddressPage = ({ address, contractData }: PageProps) => { {isContract && (