├── .gitattributes ├── .gitignore ├── README.md ├── TUTORIAL.md ├── contracts └── Greeter.sol ├── deploy └── Greeter.ts ├── example_contracts └── Token.sol ├── frontend ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.css │ ├── App.test.tsx │ ├── App.tsx │ ├── components │ │ └── Greeter.tsx │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ ├── react-app-env.d.ts │ ├── serviceWorker.ts │ └── setupTests.ts ├── tsconfig.json └── yarn.lock ├── hardhat.config.ts ├── package-lock.json ├── package.json ├── scripts └── sample-script.js ├── test └── sample-test.ts └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | #hardhat files 4 | cache 5 | artifacts 6 | frontend/src/hardhat 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Get started 2 | 3 | 1. Clone the repo and cd into it `git clone https://github.com/symfoni/hardhat-react-boilerplate.git MyProject && cd MyProject` 4 | 2. Install deps with yarn `yarn` or npm `npm install` 5 | 3. Start hardhat `npx hardhat node --watch` 6 | 7 | ![](https://media.giphy.com/media/9l6z9MzXfHX9gKzbvU/giphy.gif) 8 | 9 | ```text 10 | It runs up a Hardhat node, compile contracts, generates typescript interfaces, creates React context and instantiates your contract instances and factories with frontend provider. 11 | ``` 12 | 13 | 4. Open up a new terminal 14 | 5. Enter the frontend directory: `cd frontend` 15 | 6. Install dependencies: `npm install` 16 | 7. Import seed phrase in Metamask. The default mnemonic currently used by hardhat is `test test test test test test test test test test test junk` 17 | 1. Please note that you need to sign out from your current Metamask wallet to import a new one. **Instead of logging out**, you can use a new browser profile to do your Ethereum development: 18 | 3. Click your profile icon in the top right corner of Chrome (right next to the hamburger menu icon) 19 | 4. Click "Add" 20 | 5. Give the profile a name and click "Add" 21 | 6. In this new browser window, install Metamask and import the keyphrase above 22 | 8. Ensure Metamask RPC is set to `http://localhost:8545` and chainID `31337`. 23 | 9. Start the React app: `npm start` 24 | 25 | The frontend should open at http://localhost:3000/ 26 | 27 | Because of this default hardhat.config.ts it will first try to connect with an injected provider like Metamask (web3modal package does this). 28 | 29 | If nothing found it will try to connect with your hardhat node. On localhost and hardhat nodes it will inject your mnemonic into the frontend so you have a "browser wallet" that can both call and send transactions. NB! Dont ever put a mnemonic with actual value here. 30 | 31 | In hardhat.config.ts there is example on how to instruct your hardhat-network to use mnemonic or privatekey. 32 | 33 | ```ts 34 | const config: HardhatUserConfig = { 35 | react: { 36 | providerPriority: ["web3modal", "hardhat"], 37 | }, 38 | }; 39 | ``` 40 | 41 | Ensure you are useing RPC to http://localhost:8545. 42 | 43 | You may also need to set the chainID to 31337 if you are useing Hardhat blockchain development node. 44 | 45 | ## Invalid nonce. 46 | 47 | ```bash 48 | eth_sendRawTransaction 49 | Invalid nonce. Expected X but got X. 50 | ``` 51 | 52 | Reset your account in Metamask. 53 | 54 | # We ❤️ these **Ethereum** projects: 55 | 56 | - [Hardhat 👷](https://hardhat.org/) 57 | - [Hardhat-deploy 🤘](https://hardhat.org/plugins/hardhat-deploy.html) 58 | - [Typechain 🔌](https://github.com/ethereum-ts/Typechain#readme) 59 | - [hardhat-typechain 🧙‍♀️](https://hardhat.org/plugins/hardhat-typechain.html) 60 | - [ethers.js v5 ⺦](https://github.com/ethers-io/ethers.js#readme) 61 | - [web3modal 💸](https://github.com/Web3Modal/web3modal#web3modal) 62 | - [ts-morph 🏊‍♂️](https://github.com/dsherret/ts-morph) 63 | - [@symfoni/hardhat-react 🎻(our own)](https://www.npmjs.com/package/@symfoni/hardhat-react) 64 | -------------------------------------------------------------------------------- /TUTORIAL.md: -------------------------------------------------------------------------------- 1 | ### We ❤️ these **Ethereum** projects: 2 | 3 | - [Buidler](https://buidler.dev/) with the Deploy plugin 👷 4 | - [Typechain 🔌](https://github.com/ethereum-ts/Typechain#readme) 5 | - [ethers.js v5](https://github.com/ethers-io/ethers.js#readme) 6 | - [web3modal](https://github.com/Web3Modal/web3modal#web3modal) 7 | 8 | 😩 Deployment and testing could be sooo tedious before. With these tools, we get Ethereum-projects which are easy to develop, manage, and maintain. 9 | 10 | 🤔 It's still kind of tedious, though. We have to find where every contract is deployed and connect them to the correct Typescript class before exporting it. 11 | 12 | 🤩 What if we wired all of this together and automatically generated the Typescript files, completely instantialized based on your smart contracts? What if all of these tools worked together in harmony? 13 | 14 | ### Enter our submission to the EthOnline 2020 hackathon! 15 | 16 | # 🎻 Symfoni 17 | 18 | > The tooling aggregator that makes your development process purr 😻 19 | 20 | We wish to improve the output of Buidler(, and include Textile). You, as a developer, drop in your solidity code, and we generate a React app with: 21 | 22 | - pluggable react context with contract loading 23 | - web3modal 24 | - ethers v5 25 | - typed interfaces (!) 26 | - (storage context with easy access to Textile) 27 | 28 | > 🙈 Didn't get the time to include Textile (yet) 29 | 30 | ![](https://ethglobal.s3.amazonaws.com/rec9bgGRjbJFSIGFF/MicrosoftTeams-image.png) 31 | 32 | ### Team 33 | 34 | [🇳🇴 Robin Pedersen](https://github.com/RobertoSnap), [🇳🇴 Jon Ramvi](https://github.com/ramvi/) and [ 🇩🇪 Hendrik Bilges](https://github.com/elektronaut0815) 35 | 36 | ## Getting started with Symfoni 37 | 38 | This tutorial will get you up and running with the Greeter contract from the Buidler sample project. When you've done this once, you should have a feel for how to make your Đapps easily. 39 | 40 | ### OS Support 41 | 42 | - MacOS 43 | - Linux 44 | - _The project has only been tested on MacOS and Linux. It should, in theory, work on Windows too. Don't hesitate to reach out if you experience problems._ 45 | 46 | ## ⚙️ Setup new project 47 | 48 | - Create an empty directory for the project, enter and initialize it: 49 | 50 | `mkdir mySymfoniProject && cd mySymfoniProject && npm init -y` 51 | 52 | - Use Buidler to create a project: 53 | 54 | `npx @nomiclabs/buidler` 55 | 56 | - If you want to test Symfoni, select the `Create a sample project`. This will give you a sample smart contract to play with. **Note that you must choose this if you wish to follow this tutorial all the way through.** 57 | 58 | - If you do have project files to include, choose `Create an empty buidler.config.js` and copy/paste those solidity files into /contracts folder. 59 | 60 | - Add Chai for testing in the dev environment: 61 | 62 | `npm add --save-dev chai @types/node @types/mocha @types/chai` 63 | 64 | - Create a deploy folder in the root: 65 | 66 | `mkdir deploy` 67 | 68 | - And [create a deployment file for each of your smart contracts](https://buidler.dev/plugins/buidler-deploy.html#deploy-scripts). If you chose to make a sample project in the first step, we have created a simple deploy script that you can use: 69 | 70 | ```bash 71 | echo 'import { 72 | BuidlerRuntimeEnvironment, 73 | DeployFunction, 74 | } from "@nomiclabs/buidler/types"; 75 | 76 | const func: DeployFunction = async function(bre: BuidlerRuntimeEnvironment) { 77 | const { deploy } = bre.deployments; 78 | const { deployer } = await bre.getNamedAccounts(); 79 | await deploy("Greeter", { 80 | from: deployer, 81 | args: ["Let us play a Symfoni 🎻"], 82 | }); 83 | }; 84 | export default func;' > deploy/Greeter.ts 85 | ``` 86 | 87 | ### Now let's add the Symfoni magic ✨ 88 | 89 | - Up until now, this is all just a regular Buidler project. Now run this command to add our packages, including dependencies: 90 | 91 | ```bash 92 | npm add @nomiclabs/buidler @symfoni/buidler-react @symfoni/buidler-typechain @typechain/ethers-v5 buidler-deploy@next buidler-ethers-v5 ethers ts-generator ts-node typechain typescript 93 | ``` 94 | 95 | > If asked what version of buidler-deploy to install, choose 0.6.0-beta.35 96 | 97 | - Convert the project to a Buidler Typescript project by overwriting Buidler with the Symfoni configuration files: 98 | 99 | ```bash 100 | rm buidler.config.js 101 | cp node_modules/@symfoni/buidler-react/defaults/buidler.config.default.ts buidler.config.ts 102 | cp node_modules/@symfoni/buidler-react/defaults/tsconfig.default.json tsconfig.json 103 | ``` 104 | 105 | ## 🎨 Let's create the front-end 106 | 107 | ### Metamask 108 | 109 | Let's start by setting up Metamask with our test network and wallet. First, we need to give ourself some test-eth. 110 | 111 | - Open the Buidler config file `buidler.config.ts` and add the test network beneath the solc version. The complete file should look like this: 112 | 113 | ```typescript= 114 | import { BuidlerConfig, usePlugin } from "@nomiclabs/buidler/config"; 115 | 116 | usePlugin("buidler-ethers-v5"); 117 | usePlugin("buidler-deploy"); 118 | usePlugin("@symfoni/buidler-typechain"); 119 | usePlugin("@symfoni/buidler-react"); 120 | 121 | const config: BuidlerConfig = { 122 | solc: { 123 | version: "0.6.8", 124 | }, 125 | networks: { 126 | buidlerevm: { 127 | accounts: [ 128 | { 129 | balance: "0x1B1AE4D6E2EF500000", //5000 130 | privateKey: 131 | "0x50228cca6dd3264c74713855801d16e63a2b0e42e86fa374562316a629d03a30", 132 | }, 133 | ], 134 | }, 135 | }, 136 | }; 137 | 138 | export default config; 139 | ``` 140 | 141 | - In [Metamask](https://metamask.io/), use the test wallet mnemonic phrase: 142 | 143 | ``` 144 | shrug antique orange tragic direct drop abstract ring carry price anchor train 145 | ``` 146 | 147 | - Still in Metamask, change the network to `Localhost 8545` 148 | 149 | ### Generate the React app: 150 | 151 | - Use "Create react app" to generate a Typescript app 152 | 153 | `npx create-react-app frontend --template typescript` 154 | 155 | - Now run the "back-end" by executing `npx buidler node --watch --reset`. This will 156 | 157 | - start a development chain 158 | - compile and deploy the contracts 159 | - generate the type interfaces for the contracts, and 160 | - generate a React context 161 | 162 | - Next, open a new terminal window and go into the front-end folder: 163 | 164 | `cd frontend/` 165 | 166 | - Here we need to add some dependencies: 167 | 168 | > Note that the "Create React app" comes with an old version of Typescript, which is not compatible with Typechain, so let's also ensure Typescript is fixed at v3.9.7. Please note that v4 does not work. 169 | 170 | `npm add web3modal ethers typescript@^3.9.7` 171 | 172 | - Do a npm install: 173 | 174 | `npm i` 175 | 176 | - Serve the generated front-end application: 177 | 178 | `npm run start` 179 | 180 | > A browser should start with the default "Create react app" webpage. 181 | 182 | - Now, let's add the Buidler context to the front-end. Open `/frontend/src/App.tsx` in your preferred code editor. 183 | 184 | - Add the React context generated by Symfoni to the imports on the top of the file. If you're using the Sample Project, it should be under `import './App.css';` 185 | 186 | ```typescript 187 | import { BuidlerContext } from "./buidler/BuidlerContext"; 188 | ``` 189 | 190 | - Wrap your app in this context to have it available to use in any children: ``. In the Sample project, it should look like this: 191 | 192 | ```typescript= 193 |
194 | 195 |
196 | ``` 197 | 198 | - Please accept the connection request in the Metamask pop-up 199 | 200 | - Let's now create a component that consumes a smart contract and gives us some results. From the Buidler sample project, we have a smart contract called `Greeter.sol`. Open up a third terminal window, create a directory for components, and create a `tsx` file for the view of the smart contract: 201 | 202 | `mkdir src/components && touch src/components/Greeter.tsx` 203 | 204 | - Open the file in your favorite editor and copy & paste: 205 | 206 | ```typescript= 207 | import React, { useContext, useEffect, useState } from "react"; 208 | import { GreeterContext } from "./../buidler/BuidlerContext"; 209 | 210 | interface Props {} 211 | 212 | export const Greeter: React.FC = () => { 213 | const greeter = useContext(GreeterContext); 214 | const [message, setMessage] = useState(""); 215 | useEffect(() => { 216 | const doAsync = async () => { 217 | if (greeter.instance) { 218 | console.log("Greeter is deployed at ", greeter.instance.address); 219 | setMessage(await greeter.instance.greet()); 220 | } 221 | }; 222 | doAsync(); 223 | }, [greeter]); 224 | return ( 225 |
226 |

{message}

227 |
228 | ); 229 | }; 230 | ``` 231 | 232 | Or write this yourself and experience typed smart-contracts 233 | ![](https://media.giphy.com/media/w5KyWv8CCcfaB6vM1B/giphy.gif) 234 | 235 | Note that the Greeter context both provides you with 236 | 237 | - a contract which contains all your functions, events and info, and 238 | - a factory where you can let users quickly deploy new contract instances from the front-end. 239 | 240 | Let's import and display this component in our app, which leaves our `App.tsx` like this: 241 | 242 | ```typescript= 243 | import React from 'react'; 244 | import logo from './logo.svg'; 245 | import './App.css'; 246 | import { BuidlerContext } from "./buidler/BuidlerContext"; 247 | import { Greeter } from './components/Greeter'; // ADD THIS LINE 248 | 249 | function App() { 250 | 251 | return ( 252 |
253 |
254 | 255 | logo 256 |

