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 ( +
+
+ +
+
+
+
+
+
+
+
+ Balance: + +
+
+
+
+
+
+ +
+ ); +}; 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 ( + + ); +}; 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 && ( +
+ + + + +
+ )} + {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 => ( + + ))} +
+ )} + {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 ( + +
  • } /> ); 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 ( -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    - Balance: - -
    -
    -
    -
    -
    -
    - {isContract && ( -
    - - - - -
    - )} - {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 => ( - - ))} -
    - )} - {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