diff --git a/.changeset/hip-vans-sing.md b/.changeset/hip-vans-sing.md
new file mode 100644
index 000000000..b04c3415d
--- /dev/null
+++ b/.changeset/hip-vans-sing.md
@@ -0,0 +1,8 @@
+---
+"create-eth": patch
+---
+
+- App router migration (#535)
+- Update hardhat package plugins and add hardhat-verify (#637)
+- Update homepage file route and add Viem to README tech stack (#691)
+- Ethers v6 migration in hardhat (#692)
diff --git a/README.md b/README.md
index eab5a1184..9d6fa070f 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
๐งช An open-source, up-to-date toolkit for building decentralized applications (dapps) on the Ethereum blockchain. It's designed to make it easier for developers to create and deploy smart contracts and build user interfaces that interact with those contracts.
-โ๏ธ Built using NextJS, RainbowKit, Hardhat, Foundry, Wagmi, and Typescript.
+โ๏ธ Built using NextJS, RainbowKit, Hardhat, Wagmi, Viem, 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.
@@ -24,7 +24,7 @@
Before you begin, you need to install the following tools:
-- [Node (v18 LTS)](https://nodejs.org/en/download/)
+- [Node (>= v18.17)](https://nodejs.org/en/download/)
- Yarn ([v1](https://classic.yarnpkg.com/en/docs/install/) or [v2+](https://yarnpkg.com/getting-started/install))
- [Git](https://git-scm.com/downloads)
diff --git a/templates/base/.github/workflows/lint.yaml b/templates/base/.github/workflows/lint.yaml
index b8f6c324a..9938cae59 100644
--- a/templates/base/.github/workflows/lint.yaml
+++ b/templates/base/.github/workflows/lint.yaml
@@ -15,7 +15,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
- node: [16.x]
+ node: [lts/*]
steps:
- name: Checkout
@@ -25,13 +25,13 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- cache : yarn
+ cache: yarn
- name: Install dependencies
run: yarn install --immutable
- name: Run hardhat node, deploy contracts (& generate contracts typescript output)
- run: yarn chain & yarn deploy
+ run: yarn chain & yarn deploy
- name: Run nextjs lint
run: yarn next:lint --max-warnings=0
diff --git a/templates/base/.husky/pre-commit b/templates/base/.husky/pre-commit
index 9c6495440..44d21ba2f 100755
--- a/templates/base/.husky/pre-commit
+++ b/templates/base/.husky/pre-commit
@@ -1,4 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
-yarn lint-staged --verbose
+yarn lint-staged --verbose
\ No newline at end of file
diff --git a/templates/base/README.md b/templates/base/README.md
index 542c0f48b..7c1ad0085 100644
--- a/templates/base/README.md
+++ b/templates/base/README.md
@@ -2,7 +2,7 @@
๐งช An open-source, up-to-date toolkit for building decentralized applications (dapps) on the Ethereum blockchain. It's designed to make it easier for developers to create and deploy smart contracts and build user interfaces that interact with those contracts.
-โ๏ธ Built using NextJS, RainbowKit, Hardhat, Wagmi, and Typescript.
+โ๏ธ Built using NextJS, RainbowKit, Hardhat, Wagmi, Viem, and Typescript.
- โ
**Contract Hot Reload**: Your frontend auto-adapts to your smart contract as you edit it.
- ๐ฅ **Burner Wallet & Local Faucet**: Quickly test your application with a burner wallet and local faucet.
@@ -114,6 +114,14 @@ You can verify your smart contract on Etherscan by running:
yarn verify --network network_name
```
+eg: `yarn verify --network sepolia`
+
+This uses [etherscan-verify from hardhat-deploy](https://www.npmjs.com/package/hardhat-deploy#4-hardhat-etherscan-verify) to verify all the deployed contracts.
+
+You can alternatively use [hardhat-verify](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify) to verify your contracts, passing network name, contract address and constructor arguments (if any): `yarn hardhat-verify --network network_name contract_address "Constructor arg 1"`
+
+If the chain you're using is not supported by any of the verifying methods, you can add new supported chains to your chosen method, either [etherscan-verify](https://www.npmjs.com/package/hardhat-deploy#options-2) or [hardhat-verify](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify#adding-support-for-other-networks).
+
## Deploying your NextJS App
**Hint**: We recommend connecting your GitHub repo to Vercel (through the Vercel UI) so it gets automatically deployed when pushing to `main`.
diff --git a/templates/base/packages/nextjs/components/blockexplorer/AddressCodeTab.tsx b/templates/base/packages/nextjs/app/blockexplorer/_components/AddressCodeTab.tsx
similarity index 100%
rename from templates/base/packages/nextjs/components/blockexplorer/AddressCodeTab.tsx
rename to templates/base/packages/nextjs/app/blockexplorer/_components/AddressCodeTab.tsx
diff --git a/templates/base/packages/nextjs/app/blockexplorer/_components/AddressComponent.tsx b/templates/base/packages/nextjs/app/blockexplorer/_components/AddressComponent.tsx
new file mode 100644
index 000000000..c0c14d60e
--- /dev/null
+++ b/templates/base/packages/nextjs/app/blockexplorer/_components/AddressComponent.tsx
@@ -0,0 +1,35 @@
+import { BackButton } from "./BackButton";
+import { ContractTabs } from "./ContractTabs";
+import { Address, Balance } from "~~/components/scaffold-eth";
+
+export const AddressComponent = ({
+ address,
+ contractData,
+}: {
+ address: string;
+ contractData: { bytecode: string; assembly: string } | null;
+}) => {
+ return (
+
+ );
+};
diff --git a/templates/base/packages/nextjs/components/blockexplorer/AddressLogsTab.tsx b/templates/base/packages/nextjs/app/blockexplorer/_components/AddressLogsTab.tsx
similarity index 100%
rename from templates/base/packages/nextjs/components/blockexplorer/AddressLogsTab.tsx
rename to templates/base/packages/nextjs/app/blockexplorer/_components/AddressLogsTab.tsx
diff --git a/templates/base/packages/nextjs/components/blockexplorer/AddressStorageTab.tsx b/templates/base/packages/nextjs/app/blockexplorer/_components/AddressStorageTab.tsx
similarity index 99%
rename from templates/base/packages/nextjs/components/blockexplorer/AddressStorageTab.tsx
rename to templates/base/packages/nextjs/app/blockexplorer/_components/AddressStorageTab.tsx
index acc669123..86c4f216b 100644
--- a/templates/base/packages/nextjs/components/blockexplorer/AddressStorageTab.tsx
+++ b/templates/base/packages/nextjs/app/blockexplorer/_components/AddressStorageTab.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import { useEffect, useState } from "react";
import { Address, createPublicClient, http, toHex } from "viem";
import { hardhat } from "viem/chains";
diff --git a/templates/base/packages/nextjs/app/blockexplorer/_components/BackButton.tsx b/templates/base/packages/nextjs/app/blockexplorer/_components/BackButton.tsx
new file mode 100644
index 000000000..bdfde8b38
--- /dev/null
+++ b/templates/base/packages/nextjs/app/blockexplorer/_components/BackButton.tsx
@@ -0,0 +1,12 @@
+"use client";
+
+import { useRouter } from "next/navigation";
+
+export const BackButton = () => {
+ const router = useRouter();
+ return (
+ router.back()}>
+ Back
+
+ );
+};
diff --git a/templates/base/packages/nextjs/app/blockexplorer/_components/ContractTabs.tsx b/templates/base/packages/nextjs/app/blockexplorer/_components/ContractTabs.tsx
new file mode 100644
index 000000000..bb020ef2a
--- /dev/null
+++ b/templates/base/packages/nextjs/app/blockexplorer/_components/ContractTabs.tsx
@@ -0,0 +1,92 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { AddressCodeTab } from "./AddressCodeTab";
+import { AddressLogsTab } from "./AddressLogsTab";
+import { AddressStorageTab } from "./AddressStorageTab";
+import { PaginationButton } from "./PaginationButton";
+import { TransactionsTable } from "./TransactionsTable";
+import { createPublicClient, http } from "viem";
+import { hardhat } from "viem/chains";
+import { useFetchBlocks } from "~~/hooks/scaffold-eth";
+
+type AddressCodeTabProps = {
+ bytecode: string;
+ assembly: string;
+};
+
+type PageProps = {
+ address: string;
+ contractData: AddressCodeTabProps | null;
+};
+
+const publicClient = createPublicClient({
+ chain: hardhat,
+ transport: http(),
+});
+
+export const ContractTabs = ({ address, contractData }: PageProps) => {
+ const { blocks, transactionReceipts, currentPage, totalBlocks, setCurrentPage } = useFetchBlocks();
+ const [activeTab, setActiveTab] = useState("transactions");
+ const [isContract, setIsContract] = useState(false);
+
+ useEffect(() => {
+ const checkIsContract = async () => {
+ const contractCode = await publicClient.getBytecode({ address: address });
+ setIsContract(contractCode !== undefined && contractCode !== "0x");
+ };
+
+ checkIsContract();
+ }, [address]);
+
+ const filteredBlocks = blocks.filter(block =>
+ block.transactions.some(tx => {
+ if (typeof tx === "string") {
+ return false;
+ }
+ return tx.from.toLowerCase() === address.toLowerCase() || tx.to?.toLowerCase() === address.toLowerCase();
+ }),
+ );
+
+ return (
+ <>
+ {isContract && (
+
+ setActiveTab("transactions")}
+ >
+ Transactions
+
+ setActiveTab("code")}>
+ Code
+
+ setActiveTab("storage")}
+ >
+ Storage
+
+ setActiveTab("logs")}>
+ Logs
+
+
+ )}
+ {activeTab === "transactions" && (
+
+ )}
+ {activeTab === "code" && contractData && (
+
+ )}
+ {activeTab === "storage" && }
+ {activeTab === "logs" && }
+ >
+ );
+};
diff --git a/templates/base/packages/nextjs/components/blockexplorer/PaginationButton.tsx b/templates/base/packages/nextjs/app/blockexplorer/_components/PaginationButton.tsx
similarity index 100%
rename from templates/base/packages/nextjs/components/blockexplorer/PaginationButton.tsx
rename to templates/base/packages/nextjs/app/blockexplorer/_components/PaginationButton.tsx
diff --git a/templates/base/packages/nextjs/components/blockexplorer/SearchBar.tsx b/templates/base/packages/nextjs/app/blockexplorer/_components/SearchBar.tsx
similarity index 95%
rename from templates/base/packages/nextjs/components/blockexplorer/SearchBar.tsx
rename to templates/base/packages/nextjs/app/blockexplorer/_components/SearchBar.tsx
index abbf69190..82b883987 100644
--- a/templates/base/packages/nextjs/components/blockexplorer/SearchBar.tsx
+++ b/templates/base/packages/nextjs/app/blockexplorer/_components/SearchBar.tsx
@@ -1,5 +1,7 @@
+"use client";
+
import { useState } from "react";
-import { useRouter } from "next/router";
+import { useRouter } from "next/navigation";
import { isAddress, isHex } from "viem";
import { hardhat } from "viem/chains";
import { usePublicClient } from "wagmi";
diff --git a/templates/base/packages/nextjs/components/blockexplorer/TransactionHash.tsx b/templates/base/packages/nextjs/app/blockexplorer/_components/TransactionHash.tsx
similarity index 98%
rename from templates/base/packages/nextjs/components/blockexplorer/TransactionHash.tsx
rename to templates/base/packages/nextjs/app/blockexplorer/_components/TransactionHash.tsx
index 5d361516c..d4fd92955 100644
--- a/templates/base/packages/nextjs/components/blockexplorer/TransactionHash.tsx
+++ b/templates/base/packages/nextjs/app/blockexplorer/_components/TransactionHash.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import { useState } from "react";
import Link from "next/link";
import { CopyToClipboard } from "react-copy-to-clipboard";
diff --git a/templates/base/packages/nextjs/components/blockexplorer/TransactionsTable.tsx b/templates/base/packages/nextjs/app/blockexplorer/_components/TransactionsTable.tsx
similarity index 97%
rename from templates/base/packages/nextjs/components/blockexplorer/TransactionsTable.tsx
rename to templates/base/packages/nextjs/app/blockexplorer/_components/TransactionsTable.tsx
index e462dc946..b91892cb8 100644
--- a/templates/base/packages/nextjs/components/blockexplorer/TransactionsTable.tsx
+++ b/templates/base/packages/nextjs/app/blockexplorer/_components/TransactionsTable.tsx
@@ -1,5 +1,5 @@
+import { TransactionHash } from "./TransactionHash";
import { formatEther } from "viem";
-import { TransactionHash } from "~~/components/blockexplorer/TransactionHash";
import { Address } from "~~/components/scaffold-eth";
import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork";
import { TransactionWithFunction } from "~~/utils/scaffold-eth";
diff --git a/templates/base/packages/nextjs/components/blockexplorer/index.tsx b/templates/base/packages/nextjs/app/blockexplorer/_components/index.tsx
similarity index 70%
rename from templates/base/packages/nextjs/components/blockexplorer/index.tsx
rename to templates/base/packages/nextjs/app/blockexplorer/_components/index.tsx
index bc0a716bc..20d8eb2f4 100644
--- a/templates/base/packages/nextjs/components/blockexplorer/index.tsx
+++ b/templates/base/packages/nextjs/app/blockexplorer/_components/index.tsx
@@ -1,7 +1,7 @@
-export * from "./AddressCodeTab";
-export * from "./AddressLogsTab";
-export * from "./AddressStorageTab";
-export * from "./PaginationButton";
export * from "./SearchBar";
+export * from "./BackButton";
+export * from "./AddressCodeTab";
export * from "./TransactionHash";
+export * from "./ContractTabs";
+export * from "./PaginationButton";
export * from "./TransactionsTable";
diff --git a/templates/base/packages/nextjs/app/blockexplorer/address/[address]/page.tsx.template.mjs b/templates/base/packages/nextjs/app/blockexplorer/address/[address]/page.tsx.template.mjs
new file mode 100644
index 000000000..fb1d35111
--- /dev/null
+++ b/templates/base/packages/nextjs/app/blockexplorer/address/[address]/page.tsx.template.mjs
@@ -0,0 +1,93 @@
+import { withDefaults } from "../../../../../../../utils.js";
+
+const contents = ({ chainName, artifactsDirName }) => `
+import fs from "fs";
+import path from "path";
+import { ${chainName[0]} } from "viem/chains";
+import { AddressComponent } from "~~/app/blockexplorer/_components/AddressComponent";
+import deployedContracts from "~~/contracts/deployedContracts";
+import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract";
+
+type PageProps = {
+ params: { address: string };
+};
+
+async function fetchByteCodeAndAssembly(buildInfoDirectory: string, contractPath: string) {
+ const buildInfoFiles = fs.readdirSync(buildInfoDirectory);
+ let bytecode = "";
+ let assembly = "";
+
+ for (let i = 0; i < buildInfoFiles.length; i++) {
+ const filePath = path.join(buildInfoDirectory, buildInfoFiles[i]);
+
+ const buildInfo = JSON.parse(fs.readFileSync(filePath, "utf8"));
+
+ if (buildInfo.output.contracts[contractPath]) {
+ for (const contract in buildInfo.output.contracts[contractPath]) {
+ bytecode = buildInfo.output.contracts[contractPath][contract].evm.bytecode.object;
+ assembly = buildInfo.output.contracts[contractPath][contract].evm.bytecode.opcodes;
+ break;
+ }
+ }
+
+ if (bytecode && assembly) {
+ break;
+ }
+ }
+
+ return { bytecode, assembly };
+}
+
+const getContractData = async (address: string) => {
+ const contracts = deployedContracts as GenericContractsDeclaration | null;
+ const chainId = ${chainName[0]}.id;
+ let contractPath = "";
+
+ const buildInfoDirectory = path.join(
+ __dirname,
+ "..",
+ "..",
+ "..",
+ "..",
+ "..",
+ "..",
+ "..",
+ "${chainName[0]}",
+ "${artifactsDirName[0]}",
+ "build-info",
+ );
+
+ if (!fs.existsSync(buildInfoDirectory)) {
+ throw new Error(\`Directory \${buildInfoDirectory} not found.\`);
+ }
+
+ const deployedContractsOnChain = contracts ? contracts[chainId] : {};
+ for (const [contractName, contractInfo] of Object.entries(deployedContractsOnChain)) {
+ if (contractInfo.address.toLowerCase() === address) {
+ contractPath = \`contracts/\${contractName}.sol\`;
+ break;
+ }
+ }
+
+ if (!contractPath) {
+ // No contract found at this address
+ return null;
+ }
+
+ const { bytecode, assembly } = await fetchByteCodeAndAssembly(buildInfoDirectory, contractPath);
+
+ return { bytecode, assembly };
+};
+
+const AddressPage = async ({ params }: PageProps) => {
+ const address = params?.address as string;
+ const contractData: { bytecode: string; assembly: string } | null = await getContractData(address);
+ return ;
+};
+
+export default AddressPage;`;
+
+export default withDefaults(contents, {
+ chainName: "hardhat",
+ artifactsDirName: "artifacts",
+});
diff --git a/templates/base/packages/nextjs/app/blockexplorer/layout.tsx b/templates/base/packages/nextjs/app/blockexplorer/layout.tsx
new file mode 100644
index 000000000..1abc7ec62
--- /dev/null
+++ b/templates/base/packages/nextjs/app/blockexplorer/layout.tsx
@@ -0,0 +1,12 @@
+import { getMetadata } from "~~/utils/scaffold-eth/getMetadata";
+
+export const metadata = getMetadata({
+ title: "Block Explorer",
+ description: "Block Explorer created with ๐ Scaffold-ETH 2",
+});
+
+const BlockExplorerLayout = ({ children }: { children: React.ReactNode }) => {
+ return <>{children}>;
+};
+
+export default BlockExplorerLayout;
diff --git a/templates/base/packages/nextjs/pages/blockexplorer/index.tsx b/templates/base/packages/nextjs/app/blockexplorer/page.tsx
similarity index 91%
rename from templates/base/packages/nextjs/pages/blockexplorer/index.tsx
rename to templates/base/packages/nextjs/app/blockexplorer/page.tsx
index 2ee6487b8..80f481cac 100644
--- a/templates/base/packages/nextjs/pages/blockexplorer/index.tsx
+++ b/templates/base/packages/nextjs/app/blockexplorer/page.tsx
@@ -1,9 +1,9 @@
+"use client";
+
import { useEffect } from "react";
+import { PaginationButton, SearchBar, TransactionsTable } from "./_components";
import type { NextPage } from "next";
import { hardhat } from "viem/chains";
-import { PaginationButton } from "~~/components/blockexplorer/PaginationButton";
-import { SearchBar } from "~~/components/blockexplorer/SearchBar";
-import { TransactionsTable } from "~~/components/blockexplorer/TransactionsTable";
import { useFetchBlocks } from "~~/hooks/scaffold-eth";
import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork";
import { notification } from "~~/utils/scaffold-eth";
diff --git a/templates/base/packages/nextjs/pages/blockexplorer/transaction/[txHash].tsx b/templates/base/packages/nextjs/app/blockexplorer/transaction/[txHash]/page.tsx
similarity index 95%
rename from templates/base/packages/nextjs/pages/blockexplorer/transaction/[txHash].tsx
rename to templates/base/packages/nextjs/app/blockexplorer/transaction/[txHash]/page.tsx
index ab3bde78b..d9c45f557 100644
--- a/templates/base/packages/nextjs/pages/blockexplorer/transaction/[txHash].tsx
+++ b/templates/base/packages/nextjs/app/blockexplorer/transaction/[txHash]/page.tsx
@@ -1,5 +1,7 @@
+"use client";
+
import { useEffect, useState } from "react";
-import { useRouter } from "next/router";
+import { useRouter } from "next/navigation";
import type { NextPage } from "next";
import { Hash, Transaction, TransactionReceipt, formatEther, formatUnits } from "viem";
import { hardhat } from "viem/chains";
@@ -9,11 +11,13 @@ import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork";
import { decodeTransactionData, getFunctionDetails } from "~~/utils/scaffold-eth";
import { replacer } from "~~/utils/scaffold-eth/common";
-const TransactionPage: NextPage = () => {
+type PageProps = {
+ params: { txHash?: Hash };
+};
+const TransactionPage: NextPage = ({ params }: PageProps) => {
const client = usePublicClient({ chainId: hardhat.id });
-
+ const txHash = params?.txHash as Hash;
const router = useRouter();
- const { txHash } = router.query as { txHash?: Hash };
const [transaction, setTransaction] = useState();
const [receipt, setReceipt] = useState();
const [functionCalled, setFunctionCalled] = useState();
diff --git a/templates/base/packages/nextjs/app/debug/_components/DebugContracts.tsx b/templates/base/packages/nextjs/app/debug/_components/DebugContracts.tsx
new file mode 100644
index 000000000..195246ca0
--- /dev/null
+++ b/templates/base/packages/nextjs/app/debug/_components/DebugContracts.tsx
@@ -0,0 +1,65 @@
+"use client";
+
+import { useEffect } from "react";
+import { useLocalStorage } from "usehooks-ts";
+import { BarsArrowUpIcon } from "@heroicons/react/20/solid";
+import { ContractUI } from "~~/app/debug/_components/contract";
+import { ContractName } from "~~/utils/scaffold-eth/contract";
+import { getAllContracts } from "~~/utils/scaffold-eth/contractsData";
+
+const selectedContractStorageKey = "scaffoldEth2.selectedContract";
+const contractsData = getAllContracts();
+const contractNames = Object.keys(contractsData) as ContractName[];
+
+export function DebugContracts() {
+ const [selectedContract, setSelectedContract] = useLocalStorage(
+ selectedContractStorageKey,
+ contractNames[0],
+ );
+
+ useEffect(() => {
+ if (!contractNames.includes(selectedContract)) {
+ setSelectedContract(contractNames[0]);
+ }
+ }, [selectedContract, setSelectedContract]);
+
+ return (
+
+ {contractNames.length === 0 ? (
+
No contracts found!
+ ) : (
+ <>
+ {contractNames.length > 1 && (
+
+ {contractNames.map(contractName => (
+ setSelectedContract(contractName)}
+ >
+ {contractName}
+ {contractsData[contractName].external && (
+
+
+
+ )}
+
+ ))}
+
+ )}
+ {contractNames.map(contractName => (
+
+ ))}
+ >
+ )}
+
+ );
+}
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractInput.tsx b/templates/base/packages/nextjs/app/debug/_components/contract/ContractInput.tsx
similarity index 98%
rename from templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractInput.tsx
rename to templates/base/packages/nextjs/app/debug/_components/contract/ContractInput.tsx
index 396f4a9e3..e27c56ee4 100644
--- a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractInput.tsx
+++ b/templates/base/packages/nextjs/app/debug/_components/contract/ContractInput.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import { Dispatch, SetStateAction } from "react";
import { AbiParameter } from "abitype";
import {
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractReadMethods.tsx b/templates/base/packages/nextjs/app/debug/_components/contract/ContractReadMethods.tsx
similarity index 94%
rename from templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractReadMethods.tsx
rename to templates/base/packages/nextjs/app/debug/_components/contract/ContractReadMethods.tsx
index 451a65846..f269fa9f9 100644
--- a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractReadMethods.tsx
+++ b/templates/base/packages/nextjs/app/debug/_components/contract/ContractReadMethods.tsx
@@ -1,5 +1,5 @@
-import { ReadOnlyFunctionForm } from "./ReadOnlyFunctionForm";
import { Abi, AbiFunction } from "abitype";
+import { ReadOnlyFunctionForm } from "~~/app/debug/_components/contract";
import { Contract, ContractName, GenericContract, InheritedFunctions } from "~~/utils/scaffold-eth/contract";
export const ContractReadMethods = ({ deployedContractData }: { deployedContractData: Contract }) => {
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractUI.tsx b/templates/base/packages/nextjs/app/debug/_components/contract/ContractUI.tsx
similarity index 99%
rename from templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractUI.tsx
rename to templates/base/packages/nextjs/app/debug/_components/contract/ContractUI.tsx
index 7127e869a..49f0f2ee1 100644
--- a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractUI.tsx
+++ b/templates/base/packages/nextjs/app/debug/_components/contract/ContractUI.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import { useReducer } from "react";
import { ContractReadMethods } from "./ContractReadMethods";
import { ContractVariables } from "./ContractVariables";
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractVariables.tsx b/templates/base/packages/nextjs/app/debug/_components/contract/ContractVariables.tsx
similarity index 100%
rename from templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractVariables.tsx
rename to templates/base/packages/nextjs/app/debug/_components/contract/ContractVariables.tsx
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractWriteMethods.tsx b/templates/base/packages/nextjs/app/debug/_components/contract/ContractWriteMethods.tsx
similarity index 94%
rename from templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractWriteMethods.tsx
rename to templates/base/packages/nextjs/app/debug/_components/contract/ContractWriteMethods.tsx
index 47308d252..ee703a67c 100644
--- a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ContractWriteMethods.tsx
+++ b/templates/base/packages/nextjs/app/debug/_components/contract/ContractWriteMethods.tsx
@@ -1,5 +1,5 @@
-import { WriteOnlyFunctionForm } from "./WriteOnlyFunctionForm";
import { Abi, AbiFunction } from "abitype";
+import { WriteOnlyFunctionForm } from "~~/app/debug/_components/contract";
import { Contract, ContractName, GenericContract, InheritedFunctions } from "~~/utils/scaffold-eth/contract";
export const ContractWriteMethods = ({
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/DisplayVariable.tsx b/templates/base/packages/nextjs/app/debug/_components/contract/DisplayVariable.tsx
similarity index 96%
rename from templates/base/packages/nextjs/components/scaffold-eth/Contract/DisplayVariable.tsx
rename to templates/base/packages/nextjs/app/debug/_components/contract/DisplayVariable.tsx
index 9e4096c7f..805593d05 100644
--- a/templates/base/packages/nextjs/components/scaffold-eth/Contract/DisplayVariable.tsx
+++ b/templates/base/packages/nextjs/app/debug/_components/contract/DisplayVariable.tsx
@@ -1,10 +1,12 @@
+"use client";
+
import { useEffect } from "react";
import { InheritanceTooltip } from "./InheritanceTooltip";
+import { displayTxResult } from "./utilsDisplay";
import { Abi, AbiFunction } from "abitype";
import { Address } from "viem";
import { useContractRead } from "wagmi";
import { ArrowPathIcon } from "@heroicons/react/24/outline";
-import { displayTxResult } from "~~/components/scaffold-eth";
import { useAnimationConfig } from "~~/hooks/scaffold-eth";
import { notification } from "~~/utils/scaffold-eth";
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/InheritanceTooltip.tsx b/templates/base/packages/nextjs/app/debug/_components/contract/InheritanceTooltip.tsx
similarity index 100%
rename from templates/base/packages/nextjs/components/scaffold-eth/Contract/InheritanceTooltip.tsx
rename to templates/base/packages/nextjs/app/debug/_components/contract/InheritanceTooltip.tsx
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ReadOnlyFunctionForm.tsx b/templates/base/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx
similarity index 95%
rename from templates/base/packages/nextjs/components/scaffold-eth/Contract/ReadOnlyFunctionForm.tsx
rename to templates/base/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx
index 69fab37a1..42013eddf 100644
--- a/templates/base/packages/nextjs/components/scaffold-eth/Contract/ReadOnlyFunctionForm.tsx
+++ b/templates/base/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import { useState } from "react";
import { InheritanceTooltip } from "./InheritanceTooltip";
import { Abi, AbiFunction } from "abitype";
@@ -9,9 +11,8 @@ import {
getFunctionInputKey,
getInitialFormState,
getParsedContractFunctionArgs,
- getParsedError,
-} from "~~/components/scaffold-eth";
-import { notification } from "~~/utils/scaffold-eth";
+} from "~~/app/debug/_components/contract";
+import { getParsedError, notification } from "~~/utils/scaffold-eth";
type ReadOnlyFunctionFormProps = {
contractAddress: Address;
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/TxReceipt.tsx b/templates/base/packages/nextjs/app/debug/_components/contract/TxReceipt.tsx
similarity index 96%
rename from templates/base/packages/nextjs/components/scaffold-eth/Contract/TxReceipt.tsx
rename to templates/base/packages/nextjs/app/debug/_components/contract/TxReceipt.tsx
index 6308ed277..87e74f5b5 100644
--- a/templates/base/packages/nextjs/components/scaffold-eth/Contract/TxReceipt.tsx
+++ b/templates/base/packages/nextjs/app/debug/_components/contract/TxReceipt.tsx
@@ -2,7 +2,7 @@ import { useState } from "react";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { TransactionReceipt } from "viem";
import { CheckCircleIcon, DocumentDuplicateIcon } from "@heroicons/react/24/outline";
-import { displayTxResult } from "~~/components/scaffold-eth";
+import { displayTxResult } from "~~/app/debug/_components/contract";
export const TxReceipt = (
txResult: string | number | bigint | Record | TransactionReceipt | undefined,
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/WriteOnlyFunctionForm.tsx b/templates/base/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx
similarity index 95%
rename from templates/base/packages/nextjs/components/scaffold-eth/Contract/WriteOnlyFunctionForm.tsx
rename to templates/base/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx
index b3b09aff0..d7135018e 100644
--- a/templates/base/packages/nextjs/components/scaffold-eth/Contract/WriteOnlyFunctionForm.tsx
+++ b/templates/base/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import { useEffect, useState } from "react";
import { InheritanceTooltip } from "./InheritanceTooltip";
import { Abi, AbiFunction } from "abitype";
@@ -5,16 +7,15 @@ import { Address, TransactionReceipt } from "viem";
import { useContractWrite, useNetwork, useWaitForTransaction } from "wagmi";
import {
ContractInput,
- IntegerInput,
TxReceipt,
getFunctionInputKey,
getInitialFormState,
getParsedContractFunctionArgs,
- getParsedError,
-} from "~~/components/scaffold-eth";
+} from "~~/app/debug/_components/contract";
+import { IntegerInput } from "~~/components/scaffold-eth";
import { useTransactor } from "~~/hooks/scaffold-eth";
import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork";
-import { notification } from "~~/utils/scaffold-eth";
+import { getParsedError, notification } from "~~/utils/scaffold-eth";
type WriteOnlyFunctionFormProps = {
abi: Abi;
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/index.tsx b/templates/base/packages/nextjs/app/debug/_components/contract/index.tsx
similarity index 100%
rename from templates/base/packages/nextjs/components/scaffold-eth/Contract/index.tsx
rename to templates/base/packages/nextjs/app/debug/_components/contract/index.tsx
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/utilsContract.tsx b/templates/base/packages/nextjs/app/debug/_components/contract/utilsContract.tsx
similarity index 61%
rename from templates/base/packages/nextjs/components/scaffold-eth/Contract/utilsContract.tsx
rename to templates/base/packages/nextjs/app/debug/_components/contract/utilsContract.tsx
index 92f89e742..ad0b2569d 100644
--- a/templates/base/packages/nextjs/components/scaffold-eth/Contract/utilsContract.tsx
+++ b/templates/base/packages/nextjs/app/debug/_components/contract/utilsContract.tsx
@@ -1,5 +1,4 @@
import { AbiFunction, AbiParameter } from "abitype";
-import { BaseError as BaseViemError, DecodeErrorResultReturnType } from "viem";
/**
* Generates a key based on function metadata
@@ -9,36 +8,6 @@ const getFunctionInputKey = (functionName: string, input: AbiParameter, inputInd
return functionName + "_" + name + "_" + input.internalType + "_" + input.type;
};
-/**
- * Parses an viem/wagmi error to get a displayable string
- * @param e - error object
- * @returns parsed error string
- */
-const getParsedError = (e: any): string => {
- let message: string = e.message ?? "An unknown error occurred";
- if (e instanceof BaseViemError) {
- if (e.details) {
- message = e.details;
- } else if (e.shortMessage) {
- message = e.shortMessage;
- const cause = e.cause as { data?: DecodeErrorResultReturnType } | undefined;
- // if its not generic error, append custom error name and its args to message
- if (cause?.data && cause.data?.abiItem?.name !== "Error") {
- const customErrorArgs = cause.data.args?.toString() ?? "";
- message = `${message.replace(/reverted\.$/, "reverted with following reason:")}\n${
- cause.data.errorName
- }(${customErrorArgs})`;
- }
- } else if (e.message) {
- message = e.message;
- } else if (e.name) {
- message = e.name;
- }
- }
-
- return message;
-};
-
// This regex is used to identify array types in the form of `type[size]`
const ARRAY_TYPE_REGEX = /\[.*\]$/;
@@ -80,4 +49,4 @@ const getInitialFormState = (abiFunction: AbiFunction) => {
return initialForm;
};
-export { getFunctionInputKey, getInitialFormState, getParsedContractFunctionArgs, getParsedError };
+export { getFunctionInputKey, getInitialFormState, getParsedContractFunctionArgs };
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Contract/utilsDisplay.tsx b/templates/base/packages/nextjs/app/debug/_components/contract/utilsDisplay.tsx
similarity index 100%
rename from templates/base/packages/nextjs/components/scaffold-eth/Contract/utilsDisplay.tsx
rename to templates/base/packages/nextjs/app/debug/_components/contract/utilsDisplay.tsx
diff --git a/templates/base/packages/nextjs/app/debug/page.tsx b/templates/base/packages/nextjs/app/debug/page.tsx
new file mode 100644
index 000000000..e6fb89f50
--- /dev/null
+++ b/templates/base/packages/nextjs/app/debug/page.tsx
@@ -0,0 +1,28 @@
+import { DebugContracts } from "./_components/DebugContracts";
+import type { NextPage } from "next";
+import { getMetadata } from "~~/utils/scaffold-eth/getMetadata";
+
+export const metadata = getMetadata({
+ title: "Debug Contracts",
+ description: "Debug your deployed ๐ Scaffold-ETH 2 contracts in an easy way",
+});
+
+const Debug: NextPage = () => {
+ return (
+ <>
+
+
+
Debug Contracts
+
+ You can debug & interact with your deployed contracts here.
+ Check{" "}
+
+ packages / nextjs / app / debug / page.tsx
+
{" "}
+
+
+ >
+ );
+};
+
+export default Debug;
diff --git a/templates/base/packages/nextjs/app/layout.tsx b/templates/base/packages/nextjs/app/layout.tsx
new file mode 100644
index 000000000..824f4f3fb
--- /dev/null
+++ b/templates/base/packages/nextjs/app/layout.tsx
@@ -0,0 +1,54 @@
+import "@rainbow-me/rainbowkit/styles.css";
+import { Metadata } from "next";
+import { ScaffoldEthAppWithProviders } from "~~/components/ScaffoldEthAppWithProviders";
+import "~~/styles/globals.css";
+
+const baseUrl = process.env.NEXT_PUBLIC_VERCEL_URL
+ ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`
+ : `http://localhost:${process.env.PORT}`;
+const imageUrl = `${baseUrl}/thumbnail.jpg`;
+
+export const metadata: Metadata = {
+ metadataBase: new URL(baseUrl),
+ title: {
+ default: "Scaffold-ETH 2 App",
+ template: "%s | Scaffold-ETH 2",
+ },
+ description: "Built with ๐ Scaffold-ETH 2",
+ openGraph: {
+ title: {
+ default: "Scaffold-ETH 2 App",
+ template: "%s | Scaffold-ETH 2",
+ },
+ description: "Built with ๐ Scaffold-ETH 2",
+ images: [
+ {
+ url: imageUrl,
+ },
+ ],
+ },
+ twitter: {
+ card: "summary_large_image",
+ images: [imageUrl],
+ title: {
+ default: "Scaffold-ETH 2",
+ template: "%s | Scaffold-ETH 2",
+ },
+ description: "Built with ๐ Scaffold-ETH 2",
+ },
+ icons: {
+ icon: [{ url: "/favicon.png", sizes: "32x32", type: "image/png" }],
+ },
+};
+
+const ScaffoldEthApp = ({ children }: { children: React.ReactNode }) => {
+ return (
+
+
+ {children}
+
+
+ );
+};
+
+export default ScaffoldEthApp;
diff --git a/templates/base/packages/nextjs/pages/index.tsx b/templates/base/packages/nextjs/app/page.tsx
similarity index 95%
rename from templates/base/packages/nextjs/pages/index.tsx
rename to templates/base/packages/nextjs/app/page.tsx
index 975041722..035b3314c 100644
--- a/templates/base/packages/nextjs/pages/index.tsx
+++ b/templates/base/packages/nextjs/app/page.tsx
@@ -1,12 +1,10 @@
import Link from "next/link";
import type { NextPage } from "next";
import { BugAntIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
-import { MetaHeader } from "~~/components/MetaHeader";
const Home: NextPage = () => {
return (
<>
-
@@ -16,7 +14,7 @@ const Home: NextPage = () => {
Get started by editing{" "}
- packages/nextjs/pages/index.tsx
+ packages/nextjs/app/page.tsx
diff --git a/templates/base/packages/nextjs/components/Header.tsx b/templates/base/packages/nextjs/components/Header.tsx
index 4a5d86b15..f24a1de5f 100644
--- a/templates/base/packages/nextjs/components/Header.tsx
+++ b/templates/base/packages/nextjs/components/Header.tsx
@@ -1,7 +1,9 @@
+"use client";
+
import React, { useCallback, useRef, useState } from "react";
import Image from "next/image";
import Link from "next/link";
-import { useRouter } from "next/router";
+import { usePathname } from "next/navigation";
import { Bars3Icon, BugAntIcon } from "@heroicons/react/24/outline";
import { FaucetButton, RainbowKitCustomConnectButton } from "~~/components/scaffold-eth";
import { useOutsideClick } from "~~/hooks/scaffold-eth";
@@ -25,12 +27,12 @@ export const menuLinks: HeaderMenuLink[] = [
];
export const HeaderMenuLinks = () => {
- const router = useRouter();
+ const pathname = usePathname();
return (
<>
{menuLinks.map(({ label, href, icon }) => {
- const isActive = router.pathname === href;
+ const isActive = pathname === href;
return (
{
- const imageUrl = baseUrl + image;
-
- return (
-
- {title && (
- <>
- {title}
-
-
- >
- )}
- {description && (
- <>
-
-
-
- >
- )}
- {image && (
- <>
-
-
- >
- )}
- {twitterCard && }
-
- {children}
-
- );
-};
diff --git a/templates/base/packages/nextjs/pages/_app.tsx b/templates/base/packages/nextjs/components/ScaffoldEthAppWithProviders.tsx
similarity index 58%
rename from templates/base/packages/nextjs/pages/_app.tsx
rename to templates/base/packages/nextjs/components/ScaffoldEthAppWithProviders.tsx
index 86e4fd08f..1eb3bd3a9 100644
--- a/templates/base/packages/nextjs/pages/_app.tsx
+++ b/templates/base/packages/nextjs/components/ScaffoldEthAppWithProviders.tsx
@@ -1,21 +1,20 @@
-import { useEffect, useState } from "react";
-import type { AppProps } from "next/app";
+"use client";
+
+import { useEffect } from "react";
import { RainbowKitProvider, darkTheme, lightTheme } from "@rainbow-me/rainbowkit";
-import "@rainbow-me/rainbowkit/styles.css";
-import NextNProgress from "nextjs-progressbar";
import { Toaster } from "react-hot-toast";
-import { useDarkMode } from "usehooks-ts";
import { WagmiConfig } from "wagmi";
import { Footer } from "~~/components/Footer";
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";
-import "~~/styles/globals.css";
-const ScaffoldEthApp = ({ Component, pageProps }: AppProps) => {
+const ScaffoldEthApp = ({ children }: { children: React.ReactNode }) => {
const price = useNativeCurrencyPrice();
const setNativeCurrencyPrice = useGlobalState(state => state.setNativeCurrencyPrice);
@@ -29,9 +28,7 @@ const ScaffoldEthApp = ({ Component, pageProps }: AppProps) => {
<>
-
-
-
+ {children}
@@ -39,26 +36,19 @@ const ScaffoldEthApp = ({ Component, pageProps }: AppProps) => {
);
};
-const ScaffoldEthAppWithProviders = (props: AppProps) => {
- // This variable is required for initial client side rendering of correct theme for RainbowKit
- const [isDarkTheme, setIsDarkTheme] = useState(true);
+export const ScaffoldEthAppWithProviders = ({ children }: { children: React.ReactNode }) => {
const { isDarkMode } = useDarkMode();
- useEffect(() => {
- setIsDarkTheme(isDarkMode);
- }, [isDarkMode]);
return (
-
+
-
+ {children}
);
};
-
-export default ScaffoldEthAppWithProviders;
diff --git a/templates/base/packages/nextjs/components/SwitchTheme.tsx b/templates/base/packages/nextjs/components/SwitchTheme.tsx
index 8953326b6..b53dd6c21 100644
--- a/templates/base/packages/nextjs/components/SwitchTheme.tsx
+++ b/templates/base/packages/nextjs/components/SwitchTheme.tsx
@@ -1,6 +1,9 @@
+"use client";
+
import { useEffect } from "react";
-import { useDarkMode, useIsMounted } from "usehooks-ts";
+import { useIsMounted } from "usehooks-ts";
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();
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Address.tsx b/templates/base/packages/nextjs/components/scaffold-eth/Address.tsx
index 08c8453d3..bc8bfffa2 100644
--- a/templates/base/packages/nextjs/components/scaffold-eth/Address.tsx
+++ b/templates/base/packages/nextjs/components/scaffold-eth/Address.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import { useEffect, useState } from "react";
import Link from "next/link";
import { CopyToClipboard } from "react-copy-to-clipboard";
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Balance.tsx b/templates/base/packages/nextjs/components/scaffold-eth/Balance.tsx
index 784603a88..7811f5fd1 100644
--- a/templates/base/packages/nextjs/components/scaffold-eth/Balance.tsx
+++ b/templates/base/packages/nextjs/components/scaffold-eth/Balance.tsx
@@ -1,3 +1,6 @@
+"use client";
+
+import { useState } from "react";
import { Address } from "viem";
import { useAccountBalance } from "~~/hooks/scaffold-eth";
import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork";
@@ -5,14 +8,22 @@ import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork";
type BalanceProps = {
address?: Address;
className?: string;
+ usdMode?: boolean;
};
/**
* Display (ETH & USD) balance of an ETH address.
*/
-export const Balance = ({ address, className = "" }: BalanceProps) => {
+export const Balance = ({ address, className = "", usdMode }: BalanceProps) => {
const { targetNetwork } = useTargetNetwork();
- const { balance, price, isError, isLoading, onToggleBalance, isEthBalance } = useAccountBalance(address);
+ const { balance, price, isError, isLoading } = useAccountBalance(address);
+ const [displayUsdMode, setDisplayUsdMode] = useState(price > 0 ? Boolean(usdMode) : false);
+
+ const toggleBalanceMode = () => {
+ if (price > 0) {
+ setDisplayUsdMode(prevMode => !prevMode);
+ }
+ };
if (!address || isLoading || balance === null) {
return (
@@ -36,18 +47,18 @@ export const Balance = ({ address, className = "" }: BalanceProps) => {
return (
- {isEthBalance ? (
+ {displayUsdMode ? (
<>
- {balance?.toFixed(4)}
- {targetNetwork.nativeCurrency.symbol}
+ $
+ {(balance * price).toFixed(2)}
>
) : (
<>
- $
- {(balance * price).toFixed(2)}
+ {balance?.toFixed(4)}
+ {targetNetwork.nativeCurrency.symbol}
>
)}
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/BlockieAvatar.tsx b/templates/base/packages/nextjs/components/scaffold-eth/BlockieAvatar.tsx
index 9c1ea4d67..46f47b5af 100644
--- a/templates/base/packages/nextjs/components/scaffold-eth/BlockieAvatar.tsx
+++ b/templates/base/packages/nextjs/components/scaffold-eth/BlockieAvatar.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import { AvatarComponent } from "@rainbow-me/rainbowkit";
import { blo } from "blo";
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Faucet.tsx b/templates/base/packages/nextjs/components/scaffold-eth/Faucet.tsx
index 7589824a0..d66bcb188 100644
--- a/templates/base/packages/nextjs/components/scaffold-eth/Faucet.tsx
+++ b/templates/base/packages/nextjs/components/scaffold-eth/Faucet.tsx
@@ -1,11 +1,13 @@
+"use client";
+
import { useEffect, useState } from "react";
import { Address as AddressType, createWalletClient, http, parseEther } from "viem";
import { hardhat } from "viem/chains";
import { useNetwork } from "wagmi";
import { BanknotesIcon } from "@heroicons/react/24/outline";
-import { Address, AddressInput, Balance, EtherInput, getParsedError } from "~~/components/scaffold-eth";
+import { Address, AddressInput, Balance, EtherInput } from "~~/components/scaffold-eth";
import { useTransactor } from "~~/hooks/scaffold-eth";
-import { notification } from "~~/utils/scaffold-eth";
+import { getParsedError, notification } from "~~/utils/scaffold-eth";
// Account index to use from generated hardhat accounts.
const FAUCET_ACCOUNT_INDEX = 0;
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/FaucetButton.tsx b/templates/base/packages/nextjs/components/scaffold-eth/FaucetButton.tsx
index b9538bbe0..b582a77ec 100644
--- a/templates/base/packages/nextjs/components/scaffold-eth/FaucetButton.tsx
+++ b/templates/base/packages/nextjs/components/scaffold-eth/FaucetButton.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import { useState } from "react";
import { createWalletClient, http, parseEther } from "viem";
import { hardhat } from "viem/chains";
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Input/AddressInput.tsx b/templates/base/packages/nextjs/components/scaffold-eth/Input/AddressInput.tsx
index 4f057015a..48e64da7a 100644
--- a/templates/base/packages/nextjs/components/scaffold-eth/Input/AddressInput.tsx
+++ b/templates/base/packages/nextjs/components/scaffold-eth/Input/AddressInput.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import { useCallback, useEffect, useState } from "react";
import { blo } from "blo";
import { useDebounce } from "usehooks-ts";
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Input/EtherInput.tsx b/templates/base/packages/nextjs/components/scaffold-eth/Input/EtherInput.tsx
index 9ac76ec9a..dc774fb0b 100644
--- a/templates/base/packages/nextjs/components/scaffold-eth/Input/EtherInput.tsx
+++ b/templates/base/packages/nextjs/components/scaffold-eth/Input/EtherInput.tsx
@@ -1,4 +1,6 @@
-import { useMemo, useState } from "react";
+"use client";
+
+import { useEffect, useMemo, useState } from "react";
import { ArrowsRightLeftIcon } from "@heroicons/react/24/outline";
import { CommonInputProps, InputBase, SIGNED_NUMBER_REGEX } from "~~/components/scaffold-eth";
import { useGlobalState } from "~~/services/store/store";
@@ -43,22 +45,33 @@ function displayValueToEtherValue(usdMode: boolean, displayValue: string, native
*
* onChange will always be called with the value in ETH
*/
-export const EtherInput = ({ value, name, placeholder, onChange, disabled }: CommonInputProps) => {
+export const EtherInput = ({
+ value,
+ name,
+ placeholder,
+ onChange,
+ disabled,
+ usdMode,
+}: CommonInputProps & { usdMode?: boolean }) => {
const [transitoryDisplayValue, setTransitoryDisplayValue] = useState();
const nativeCurrencyPrice = useGlobalState(state => state.nativeCurrencyPrice);
- const [usdMode, setUSDMode] = useState(false);
+ const [internalUsdMode, setInternalUSDMode] = useState(nativeCurrencyPrice > 0 ? Boolean(usdMode) : false);
+
+ useEffect(() => {
+ setInternalUSDMode(nativeCurrencyPrice > 0 ? Boolean(usdMode) : false);
+ }, [usdMode, nativeCurrencyPrice]);
// The displayValue is derived from the ether value that is controlled outside of the component
// In usdMode, it is converted to its usd value, in regular mode it is unaltered
const displayValue = useMemo(() => {
- const newDisplayValue = etherValueToDisplayValue(usdMode, value, nativeCurrencyPrice);
+ const newDisplayValue = etherValueToDisplayValue(internalUsdMode, value, nativeCurrencyPrice);
if (transitoryDisplayValue && parseFloat(newDisplayValue) === parseFloat(transitoryDisplayValue)) {
return transitoryDisplayValue;
}
// Clear any transitory display values that might be set
setTransitoryDisplayValue(undefined);
return newDisplayValue;
- }, [nativeCurrencyPrice, transitoryDisplayValue, usdMode, value]);
+ }, [nativeCurrencyPrice, transitoryDisplayValue, internalUsdMode, value]);
const handleChangeNumber = (newValue: string) => {
if (newValue && !SIGNED_NUMBER_REGEX.test(newValue)) {
@@ -67,7 +80,7 @@ export const EtherInput = ({ value, name, placeholder, onChange, disabled }: Com
// Following condition is a fix to prevent usdMode from experiencing different display values
// than what the user entered. This can happen due to floating point rounding errors that are introduced in the back and forth conversion
- if (usdMode) {
+ if (internalUsdMode) {
const decimals = newValue.split(".")[1];
if (decimals && decimals.length > MAX_DECIMALS_USD) {
return;
@@ -82,12 +95,14 @@ export const EtherInput = ({ value, name, placeholder, onChange, disabled }: Com
setTransitoryDisplayValue(undefined);
}
- const newEthValue = displayValueToEtherValue(usdMode, newValue, nativeCurrencyPrice);
+ const newEthValue = displayValueToEtherValue(internalUsdMode, newValue, nativeCurrencyPrice);
onChange(newEthValue);
};
const toggleMode = () => {
- setUSDMode(!usdMode);
+ if (nativeCurrencyPrice > 0) {
+ setInternalUSDMode(!internalUsdMode);
+ }
};
return (
@@ -97,15 +112,24 @@ export const EtherInput = ({ value, name, placeholder, onChange, disabled }: Com
placeholder={placeholder}
onChange={handleChangeNumber}
disabled={disabled}
- prefix={{usdMode ? "$" : "ฮ"} }
+ prefix={{internalUsdMode ? "$" : "ฮ"} }
suffix={
- 0 ? "" : "hidden"}`}
- onClick={toggleMode}
- disabled={!usdMode && !nativeCurrencyPrice}
+ 0
+ ? ""
+ : "tooltip tooltip-secondary before:content-[attr(data-tip)] before:right-[-10px] before:left-auto before:transform-none"
+ }`}
+ data-tip="Unable to fetch price"
>
-
-
+
+
+
+
}
/>
);
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/Input/IntegerInput.tsx b/templates/base/packages/nextjs/components/scaffold-eth/Input/IntegerInput.tsx
index f9b436124..5d372310e 100644
--- a/templates/base/packages/nextjs/components/scaffold-eth/Input/IntegerInput.tsx
+++ b/templates/base/packages/nextjs/components/scaffold-eth/Input/IntegerInput.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import { useCallback, useEffect, useState } from "react";
import { CommonInputProps, InputBase, IntegerVariant, isValidInteger } from "~~/components/scaffold-eth";
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/ProgressBar.tsx b/templates/base/packages/nextjs/components/scaffold-eth/ProgressBar.tsx
new file mode 100644
index 000000000..9ade9bc35
--- /dev/null
+++ b/templates/base/packages/nextjs/components/scaffold-eth/ProgressBar.tsx
@@ -0,0 +1,72 @@
+"use client";
+
+import { useEffect } from "react";
+import NProgress from "nprogress";
+
+type PushStateInput = [data: any, unused: string, url?: string | URL | null | undefined];
+
+export function ProgressBar() {
+ const height = "3px";
+ const color = "#2299dd";
+
+ const styles = (
+
+ );
+
+ useEffect(() => {
+ NProgress.configure({ showSpinner: false });
+
+ const handleAnchorClick = (event: MouseEvent) => {
+ const targetUrl = (event.currentTarget as HTMLAnchorElement).href;
+ const currentUrl = location.href;
+ if (targetUrl !== currentUrl) {
+ NProgress.start();
+ }
+ };
+
+ const handleMutation: MutationCallback = () => {
+ const anchorElements = document.querySelectorAll("a");
+ anchorElements.forEach(anchor => anchor.addEventListener("click", handleAnchorClick));
+ };
+
+ const mutationObserver = new MutationObserver(handleMutation);
+ mutationObserver.observe(document, { childList: true, subtree: true });
+
+ window.history.pushState = new Proxy(window.history.pushState, {
+ apply: (target, thisArg, argArray: PushStateInput) => {
+ NProgress.done();
+ return target.apply(thisArg, argArray);
+ },
+ });
+ });
+
+ return styles;
+}
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/NetworkOptions.tsx b/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/NetworkOptions.tsx
index bd0e1bcc4..b81fe1a3e 100644
--- a/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/NetworkOptions.tsx
+++ b/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/NetworkOptions.tsx
@@ -1,7 +1,7 @@
-import { useDarkMode } from "usehooks-ts";
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();
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/index.tsx b/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/index.tsx
index 6b7dbc2c5..d0d60403e 100644
--- a/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/index.tsx
+++ b/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/index.tsx
@@ -1,3 +1,6 @@
+"use client";
+
+// @refresh reset
import { Balance } from "../Balance";
import { AddressInfoDropdown } from "./AddressInfoDropdown";
import { AddressQRCodeModal } from "./AddressQRCodeModal";
diff --git a/templates/base/packages/nextjs/components/scaffold-eth/index.tsx b/templates/base/packages/nextjs/components/scaffold-eth/index.tsx
index 5d51ad568..bf1e8a749 100644
--- a/templates/base/packages/nextjs/components/scaffold-eth/index.tsx
+++ b/templates/base/packages/nextjs/components/scaffold-eth/index.tsx
@@ -1,7 +1,6 @@
export * from "./Address";
export * from "./Balance";
export * from "./BlockieAvatar";
-export * from "./Contract";
export * from "./Faucet";
export * from "./FaucetButton";
export * from "./Input";
diff --git a/templates/base/packages/nextjs/hooks/scaffold-eth/useAutoConnect.ts b/templates/base/packages/nextjs/hooks/scaffold-eth/useAutoConnect.ts
index 2f8c75d2d..f0f9129a7 100644
--- a/templates/base/packages/nextjs/hooks/scaffold-eth/useAutoConnect.ts
+++ b/templates/base/packages/nextjs/hooks/scaffold-eth/useAutoConnect.ts
@@ -1,4 +1,3 @@
-import { useEffect } from "react";
import { useEffectOnce, useLocalStorage, useReadLocalStorage } from "usehooks-ts";
import { Chain, hardhat } from "viem/chains";
import { Connector, useAccount, useConnect } from "wagmi";
@@ -61,19 +60,15 @@ export const useAutoConnect = (): void => {
const wagmiWalletValue = useReadLocalStorage(WAGMI_WALLET_STORAGE_KEY);
const [walletId, setWalletId] = useLocalStorage(SCAFFOLD_WALLET_STORAGE_KEY, wagmiWalletValue ?? "");
const connectState = useConnect();
- const accountState = useAccount();
-
- useEffect(() => {
- if (accountState.isConnected) {
- // user is connected, set walletName
- setWalletId(accountState.connector?.id ?? "");
- } else {
- // user has disconnected, reset walletName
+ useAccount({
+ onConnect({ connector }) {
+ setWalletId(connector?.id ?? "");
+ },
+ onDisconnect() {
window.localStorage.setItem(WAGMI_WALLET_STORAGE_KEY, JSON.stringify(""));
setWalletId("");
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [accountState.isConnected, accountState.connector?.name]);
+ },
+ });
useEffectOnce(() => {
const initialConnector = getInitialConnector(getTargetNetworks()[0], walletId, connectState.connectors);
diff --git a/templates/base/packages/nextjs/hooks/scaffold-eth/useDarkMode.ts b/templates/base/packages/nextjs/hooks/scaffold-eth/useDarkMode.ts
new file mode 100644
index 000000000..c0ac96ef0
--- /dev/null
+++ b/templates/base/packages/nextjs/hooks/scaffold-eth/useDarkMode.ts
@@ -0,0 +1,43 @@
+import { useEffect, useState } from "react";
+import { useLocalStorage, useMediaQuery, useReadLocalStorage } from "usehooks-ts";
+
+const COLOR_SCHEME_QUERY = "(prefers-color-scheme: dark)";
+
+interface UseDarkModeOutput {
+ isDarkMode: boolean;
+ toggle: () => void;
+ enable: () => void;
+ disable: () => void;
+}
+
+const LOCAL_STORAGE_THEME_KEY = "usehooks-ts-dark-mode";
+
+export function useDarkMode(defaultValue?: boolean): UseDarkModeOutput {
+ const isDarkOS = useMediaQuery(COLOR_SCHEME_QUERY);
+ const [prevIsDarkOs, setPrevIsDarkOs] = useState(isDarkOS);
+
+ const initialStorageValue: boolean | null = useReadLocalStorage(LOCAL_STORAGE_THEME_KEY);
+ const [isDarkMode, setIsDarkMode] = useLocalStorage(LOCAL_STORAGE_THEME_KEY, Boolean(defaultValue));
+
+ // set if no init value
+ useEffect(() => {
+ if (initialStorageValue === null) {
+ setIsDarkMode(defaultValue || isDarkOS);
+ }
+ }, [defaultValue, isDarkOS, setIsDarkMode, initialStorageValue]);
+
+ // update on os color change
+ useEffect(() => {
+ if (isDarkOS !== prevIsDarkOs) {
+ setPrevIsDarkOs(isDarkOS);
+ setIsDarkMode(isDarkOS);
+ }
+ }, [isDarkOS, prevIsDarkOs, setIsDarkMode]);
+
+ return {
+ isDarkMode,
+ toggle: () => setIsDarkMode(prev => !prev),
+ enable: () => setIsDarkMode(true),
+ disable: () => setIsDarkMode(false),
+ };
+}
diff --git a/templates/base/packages/nextjs/hooks/scaffold-eth/useNetworkColor.ts b/templates/base/packages/nextjs/hooks/scaffold-eth/useNetworkColor.ts
index 3e7e4fbba..4f83f3f23 100644
--- a/templates/base/packages/nextjs/hooks/scaffold-eth/useNetworkColor.ts
+++ b/templates/base/packages/nextjs/hooks/scaffold-eth/useNetworkColor.ts
@@ -1,5 +1,5 @@
+import { useDarkMode } from "./useDarkMode";
import { useTargetNetwork } from "./useTargetNetwork";
-import { useDarkMode } from "usehooks-ts";
import { ChainWithAttributes } from "~~/utils/scaffold-eth";
export const DEFAULT_NETWORK_COLOR: [string, string] = ["#666666", "#bbbbbb"];
diff --git a/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldContractWrite.ts b/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldContractWrite.ts
index e1278d84d..cc9983407 100644
--- a/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldContractWrite.ts
+++ b/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldContractWrite.ts
@@ -2,9 +2,8 @@ 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 { notification } from "~~/utils/scaffold-eth";
+import { getParsedError, notification } from "~~/utils/scaffold-eth";
import { ContractAbi, ContractName, UseScaffoldWriteConfig } from "~~/utils/scaffold-eth/contract";
type UpdatedArgs = Parameters>["writeAsync"]>[0];
diff --git a/templates/base/packages/nextjs/hooks/scaffold-eth/useTransactor.tsx b/templates/base/packages/nextjs/hooks/scaffold-eth/useTransactor.tsx
index b725f344f..73a38f0ea 100644
--- a/templates/base/packages/nextjs/hooks/scaffold-eth/useTransactor.tsx
+++ b/templates/base/packages/nextjs/hooks/scaffold-eth/useTransactor.tsx
@@ -1,8 +1,7 @@
import { WriteContractResult, getPublicClient } from "@wagmi/core";
import { Hash, SendTransactionParameters, TransactionReceipt, WalletClient } from "viem";
import { useWalletClient } from "wagmi";
-import { getParsedError } from "~~/components/scaffold-eth";
-import { getBlockExplorerTxLink, notification } from "~~/utils/scaffold-eth";
+import { getBlockExplorerTxLink, getParsedError, notification } from "~~/utils/scaffold-eth";
type TransactionFunc = (
tx: (() => Promise) | (() => Promise) | SendTransactionParameters,
diff --git a/templates/base/packages/nextjs/next.config.js b/templates/base/packages/nextjs/next.config.js
index 3fb355215..d76586913 100644
--- a/templates/base/packages/nextjs/next.config.js
+++ b/templates/base/packages/nextjs/next.config.js
@@ -11,6 +11,7 @@ const nextConfig = {
},
webpack: config => {
config.resolve.fallback = { fs: false, net: false, tls: false };
+ config.externals.push("pino-pretty", "lokijs", "encoding");
return config;
},
};
diff --git a/templates/base/packages/nextjs/package.json b/templates/base/packages/nextjs/package.json
index afa10f97c..fedbaef28 100644
--- a/templates/base/packages/nextjs/package.json
+++ b/templates/base/packages/nextjs/package.json
@@ -21,8 +21,8 @@
"@uniswap/v2-sdk": "~3.0.1",
"blo": "~1.0.1",
"daisyui": "~4.4.19",
- "next": "~13.1.6",
- "nextjs-progressbar": "~0.0.16",
+ "next": "~14.0.4",
+ "nprogress": "~0.2.0",
"qrcode.react": "~3.1.0",
"react": "~18.2.0",
"react-copy-to-clipboard": "~5.1.0",
@@ -37,12 +37,13 @@
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "~4.1.1",
"@types/node": "^17.0.45",
+ "@types/nprogress": "^0",
"@types/react": "^18.0.21",
"@types/react-copy-to-clipboard": "^5.0.4",
"@typescript-eslint/eslint-plugin": "~5.40.0",
"autoprefixer": "~10.4.12",
"eslint": "~8.24.0",
- "eslint-config-next": "~13.1.6",
+ "eslint-config-next": "~14.0.4",
"eslint-config-prettier": "~8.5.0",
"eslint-plugin-prettier": "~4.2.1",
"postcss": "~8.4.16",
@@ -50,6 +51,6 @@
"tailwindcss": "~3.3.3",
"type-fest": "~4.6.0",
"typescript": "~5.1.6",
- "vercel": "~28.15.1"
+ "vercel": "~32.4.1"
}
}
diff --git a/templates/base/packages/nextjs/pages/blockexplorer/address/[address].tsx.template.mjs b/templates/base/packages/nextjs/pages/blockexplorer/address/[address].tsx.template.mjs
deleted file mode 100644
index 423d658a0..000000000
--- a/templates/base/packages/nextjs/pages/blockexplorer/address/[address].tsx.template.mjs
+++ /dev/null
@@ -1,198 +0,0 @@
-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 { Address as AddressType, createPublicClient, http } from "viem";
-import { ${chainName[0]} } from "viem/chains";
-import {
- AddressCodeTab,
- AddressLogsTab,
- AddressStorageTab,
- PaginationButton,
- TransactionsTable,
-} from "~~/components/blockexplorer/";
-import { Address, Balance } from "~~/components/scaffold-eth";
-import deployedContracts from "~~/contracts/deployedContracts";
-import { useFetchBlocks } from "~~/hooks/scaffold-eth";
-import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract";
-
-type AddressCodeTabProps = {
- bytecode: string;
- assembly: string;
-};
-
-type PageProps = {
- address: AddressType;
- contractData: AddressCodeTabProps | null;
-};
-
-const publicClient = createPublicClient({
- chain: ${chainName[0]},
- transport: http(),
-});
-
-const AddressPage = ({ address, contractData }: PageProps) => {
- const router = useRouter();
- const { blocks, transactionReceipts, currentPage, totalBlocks, setCurrentPage } = useFetchBlocks();
- const [activeTab, setActiveTab] = useState("transactions");
- const [isContract, setIsContract] = useState(false);
-
- useEffect(() => {
- const checkIsContract = async () => {
- const contractCode = await publicClient.getBytecode({ address: address });
- setIsContract(contractCode !== undefined && contractCode !== "0x");
- };
-
- checkIsContract();
- }, [address]);
-
- const filteredBlocks = blocks.filter(block =>
- block.transactions.some(tx => {
- if (typeof tx === "string") {
- return false;
- }
- return tx.from.toLowerCase() === address.toLowerCase() || tx.to?.toLowerCase() === address.toLowerCase();
- }),
- );
-
- return (
-
-
- router.back()}>
- Back
-
-
-
- {isContract && (
-
- setActiveTab("transactions")}
- >
- Transactions
-
- setActiveTab("code")}>
- Code
-
- setActiveTab("storage")}
- >
- Storage
-
- setActiveTab("logs")}>
- Logs
-
-
- )}
- {activeTab === "transactions" && (
-
- )}
- {activeTab === "code" && contractData && (
-
- )}
- {activeTab === "storage" &&
}
- {activeTab === "logs" &&
}
-
- );
-};
-
-export default AddressPage;
-
-async function fetchByteCodeAndAssembly(buildInfoDirectory: string, contractPath: string) {
- const buildInfoFiles = fs.readdirSync(buildInfoDirectory);
- let bytecode = "";
- let assembly = "";
-
- for (let i = 0; i < buildInfoFiles.length; i++) {
- const filePath = path.join(buildInfoDirectory, buildInfoFiles[i]);
-
- const buildInfo = JSON.parse(fs.readFileSync(filePath, "utf8"));
-
- if (buildInfo.output.contracts[contractPath]) {
- for (const contract in buildInfo.output.contracts[contractPath]) {
- bytecode = buildInfo.output.contracts[contractPath][contract].evm.bytecode.object;
- assembly = buildInfo.output.contracts[contractPath][contract].evm.bytecode.opcodes;
- break;
- }
- }
-
- if (bytecode && assembly) {
- break;
- }
- }
-
- return { bytecode, assembly };
-}
-
-export const getServerSideProps: GetServerSideProps = async context => {
- const address = (context.params?.address as string).toLowerCase();
- const contracts = deployedContracts as GenericContractsDeclaration | null;
- const chainId = ${chainName[0]}.id;
- let contractPath = "";
-
- const buildInfoDirectory = path.join(
- __dirname,
- "..",
- "..",
- "..",
- "..",
- "..",
- "..",
- "${chainName[0]}",
- "${artifactsDirName[0]}",
- "build-info",
- );
-
- if (!fs.existsSync(buildInfoDirectory)) {
- throw new Error(\`Directory \${buildInfoDirectory} not found.\`);
- }
-
- const deployedContractsOnChain = contracts ? contracts[chainId] : {};
- for (const [contractName, contractInfo] of Object.entries(deployedContractsOnChain)) {
- if (contractInfo.address.toLowerCase() === address) {
- contractPath = \`contracts/\${contractName}.sol\`;
- break;
- }
- }
-
- if (!contractPath) {
- // No contract found at this address
- return { props: { address, contractData: null } };
- }
-
- const { bytecode, assembly } = await fetchByteCodeAndAssembly(buildInfoDirectory, contractPath);
-
- return { props: { address, contractData: { bytecode, assembly } } };
-};
-`;
-
-export default withDefaults(contents, {
- chainName: "hardhat",
- artifactsDirName: "artifacts",
-});
diff --git a/templates/base/packages/nextjs/pages/debug.tsx b/templates/base/packages/nextjs/pages/debug.tsx
deleted file mode 100644
index 824c8ceb8..000000000
--- a/templates/base/packages/nextjs/pages/debug.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import { useEffect } from "react";
-import type { NextPage } from "next";
-import { useLocalStorage } from "usehooks-ts";
-import { BarsArrowUpIcon } from "@heroicons/react/20/solid";
-import { MetaHeader } from "~~/components/MetaHeader";
-import { ContractUI } from "~~/components/scaffold-eth";
-import { ContractName } from "~~/utils/scaffold-eth/contract";
-import { getAllContracts } from "~~/utils/scaffold-eth/contractsData";
-
-const selectedContractStorageKey = "scaffoldEth2.selectedContract";
-const contractsData = getAllContracts();
-const contractNames = Object.keys(contractsData) as ContractName[];
-
-const Debug: NextPage = () => {
- const [selectedContract, setSelectedContract] = useLocalStorage(
- selectedContractStorageKey,
- contractNames[0],
- );
-
- useEffect(() => {
- if (!contractNames.includes(selectedContract)) {
- setSelectedContract(contractNames[0]);
- }
- }, [selectedContract, setSelectedContract]);
-
- return (
- <>
-
-
- {contractNames.length === 0 ? (
-
No contracts found!
- ) : (
- <>
- {contractNames.length > 1 && (
-
- {contractNames.map(contractName => (
- setSelectedContract(contractName)}
- >
- {contractName}
- {contractsData[contractName].external && (
-
-
-
- )}
-
- ))}
-
- )}
- {contractNames.map(contractName => (
-
- ))}
- >
- )}
-
-
-
Debug Contracts
-
- You can debug & interact with your deployed contracts here.
- Check{" "}
-
- packages / nextjs / pages / debug.tsx
-
{" "}
-
-
- >
- );
-};
-
-export default Debug;
diff --git a/templates/base/packages/nextjs/tailwind.config.js b/templates/base/packages/nextjs/tailwind.config.js
index 35cd0131f..e1ca91dd9 100644
--- a/templates/base/packages/nextjs/tailwind.config.js
+++ b/templates/base/packages/nextjs/tailwind.config.js
@@ -1,6 +1,6 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
- content: ["./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}", "./utils/**/*.{js,ts,jsx,tsx}"],
+ content: ["./app/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}", "./utils/**/*.{js,ts,jsx,tsx}"],
plugins: [require("daisyui")],
darkTheme: "scaffoldEthDark",
// DaisyUI theme colors
diff --git a/templates/base/packages/nextjs/tsconfig.json b/templates/base/packages/nextjs/tsconfig.json
index 708cc8ed1..7d1e6d928 100644
--- a/templates/base/packages/nextjs/tsconfig.json
+++ b/templates/base/packages/nextjs/tsconfig.json
@@ -16,8 +16,13 @@
"incremental": true,
"paths": {
"~~/*": ["./*"]
- }
+ },
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ]
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
diff --git a/templates/base/packages/nextjs/utils/scaffold-eth/getMetadata.ts b/templates/base/packages/nextjs/utils/scaffold-eth/getMetadata.ts
new file mode 100644
index 000000000..533784d01
--- /dev/null
+++ b/templates/base/packages/nextjs/utils/scaffold-eth/getMetadata.ts
@@ -0,0 +1,34 @@
+import type { Metadata } from "next";
+
+export const getMetadata = ({
+ title,
+ description,
+ imageRelativePath = "/thumbnail.jpg",
+}: {
+ title: string;
+ description: string;
+ imageRelativePath?: string;
+}): Metadata => {
+ const baseUrl = process.env.NEXT_PUBLIC_VERCEL_URL
+ ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`
+ : `http://localhost:${process.env.PORT}`;
+ const imageUrl = `${baseUrl}${imageRelativePath}`;
+ return {
+ title: title,
+ description: description,
+ openGraph: {
+ title: title,
+ description: description,
+ images: [
+ {
+ url: imageUrl,
+ },
+ ],
+ },
+ twitter: {
+ title: title,
+ description: description,
+ images: [imageUrl],
+ },
+ };
+};
diff --git a/templates/base/packages/nextjs/utils/scaffold-eth/getParsedError.ts b/templates/base/packages/nextjs/utils/scaffold-eth/getParsedError.ts
new file mode 100644
index 000000000..31e7414d2
--- /dev/null
+++ b/templates/base/packages/nextjs/utils/scaffold-eth/getParsedError.ts
@@ -0,0 +1,31 @@
+import { BaseError as BaseViemError, DecodeErrorResultReturnType } from "viem";
+
+/**
+ * Parses an viem/wagmi error to get a displayable string
+ * @param e - error object
+ * @returns parsed error string
+ */
+export const getParsedError = (e: any): string => {
+ let message: string = e.message ?? "An unknown error occurred";
+ if (e instanceof BaseViemError) {
+ if (e.details) {
+ message = e.details;
+ } else if (e.shortMessage) {
+ message = e.shortMessage;
+ const cause = e.cause as { data?: DecodeErrorResultReturnType } | undefined;
+ // if its not generic error, append custom error name and its args to message
+ if (cause?.data && cause.data?.abiItem?.name !== "Error") {
+ const customErrorArgs = cause.data.args?.toString() ?? "";
+ message = `${message.replace(/reverted\.$/, "reverted with following reason:")}\n${
+ cause.data.errorName
+ }(${customErrorArgs})`;
+ }
+ } else if (e.message) {
+ message = e.message;
+ } else if (e.name) {
+ message = e.name;
+ }
+ }
+
+ return message;
+};
diff --git a/templates/base/packages/nextjs/utils/scaffold-eth/index.ts b/templates/base/packages/nextjs/utils/scaffold-eth/index.ts
index c6a6bd2e2..6d69193d5 100644
--- a/templates/base/packages/nextjs/utils/scaffold-eth/index.ts
+++ b/templates/base/packages/nextjs/utils/scaffold-eth/index.ts
@@ -3,3 +3,4 @@ export * from "./networks";
export * from "./notification";
export * from "./block";
export * from "./decodeTxData";
+export * from "./getParsedError";
diff --git a/templates/extensions/foundry/packages/nextjs/pages/blockexplorer/address/[address].tsx.args.mjs b/templates/extensions/foundry/packages/nextjs/app/blockexplorer/address/[address]/page.tsx.args.mjs
similarity index 100%
rename from templates/extensions/foundry/packages/nextjs/pages/blockexplorer/address/[address].tsx.args.mjs
rename to templates/extensions/foundry/packages/nextjs/app/blockexplorer/address/[address]/page.tsx.args.mjs
diff --git a/templates/extensions/hardhat/package.json b/templates/extensions/hardhat/package.json
index bdb616060..ff027311e 100644
--- a/templates/extensions/hardhat/package.json
+++ b/templates/extensions/hardhat/package.json
@@ -5,6 +5,7 @@
"fork": "yarn workspace @se-2/hardhat fork",
"deploy": "yarn workspace @se-2/hardhat deploy",
"verify": "yarn workspace @se-2/hardhat verify",
+ "hardhat-verify": "yarn workspace @se-2/hardhat hardhat-verify",
"compile": "yarn workspace @se-2/hardhat compile",
"generate": "yarn workspace @se-2/hardhat generate",
"hardhat:lint": "yarn workspace @se-2/hardhat lint",
diff --git a/templates/extensions/hardhat/packages/hardhat/deploy/00_deploy_your_contract.ts b/templates/extensions/hardhat/packages/hardhat/deploy/00_deploy_your_contract.ts
index 418b91218..1c12ac796 100644
--- a/templates/extensions/hardhat/packages/hardhat/deploy/00_deploy_your_contract.ts
+++ b/templates/extensions/hardhat/packages/hardhat/deploy/00_deploy_your_contract.ts
@@ -1,5 +1,6 @@
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
+import { Contract } from "ethers";
/**
* Deploys a contract named "YourContract" using the deployer account and
@@ -31,8 +32,9 @@ const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEn
autoMine: true,
});
- // Get the deployed contract
- // const yourContract = await hre.ethers.getContract("YourContract", deployer);
+ // Get the deployed contract to interact with it after deploying.
+ const yourContract = await hre.ethers.getContract("YourContract", deployer);
+ console.log("๐ Initial greeting:", await yourContract.greeting());
};
export default deployYourContract;
diff --git a/templates/extensions/hardhat/packages/hardhat/hardhat.config.ts b/templates/extensions/hardhat/packages/hardhat/hardhat.config.ts
index 6a6f5e063..3925b6f29 100644
--- a/templates/extensions/hardhat/packages/hardhat/hardhat.config.ts
+++ b/templates/extensions/hardhat/packages/hardhat/hardhat.config.ts
@@ -1,10 +1,14 @@
import * as dotenv from "dotenv";
dotenv.config();
import { HardhatUserConfig } from "hardhat/config";
-import "@nomicfoundation/hardhat-toolbox";
+import "@nomicfoundation/hardhat-ethers";
+import "@nomicfoundation/hardhat-chai-matchers";
+import "@typechain/hardhat";
+import "hardhat-gas-reporter";
+import "solidity-coverage";
+import "@nomicfoundation/hardhat-verify";
import "hardhat-deploy";
-import "@matterlabs/hardhat-zksync-solc";
-import "@matterlabs/hardhat-zksync-verify";
+import "hardhat-deploy-ethers";
// If not set, it uses ours Alchemy's default API key.
// You can get your own at https://dashboard.alchemyapi.io
@@ -86,18 +90,6 @@ const config: HardhatUserConfig = {
url: `https://polygonzkevm-testnet.g.alchemy.com/v2/${providerApiKey}`,
accounts: [deployerPrivateKey],
},
- zkSyncTestnet: {
- url: "https://testnet.era.zksync.dev",
- zksync: true,
- accounts: [deployerPrivateKey],
- verifyURL: "https://zksync2-testnet-explorer.zksync.dev/contract_verification",
- },
- zkSync: {
- url: "https://mainnet.era.zksync.io",
- zksync: true,
- accounts: [deployerPrivateKey],
- verifyURL: "https://zksync2-mainnet-explorer.zksync.io/contract_verification",
- },
gnosis: {
url: "https://rpc.gnosischain.com",
accounts: [deployerPrivateKey],
@@ -131,11 +123,19 @@ const config: HardhatUserConfig = {
accounts: [deployerPrivateKey],
},
},
+ // configuration for harhdat-verify plugin
+ etherscan: {
+ apiKey: `${etherscanApiKey}`,
+ },
+ // configuration for etherscan-verify from hardhat-deploy plugin
verify: {
etherscan: {
apiKey: `${etherscanApiKey}`,
},
},
+ sourcify: {
+ enabled: false,
+ },
};
export default config;
diff --git a/templates/extensions/hardhat/packages/hardhat/package.json b/templates/extensions/hardhat/packages/hardhat/package.json
index 0c9eb361e..593588658 100644
--- a/templates/extensions/hardhat/packages/hardhat/package.json
+++ b/templates/extensions/hardhat/packages/hardhat/package.json
@@ -11,20 +11,18 @@
"lint": "eslint --config ./.eslintrc.json --ignore-path ./.eslintignore ./*.ts ./deploy/**/*.ts ./scripts/**/*.ts ./test/**/*.ts",
"lint-staged": "eslint --config ./.eslintrc.json --ignore-path ./.eslintignore",
"test": "REPORT_GAS=true hardhat test --network hardhat",
- "verify": "hardhat etherscan-verify"
+ "verify": "hardhat etherscan-verify",
+ "hardhat-verify": "hardhat verify"
},
"devDependencies": {
"@ethersproject/abi": "~5.7.0",
"@ethersproject/providers": "~5.7.1",
- "@matterlabs/hardhat-zksync-solc": "~0.3.17",
- "@matterlabs/hardhat-zksync-verify": "~0.1.8",
- "@nomicfoundation/hardhat-chai-matchers": "~1.0.3",
+ "@nomicfoundation/hardhat-chai-matchers": "~2.0.3",
+ "@nomicfoundation/hardhat-ethers": "~3.0.5",
"@nomicfoundation/hardhat-network-helpers": "~1.0.6",
- "@nomicfoundation/hardhat-toolbox": "~2.0.0",
- "@nomiclabs/hardhat-ethers": "npm:hardhat-deploy-ethers@~0.3.0-beta.13",
- "@nomiclabs/hardhat-etherscan": "~3.1.0",
+ "@nomicfoundation/hardhat-verify": "~2.0.3",
"@typechain/ethers-v5": "~10.1.0",
- "@typechain/hardhat": "~6.1.3",
+ "@typechain/hardhat": "~9.1.0",
"@types/eslint": "~8",
"@types/mocha": "~9.1.1",
"@types/prettier": "~2",
@@ -35,18 +33,20 @@
"eslint": "~8.26.0",
"eslint-config-prettier": "~8.5.0",
"eslint-plugin-prettier": "~4.2.1",
- "ethers": "~5.7.1",
- "hardhat": "~2.17.3",
- "hardhat-deploy": "~0.11.31",
+ "ethers": "~6.10.0",
+ "hardhat": "~2.19.4",
+ "hardhat-deploy": "~0.11.45",
+ "hardhat-deploy-ethers": "~0.4.1",
"hardhat-gas-reporter": "~1.0.9",
"prettier": "~2.8.4",
- "solidity-coverage": "~0.8.2",
+ "solidity-coverage": "~0.8.5",
"ts-node": "~10.9.1",
"typechain": "~8.1.0",
"typescript": "~5.1.6"
},
"dependencies": {
"@openzeppelin/contracts": "~4.8.1",
+ "@typechain/ethers-v6": "~0.5.1",
"dotenv": "~16.0.3",
"envfile": "~6.18.0",
"qrcode": "~1.5.1"
diff --git a/templates/extensions/hardhat/packages/hardhat/scripts/listAccount.ts b/templates/extensions/hardhat/packages/hardhat/scripts/listAccount.ts
index 0f95f7275..ffc46d491 100644
--- a/templates/extensions/hardhat/packages/hardhat/scripts/listAccount.ts
+++ b/templates/extensions/hardhat/packages/hardhat/scripts/listAccount.ts
@@ -24,10 +24,11 @@ async function main() {
try {
const network = availableNetworks[networkName];
if (!("url" in network)) continue;
- const provider = new ethers.providers.JsonRpcProvider(network.url);
+ const provider = new ethers.JsonRpcProvider(network.url);
+ await provider._detectNetwork();
const balance = await provider.getBalance(address);
console.log("--", networkName, "-- ๐ก");
- console.log(" balance:", +ethers.utils.formatEther(balance));
+ console.log(" balance:", +ethers.formatEther(balance));
console.log(" nonce:", +(await provider.getTransactionCount(address)));
} catch (e) {
console.log("Can't connect to network", networkName);
diff --git a/templates/extensions/hardhat/packages/hardhat/test/YourContract.ts b/templates/extensions/hardhat/packages/hardhat/test/YourContract.ts
index 67a6174ae..a44cf04e1 100644
--- a/templates/extensions/hardhat/packages/hardhat/test/YourContract.ts
+++ b/templates/extensions/hardhat/packages/hardhat/test/YourContract.ts
@@ -10,7 +10,7 @@ describe("YourContract", function () {
const [owner] = await ethers.getSigners();
const yourContractFactory = await ethers.getContractFactory("YourContract");
yourContract = (await yourContractFactory.deploy(owner.address)) as YourContract;
- await yourContract.deployed();
+ await yourContract.waitForDeployment();
});
describe("Deployment", function () {
diff --git a/templates/extensions/hardhat/packages/nextjs/pages/blockexplorer/address/[address].tsx.args.mjs b/templates/extensions/hardhat/packages/nextjs/app/blockexplorer/address/[address]/page.tsx.args.mjs
similarity index 100%
rename from templates/extensions/hardhat/packages/nextjs/pages/blockexplorer/address/[address].tsx.args.mjs
rename to templates/extensions/hardhat/packages/nextjs/app/blockexplorer/address/[address]/page.tsx.args.mjs