257 | Edit src/App.tsx and save to reload. 258 |

259 | 265 | Learn React 266 | 267 | 268 |
269 |
270 |
271 | ); 272 | } 273 | 274 | export default App; 275 | ``` 276 | 277 | > The buidler context will now ask `web3modal` for an injected provider. If it finds a provider, you should see the Greeting from the smart contract in the browser and the address the contract is deployed at in the console. 278 | 279 | [📚 You can see a full working example of the Greeter sample here.](https://github.com/symfoni/buidler-react-boilerplate) 280 | 281 | **Thanks for completing our tutorial! 🥳** 282 | 283 | We hope you can use this to write Ethereum applications more efficiently 📈 . If you have any feedback, good or bad, please don't hesitate to ping us at [@\_robertosnap](https://twitter.com/_robertosnap/)🐦 or with an Issue here on Github. 284 | 285 | # Tutorial 2: Create an ERC20 token with Symfoni 286 | 287 | This tutorial aims to create a view where the user can create new ERC20 tokens using a web app and how easy it is to do with Symfoni 🎻. 288 | 289 | - In your third terminal window from the tutorial above, go back to the project root directory 290 | 291 | `cd ..` 292 | 293 | - We can use the ERC20 code from OpenZeppelin. Install the OpenZeppelin codebase: 294 | 295 | `npm i --save @openzeppelin/contracts` 296 | 297 | - Now, we create our own ERC20, which inherits its code from OpenZeppelin. Our ERC20 token takes the following inputs 298 | - Name of the new token 299 | - Symbol, i.e., a shorthand for the token name, and 300 | - Amount; the number of tokens to mint and return to the token creator 301 | 302 | ```bash 303 | echo '//SPDX-License-Identifier: Unlicense 304 | pragma solidity ^0.6.8; 305 | 306 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 307 | 308 | contract Token is ERC20 { 309 | constructor( 310 | string memory name, 311 | string memory symbol, 312 | uint256 amount 313 | ) public ERC20(name, symbol) { 314 | _mint(msg.sender, amount); 315 | } 316 | }' > contracts/Token.sol 317 | ``` 318 | 319 | > Note that since the user will deploy the new tokens, we do not need Buidler to deploy anything. Hence we don't need a `deploy/Token.ts` file, like we did with the greeter. 320 | 321 | - Restart Buidler to have Symfoni generate the typed React context for the contract. In your first terminal window, from the tutorial above, hit `Ctrl+c`, `arrow up`, and hit `enter`. 322 | 323 | - Let's create a simple view for creating new tokens with only a text field and a submit button. In this component, we import 324 | - our TokenContext where we will use its factory to deploy it 325 | - the signer context to get access to the user's wallet 326 | 327 | ```bash 328 | echo ' 329 | import { ethers } from "ethers"; 330 | import React, { useContext, useState } from "react"; 331 | import { SignerContext, TokenContext } from "./../buidler/BuidlerContext"; 332 | interface Props { } 333 | 334 | export const MyToken: React.FC = () => { 335 | const token = useContext(TokenContext) 336 | const [signer] = useContext(SignerContext) 337 | const [inputName, setInputName] = useState(""); 338 | 339 | 340 | const deployToken = async (e: React.MouseEvent) => { 341 | e.preventDefault() 342 | if (!token.factory) throw Error("Could not get token factory") 343 | if (!signer) throw Error("Could not get signer") 344 | const symbol = inputName.substr(0, 3).toUpperCase(); 345 | const amount = ethers.utils.parseEther("5000") 346 | const myAddress = await signer.getAddress() 347 | 348 | const myToken = await token.factory.deploy(inputName, symbol, amount) 349 | await myToken.deployed() 350 | 351 | const currentBalance = await myToken.balanceOf(myAddress) 352 | console.log("My current balance is ", ethers.utils.formatEther(currentBalance)) 353 | } 354 | return ( 355 |
356 | setInputName(e.target.value)}> 357 | 358 |
359 | ) 360 | }' > frontend/src/components/MyToken.tsx 361 | ``` 362 | 363 | - Import and display the MyToken component in `App.tsx`: 364 | 365 | ```typescript 366 | import React from 'react'; 367 | import logo from './logo.svg'; 368 | import './App.css'; 369 | import { BuidlerContext } from "./buidler/BuidlerContext"; 370 | import { Greeter } from './components/Greeter'; 371 | import { MyToken } from './components/MyToken'; // ADD THIS LINE 372 | 373 | function App() { 374 | return ( 375 |
376 |
377 | 378 | logo 379 |

