├── .gitattributes ├── .gitignore ├── frontend ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo310.png │ ├── manifest.json │ └── index.html ├── src │ ├── components │ │ ├── WaitingForTransactionMessage.js │ │ ├── NoTokensMessage.js │ │ ├── NetworkErrorMessage.js │ │ ├── TransactionErrorMessage.js │ │ ├── NoWalletDetected.js │ │ ├── Loading.js │ │ ├── ConnectWallet.js │ │ ├── Transfer.js │ │ └── Dapp.js │ └── index.js ├── .gitignore ├── package.json └── README.md ├── hardhat.config.js ├── LICENSE ├── package.json ├── tasks └── faucet.js ├── scripts └── deploy.js ├── contracts └── Token.sol ├── README.md └── test └── Token.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | 4 | #Hardhat files 5 | cache 6 | artifacts 7 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NomicFoundation/hardhat-boilerplate/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NomicFoundation/hardhat-boilerplate/HEAD/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NomicFoundation/hardhat-boilerplate/HEAD/frontend/public/logo310.png -------------------------------------------------------------------------------- /frontend/src/components/WaitingForTransactionMessage.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export function WaitingForTransactionMessage({ txHash }) { 4 | return ( 5 |
6 | Waiting for transaction {txHash} to be mined 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomicfoundation/hardhat-toolbox"); 2 | 3 | // The next line is part of the sample project, you don't need it in your 4 | // project. It imports a Hardhat task definition, that can be used for 5 | // testing the frontend. 6 | require("./tasks/faucet"); 7 | 8 | /** @type import('hardhat/config').HardhatUserConfig */ 9 | module.exports = { 10 | solidity: "0.8.17", 11 | }; 12 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | src/contracts 4 | 5 | # dependencies 6 | /node_modules 7 | /.pnp 8 | .pnp.js 9 | 10 | # testing 11 | /coverage 12 | 13 | # production 14 | /build 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /frontend/src/components/NoTokensMessage.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export function NoTokensMessage({ selectedAddress }) { 4 | return ( 5 | <> 6 |

You don't have tokens to transfer

7 |

8 | To get some tokens, open a terminal in the root of the repository and run: 9 |
10 |
11 | npx hardhat --network localhost faucet {selectedAddress} 12 |

