diff --git a/package.json b/package.json index 4cc4f1f..10a19d3 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,5 @@ "devDependencies": { "husky": "^8.0.1", "lint-staged": "^13.0.3" - }, - "resolutions": { - "usehooks-ts@^2.7.2": "patch:usehooks-ts@npm:^2.7.2#./.yarn/patches/usehooks-ts-npm-2.7.2-fceffe0e43.patch" } } diff --git a/packages/hardhat/hardhat.config.ts b/packages/hardhat/hardhat.config.ts index e738b58..a34332c 100644 --- a/packages/hardhat/hardhat.config.ts +++ b/packages/hardhat/hardhat.config.ts @@ -62,16 +62,16 @@ const config: HardhatUserConfig = { url: `https://arb-mainnet.g.alchemy.com/v2/${providerApiKey}`, accounts: [deployerPrivateKey], }, - arbitrumGoerli: { - url: `https://arb-goerli.g.alchemy.com/v2/${providerApiKey}`, + arbitrumSepolia: { + url: `https://arb-sepolia.g.alchemy.com/v2/${providerApiKey}`, accounts: [deployerPrivateKey], }, optimism: { url: `https://opt-mainnet.g.alchemy.com/v2/${providerApiKey}`, accounts: [deployerPrivateKey], }, - optimismGoerli: { - url: `https://opt-goerli.g.alchemy.com/v2/${providerApiKey}`, + optimismSepolia: { + url: `https://opt-sepolia.g.alchemy.com/v2/${providerApiKey}`, accounts: [deployerPrivateKey], }, polygon: { diff --git a/packages/nextjs/app/debug/_components/DebugContracts.tsx b/packages/nextjs/app/debug/_components/DebugContracts.tsx index 195246c..82bc18a 100644 --- a/packages/nextjs/app/debug/_components/DebugContracts.tsx +++ b/packages/nextjs/app/debug/_components/DebugContracts.tsx @@ -15,6 +15,7 @@ export function DebugContracts() { const [selectedContract, setSelectedContract] = useLocalStorage( selectedContractStorageKey, contractNames[0], + { initializeWithValue: false }, ); useEffect(() => { diff --git a/packages/nextjs/app/debug/_components/contract/ContractInput.tsx b/packages/nextjs/app/debug/_components/contract/ContractInput.tsx index e27c56e..766431e 100644 --- a/packages/nextjs/app/debug/_components/contract/ContractInput.tsx +++ b/packages/nextjs/app/debug/_components/contract/ContractInput.tsx @@ -1,6 +1,8 @@ "use client"; import { Dispatch, SetStateAction } from "react"; +import { Tuple } from "./Tuple"; +import { TupleArray } from "./TupleArray"; import { AbiParameter } from "abitype"; import { AddressInput, @@ -10,6 +12,7 @@ import { IntegerInput, IntegerVariant, } from "~~/components/scaffold-eth"; +import { AbiParameterTuple } from "~~/utils/scaffold-eth/contract"; type ContractInputProps = { setForm: Dispatch>>; @@ -31,17 +34,51 @@ export const ContractInput = ({ setForm, form, stateObjectKey, paramType }: Cont }, }; - if (paramType.type === "address") { - return ; - } else if (paramType.type === "bytes32") { - return ; - } else if (paramType.type === "bytes") { - return ; - } else if (paramType.type === "string") { - return ; - } else if (paramType.type.includes("int") && !paramType.type.includes("[")) { - return ; - } + const renderInput = () => { + switch (paramType.type) { + case "address": + return ; + case "bytes32": + return ; + case "bytes": + return ; + case "string": + return ; + case "tuple": + return ( + + ); + default: + // Handling 'int' types and 'tuple[]' types + if (paramType.type.includes("int") && !paramType.type.includes("[")) { + return ; + } else if (paramType.type.startsWith("tuple[")) { + return ( + + ); + } else { + return ; + } + } + }; - return ; + return ( +
+
+ {paramType.name && {paramType.name}} + {paramType.type} +
+ {renderInput()} +
+ ); }; diff --git a/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx b/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx index 42013ed..91bae12 100644 --- a/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx +++ b/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx @@ -11,6 +11,7 @@ import { getFunctionInputKey, getInitialFormState, getParsedContractFunctionArgs, + transformAbiFunction, } from "~~/app/debug/_components/contract"; import { getParsedError, notification } from "~~/utils/scaffold-eth"; @@ -42,7 +43,8 @@ export const ReadOnlyFunctionForm = ({ }, }); - const inputElements = abiFunction.inputs.map((input, inputIndex) => { + const transformedFunction = transformAbiFunction(abiFunction); + const inputElements = transformedFunction.inputs.map((input, inputIndex) => { const key = getFunctionInputKey(abiFunction.name, input, inputIndex); return ( >>; + parentStateObjectKey: string; + parentForm: Record | undefined; +}; + +export const Tuple = ({ abiTupleParameter, setParentForm, parentStateObjectKey }: TupleProps) => { + const [form, setForm] = useState>(() => getInitalTupleFormState(abiTupleParameter)); + + useEffect(() => { + const values = Object.values(form); + const argsStruct: Record = {}; + abiTupleParameter.components.forEach((component, componentIndex) => { + argsStruct[component.name || `input_${componentIndex}_`] = values[componentIndex]; + }); + + setParentForm(parentForm => ({ ...parentForm, [parentStateObjectKey]: JSON.stringify(argsStruct, replacer) })); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(form, replacer)]); + + return ( +
+
+ +
+

{abiTupleParameter.internalType}

+
+
+ {abiTupleParameter?.components?.map((param, index) => { + const key = getFunctionInputKey(abiTupleParameter.name || "tuple", param, index); + return ; + })} +
+
+
+ ); +}; diff --git a/packages/nextjs/app/debug/_components/contract/TupleArray.tsx b/packages/nextjs/app/debug/_components/contract/TupleArray.tsx new file mode 100644 index 0000000..1eb23c2 --- /dev/null +++ b/packages/nextjs/app/debug/_components/contract/TupleArray.tsx @@ -0,0 +1,139 @@ +import { Dispatch, SetStateAction, useEffect, useState } from "react"; +import { ContractInput } from "./ContractInput"; +import { getFunctionInputKey, getInitalTupleArrayFormState } from "./utilsContract"; +import { replacer } from "~~/utils/scaffold-eth/common"; +import { AbiParameterTuple } from "~~/utils/scaffold-eth/contract"; + +type TupleArrayProps = { + abiTupleParameter: AbiParameterTuple & { isVirtual?: true }; + setParentForm: Dispatch>>; + parentStateObjectKey: string; + parentForm: Record | undefined; +}; + +export const TupleArray = ({ abiTupleParameter, setParentForm, parentStateObjectKey }: TupleArrayProps) => { + const [form, setForm] = useState>(() => getInitalTupleArrayFormState(abiTupleParameter)); + const [additionalInputs, setAdditionalInputs] = useState>([ + abiTupleParameter.components, + ]); + + const depth = (abiTupleParameter.type.match(/\[\]/g) || []).length; + + useEffect(() => { + // Extract and group fields based on index prefix + const groupedFields = Object.keys(form).reduce((acc, key) => { + const [indexPrefix, ...restArray] = key.split("_"); + const componentName = restArray.join("_"); + if (!acc[indexPrefix]) { + acc[indexPrefix] = {}; + } + acc[indexPrefix][componentName] = form[key]; + return acc; + }, {} as Record>); + + let argsArray: Array> = []; + + Object.keys(groupedFields).forEach(key => { + const currentKeyValues = Object.values(groupedFields[key]); + + const argsStruct: Record = {}; + abiTupleParameter.components.forEach((component, componentIndex) => { + argsStruct[component.name || `input_${componentIndex}_`] = currentKeyValues[componentIndex]; + }); + + argsArray.push(argsStruct); + }); + + if (depth > 1) { + argsArray = argsArray.map(args => { + return args[abiTupleParameter.components[0].name || "tuple"]; + }); + } + + setParentForm(parentForm => { + return { ...parentForm, [parentStateObjectKey]: JSON.stringify(argsArray, replacer) }; + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(form, replacer)]); + + const addInput = () => { + setAdditionalInputs(previousValue => { + const newAdditionalInputs = [...previousValue, abiTupleParameter.components]; + + // Add the new inputs to the form + setForm(form => { + const newForm = { ...form }; + abiTupleParameter.components.forEach((component, componentIndex) => { + const key = getFunctionInputKey( + `${newAdditionalInputs.length - 1}_${abiTupleParameter.name || "tuple"}`, + component, + componentIndex, + ); + newForm[key] = ""; + }); + return newForm; + }); + + return newAdditionalInputs; + }); + }; + + const removeInput = () => { + // Remove the last inputs from the form + setForm(form => { + const newForm = { ...form }; + abiTupleParameter.components.forEach((component, componentIndex) => { + const key = getFunctionInputKey( + `${additionalInputs.length - 1}_${abiTupleParameter.name || "tuple"}`, + component, + componentIndex, + ); + delete newForm[key]; + }); + return newForm; + }); + setAdditionalInputs(inputs => inputs.slice(0, -1)); + }; + + return ( +
+
+ +
+

{abiTupleParameter.internalType}

+
+
+ {additionalInputs.map((additionalInput, additionalIndex) => ( +
+ + {depth > 1 ? `${additionalIndex}` : `tuple[${additionalIndex}]`} + +
+ {additionalInput.map((param, index) => { + const key = getFunctionInputKey( + `${additionalIndex}_${abiTupleParameter.name || "tuple"}`, + param, + index, + ); + return ( + + ); + })} +
+
+ ))} +
+ + {additionalInputs.length > 0 && ( + + )} +
+
+
+
+ ); +}; diff --git a/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx b/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx index d713501..70b44a3 100644 --- a/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx +++ b/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx @@ -11,6 +11,7 @@ import { getFunctionInputKey, getInitialFormState, getParsedContractFunctionArgs, + transformAbiFunction, } from "~~/app/debug/_components/contract"; import { IntegerInput } from "~~/components/scaffold-eth"; import { useTransactor } from "~~/hooks/scaffold-eth"; @@ -72,7 +73,8 @@ export const WriteOnlyFunctionForm = ({ }, [txResult]); // TODO use `useMemo` to optimize also update in ReadOnlyFunctionForm - const inputs = abiFunction.inputs.map((input, inputIndex) => { + const transformedFunction = transformAbiFunction(abiFunction); + const inputs = transformedFunction.inputs.map((input, inputIndex) => { const key = getFunctionInputKey(abiFunction.name, input, inputIndex); return ( {inputs} {abiFunction.stateMutability === "payable" ? ( - { - setDisplayedTxResult(undefined); - setTxValue(updatedTxValue); - }} - placeholder="value (wei)" - /> +
+
+ payable value + wei +
+ { + setDisplayedTxResult(undefined); + setTxValue(updatedTxValue); + }} + placeholder="value (wei)" + /> +
) : null}
{!zeroInputs && ( diff --git a/packages/nextjs/app/debug/_components/contract/utilsContract.tsx b/packages/nextjs/app/debug/_components/contract/utilsContract.tsx index ad0b256..023efe8 100644 --- a/packages/nextjs/app/debug/_components/contract/utilsContract.tsx +++ b/packages/nextjs/app/debug/_components/contract/utilsContract.tsx @@ -1,4 +1,5 @@ import { AbiFunction, AbiParameter } from "abitype"; +import { AbiParameterTuple } from "~~/utils/scaffold-eth/contract"; /** * Generates a key based on function metadata @@ -8,35 +9,56 @@ const getFunctionInputKey = (functionName: string, input: AbiParameter, inputInd return functionName + "_" + name + "_" + input.internalType + "_" + input.type; }; -// This regex is used to identify array types in the form of `type[size]` -const ARRAY_TYPE_REGEX = /\[.*\]$/; +const isJsonString = (str: string) => { + try { + JSON.parse(str); + return true; + } catch (e) { + return false; + } +}; + +// Recursive function to deeply parse JSON strings, correctly handling nested arrays and encoded JSON strings +const deepParseValues = (value: any): any => { + if (typeof value === "string") { + if (isJsonString(value)) { + const parsed = JSON.parse(value); + return deepParseValues(parsed); + } else { + // It's a string but not a JSON string, return as is + return value; + } + } else if (Array.isArray(value)) { + // If it's an array, recursively parse each element + return value.map(element => deepParseValues(element)); + } else if (typeof value === "object" && value !== null) { + // If it's an object, recursively parse each value + return Object.entries(value).reduce((acc: any, [key, val]) => { + acc[key] = deepParseValues(val); + return acc; + }, {}); + } + + // Handle boolean values represented as strings + if (value === "true" || value === "1" || value === "0x1" || value === "0x01" || value === "0x0001") { + return true; + } else if (value === "false" || value === "0" || value === "0x0" || value === "0x00" || value === "0x0000") { + return false; + } + + return value; +}; /** - * Parses form input with array support + * parses form input with array support */ const getParsedContractFunctionArgs = (form: Record) => { - const keys = Object.keys(form); - const parsedArguments = keys.map(key => { - try { - const keySplitArray = key.split("_"); - const baseTypeOfArg = keySplitArray[keySplitArray.length - 1]; - let valueOfArg = form[key]; - - if (ARRAY_TYPE_REGEX.test(baseTypeOfArg) || baseTypeOfArg === "tuple") { - valueOfArg = JSON.parse(valueOfArg); - } else if (baseTypeOfArg === "bool") { - if (["true", "1", "0x1", "0x01", "0x0001"].includes(valueOfArg)) { - valueOfArg = 1; - } else { - valueOfArg = 0; - } - } - return valueOfArg; - } catch (error: any) { - // ignore error, it will be handled when sending/reading from a function - } + return Object.keys(form).map(key => { + const valueOfArg = form[key]; + + // Attempt to deeply parse JSON strings + return deepParseValues(valueOfArg); }); - return parsedArguments; }; const getInitialFormState = (abiFunction: AbiFunction) => { @@ -49,4 +71,79 @@ const getInitialFormState = (abiFunction: AbiFunction) => { return initialForm; }; -export { getFunctionInputKey, getInitialFormState, getParsedContractFunctionArgs }; +const getInitalTupleFormState = (abiTupleParameter: AbiParameterTuple) => { + const initialForm: Record = {}; + if (abiTupleParameter.components.length === 0) return initialForm; + + abiTupleParameter.components.forEach((component, componentIndex) => { + const key = getFunctionInputKey(abiTupleParameter.name || "tuple", component, componentIndex); + initialForm[key] = ""; + }); + return initialForm; +}; + +const getInitalTupleArrayFormState = (abiTupleParameter: AbiParameterTuple) => { + const initialForm: Record = {}; + if (abiTupleParameter.components.length === 0) return initialForm; + abiTupleParameter.components.forEach((component, componentIndex) => { + const key = getFunctionInputKey("0_" + abiTupleParameter.name || "tuple", component, componentIndex); + initialForm[key] = ""; + }); + return initialForm; +}; + +const adjustInput = (input: AbiParameterTuple): AbiParameter => { + if (input.type.startsWith("tuple[")) { + const depth = (input.type.match(/\[\]/g) || []).length; + return { + ...input, + components: transformComponents(input.components, depth, { + internalType: input.internalType || "struct", + name: input.name, + }), + }; + } else if (input.components) { + return { + ...input, + components: input.components.map(value => adjustInput(value as AbiParameterTuple)), + }; + } + return input; +}; + +const transformComponents = ( + components: readonly AbiParameter[], + depth: number, + parentComponentData: { internalType?: string; name?: string }, +): AbiParameter[] => { + // Base case: if depth is 1 or no components, return the original components + if (depth === 1 || !components) { + return [...components]; + } + + // Recursive case: wrap components in an additional tuple layer + const wrappedComponents: AbiParameter = { + internalType: `${parentComponentData.internalType || "struct"}`.replace(/\[\]/g, "") + "[]".repeat(depth - 1), + name: `${parentComponentData.name || "tuple"}`, + type: `tuple${"[]".repeat(depth - 1)}`, + components: transformComponents(components, depth - 1, parentComponentData), + }; + + return [wrappedComponents]; +}; + +const transformAbiFunction = (abiFunction: AbiFunction): AbiFunction => { + return { + ...abiFunction, + inputs: abiFunction.inputs.map(value => adjustInput(value as AbiParameterTuple)), + }; +}; + +export { + getFunctionInputKey, + getInitialFormState, + getParsedContractFunctionArgs, + getInitalTupleFormState, + getInitalTupleArrayFormState, + transformAbiFunction, +}; diff --git a/packages/nextjs/app/layout.tsx b/packages/nextjs/app/layout.tsx index 824f4f3..c0efb0c 100644 --- a/packages/nextjs/app/layout.tsx +++ b/packages/nextjs/app/layout.tsx @@ -1,6 +1,7 @@ import "@rainbow-me/rainbowkit/styles.css"; import { Metadata } from "next"; import { ScaffoldEthAppWithProviders } from "~~/components/ScaffoldEthAppWithProviders"; +import { ThemeProvider } from "~~/components/ThemeProvider"; import "~~/styles/globals.css"; const baseUrl = process.env.NEXT_PUBLIC_VERCEL_URL @@ -43,9 +44,11 @@ export const metadata: Metadata = { const ScaffoldEthApp = ({ children }: { children: React.ReactNode }) => { return ( - + - {children} + + {children} + ); diff --git a/packages/nextjs/components/ScaffoldEthAppWithProviders.tsx b/packages/nextjs/components/ScaffoldEthAppWithProviders.tsx index 1eb3bd3..56a82f6 100644 --- a/packages/nextjs/components/ScaffoldEthAppWithProviders.tsx +++ b/packages/nextjs/components/ScaffoldEthAppWithProviders.tsx @@ -1,7 +1,8 @@ "use client"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { RainbowKitProvider, darkTheme, lightTheme } from "@rainbow-me/rainbowkit"; +import { useTheme } from "next-themes"; import { Toaster } from "react-hot-toast"; import { WagmiConfig } from "wagmi"; import { Footer } from "~~/components/Footer"; @@ -9,7 +10,6 @@ import { Header } from "~~/components/Header"; import { BlockieAvatar } from "~~/components/scaffold-eth"; import { ProgressBar } from "~~/components/scaffold-eth/ProgressBar"; import { useNativeCurrencyPrice } from "~~/hooks/scaffold-eth"; -import { useDarkMode } from "~~/hooks/scaffold-eth/useDarkMode"; import { useGlobalState } from "~~/services/store/store"; import { wagmiConfig } from "~~/services/web3/wagmiConfig"; import { appChains } from "~~/services/web3/wagmiConnectors"; @@ -37,7 +37,13 @@ const ScaffoldEthApp = ({ children }: { children: React.ReactNode }) => { }; export const ScaffoldEthAppWithProviders = ({ children }: { children: React.ReactNode }) => { - const { isDarkMode } = useDarkMode(); + const { resolvedTheme } = useTheme(); + const isDarkMode = resolvedTheme === "dark"; + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); return ( @@ -45,7 +51,7 @@ export const ScaffoldEthAppWithProviders = ({ children }: { children: React.Reac {children} diff --git a/packages/nextjs/components/SwitchTheme.tsx b/packages/nextjs/components/SwitchTheme.tsx index b53dd6c..0c2a638 100644 --- a/packages/nextjs/components/SwitchTheme.tsx +++ b/packages/nextjs/components/SwitchTheme.tsx @@ -1,34 +1,44 @@ "use client"; -import { useEffect } from "react"; -import { useIsMounted } from "usehooks-ts"; +import { useEffect, useState } from "react"; +import { useTheme } from "next-themes"; import { MoonIcon, SunIcon } from "@heroicons/react/24/outline"; -import { useDarkMode } from "~~/hooks/scaffold-eth/useDarkMode"; export const SwitchTheme = ({ className }: { className?: string }) => { - const { isDarkMode, toggle } = useDarkMode(); - const isMounted = useIsMounted(); + const { setTheme, resolvedTheme } = useTheme(); + const [mounted, setMounted] = useState(false); + + const isDarkMode = resolvedTheme === "dark"; + + const handleToggle = () => { + if (isDarkMode) { + setTheme("light"); + return; + } + setTheme("dark"); + }; useEffect(() => { - const body = document.body; - body.setAttribute("data-theme", isDarkMode ? "scaffoldEthDark" : "scaffoldEth"); - }, [isDarkMode]); + setMounted(true); + }, []); + + if (!mounted) return null; return ( -
+
- {isMounted() && ( + { - )} + }
); }; diff --git a/packages/nextjs/components/ThemeProvider.tsx b/packages/nextjs/components/ThemeProvider.tsx new file mode 100644 index 0000000..3a7798f --- /dev/null +++ b/packages/nextjs/components/ThemeProvider.tsx @@ -0,0 +1,9 @@ +"use client"; + +import * as React from "react"; +import { ThemeProvider as NextThemesProvider } from "next-themes"; +import { type ThemeProviderProps } from "next-themes/dist/types"; + +export const ThemeProvider = ({ children, ...props }: ThemeProviderProps) => { + return {children}; +}; diff --git a/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/NetworkOptions.tsx b/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/NetworkOptions.tsx index b81fe1a..570cbb9 100644 --- a/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/NetworkOptions.tsx +++ b/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/NetworkOptions.tsx @@ -1,7 +1,7 @@ +import { useTheme } from "next-themes"; import { useNetwork, useSwitchNetwork } from "wagmi"; import { ArrowsRightLeftIcon } from "@heroicons/react/24/solid"; import { getNetworkColor } from "~~/hooks/scaffold-eth"; -import { useDarkMode } from "~~/hooks/scaffold-eth/useDarkMode"; import { getTargetNetworks } from "~~/utils/scaffold-eth"; const allowedNetworks = getTargetNetworks(); @@ -11,9 +11,10 @@ type NetworkOptionsProps = { }; export const NetworkOptions = ({ hidden = false }: NetworkOptionsProps) => { - const { isDarkMode } = useDarkMode(); const { switchNetwork } = useSwitchNetwork(); const { chain } = useNetwork(); + const { resolvedTheme } = useTheme(); + const isDarkMode = resolvedTheme === "dark"; return ( <> diff --git a/packages/nextjs/hooks/scaffold-eth/useAutoConnect.ts b/packages/nextjs/hooks/scaffold-eth/useAutoConnect.ts index f0f9129..549c7fe 100644 --- a/packages/nextjs/hooks/scaffold-eth/useAutoConnect.ts +++ b/packages/nextjs/hooks/scaffold-eth/useAutoConnect.ts @@ -58,7 +58,9 @@ const getInitialConnector = ( */ export const useAutoConnect = (): void => { const wagmiWalletValue = useReadLocalStorage(WAGMI_WALLET_STORAGE_KEY); - const [walletId, setWalletId] = useLocalStorage(SCAFFOLD_WALLET_STORAGE_KEY, wagmiWalletValue ?? ""); + const [walletId, setWalletId] = useLocalStorage(SCAFFOLD_WALLET_STORAGE_KEY, wagmiWalletValue ?? "", { + initializeWithValue: false, + }); const connectState = useConnect(); useAccount({ onConnect({ connector }) { diff --git a/packages/nextjs/hooks/scaffold-eth/useBurnerWallet.ts b/packages/nextjs/hooks/scaffold-eth/useBurnerWallet.ts index bb75706..0538fc3 100644 --- a/packages/nextjs/hooks/scaffold-eth/useBurnerWallet.ts +++ b/packages/nextjs/hooks/scaffold-eth/useBurnerWallet.ts @@ -57,7 +57,9 @@ type BurnerAccount = { * Creates a burner wallet */ export const useBurnerWallet = (): BurnerAccount => { - const [burnerSk, setBurnerSk] = useLocalStorage(burnerStorageKey, newDefaultPrivateKey); + const [burnerSk, setBurnerSk] = useLocalStorage(burnerStorageKey, newDefaultPrivateKey, { + initializeWithValue: false, + }); const publicClient = usePublicClient(); const [walletClient, setWalletClient] = useState>(); diff --git a/packages/nextjs/hooks/scaffold-eth/useNetworkColor.ts b/packages/nextjs/hooks/scaffold-eth/useNetworkColor.ts index 4f83f3f..ec9c770 100644 --- a/packages/nextjs/hooks/scaffold-eth/useNetworkColor.ts +++ b/packages/nextjs/hooks/scaffold-eth/useNetworkColor.ts @@ -1,5 +1,5 @@ -import { useDarkMode } from "./useDarkMode"; import { useTargetNetwork } from "./useTargetNetwork"; +import { useTheme } from "next-themes"; import { ChainWithAttributes } from "~~/utils/scaffold-eth"; export const DEFAULT_NETWORK_COLOR: [string, string] = ["#666666", "#bbbbbb"]; @@ -13,8 +13,10 @@ export function getNetworkColor(network: ChainWithAttributes, isDarkMode: boolea * Gets the color of the target network */ export const useNetworkColor = () => { - const { isDarkMode } = useDarkMode(); + const { resolvedTheme } = useTheme(); const { targetNetwork } = useTargetNetwork(); + const isDarkMode = resolvedTheme === "dark"; + return getNetworkColor(targetNetwork, isDarkMode); }; diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index ca3af70..77df898 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -16,13 +16,14 @@ "dependencies": { "@ethersproject/providers": "^5.7.2", "@heroicons/react": "^2.0.11", - "@rainbow-me/rainbowkit": "1.3.0", + "@rainbow-me/rainbowkit": "1.3.5", "@uniswap/sdk-core": "^4.0.1", "@uniswap/v2-sdk": "^3.0.1", "blo": "^1.0.1", - "daisyui": "^4.4.19", + "daisyui": "4.5.0", "firebase-admin": "^11.11.1", "next": "^14.0.4", + "next-themes": "^0.2.1", "nprogress": "^0.2.0", "qrcode.react": "^3.1.0", "react": "^18.2.0", @@ -30,7 +31,7 @@ "react-dom": "^18.2.0", "react-hot-toast": "^2.4.0", "use-debounce": "^8.0.4", - "usehooks-ts": "^2.7.2", + "usehooks-ts": "^2.13.0", "viem": "1.19.9", "wagmi": "1.4.12", "zustand": "^4.1.2" diff --git a/packages/nextjs/tailwind.config.js b/packages/nextjs/tailwind.config.js index e1ca91d..d0e358b 100644 --- a/packages/nextjs/tailwind.config.js +++ b/packages/nextjs/tailwind.config.js @@ -2,12 +2,12 @@ module.exports = { content: ["./app/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}", "./utils/**/*.{js,ts,jsx,tsx}"], plugins: [require("daisyui")], - darkTheme: "scaffoldEthDark", + darkTheme: "dark", // DaisyUI theme colors daisyui: { themes: [ { - scaffoldEth: { + light: { primary: "#93BBFB", "primary-content": "#212638", secondary: "#DAE8FF", @@ -39,7 +39,7 @@ module.exports = { }, }, { - scaffoldEthDark: { + dark: { primary: "#212638", "primary-content": "#F9FBFF", secondary: "#323f61", diff --git a/packages/nextjs/utils/scaffold-eth/contract.ts b/packages/nextjs/utils/scaffold-eth/contract.ts index ac7fe4a..f092d5a 100644 --- a/packages/nextjs/utils/scaffold-eth/contract.ts +++ b/packages/nextjs/utils/scaffold-eth/contract.ts @@ -1,5 +1,6 @@ import { Abi, + AbiParameter, AbiParameterToPrimitiveType, AbiParametersToPrimitiveTypes, ExtractAbiEvent, @@ -290,3 +291,5 @@ export type UseScaffoldEventHistoryData< }[] > | undefined; + +export type AbiParameterTuple = Extract; diff --git a/packages/nextjs/utils/scaffold-eth/networks.ts b/packages/nextjs/utils/scaffold-eth/networks.ts index e2a39ac..7898984 100644 --- a/packages/nextjs/utils/scaffold-eth/networks.ts +++ b/packages/nextjs/utils/scaffold-eth/networks.ts @@ -35,13 +35,13 @@ export const NETWORKS_EXTRA_DATA: Record = { color: "#92D9FA", nativeCurrencyTokenAddress: "0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0", }, - [chains.optimismGoerli.id]: { + [chains.optimismSepolia.id]: { color: "#f01a37", }, [chains.optimism.id]: { color: "#f01a37", }, - [chains.arbitrumGoerli.id]: { + [chains.arbitrumSepolia.id]: { color: "#28a0f0", }, [chains.arbitrum.id]: { diff --git a/yarn.lock b/yarn.lock index b62ee8f..c29da88 100644 --- a/yarn.lock +++ b/yarn.lock @@ -322,10 +322,10 @@ __metadata: languageName: node linkType: hard -"@emotion/hash@npm:^0.8.0": - version: 0.8.0 - resolution: "@emotion/hash@npm:0.8.0" - checksum: 4b35d88a97e67275c1d990c96d3b0450451d089d1508619488fc0acb882cb1ac91e93246d471346ebd1b5402215941ef4162efe5b51534859b39d8b3a0e3ffaa +"@emotion/hash@npm:^0.9.0": + version: 0.9.1 + resolution: "@emotion/hash@npm:0.9.1" + checksum: 716e17e48bf9047bf9383982c071de49f2615310fb4e986738931776f5a823bc1f29c84501abe0d3df91a3803c80122d24e28b57351bca9e01356ebb33d89876 languageName: node linkType: hard @@ -2117,24 +2117,23 @@ __metadata: languageName: node linkType: hard -"@rainbow-me/rainbowkit@npm:1.3.0": - version: 1.3.0 - resolution: "@rainbow-me/rainbowkit@npm:1.3.0" - dependencies: - "@vanilla-extract/css": 1.9.1 - "@vanilla-extract/dynamic": 2.0.2 - "@vanilla-extract/sprinkles": 1.5.0 - clsx: 1.1.1 - i18n-js: ^4.3.2 - qrcode: 1.5.0 - react-remove-scroll: 2.5.4 - ua-parser-js: ^1.0.35 +"@rainbow-me/rainbowkit@npm:1.3.5": + version: 1.3.5 + resolution: "@rainbow-me/rainbowkit@npm:1.3.5" + dependencies: + "@vanilla-extract/css": 1.14.0 + "@vanilla-extract/dynamic": 2.1.0 + "@vanilla-extract/sprinkles": 1.6.1 + clsx: 2.1.0 + qrcode: 1.5.3 + react-remove-scroll: 2.5.7 + ua-parser-js: ^1.0.37 peerDependencies: react: ">=17" react-dom: ">=17" viem: ~0.3.19 || ^1.0.0 wagmi: ~1.0.1 || ~1.1.0 || ~1.2.0 || ~1.3.0 || ~1.4.0 - checksum: d038e0543d199da2b727c0b1d58cb07efadd0be08f5e15cab1b269f26ae5b332da46de36102f6a18a32afda93057fbea39fcad1ed287c3bb874aec91d9b2094f + checksum: b8e86aaf71c489757f3da8ff168d00bbd47a19e63e6077d7731f091d2d5bce76207791639615898b89b8aef167db68e41716b921bad35f345704e0d04f670312 languageName: node linkType: hard @@ -2300,7 +2299,7 @@ __metadata: dependencies: "@ethersproject/providers": ^5.7.2 "@heroicons/react": ^2.0.11 - "@rainbow-me/rainbowkit": 1.3.0 + "@rainbow-me/rainbowkit": 1.3.5 "@trivago/prettier-plugin-sort-imports": ^4.1.1 "@types/node": ^17.0.35 "@types/nprogress": ^0 @@ -2312,13 +2311,14 @@ __metadata: "@uniswap/v2-sdk": ^3.0.1 autoprefixer: ^10.4.12 blo: ^1.0.1 - daisyui: ^4.4.19 + daisyui: 4.5.0 eslint: ^8.15.0 eslint-config-next: ^14.0.4 eslint-config-prettier: ^8.5.0 eslint-plugin-prettier: ^4.2.1 firebase-admin: ^11.11.1 next: ^14.0.4 + next-themes: ^0.2.1 nprogress: ^0.2.0 postcss: ^8.4.16 prettier: ^2.8.4 @@ -2331,7 +2331,7 @@ __metadata: type-fest: ^4.6.0 typescript: ^5.1.6 use-debounce: ^8.0.4 - usehooks-ts: ^2.7.2 + usehooks-ts: ^2.13.0 vercel: ^32.4.1 viem: 1.19.9 wagmi: 1.4.12 @@ -3204,11 +3204,11 @@ __metadata: linkType: hard "@types/react-dom@npm:^18.2.18": - version: 18.2.18 - resolution: "@types/react-dom@npm:18.2.18" + version: 18.2.19 + resolution: "@types/react-dom@npm:18.2.19" dependencies: "@types/react": "*" - checksum: 8e3da404c980e2b2a76da3852f812ea6d8b9d0e7f5923fbaf3bfbbbfa1d59116ff91c129de8f68e9b7668a67ae34484fe9df74d5a7518cf8591ec07a0c4dad57 + checksum: 087a19d8e4c1c0900ec4ac5ddb749a811a38274b25683d233c11755d2895cc6e475e8bf9bea3dee36519769298e078d4c2feab9ab4bd13b26bc2a6170716437e languageName: node linkType: hard @@ -3565,31 +3565,31 @@ __metadata: languageName: node linkType: hard -"@vanilla-extract/css@npm:1.9.1": - version: 1.9.1 - resolution: "@vanilla-extract/css@npm:1.9.1" +"@vanilla-extract/css@npm:1.14.0": + version: 1.14.0 + resolution: "@vanilla-extract/css@npm:1.14.0" dependencies: - "@emotion/hash": ^0.8.0 + "@emotion/hash": ^0.9.0 "@vanilla-extract/private": ^1.0.3 - ahocorasick: 1.0.2 chalk: ^4.1.1 - css-what: ^5.0.1 + css-what: ^6.1.0 cssesc: ^3.0.0 csstype: ^3.0.7 - deep-object-diff: ^1.1.0 + deep-object-diff: ^1.1.9 deepmerge: ^4.2.2 media-query-parser: ^2.0.2 + modern-ahocorasick: ^1.0.0 outdent: ^0.8.0 - checksum: 91127d8e2eaaf521b155a39fb6a93e0fbfe5ddd5a8bdf1c732aba278cfdb9505cba24c755a982bc47682b9ff582a06d11e51e6f8d810404164ba098799ce6800 + checksum: ca12de26f72b908c7ac2ee9319faff885fb451ab3da7cef86aae8660c7e042c447041be40ac8c6155d87e8a709efb0697214db3a6cce211e8ef3c110d4dcf8a1 languageName: node linkType: hard -"@vanilla-extract/dynamic@npm:2.0.2": - version: 2.0.2 - resolution: "@vanilla-extract/dynamic@npm:2.0.2" +"@vanilla-extract/dynamic@npm:2.1.0": + version: 2.1.0 + resolution: "@vanilla-extract/dynamic@npm:2.1.0" dependencies: "@vanilla-extract/private": ^1.0.3 - checksum: c6f22606bce094a5682c6d842be6d6965b9448f8973eaabdaa7a7438146458c22490c1c63345f08d2226f1c95ac20731835a5140c2c18646b2c1287b65607b84 + checksum: a6f129d096286cf7e2c7c868f12866d2da2967cd7591e071eb6420f0fafccce86c58e1d408864109fc894332399e731fe8ae67e2c92d456feb4b21c602287f6d languageName: node linkType: hard @@ -3600,12 +3600,12 @@ __metadata: languageName: node linkType: hard -"@vanilla-extract/sprinkles@npm:1.5.0": - version: 1.5.0 - resolution: "@vanilla-extract/sprinkles@npm:1.5.0" +"@vanilla-extract/sprinkles@npm:1.6.1": + version: 1.6.1 + resolution: "@vanilla-extract/sprinkles@npm:1.6.1" peerDependencies: "@vanilla-extract/css": ^1.0.0 - checksum: 522b96afe856a72d76072ab41453edc35117772a6ac829c8a5ad09cddd69152e152b1cbda0b4344d50dbde59d0effa39301beb7acf319ef88f485966c89c80fd + checksum: 13c53a94b19f9226c2684b0da06b98022d2faaf504cb5a4a8e5a0abae499aaa7855c17aa6d88534800d9427696b3248f4fc1a5332bde336c445d837017569f3b languageName: node linkType: hard @@ -4513,13 +4513,6 @@ __metadata: languageName: node linkType: hard -"ahocorasick@npm:1.0.2": - version: 1.0.2 - resolution: "ahocorasick@npm:1.0.2" - checksum: a13ce4403554ae782cf5e28d468a732acf1fd3d0bff251f5dcfddfa5497b6cc343948d69cd94dfdbe8d4dfdb81e2b34cb1c92079e6301f38b0143d314fb95bd6 - languageName: node - linkType: hard - "ajv@npm:8.6.3": version: 8.6.3 resolution: "ajv@npm:8.6.3" @@ -5151,7 +5144,7 @@ __metadata: languageName: node linkType: hard -"bignumber.js@npm:*, bignumber.js@npm:^9.0.0": +"bignumber.js@npm:^9.0.0": version: 9.1.2 resolution: "bignumber.js@npm:9.1.2" checksum: 582c03af77ec9cb0ebd682a373ee6c66475db94a4325f92299621d544aa4bd45cb45fd60001610e94aef8ae98a0905fa538241d9638d4422d57abbeeac6fadaf @@ -5794,10 +5787,10 @@ __metadata: languageName: node linkType: hard -"clsx@npm:1.1.1": - version: 1.1.1 - resolution: "clsx@npm:1.1.1" - checksum: ff052650329773b9b245177305fc4c4dc3129f7b2be84af4f58dc5defa99538c61d4207be7419405a5f8f3d92007c954f4daba5a7b74e563d5de71c28c830063 +"clsx@npm:2.1.0": + version: 2.1.0 + resolution: "clsx@npm:2.1.0" + checksum: 43fefc29b6b49c9476fbce4f8b1cc75c27b67747738e598e6651dd40d63692135dc60b18fa1c5b78a2a9ba8ae6fd2055a068924b94e20b42039bd53b78b98e1d languageName: node linkType: hard @@ -6118,10 +6111,10 @@ __metadata: languageName: node linkType: hard -"css-what@npm:^5.0.1": - version: 5.1.0 - resolution: "css-what@npm:5.1.0" - checksum: 0b75d1bac95c885c168573c85744a6c6843d8c33345f54f717218b37ea6296b0e99bb12105930ea170fd4a921990392a7c790c16c585c1d8960c49e2b7ec39f7 +"css-what@npm:^6.1.0": + version: 6.1.0 + resolution: "css-what@npm:6.1.0" + checksum: b975e547e1e90b79625918f84e67db5d33d896e6de846c9b584094e529f0c63e2ab85ee33b9daffd05bff3a146a1916bec664e18bb76dd5f66cbff9fc13b2bbe languageName: node linkType: hard @@ -6148,7 +6141,7 @@ __metadata: languageName: node linkType: hard -"daisyui@npm:^4.4.19": +"daisyui@npm:4.5.0": version: 4.5.0 resolution: "daisyui@npm:4.5.0" dependencies: @@ -6273,7 +6266,7 @@ __metadata: languageName: node linkType: hard -"deep-object-diff@npm:^1.1.0": +"deep-object-diff@npm:^1.1.9": version: 1.1.9 resolution: "deep-object-diff@npm:1.1.9" checksum: ecd42455e4773f653595d28070295e7aaa8402db5f8ab21d0bec115a7cb4de5e207a5665514767da5f025c96597f1d3a0a4888aeb4dd49e03c996871a3aa05ef @@ -9403,17 +9396,6 @@ __metadata: languageName: node linkType: hard -"i18n-js@npm:^4.3.2": - version: 4.3.2 - resolution: "i18n-js@npm:4.3.2" - dependencies: - bignumber.js: "*" - lodash: "*" - make-plural: "*" - checksum: 08a051dba75d93447e2021c1feb92f34c9034b5a818957885ea19ac58954d764c848c4deaaf46ada3e15d51f61ad9f7e0bf4ddffa3c765871ff1b26638ca0a8f - languageName: node - linkType: hard - "iconv-lite@npm:0.4.24": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24" @@ -10730,6 +10712,13 @@ __metadata: languageName: node linkType: hard +"lodash.debounce@npm:^4.0.8": + version: 4.0.8 + resolution: "lodash.debounce@npm:4.0.8" + checksum: a3f527d22c548f43ae31c861ada88b2637eb48ac6aa3eb56e82d44917971b8aa96fbb37aa60efea674dc4ee8c42074f90f7b1f772e9db375435f6c83a19b3bc6 + languageName: node + linkType: hard + "lodash.defaults@npm:^4.2.0": version: 4.2.0 resolution: "lodash.defaults@npm:4.2.0" @@ -10814,7 +10803,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:*, lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.21": +"lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 @@ -10985,13 +10974,6 @@ __metadata: languageName: node linkType: hard -"make-plural@npm:*": - version: 7.3.0 - resolution: "make-plural@npm:7.3.0" - checksum: bb39b4b77533f0d5fb94eec128340b54dda8c1707d6b0a98c148e5d7bc23094e123f88275a61573fa31dc2f5d7352215cee0df523cd69b5d8fcb3969a2bcf8f8 - languageName: node - linkType: hard - "markdown-it-anchor@npm:^8.4.1": version: 8.6.7 resolution: "markdown-it-anchor@npm:8.6.7" @@ -11463,6 +11445,13 @@ __metadata: languageName: node linkType: hard +"modern-ahocorasick@npm:^1.0.0": + version: 1.0.1 + resolution: "modern-ahocorasick@npm:1.0.1" + checksum: ec83479f406511f37a966d66ce1c2b1701bb4a2cc2aabbbc257001178c9fbc48ce748c88eb10dfe72ba8b7f991a0bc7f1fa14683f444685edd1a9eeb32ecbc1e + languageName: node + linkType: hard + "module-error@npm:^1.0.1, module-error@npm:^1.0.2": version: 1.0.2 resolution: "module-error@npm:1.0.2" @@ -11601,6 +11590,17 @@ __metadata: languageName: node linkType: hard +"next-themes@npm:^0.2.1": + version: 0.2.1 + resolution: "next-themes@npm:0.2.1" + peerDependencies: + next: "*" + react: "*" + react-dom: "*" + checksum: ebc248b956138e73436c4ed0a0f0a877a0a48a33156db577029b8b8469e48b5c777d61abf2baeb75953378febea74e067a4869ff90b4a3e94fce123289b862ba + languageName: node + linkType: hard + "next@npm:^14.0.4": version: 14.0.4 resolution: "next@npm:14.0.4" @@ -12833,20 +12833,6 @@ __metadata: languageName: node linkType: hard -"qrcode@npm:1.5.0": - version: 1.5.0 - resolution: "qrcode@npm:1.5.0" - dependencies: - dijkstrajs: ^1.0.1 - encode-utf8: ^1.0.3 - pngjs: ^5.0.0 - yargs: ^15.3.1 - bin: - qrcode: bin/qrcode - checksum: a0857713d4390937900a2789d5a065700f7cf78cd760e773bf8524c0e907ff629db19c9bdd4210aac55b8eef53ec1c7bcaa2acf01f340ef049c53098388a45a0 - languageName: node - linkType: hard - "qrcode@npm:1.5.3, qrcode@npm:^1.5.1": version: 1.5.3 resolution: "qrcode@npm:1.5.3" @@ -12998,7 +12984,7 @@ __metadata: languageName: node linkType: hard -"react-remove-scroll-bar@npm:^2.3.3": +"react-remove-scroll-bar@npm:^2.3.4": version: 2.3.4 resolution: "react-remove-scroll-bar@npm:2.3.4" dependencies: @@ -13014,11 +13000,11 @@ __metadata: languageName: node linkType: hard -"react-remove-scroll@npm:2.5.4": - version: 2.5.4 - resolution: "react-remove-scroll@npm:2.5.4" +"react-remove-scroll@npm:2.5.7": + version: 2.5.7 + resolution: "react-remove-scroll@npm:2.5.7" dependencies: - react-remove-scroll-bar: ^2.3.3 + react-remove-scroll-bar: ^2.3.4 react-style-singleton: ^2.2.1 tslib: ^2.1.0 use-callback-ref: ^1.3.0 @@ -13029,7 +13015,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 01b0f65542a4c8803ee748b4e6cf2adad66d034e15fb72e8455773b0d7b178ec806b3194d74f412db7064670c45552cc666c04e9fb3b5d466dce5fb48e634825 + checksum: e0dbb6856beaed2cff4996d9ca62d775686ff72e3e9de34043034d932223b588993b2fc7a18644750dd3d73eb19bd3f2cedb8d91f0e424c1ef8403010da24b1d languageName: node linkType: hard @@ -15220,7 +15206,7 @@ __metadata: languageName: node linkType: hard -"ua-parser-js@npm:^1.0.35": +"ua-parser-js@npm:^1.0.37": version: 1.0.37 resolution: "ua-parser-js@npm:1.0.37" checksum: 4d481c720d523366d7762dc8a46a1b58967d979aacf786f9ceceb1cd767de069f64a4bdffb63956294f1c0696eb465ddb950f28ba90571709e33521b4bd75e07 @@ -15518,23 +15504,14 @@ __metadata: languageName: node linkType: hard -"usehooks-ts@npm:^2.7.2": - version: 2.9.1 - resolution: "usehooks-ts@npm:2.9.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 36f1e4142ce23bc019b81d2e93aefd7f2c350abcf255598c21627114a69a2f2f116b35dc3a353375f09c6e4c9b704a04f104e3d10e98280545c097feca66c30a - languageName: node - linkType: hard - -"usehooks-ts@patch:usehooks-ts@npm:^2.7.2#./.yarn/patches/usehooks-ts-npm-2.7.2-fceffe0e43.patch::locator=se-2%40workspace%3A.": - version: 2.9.1 - resolution: "usehooks-ts@patch:usehooks-ts@npm%3A2.9.1#./.yarn/patches/usehooks-ts-npm-2.7.2-fceffe0e43.patch::version=2.9.1&hash=68bde7&locator=se-2%40workspace%3A." +"usehooks-ts@npm:^2.13.0": + version: 2.13.0 + resolution: "usehooks-ts@npm:2.13.0" + dependencies: + lodash.debounce: ^4.0.8 peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 6807ae0e6ffd158790e5018d3a322efd0fe97853b6b83751725e5e81d0962cb6230ca65a24341bb5cee3f1bc330721611440c399fc950723153c5cc61eb74b04 + react: ^16.8.0 || ^17 || ^18 + checksum: ad07930e1b5c70392603eb8b3f199f44349c75406fe31013f79b0fb7fdece59f47f8dba09b6f1fafaa00d68f43240dbb13cdc1afb89b647f1d53504599a51ca0 languageName: node linkType: hard