380 | Edit src/App.tsx and save to reload. 381 |

382 | 388 | Learn React 389 | 390 | 391 | 392 |
393 |
394 |
395 | ); 396 | } 397 | 398 | export default App; 399 | ``` 400 | 401 | ### What am I seeing? 👀 402 | 403 | In `frontend/src/components/MyToken.tsx` the smart contract is available like any regular typed object: 404 | 405 | ![](https://i.imgur.com/HIceyBc.gif) 406 | 407 | We check our balance through the myToken.balanceOf() function. We see that the return value is a BigNumber, so before outputting it to console, we format it with ethers utils. 408 | 409 | Let's name our contract and deploy it. Our wallet provider should pop up with confirmations to deploy and transfer this new token. 410 | 411 | ![](https://i.imgur.com/Ne7DMQx.gif) 412 | 413 | It couldn't be easier! 414 | 415 | Thanks again for completing our two tutorials and checking out our submission to the EthOnline 2020 hackathon! 416 | 417 | [🙌 You can see a full working example of the ERC20 project here.](https://github.com/symfoni/buidler-react-boilerplate/tree/bonus-erc20) 418 | 419 | # Troubleshooting 420 | 421 | - Some problem? 422 | - Try running `npm cache verify` and start again from the top 423 | 424 | # Where to go from here 425 | 426 | - Use Symfoni for your smart contracts, and start developing zero-friction Ethereum applications 🚀 427 | -------------------------------------------------------------------------------- /contracts/Greeter.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.7.0; 3 | 4 | import "hardhat/console.sol"; 5 | 6 | contract Greeter { 7 | string greeting; 8 | 9 | constructor(string memory _greeting) { 10 | console.log("Deploying a Greeter with greeting:", _greeting); 11 | greeting = _greeting; 12 | } 13 | 14 | function greet() public view returns (string memory) { 15 | return greeting; 16 | } 17 | 18 | function setGreeting(string memory _greeting) public { 19 | console.log("Changing greeting from '%s' to '%s'", greeting, _greeting); 20 | greeting = _greeting; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /deploy/Greeter.ts: -------------------------------------------------------------------------------- 1 | module.exports = async ({ 2 | getNamedAccounts, 3 | deployments, 4 | getChainId, 5 | getUnnamedAccounts, 6 | }) => { 7 | const { deploy } = deployments; 8 | const { deployer } = await getNamedAccounts(); 9 | 10 | // the following will only deploy "GenericMetaTxProcessor" if the contract was never deployed or if the code changed since last deployment 11 | await deploy("Greeter", { 12 | from: deployer, 13 | // gas: 4000000, 14 | args: ["Greeting set from ./deploy/Greeter.ts"], 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /example_contracts/Token.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | // Add Solidity compiler 0.6.2 to you hardhat.config.ts 3 | pragma solidity ^0.6.2; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | contract Token is ERC20 { 8 | constructor( 9 | string memory name, 10 | string memory symbol, 11 | uint256 amount 12 | ) public ERC20(name, symbol) { 13 | _mint(msg.sender, amount); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | /src/buidler -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Get started 2 | 3 | 1. Clone the repo and cd into it `git clone https://github.com/symfoni/hardhat-react-boilerplate.git MyProject && cd MyProject` 4 | 2. Install deps with yarn `yarn` or npm `npm install` 5 | 3. Start hardhat `npx hardhat node --watch` 6 | ![](https://media.giphy.com/media/9l6z9MzXfHX9gKzbvU/giphy.gif) 7 | 8 | ```text 9 | It runs up a Hardhat node, compile contracts, generates typescript interfaces, creates React context and instantiates your contract instances and factories with frontend provider. 10 | ``` 11 | 12 | 4. Open up a new terminal 13 | 5. `cd frontend` 14 | 6. Install deps with yarn `yarn` or npm `npm install` 15 | 7. Start React app with yarn `yarn start` or npm `npm start` 16 | 17 | The frontend should start up at http://localhost:3000/. 18 | 19 | Because of this default hardhat.config.ts it will first try to connect with an injected provider like Metamask (web3modal package does this). 20 | 21 | If nothing found it will try to connect with your hardhat node. On localhost and hardhat nodes it will inject your mnemonic into the frontend so you have a "browser wallet" that can both call and send transactions. NB! Dont ever put a mnemonic with actual value here. We will limit this feature going forward so its more explicit. 22 | 23 | ```ts 24 | const config: HardhatUserConfig = { 25 | react: { 26 | providerPriority: ["web3modal", "hardhat"], 27 | }, 28 | }; 29 | ``` 30 | 31 | The default mnemonic currently used by hardhat is `test test test test test test test test test test test junk` 32 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^4.2.4", 7 | "@testing-library/react": "^9.3.2", 8 | "@testing-library/user-event": "^7.1.2", 9 | "@types/jest": "^24.0.0", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^16.9.0", 12 | "@types/react-dom": "^16.9.0", 13 | "ethers": "^5.0.17", 14 | "react": "^16.14.0", 15 | "react-dom": "^16.14.0", 16 | "react-scripts": "3.4.3", 17 | "typescript": "^3.9.7", 18 | "web3modal": "^1.9.1" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": "react-app" 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.2%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | }, 41 | "devDependencies": { 42 | "@ethersproject/contracts": "^5.4.1", 43 | "@ethersproject/providers": "^5.4.3" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wittyCodeX/hardhat-react/d365dcb5626ecd14738e1e748ab7adaee4d223e2/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wittyCodeX/hardhat-react/d365dcb5626ecd14738e1e748ab7adaee4d223e2/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wittyCodeX/hardhat-react/d365dcb5626ecd14738e1e748ab7adaee4d223e2/frontend/public/logo512.png -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App 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": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import logo from './logo.svg'; 3 | import './App.css'; 4 | import { Symfoni } from "./hardhat/SymfoniContext"; 5 | import { Greeter } from './components/Greeter'; 6 | 7 | function App() { 8 | 9 | return ( 10 |
11 |
12 | 13 | logo 14 |