13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/components/NetworkErrorMessage.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export function NetworkErrorMessage({ message, dismiss }) { 4 | return ( 5 |
6 | {message} 7 | 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/components/TransactionErrorMessage.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export function TransactionErrorMessage({ message, dismiss }) { 4 | return ( 5 |
6 | Error sending transaction: {message.substring(0, 100)} 7 | 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import { Dapp } from "./components/Dapp"; 4 | 5 | // We import bootstrap here, but you can remove if you want 6 | import "bootstrap/dist/css/bootstrap.css"; 7 | 8 | // This is the entry point of your application, but it just renders the Dapp 9 | // react component. All of the logic is contained in it. 10 | 11 | const root = ReactDOM.createRoot(document.getElementById("root")); 12 | 13 | root.render( 14 | 15 | 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Hardhat React Dapp", 3 | "name": "Hardhat Create React Dapp Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo310.png", 17 | "type": "image/png", 18 | "sizes": "310x310" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "bootstrap": "^4.4.1", 7 | "ethers": "^5.4.7", 8 | "react": "^18.2.0", 9 | "react-dom": "^18.2.0" 10 | }, 11 | "devDependencies": { 12 | "react-scripts": "5.0.1" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "eject": "react-scripts eject" 18 | }, 19 | "eslintConfig": { 20 | "extends": "react-app" 21 | }, 22 | "browserslist": { 23 | "production": [ 24 | ">0.2%", 25 | "not dead", 26 | "not op_mini all" 27 | ], 28 | "development": [ 29 | "last 1 chrome version", 30 | "last 1 firefox version", 31 | "last 1 safari version" 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /frontend/src/components/NoWalletDetected.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export function NoWalletDetected() { 4 | return ( 5 |
6 |
7 |
8 |

9 | No Ethereum wallet was detected.
10 | Please install{" "} 11 | 16 | Coinbase Wallet 17 | 18 | or{" "} 19 | 20 | MetaMask 21 | 22 | . 23 |

24 |
25 |
26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/components/Loading.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export function Loading() { 4 | return ( 5 |
16 |
29 |
30 | Loading... 31 |
32 |
33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /frontend/src/components/ConnectWallet.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { NetworkErrorMessage } from "./NetworkErrorMessage"; 4 | 5 | export function ConnectWallet({ connectWallet, networkError, dismiss }) { 6 | return ( 7 |
8 |
9 |
10 | {/* Wallet network should be set to Localhost:8545. */} 11 | {networkError && ( 12 | 16 | )} 17 |
18 |
19 |

Please connect to your wallet.

20 | 27 |
28 |
29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Nomic Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hardhat-boilerplate", 3 | "version": "1.0.0", 4 | "description": "A boilerplate repository to get you started with Hardhat and Ethereum development", 5 | "scripts": { 6 | "test": "hardhat test" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/NomicFoundation/hardhat-boilerplate.git" 11 | }, 12 | "author": "Nomic Foundation", 13 | "license": "MIT", 14 | "bugs": { 15 | "url": "https://github.com/NomicFoundation/hardhat-boilerplate/issues" 16 | }, 17 | "homepage": "https://github.com/NomicFoundation/hardhat-boilerplate#readme", 18 | "devDependencies": { 19 | "@ethersproject/abi": "^5.7.0", 20 | "@ethersproject/providers": "^5.7.2", 21 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.5", 22 | "@nomicfoundation/hardhat-network-helpers": "^1.0.7", 23 | "@nomicfoundation/hardhat-toolbox": "^2.0.0", 24 | "@nomiclabs/hardhat-ethers": "^2.2.1", 25 | "@nomiclabs/hardhat-etherscan": "^3.1.4", 26 | "chai": "^4.3.7", 27 | "ethers": "^5.7.2", 28 | "hardhat": "^2.12.5", 29 | "hardhat-gas-reporter": "^1.0.9", 30 | "solidity-coverage": "^0.8.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/components/Transfer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export function Transfer({ transferTokens, tokenSymbol }) { 4 | return ( 5 |
6 |

Transfer

7 |
{ 9 | // This function just calls the transferTokens callback with the 10 | // form's data. 11 | event.preventDefault(); 12 | 13 | const formData = new FormData(event.target); 14 | const to = formData.get("to"); 15 | const amount = formData.get("amount"); 16 | 17 | if (to && amount) { 18 | transferTokens(to, amount); 19 | } 20 | }} 21 | > 22 |
23 | 24 | 32 |
33 |
34 | 35 | 36 |
37 |
38 | 39 |
40 |
41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /tasks/faucet.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | 3 | // This file is only here to make interacting with the Dapp easier, 4 | // feel free to ignore it if you don't need it. 5 | 6 | task("faucet", "Sends ETH and tokens to an address") 7 | .addPositionalParam("receiver", "The address that will receive them") 8 | .setAction(async ({ receiver }, { ethers }) => { 9 | if (network.name === "hardhat") { 10 | console.warn( 11 | "You are running the faucet task with Hardhat network, which" + 12 | "gets automatically created and destroyed every time. Use the Hardhat" + 13 | " option '--network localhost'" 14 | ); 15 | } 16 | 17 | const addressesFile = 18 | __dirname + "/../frontend/src/contracts/contract-address.json"; 19 | 20 | if (!fs.existsSync(addressesFile)) { 21 | console.error("You need to deploy your contract first"); 22 | return; 23 | } 24 | 25 | const addressJson = fs.readFileSync(addressesFile); 26 | const address = JSON.parse(addressJson); 27 | 28 | if ((await ethers.provider.getCode(address.Token)) === "0x") { 29 | console.error("You need to deploy your contract first"); 30 | return; 31 | } 32 | 33 | const token = await ethers.getContractAt("Token", address.Token); 34 | const [sender] = await ethers.getSigners(); 35 | 36 | const tx = await token.transfer(receiver, 100); 37 | await tx.wait(); 38 | 39 | const tx2 = await sender.sendTransaction({ 40 | to: receiver, 41 | value: ethers.constants.WeiPerEther, 42 | }); 43 | await tx2.wait(); 44 | 45 | console.log(`Transferred 1 ETH and 100 tokens to ${receiver}`); 46 | }); 47 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Sample React Dapp 2 | 3 | This directory has a sample Dapp to interact with your contracts, built using 4 | React. 5 | 6 | ## Running the Dapp 7 | 8 | This project uses [`create-react-app`](https://create-react-app.dev/), so most 9 | configuration files are handled by it. 10 | 11 | To run it, you just need to execute `npm start` in a terminal, and open 12 | [http://localhost:3000](http://localhost:3000). 13 | 14 | To learn more about what `create-react-app` offers, you can read 15 | [its documentation](https://create-react-app.dev/docs/getting-started). 16 | 17 | ## Architecture of the Dapp 18 | 19 | This Dapp consists of multiple React Components, which you can find in 20 | `src/components`. 21 | 22 | Most of them are presentational components, have no logic, and just render HTML. 23 | 24 | The core functionality is implemented in `src/components/Dapp.js`, which has 25 | examples of how to connect to the user's wallet, initialize your Ethereum 26 | connection and contracts, read from the contract's state, and send transactions. 27 | 28 | You can use the `Dapp` component as a starting point for your project. It has 29 | comments explaining each part of its code, and indicating what's specific to 30 | this project, and what can be reused. 31 | 32 | ## Getting help and news 33 | 34 | If you need help with this project or with Hardhat in general, please read [this guide](https://hardhat.org/hardhat-runner/docs/guides/getting-help) to learn where and how to get it. 35 | 36 | [Follow us on Twitter](https://twitter.com/HardhatHQ) to get the latest news about Hardhat, and don't forget to star [our GitHub repository](https://github.com/NomicFoundation/hardhat)! 37 | 38 | **Happy _building_!** 39 | -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Hardhat + React App = Dapp 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /scripts/deploy.js: -------------------------------------------------------------------------------- 1 | // This is a script for deploying your contracts. You can adapt it to deploy 2 | // yours, or create new ones. 3 | 4 | const path = require("path"); 5 | 6 | async function main() { 7 | // This is just a convenience check 8 | if (network.name === "hardhat") { 9 | console.warn( 10 | "You are trying to deploy a contract to the Hardhat Network, which" + 11 | "gets automatically created and destroyed every time. Use the Hardhat" + 12 | " option '--network localhost'" 13 | ); 14 | } 15 | 16 | // ethers is available in the global scope 17 | const [deployer] = await ethers.getSigners(); 18 | console.log( 19 | "Deploying the contracts with the account:", 20 | await deployer.getAddress() 21 | ); 22 | 23 | console.log("Account balance:", (await deployer.getBalance()).toString()); 24 | 25 | const Token = await ethers.getContractFactory("Token"); 26 | const token = await Token.deploy(); 27 | await token.deployed(); 28 | 29 | console.log("Token address:", token.address); 30 | 31 | // We also save the contract's artifacts and address in the frontend directory 32 | saveFrontendFiles(token); 33 | } 34 | 35 | function saveFrontendFiles(token) { 36 | const fs = require("fs"); 37 | const contractsDir = path.join(__dirname, "..", "frontend", "src", "contracts"); 38 | 39 | if (!fs.existsSync(contractsDir)) { 40 | fs.mkdirSync(contractsDir); 41 | } 42 | 43 | fs.writeFileSync( 44 | path.join(contractsDir, "contract-address.json"), 45 | JSON.stringify({ Token: token.address }, undefined, 2) 46 | ); 47 | 48 | const TokenArtifact = artifacts.readArtifactSync("Token"); 49 | 50 | fs.writeFileSync( 51 | path.join(contractsDir, "Token.json"), 52 | JSON.stringify(TokenArtifact, null, 2) 53 | ); 54 | } 55 | 56 | main() 57 | .then(() => process.exit(0)) 58 | .catch((error) => { 59 | console.error(error); 60 | process.exit(1); 61 | }); 62 | -------------------------------------------------------------------------------- /contracts/Token.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: UNLICENSED 2 | 3 | // Solidity files have to start with this pragma. 4 | // It will be used by the Solidity compiler to validate its version. 5 | pragma solidity ^0.8.9; 6 | 7 | // We import this library to be able to use console.log 8 | import "hardhat/console.sol"; 9 | 10 | 11 | // This is the main building block for smart contracts. 12 | contract Token { 13 | // Some string type variables to identify the token. 14 | string public name = "My Hardhat Token"; 15 | string public symbol = "MHT"; 16 | 17 | // The fixed amount of tokens stored in an unsigned integer type variable. 18 | uint256 public totalSupply = 1000000; 19 | 20 | // An address type variable is used to store ethereum accounts. 21 | address public owner; 22 | 23 | // A mapping is a key/value map. Here we store each account balance. 24 | mapping(address => uint256) balances; 25 | 26 | // The Transfer event helps off-chain aplications understand 27 | // what happens within your contract. 28 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 29 | 30 | /** 31 | * Contract initialization. 32 | */ 33 | constructor() { 34 | // The totalSupply is assigned to the transaction sender, which is the 35 | // account that is deploying the contract. 36 | balances[msg.sender] = totalSupply; 37 | owner = msg.sender; 38 | } 39 | 40 | /** 41 | * A function to transfer tokens. 42 | * 43 | * The `external` modifier makes a function *only* callable from outside 44 | * the contract. 45 | */ 46 | function transfer(address to, uint256 amount) external { 47 | // Check if the transaction sender has enough tokens. 48 | // If `require`'s first argument evaluates to `false` then the 49 | // transaction will revert. 50 | require(balances[msg.sender] >= amount, "Not enough tokens"); 51 | 52 | // We can print messages and values using console.log, a feature of 53 | // Hardhat Network: 54 | console.log( 55 | "Transferring from %s to %s %s tokens", 56 | msg.sender, 57 | to, 58 | amount 59 | ); 60 | 61 | // Transfer the amount. 62 | balances[msg.sender] -= amount; 63 | balances[to] += amount; 64 | 65 | // Notify off-chain applications of the transfer. 66 | emit Transfer(msg.sender, to, amount); 67 | } 68 | 69 | /** 70 | * Read only function to retrieve the token balance of a given account. 71 | * 72 | * The `view` modifier indicates that it doesn't modify the contract's 73 | * state, which allows us to call it without executing a transaction. 74 | */ 75 | function balanceOf(address account) external view returns (uint256) { 76 | return balances[account]; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hardhat Boilerplate 2 | 3 | This repository contains a sample project that you can use as the starting point 4 | for your Ethereum project. It's also a great fit for learning the basics of 5 | smart contract development. 6 | 7 | This project is intended to be used with the 8 | [Hardhat Beginners Tutorial](https://hardhat.org/tutorial), but you should be 9 | able to follow it by yourself by reading the README and exploring its 10 | `contracts`, `tests`, `scripts` and `frontend` directories. 11 | 12 | ## Quick start 13 | 14 | The first things you need to do are cloning this repository and installing its 15 | dependencies: 16 | 17 | ```sh 18 | git clone https://github.com/NomicFoundation/hardhat-boilerplate.git 19 | cd hardhat-boilerplate 20 | npm install 21 | ``` 22 | 23 | Once installed, let's run Hardhat's testing network: 24 | 25 | ```sh 26 | npx hardhat node 27 | ``` 28 | 29 | Then, on a new terminal, go to the repository's root folder and run this to 30 | deploy your contract: 31 | 32 | ```sh 33 | npx hardhat run scripts/deploy.js --network localhost 34 | ``` 35 | 36 | Finally, we can run the frontend with: 37 | 38 | ```sh 39 | cd frontend 40 | npm install 41 | npm start 42 | ``` 43 | 44 | Open [http://localhost:3000/](http://localhost:3000/) to see your Dapp. You will 45 | need to have [Coinbase Wallet](https://www.coinbase.com/wallet) or [Metamask](https://metamask.io) installed and listening to 46 | `localhost 8545`. 47 | 48 | ## User Guide 49 | 50 | You can find detailed instructions on using this repository and many tips in [its documentation](https://hardhat.org/tutorial). 51 | 52 | - [Writing and compiling contracts](https://hardhat.org/tutorial/writing-and-compiling-contracts/) 53 | - [Setting up the environment](https://hardhat.org/tutorial/setting-up-the-environment/) 54 | - [Testing Contracts](https://hardhat.org/tutorial/testing-contracts/) 55 | - [Setting up your wallet](https://hardhat.org/tutorial/boilerplate-project#how-to-use-it) 56 | - [Hardhat's full documentation](https://hardhat.org/docs/) 57 | 58 | For a complete introduction to Hardhat, refer to [this guide](https://hardhat.org/getting-started/#overview). 59 | 60 | ## What's Included? 61 | 62 | This repository uses our recommended hardhat setup, by using our [`@nomicfoundation/hardhat-toolbox`](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-toolbox). When you use this plugin, you'll be able to: 63 | 64 | - Deploy and interact with your contracts using [ethers.js](https://docs.ethers.io/v5/) and the [`hardhat-ethers`](https://hardhat.org/hardhat-runner/plugins/nomiclabs-hardhat-ethers) plugin. 65 | - Test your contracts with [Mocha](https://mochajs.org/), [Chai](https://chaijs.com/) and our own [Hardhat Chai Matchers](https://hardhat.org/hardhat-chai-matchers) plugin. 66 | - Interact with Hardhat Network with our [Hardhat Network Helpers](https://hardhat.org/hardhat-network-helpers). 67 | - Verify the source code of your contracts with the [hardhat-etherscan](https://hardhat.org/hardhat-runner/plugins/nomiclabs-hardhat-etherscan) plugin. 68 | - Get metrics on the gas used by your contracts with the [hardhat-gas-reporter](https://github.com/cgewecke/hardhat-gas-reporter) plugin. 69 | - Measure your tests coverage with [solidity-coverage](https://github.com/sc-forks/solidity-coverage). 70 | 71 | This project also includes [a sample frontend/Dapp](./frontend), which uses [Create React App](https://github.com/facebook/create-react-app). 72 | 73 | ## Troubleshooting 74 | 75 | - `Invalid nonce` errors: if you are seeing this error on the `npx hardhat node` 76 | console, try resetting your Metamask account. This will reset the account's 77 | transaction history and also the nonce. Open Metamask, click on your account 78 | followed by `Settings > Advanced > Clear activity tab data`. 79 | 80 | ## Setting up your editor 81 | 82 | [Hardhat for Visual Studio Code](https://hardhat.org/hardhat-vscode) is the official Hardhat extension that adds advanced support for Solidity to VSCode. If you use Visual Studio Code, give it a try! 83 | 84 | ## Getting help and updates 85 | 86 | If you need help with this project, or with Hardhat in general, please read [this guide](https://hardhat.org/hardhat-runner/docs/guides/getting-help) to learn where and how to get it. 87 | 88 | For the latest news about Hardhat, [follow us on Twitter](https://twitter.com/HardhatHQ), and don't forget to star [our GitHub repository](https://github.com/NomicFoundation/hardhat)! 89 | 90 | **Happy _building_!** 91 | -------------------------------------------------------------------------------- /test/Token.js: -------------------------------------------------------------------------------- 1 | // This is an example test file. Hardhat will run every *.js file in `test/`, 2 | // so feel free to add new ones. 3 | 4 | // Hardhat tests are normally written with Mocha and Chai. 5 | 6 | // We import Chai to use its asserting functions here. 7 | const { expect } = require("chai"); 8 | 9 | // We use `loadFixture` to share common setups (or fixtures) between tests. 10 | // Using this simplifies your tests and makes them run faster, by taking 11 | // advantage or Hardhat Network's snapshot functionality. 12 | const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers"); 13 | 14 | // `describe` is a Mocha function that allows you to organize your tests. 15 | // Having your tests organized makes debugging them easier. All Mocha 16 | // functions are available in the global scope. 17 | // 18 | // `describe` receives the name of a section of your test suite, and a 19 | // callback. The callback must define the tests of that section. This callback 20 | // can't be an async function. 21 | describe("Token contract", function () { 22 | // We define a fixture to reuse the same setup in every test. We use 23 | // loadFixture to run this setup once, snapshot that state, and reset Hardhat 24 | // Network to that snapshot in every test. 25 | async function deployTokenFixture() { 26 | // Get the ContractFactory and Signers here. 27 | const Token = await ethers.getContractFactory("Token"); 28 | const [owner, addr1, addr2] = await ethers.getSigners(); 29 | 30 | // To deploy our contract, we just have to call Token.deploy() and await 31 | // for it to be deployed(), which happens onces its transaction has been 32 | // mined. 33 | const hardhatToken = await Token.deploy(); 34 | 35 | await hardhatToken.deployed(); 36 | 37 | // Fixtures can return anything you consider useful for your tests 38 | return { Token, hardhatToken, owner, addr1, addr2 }; 39 | } 40 | 41 | // You can nest describe calls to create subsections. 42 | describe("Deployment", function () { 43 | // `it` is another Mocha function. This is the one you use to define your 44 | // tests. It receives the test name, and a callback function. 45 | // 46 | // If the callback function is async, Mocha will `await` it. 47 | it("Should set the right owner", async function () { 48 | // We use loadFixture to setup our environment, and then assert that 49 | // things went well 50 | const { hardhatToken, owner } = await loadFixture(deployTokenFixture); 51 | 52 | // Expect receives a value and wraps it in an assertion object. These 53 | // objects have a lot of utility methods to assert values. 54 | 55 | // This test expects the owner variable stored in the contract to be 56 | // equal to our Signer's owner. 57 | expect(await hardhatToken.owner()).to.equal(owner.address); 58 | }); 59 | 60 | it("Should assign the total supply of tokens to the owner", async function () { 61 | const { hardhatToken, owner } = await loadFixture(deployTokenFixture); 62 | const ownerBalance = await hardhatToken.balanceOf(owner.address); 63 | expect(await hardhatToken.totalSupply()).to.equal(ownerBalance); 64 | }); 65 | }); 66 | 67 | describe("Transactions", function () { 68 | it("Should transfer tokens between accounts", async function () { 69 | const { hardhatToken, owner, addr1, addr2 } = await loadFixture(deployTokenFixture); 70 | // Transfer 50 tokens from owner to addr1 71 | await expect(hardhatToken.transfer(addr1.address, 50)) 72 | .to.changeTokenBalances(hardhatToken, [owner, addr1], [-50, 50]); 73 | 74 | // Transfer 50 tokens from addr1 to addr2 75 | // We use .connect(signer) to send a transaction from another account 76 | await expect(hardhatToken.connect(addr1).transfer(addr2.address, 50)) 77 | .to.changeTokenBalances(hardhatToken, [addr1, addr2], [-50, 50]); 78 | }); 79 | 80 | it("should emit Transfer events", async function () { 81 | const { hardhatToken, owner, addr1, addr2 } = await loadFixture(deployTokenFixture); 82 | 83 | // Transfer 50 tokens from owner to addr1 84 | await expect(hardhatToken.transfer(addr1.address, 50)) 85 | .to.emit(hardhatToken, "Transfer").withArgs(owner.address, addr1.address, 50) 86 | 87 | // Transfer 50 tokens from addr1 to addr2 88 | // We use .connect(signer) to send a transaction from another account 89 | await expect(hardhatToken.connect(addr1).transfer(addr2.address, 50)) 90 | .to.emit(hardhatToken, "Transfer").withArgs(addr1.address, addr2.address, 50) 91 | }); 92 | 93 | it("Should fail if sender doesn't have enough tokens", async function () { 94 | const { hardhatToken, owner, addr1 } = await loadFixture(deployTokenFixture); 95 | const initialOwnerBalance = await hardhatToken.balanceOf( 96 | owner.address 97 | ); 98 | 99 | // Try to send 1 token from addr1 (0 tokens) to owner (1000 tokens). 100 | // `require` will evaluate false and revert the transaction. 101 | await expect( 102 | hardhatToken.connect(addr1).transfer(owner.address, 1) 103 | ).to.be.revertedWith("Not enough tokens"); 104 | 105 | // Owner balance shouldn't have changed. 106 | expect(await hardhatToken.balanceOf(owner.address)).to.equal( 107 | initialOwnerBalance 108 | ); 109 | }); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /frontend/src/components/Dapp.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | // We'll use ethers to interact with the Ethereum network and our contract 4 | import { ethers } from "ethers"; 5 | 6 | // We import the contract's artifacts and address here, as we are going to be 7 | // using them with ethers 8 | import TokenArtifact from "../contracts/Token.json"; 9 | import contractAddress from "../contracts/contract-address.json"; 10 | 11 | // All the logic of this dapp is contained in the Dapp component. 12 | // These other components are just presentational ones: they don't have any 13 | // logic. They just render HTML. 14 | import { NoWalletDetected } from "./NoWalletDetected"; 15 | import { ConnectWallet } from "./ConnectWallet"; 16 | import { Loading } from "./Loading"; 17 | import { Transfer } from "./Transfer"; 18 | import { TransactionErrorMessage } from "./TransactionErrorMessage"; 19 | import { WaitingForTransactionMessage } from "./WaitingForTransactionMessage"; 20 | import { NoTokensMessage } from "./NoTokensMessage"; 21 | 22 | // This is the default id used by the Hardhat Network 23 | const HARDHAT_NETWORK_ID = '31337'; 24 | 25 | // This is an error code that indicates that the user canceled a transaction 26 | const ERROR_CODE_TX_REJECTED_BY_USER = 4001; 27 | 28 | // This component is in charge of doing these things: 29 | // 1. It connects to the user's wallet 30 | // 2. Initializes ethers and the Token contract 31 | // 3. Polls the user balance to keep it updated. 32 | // 4. Transfers tokens by sending transactions 33 | // 5. Renders the whole application 34 | // 35 | // Note that (3) and (4) are specific of this sample application, but they show 36 | // you how to keep your Dapp and contract's state in sync, and how to send a 37 | // transaction. 38 | export class Dapp extends React.Component { 39 | constructor(props) { 40 | super(props); 41 | 42 | // We store multiple things in Dapp's state. 43 | // You don't need to follow this pattern, but it's an useful example. 44 | this.initialState = { 45 | // The info of the token (i.e. It's Name and symbol) 46 | tokenData: undefined, 47 | // The user's address and balance 48 | selectedAddress: undefined, 49 | balance: undefined, 50 | // The ID about transactions being sent, and any possible error with them 51 | txBeingSent: undefined, 52 | transactionError: undefined, 53 | networkError: undefined, 54 | }; 55 | 56 | this.state = this.initialState; 57 | } 58 | 59 | render() { 60 | // Ethereum wallets inject the window.ethereum object. If it hasn't been 61 | // injected, we instruct the user to install a wallet. 62 | if (window.ethereum === undefined) { 63 | return ; 64 | } 65 | 66 | // The next thing we need to do, is to ask the user to connect their wallet. 67 | // When the wallet gets connected, we are going to save the users's address 68 | // in the component's state. So, if it hasn't been saved yet, we have 69 | // to show the ConnectWallet component. 70 | // 71 | // Note that we pass it a callback that is going to be called when the user 72 | // clicks a button. This callback just calls the _connectWallet method. 73 | if (!this.state.selectedAddress) { 74 | return ( 75 | this._connectWallet()} 77 | networkError={this.state.networkError} 78 | dismiss={() => this._dismissNetworkError()} 79 | /> 80 | ); 81 | } 82 | 83 | // If the token data or the user's balance hasn't loaded yet, we show 84 | // a loading component. 85 | if (!this.state.tokenData || !this.state.balance) { 86 | return ; 87 | } 88 | 89 | // If everything is loaded, we render the application. 90 | return ( 91 |
92 |
93 |
94 |

95 | {this.state.tokenData.name} ({this.state.tokenData.symbol}) 96 |

97 |

98 | Welcome {this.state.selectedAddress}, you have{" "} 99 | 100 | {this.state.balance.toString()} {this.state.tokenData.symbol} 101 | 102 | . 103 |

104 |
105 |
106 | 107 |
108 | 109 |
110 |
111 | {/* 112 | Sending a transaction isn't an immediate action. You have to wait 113 | for it to be mined. 114 | If we are waiting for one, we show a message here. 115 | */} 116 | {this.state.txBeingSent && ( 117 | 118 | )} 119 | 120 | {/* 121 | Sending a transaction can fail in multiple ways. 122 | If that happened, we show a message here. 123 | */} 124 | {this.state.transactionError && ( 125 | this._dismissTransactionError()} 128 | /> 129 | )} 130 |
131 |
132 | 133 |
134 |
135 | {/* 136 | If the user has no tokens, we don't show the Transfer form 137 | */} 138 | {this.state.balance.eq(0) && ( 139 | 140 | )} 141 | 142 | {/* 143 | This component displays a form that the user can use to send a 144 | transaction and transfer some tokens. 145 | The component doesn't have logic, it just calls the transferTokens 146 | callback. 147 | */} 148 | {this.state.balance.gt(0) && ( 149 | 151 | this._transferTokens(to, amount) 152 | } 153 | tokenSymbol={this.state.tokenData.symbol} 154 | /> 155 | )} 156 |
157 |
158 |
159 | ); 160 | } 161 | 162 | componentWillUnmount() { 163 | // We poll the user's balance, so we have to stop doing that when Dapp 164 | // gets unmounted 165 | this._stopPollingData(); 166 | } 167 | 168 | async _connectWallet() { 169 | // This method is run when the user clicks the Connect. It connects the 170 | // dapp to the user's wallet, and initializes it. 171 | 172 | // To connect to the user's wallet, we have to run this method. 173 | // It returns a promise that will resolve to the user's address. 174 | const [selectedAddress] = await window.ethereum.request({ method: 'eth_requestAccounts' }); 175 | 176 | // Once we have the address, we can initialize the application. 177 | 178 | // First we check the network 179 | this._checkNetwork(); 180 | 181 | this._initialize(selectedAddress); 182 | 183 | // We reinitialize it whenever the user changes their account. 184 | window.ethereum.on("accountsChanged", ([newAddress]) => { 185 | this._stopPollingData(); 186 | // `accountsChanged` event can be triggered with an undefined newAddress. 187 | // This happens when the user removes the Dapp from the "Connected 188 | // list of sites allowed access to your addresses" (Metamask > Settings > Connections) 189 | // To avoid errors, we reset the dapp state 190 | if (newAddress === undefined) { 191 | return this._resetState(); 192 | } 193 | 194 | this._initialize(newAddress); 195 | }); 196 | } 197 | 198 | _initialize(userAddress) { 199 | // This method initializes the dapp 200 | 201 | // We first store the user's address in the component's state 202 | this.setState({ 203 | selectedAddress: userAddress, 204 | }); 205 | 206 | // Then, we initialize ethers, fetch the token's data, and start polling 207 | // for the user's balance. 208 | 209 | // Fetching the token data and the user's balance are specific to this 210 | // sample project, but you can reuse the same initialization pattern. 211 | this._initializeEthers(); 212 | this._getTokenData(); 213 | this._startPollingData(); 214 | } 215 | 216 | async _initializeEthers() { 217 | // We first initialize ethers by creating a provider using window.ethereum 218 | this._provider = new ethers.providers.Web3Provider(window.ethereum); 219 | 220 | // Then, we initialize the contract using that provider and the token's 221 | // artifact. You can do this same thing with your contracts. 222 | this._token = new ethers.Contract( 223 | contractAddress.Token, 224 | TokenArtifact.abi, 225 | this._provider.getSigner(0) 226 | ); 227 | } 228 | 229 | // The next two methods are needed to start and stop polling data. While 230 | // the data being polled here is specific to this example, you can use this 231 | // pattern to read any data from your contracts. 232 | // 233 | // Note that if you don't need it to update in near real time, you probably 234 | // don't need to poll it. If that's the case, you can just fetch it when you 235 | // initialize the app, as we do with the token data. 236 | _startPollingData() { 237 | this._pollDataInterval = setInterval(() => this._updateBalance(), 1000); 238 | 239 | // We run it once immediately so we don't have to wait for it 240 | this._updateBalance(); 241 | } 242 | 243 | _stopPollingData() { 244 | clearInterval(this._pollDataInterval); 245 | this._pollDataInterval = undefined; 246 | } 247 | 248 | // The next two methods just read from the contract and store the results 249 | // in the component state. 250 | async _getTokenData() { 251 | const name = await this._token.name(); 252 | const symbol = await this._token.symbol(); 253 | 254 | this.setState({ tokenData: { name, symbol } }); 255 | } 256 | 257 | async _updateBalance() { 258 | const balance = await this._token.balanceOf(this.state.selectedAddress); 259 | this.setState({ balance }); 260 | } 261 | 262 | // This method sends an ethereum transaction to transfer tokens. 263 | // While this action is specific to this application, it illustrates how to 264 | // send a transaction. 265 | async _transferTokens(to, amount) { 266 | // Sending a transaction is a complex operation: 267 | // - The user can reject it 268 | // - It can fail before reaching the ethereum network (i.e. if the user 269 | // doesn't have ETH for paying for the tx's gas) 270 | // - It has to be mined, so it isn't immediately confirmed. 271 | // Note that some testing networks, like Hardhat Network, do mine 272 | // transactions immediately, but your dapp should be prepared for 273 | // other networks. 274 | // - It can fail once mined. 275 | // 276 | // This method handles all of those things, so keep reading to learn how to 277 | // do it. 278 | 279 | try { 280 | // If a transaction fails, we save that error in the component's state. 281 | // We only save one such error, so before sending a second transaction, we 282 | // clear it. 283 | this._dismissTransactionError(); 284 | 285 | // We send the transaction, and save its hash in the Dapp's state. This 286 | // way we can indicate that we are waiting for it to be mined. 287 | const tx = await this._token.transfer(to, amount); 288 | this.setState({ txBeingSent: tx.hash }); 289 | 290 | // We use .wait() to wait for the transaction to be mined. This method 291 | // returns the transaction's receipt. 292 | const receipt = await tx.wait(); 293 | 294 | // The receipt, contains a status flag, which is 0 to indicate an error. 295 | if (receipt.status === 0) { 296 | // We can't know the exact error that made the transaction fail when it 297 | // was mined, so we throw this generic one. 298 | throw new Error("Transaction failed"); 299 | } 300 | 301 | // If we got here, the transaction was successful, so you may want to 302 | // update your state. Here, we update the user's balance. 303 | await this._updateBalance(); 304 | } catch (error) { 305 | // We check the error code to see if this error was produced because the 306 | // user rejected a tx. If that's the case, we do nothing. 307 | if (error.code === ERROR_CODE_TX_REJECTED_BY_USER) { 308 | return; 309 | } 310 | 311 | // Other errors are logged and stored in the Dapp's state. This is used to 312 | // show them to the user, and for debugging. 313 | console.error(error); 314 | this.setState({ transactionError: error }); 315 | } finally { 316 | // If we leave the try/catch, we aren't sending a tx anymore, so we clear 317 | // this part of the state. 318 | this.setState({ txBeingSent: undefined }); 319 | } 320 | } 321 | 322 | // This method just clears part of the state. 323 | _dismissTransactionError() { 324 | this.setState({ transactionError: undefined }); 325 | } 326 | 327 | // This method just clears part of the state. 328 | _dismissNetworkError() { 329 | this.setState({ networkError: undefined }); 330 | } 331 | 332 | // This is an utility method that turns an RPC error into a human readable 333 | // message. 334 | _getRpcErrorMessage(error) { 335 | if (error.data) { 336 | return error.data.message; 337 | } 338 | 339 | return error.message; 340 | } 341 | 342 | // This method resets the state 343 | _resetState() { 344 | this.setState(this.initialState); 345 | } 346 | 347 | async _switchChain() { 348 | const chainIdHex = `0x${HARDHAT_NETWORK_ID.toString(16)}` 349 | await window.ethereum.request({ 350 | method: "wallet_switchEthereumChain", 351 | params: [{ chainId: chainIdHex }], 352 | }); 353 | await this._initialize(this.state.selectedAddress); 354 | } 355 | 356 | // This method checks if the selected network is Localhost:8545 357 | _checkNetwork() { 358 | if (window.ethereum.networkVersion !== HARDHAT_NETWORK_ID) { 359 | this._switchChain(); 360 | } 361 | } 362 | } 363 | --------------------------------------------------------------------------------