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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------