15 | Edit src/App.tsx and save to reload. 16 |

17 | 23 | Learn React 24 | 25 | 26 |
27 |
28 |
29 | ); 30 | } 31 | 32 | export default App; 33 | -------------------------------------------------------------------------------- /frontend/src/components/Greeter.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from "react"; 2 | import { GreeterContext } from "./../hardhat/SymfoniContext"; 3 | 4 | interface Props {} 5 | 6 | export const Greeter: React.FC = () => { 7 | const greeter = useContext(GreeterContext); 8 | const [message, setMessage] = useState(""); 9 | const [inputGreeting, setInputGreeting] = useState(""); 10 | useEffect(() => { 11 | const doAsync = async () => { 12 | if (!greeter.instance) return; 13 | console.log("Greeter is deployed at ", greeter.instance.address); 14 | setMessage(await greeter.instance.greet()); 15 | }; 16 | doAsync(); 17 | }, [greeter]); 18 | 19 | const handleSetGreeting = async ( 20 | e: React.MouseEvent 21 | ) => { 22 | e.preventDefault(); 23 | if (!greeter.instance) throw Error("Greeter instance not ready"); 24 | if (greeter.instance) { 25 | const tx = await greeter.instance.setGreeting(inputGreeting); 26 | console.log("setGreeting tx", tx); 27 | await tx.wait(); 28 | const _message = await greeter.instance.greet(); 29 | console.log("New greeting mined, result: ", _message); 30 | setMessage(_message); 31 | setInputGreeting(""); 32 | } 33 | }; 34 | return ( 35 |
36 |

{message}

37 | setInputGreeting(e.target.value)} 40 | > 41 | 42 |
43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want your app to work offline and load faster, you can change 15 | // unregister() to register() below. Note this comes with some pitfalls. 16 | // Learn more about service workers: https://bit.ly/CRA-PWA 17 | serviceWorker.unregister(); 18 | -------------------------------------------------------------------------------- /frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /frontend/src/serviceWorker.ts: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | type Config = { 24 | onSuccess?: (registration: ServiceWorkerRegistration) => void; 25 | onUpdate?: (registration: ServiceWorkerRegistration) => void; 26 | }; 27 | 28 | export function register(config?: Config) { 29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 30 | // The URL constructor is available in all browsers that support SW. 31 | const publicUrl = new URL( 32 | process.env.PUBLIC_URL, 33 | window.location.href 34 | ); 35 | if (publicUrl.origin !== window.location.origin) { 36 | // Our service worker won't work if PUBLIC_URL is on a different origin 37 | // from what our page is served on. This might happen if a CDN is used to 38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 39 | return; 40 | } 41 | 42 | window.addEventListener('load', () => { 43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 44 | 45 | if (isLocalhost) { 46 | // This is running on localhost. Let's check if a service worker still exists or not. 47 | checkValidServiceWorker(swUrl, config); 48 | 49 | // Add some additional logging to localhost, pointing developers to the 50 | // service worker/PWA documentation. 51 | navigator.serviceWorker.ready.then(() => { 52 | console.log( 53 | 'This web app is being served cache-first by a service ' + 54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 55 | ); 56 | }); 57 | } else { 58 | // Is not localhost. Just register service worker 59 | registerValidSW(swUrl, config); 60 | } 61 | }); 62 | } 63 | } 64 | 65 | function registerValidSW(swUrl: string, config?: Config) { 66 | navigator.serviceWorker 67 | .register(swUrl) 68 | .then(registration => { 69 | registration.onupdatefound = () => { 70 | const installingWorker = registration.installing; 71 | if (installingWorker == null) { 72 | return; 73 | } 74 | installingWorker.onstatechange = () => { 75 | if (installingWorker.state === 'installed') { 76 | if (navigator.serviceWorker.controller) { 77 | // At this point, the updated precached content has been fetched, 78 | // but the previous service worker will still serve the older 79 | // content until all client tabs are closed. 80 | console.log( 81 | 'New content is available and will be used when all ' + 82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 83 | ); 84 | 85 | // Execute callback 86 | if (config && config.onUpdate) { 87 | config.onUpdate(registration); 88 | } 89 | } else { 90 | // At this point, everything has been precached. 91 | // It's the perfect time to display a 92 | // "Content is cached for offline use." message. 93 | console.log('Content is cached for offline use.'); 94 | 95 | // Execute callback 96 | if (config && config.onSuccess) { 97 | config.onSuccess(registration); 98 | } 99 | } 100 | } 101 | }; 102 | }; 103 | }) 104 | .catch(error => { 105 | console.error('Error during service worker registration:', error); 106 | }); 107 | } 108 | 109 | function checkValidServiceWorker(swUrl: string, config?: Config) { 110 | // Check if the service worker can be found. If it can't reload the page. 111 | fetch(swUrl, { 112 | headers: { 'Service-Worker': 'script' } 113 | }) 114 | .then(response => { 115 | // Ensure service worker exists, and that we really are getting a JS file. 116 | const contentType = response.headers.get('content-type'); 117 | if ( 118 | response.status === 404 || 119 | (contentType != null && contentType.indexOf('javascript') === -1) 120 | ) { 121 | // No service worker found. Probably a different app. Reload the page. 122 | navigator.serviceWorker.ready.then(registration => { 123 | registration.unregister().then(() => { 124 | window.location.reload(); 125 | }); 126 | }); 127 | } else { 128 | // Service worker found. Proceed as normal. 129 | registerValidSW(swUrl, config); 130 | } 131 | }) 132 | .catch(() => { 133 | console.log( 134 | 'No internet connection found. App is running in offline mode.' 135 | ); 136 | }); 137 | } 138 | 139 | export function unregister() { 140 | if ('serviceWorker' in navigator) { 141 | navigator.serviceWorker.ready 142 | .then(registration => { 143 | registration.unregister(); 144 | }) 145 | .catch(error => { 146 | console.error(error.message); 147 | }); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /frontend/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "react" 21 | }, 22 | "include": [ 23 | "src" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig, task } from "hardhat/config"; 2 | import "@nomiclabs/hardhat-waffle"; 3 | import "@nomiclabs/hardhat-ethers"; 4 | import "hardhat-deploy-ethers"; 5 | import "hardhat-deploy"; 6 | import "@symfoni/hardhat-react"; 7 | import "hardhat-typechain"; 8 | import "@typechain/ethers-v5"; 9 | 10 | // This is a sample Hardhat task. To learn how to create your own go to 11 | // https://hardhat.org/guides/create-task.html 12 | task("accounts", "Prints the list of accounts", async (args, hre) => { 13 | const accounts = await hre.ethers.getSigners(); 14 | for (const account of accounts) { 15 | console.log(account.address); 16 | } 17 | }); 18 | 19 | // You need to export an object to set up your config 20 | // Go to https://hardhat.org/config/ to learn more 21 | 22 | /** 23 | * @type import('hardhat/config').HardhatUserConfig 24 | */ 25 | const config: HardhatUserConfig = { 26 | react: { 27 | providerPriority: ["web3modal", "hardhat"], 28 | }, 29 | networks: { 30 | hardhat: { 31 | chainId: 1337, 32 | inject: false, // optional. If true, it will EXPOSE your mnemonic in your frontend code. Then it would be available as an "in-page browser wallet" / signer which can sign without confirmation. 33 | accounts: { 34 | mnemonic: "test test test test test test test test test test test junk", // test test test test test test test test test test test junk 35 | }, 36 | }, 37 | // hardhat: { 38 | // accounts: [ 39 | // { 40 | // balance: "10000000000000000000000", 41 | // privateKey: 42 | // "0xe87d780e4c31c953a68aef2763df56599c9cfe73df4740fc24c2d0f5acd21bae", 43 | // }, 44 | // ], 45 | // }, 46 | }, 47 | solidity: { 48 | compilers: [ 49 | { 50 | version: "0.7.3", 51 | settings: { 52 | optimizer: { 53 | enabled: true, 54 | runs: 50, 55 | }, 56 | }, 57 | }, 58 | ], 59 | }, 60 | }; 61 | export default config; 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hardhat-project", 3 | "devDependencies": { 4 | "@nomiclabs/hardhat-ethers": "^2.0.0", 5 | "@nomiclabs/hardhat-waffle": "^2.0.0", 6 | "@openzeppelin/contracts": "^3.2.0", 7 | "@symfoni/hardhat-react": "^0.1.14", 8 | "@typechain/ethers-v5": "^4.0.0", 9 | "@types/chai": "^4.2.14", 10 | "@types/mocha": "^8.0.4", 11 | "@types/node": "^14.14.9", 12 | "chai": "^4.2.0", 13 | "ethereum-waffle": "^3.2.0", 14 | "ethers": "^5.0.21", 15 | "hardhat": "^2.0.8", 16 | "hardhat-deploy": "^0.7.0-beta.44", 17 | "hardhat-deploy-ethers": "^0.3.0-beta.7", 18 | "hardhat-typechain": "^0.3.4", 19 | "ts-generator": "^0.1.1", 20 | "ts-morph": "^9.0.0", 21 | "ts-node": "^9.0.0", 22 | "typechain": "^4.0.1", 23 | "typescript": "^4.1.2" 24 | }, 25 | "scripts": { 26 | "test": "npx hardhat test", 27 | "node": "npx hardhat node --watch", 28 | "frontend": "cd frontend && npm run start" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/symfoni/hardhat-react-boilerplate.git" 33 | }, 34 | "author": "Robertosnap", 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/symfoni/hardhat-react-boilerplate/issues" 38 | }, 39 | "homepage": "https://github.com/symfoni/hardhat-react-boilerplate#readme", 40 | "dependencies": {} 41 | } 42 | -------------------------------------------------------------------------------- /scripts/sample-script.js: -------------------------------------------------------------------------------- 1 | // We require the Hardhat Runtime Environment explicitly here. This is optional 2 | // but useful for running the script in a standalone fashion through `node