├── README.md ├── backend ├── .env.example ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .prettierignore ├── .solhint.json ├── .solhintignore ├── README.md ├── contracts │ └── ExampleContract.sol ├── hardhat.config.ts ├── package-lock.json ├── package.json ├── scripts │ └── deploy.ts ├── test │ └── index.ts └── tsconfig.json └── frontend ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── .vscode └── settings.json ├── LICENSE ├── __tests__ └── fake_test.ts ├── assets ├── screenshot.png └── template-usage.png ├── commitlint.config.js ├── index.html ├── jest.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── src ├── App.css ├── App.tsx ├── favicon.svg ├── index.css ├── logo.svg ├── main.tsx ├── utils │ └── useGetContract.ts └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.json ├── vite.config.ts └── yarn.lock /README.md: -------------------------------------------------------------------------------- 1 | ![GitHub Banner](https://user-images.githubusercontent.com/40567147/159485872-7f63766a-3c91-48dc-aa37-fb5894232acc.png) 2 | 3 | This starter template comes as a monorepo for your next fullstack dApp Development. This will be your tools: 4 | :pager: React + Vite + Typescript 5 | :page_with_curl: Solidity + Hardhat + Typescript 6 | 7 | Typescript is integrated in the frontend as well in the smart contract part. This gives you a HUGE advantage, why? Because you can use types from the smart contract in your frontend part ( with the help of typechain https://github.com/dethcrypto/TypeChain). 8 | 9 | 10 | ## How to start 11 | 12 | ### Backend 13 | pre: cd into /backend 14 | 15 | 0) start local testnet ---> npm run testnet 16 | 1) Compile contracts ---> npm run build 17 | 2) Test contracts --> npm run test 18 | 3) Deploy contracts --> npm run deploy 19 | 20 | ### Frontend 21 | pre: cd into /frontend 22 | 23 | 1) Install dependencies ---> npm install 24 | 2) start frontend ---> npm run dev 25 | 3) build --> npm run build 26 | 27 | Here are some ready to use IPFS services 📡, that you can easily use for your next project 🚀 28 | 29 | 🔗 https://pinata.cloud 30 | 🔗 https://nft.storage 31 | 🔗 https://docs.moralis.io/moralis-dapp/files/ipfs 32 | 🔗 https://infura.io/product/ipfs 33 | 34 | 🌞 GM TO ALL OF YOU AND KEEP LEARNING WEB 3 -Johannes (https://twitter.com/XamHans) 35 | 36 | PS: If you are looking for a Web3 Job checkout my newest project :green_heart: : https://www.newdevsontheblock.com/ 37 | -------------------------------------------------------------------------------- /backend/.env.example: -------------------------------------------------------------------------------- 1 | ETHERSCAN_API_KEY 2 | ROPSTEN_URL=https://eth-ropsten.alchemyapi.io/v2/ 3 | PRIVATE_KEY 4 | -------------------------------------------------------------------------------- /backend/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage 5 | -------------------------------------------------------------------------------- /backend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: false, 4 | es2021: true, 5 | mocha: true, 6 | node: true, 7 | }, 8 | plugins: ["@typescript-eslint"], 9 | extends: [ 10 | "standard", 11 | "plugin:prettier/recommended", 12 | "plugin:node/recommended", 13 | ], 14 | parser: "@typescript-eslint/parser", 15 | parserOptions: { 16 | ecmaVersion: 12, 17 | }, 18 | rules: { 19 | "node/no-unsupported-features/es-syntax": [ 20 | "error", 21 | { ignores: ["modules"] }, 22 | ], 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | 7 | #Hardhat files 8 | cache 9 | artifacts 10 | -------------------------------------------------------------------------------- /backend/.npmignore: -------------------------------------------------------------------------------- 1 | hardhat.config.ts 2 | scripts 3 | test 4 | -------------------------------------------------------------------------------- /backend/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage* 5 | gasReporterOutput.json 6 | -------------------------------------------------------------------------------- /backend/.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "compiler-version": ["error", "^0.8.0"], 5 | "func-visibility": ["warn", { "ignoreConstructors": true }] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/.solhintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # Advanced Sample Hardhat Project 2 | 3 | This project demonstrates an advanced Hardhat use case, integrating other tools commonly used alongside Hardhat in the ecosystem. 4 | 5 | The project comes with a sample contract, a test for that contract, a sample script that deploys that contract, and an example of a task implementation, which simply lists the available accounts. It also comes with a variety of other tools, preconfigured to work with the project code. 6 | 7 | Try running some of the following tasks: 8 | 9 | ```shell 10 | npx hardhat accounts 11 | npx hardhat compile 12 | npx hardhat clean 13 | npx hardhat test 14 | npx hardhat node 15 | npx hardhat help 16 | REPORT_GAS=true npx hardhat test 17 | npx hardhat coverage 18 | npx hardhat run scripts/deploy.ts 19 | TS_NODE_FILES=true npx ts-node scripts/deploy.ts 20 | npx eslint '**/*.{js,ts}' 21 | npx eslint '**/*.{js,ts}' --fix 22 | npx prettier '**/*.{json,sol,md}' --check 23 | npx prettier '**/*.{json,sol,md}' --write 24 | npx solhint 'contracts/**/*.sol' 25 | npx solhint 'contracts/**/*.sol' --fix 26 | ``` 27 | 28 | # Etherscan verification 29 | 30 | To try out Etherscan verification, you first need to deploy a contract to an Ethereum network that's supported by Etherscan, such as Ropsten. 31 | 32 | In this project, copy the .env.example file to a file named .env, and then edit it to fill in the details. Enter your Etherscan API key, your Ropsten node URL (eg from Alchemy), and the private key of the account which will send the deployment transaction. With a valid .env file in place, first deploy your contract: 33 | 34 | ```shell 35 | hardhat run --network ropsten scripts/sample-script.ts 36 | ``` 37 | 38 | Then, copy the deployment address and paste it in to replace `DEPLOYED_CONTRACT_ADDRESS` in this command: 39 | 40 | ```shell 41 | npx hardhat verify --network ropsten DEPLOYED_CONTRACT_ADDRESS "Hello, Hardhat!" 42 | ``` 43 | 44 | # Performance optimizations 45 | 46 | For faster runs of your tests and scripts, consider skipping ts-node's type checking by setting the environment variable `TS_NODE_TRANSPILE_ONLY` to `1` in hardhat's environment. For more details see [the documentation](https://hardhat.org/guides/typescript.html#performance-optimizations). 47 | -------------------------------------------------------------------------------- /backend/contracts/ExampleContract.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | //specific solidity cersion 3 | pragma solidity ^0.8.7; 4 | // we can use the console.log func from hardhat for debugging (like in javascript) 5 | import "hardhat/console.sol"; 6 | 7 | // openzeppelin provides libaries of different use cases, this one provides a counter with best practices 8 | // a simple way to get a counter that can only be incremented or decremented. Very useful for ID generation, counting contract activity, among others. 9 | import "@openzeppelin/contracts/utils/Counters.sol"; 10 | 11 | contract ExampleContract { 12 | // this is how to define a enum, the values are defined in the {} 13 | enum ExampleEnum{CREATED, OPEN, CLOSED} 14 | 15 | // a struct is like a class but without any implementation logic 16 | struct AccountInfo { 17 | uint id; 18 | string name; 19 | address accountAddress; 20 | } 21 | // Functions in a library can be used by many contracts. If you have many contracts that have some common code, then you can deploy that common code as a library. 22 | // The directive using A for B; can be used to attach library functions of library A to a given type B. These functions will used the caller type as their first parameter (identified using self). 23 | using Counters for Counters.Counter; 24 | Counters.Counter private accountId; 25 | 26 | // mapping (technically a hashtable) stores keys to values (key => value) good for association topics 27 | // this mapping is used to keep track of the ids (number) to the right account address (hash) 28 | mapping ( uint=> AccountInfo ) private accounts; 29 | 30 | // an event is used to let other parties know that something important has happend in your contract 31 | // other contracts or event frontends can subscribe to your events 32 | event accountCreatedEvent(address indexed accountAddress, string name); 33 | 34 | function addAccount(string calldata _name) external { 35 | // require is like an early out criterium, if the condition is false, the function will be stopped and the error message (defined as second parameter) will be returned 36 | // msg. sender is the address who calls the contract 37 | require(msg.sender != address(0x0), "The sender address must exist"); 38 | accountId.increment(); 39 | uint accountId = accountId.current(); 40 | // create new object from type AccountInfo 41 | // memory indicates that the object only lives during the function call, if you want to store it permanently on the blockchain use the storage keyword 42 | address accAddress = address(msg.sender); 43 | AccountInfo memory newAccount = AccountInfo(accountId, _name, accAddress); 44 | accounts[accountId] = newAccount; 45 | // trigger event that a new account has been created 46 | emit accountCreatedEvent(accAddress, _name); 47 | console.log("NEW ACCOUNT ADDED", newAccount.name); 48 | } 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /backend/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv"; 2 | 3 | import { HardhatUserConfig, task } from "hardhat/config"; 4 | import "@nomiclabs/hardhat-etherscan"; 5 | import "@nomiclabs/hardhat-waffle"; 6 | import "@typechain/hardhat"; 7 | import "hardhat-gas-reporter"; 8 | import "solidity-coverage"; 9 | 10 | dotenv.config(); 11 | 12 | // This is a sample Hardhat task. To learn how to create your own go to 13 | // https://hardhat.org/guides/create-task.html 14 | task("accounts", "Prints the list of accounts", async (taskArgs, hre) => { 15 | const accounts = await hre.ethers.getSigners(); 16 | 17 | for (const account of accounts) { 18 | console.log(account.address); 19 | } 20 | }); 21 | 22 | // You need to export an object to set up your config 23 | // Go to https://hardhat.org/config/ to learn more 24 | 25 | const config: HardhatUserConfig = { 26 | solidity: "0.8.7", 27 | networks: { 28 | ropsten: { 29 | url: process.env.ROPSTEN_URL || "", 30 | accounts: 31 | process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], 32 | }, 33 | hardhat: { 34 | chainId: 1337 35 | }, 36 | }, 37 | gasReporter: { 38 | enabled: process.env.REPORT_GAS !== undefined, 39 | currency: "USD", 40 | }, 41 | etherscan: { 42 | apiKey: process.env.ETHERSCAN_API_KEY, 43 | }, 44 | }; 45 | 46 | export default config; 47 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "scripts": { 4 | "testnet": "npx hardhat node", 5 | "deploy": "npx hardhat run --network localhost scripts/deploy.ts", 6 | "compile": "npx hardhat compile", 7 | "test": "npx hardhat test" 8 | 9 | }, 10 | 11 | "devDependencies": { 12 | 13 | 14 | "@nomiclabs/hardhat-ethers": "^2.0.0", 15 | 16 | 17 | "@nomiclabs/hardhat-etherscan": "^2.1.3", 18 | 19 | 20 | "@nomiclabs/hardhat-waffle": "^2.0.0", 21 | 22 | 23 | "@typechain/ethers-v5": "^7.0.1", 24 | 25 | 26 | "@typechain/hardhat": "^2.3.0", 27 | 28 | 29 | "@types/chai": "^4.2.21", 30 | 31 | 32 | "@types/mocha": "^9.0.0", 33 | 34 | 35 | "@types/node": "^12.0.0", 36 | 37 | 38 | "@typescript-eslint/eslint-plugin": "^4.29.1", 39 | 40 | 41 | "@typescript-eslint/parser": "^4.29.1", 42 | 43 | 44 | "chai": "^4.2.0", 45 | 46 | 47 | "dotenv": "^10.0.0", 48 | 49 | 50 | "eslint": "^7.29.0", 51 | 52 | 53 | "eslint-config-prettier": "^8.3.0", 54 | 55 | 56 | "eslint-config-standard": "^16.0.3", 57 | 58 | 59 | "eslint-plugin-import": "^2.23.4", 60 | 61 | 62 | "eslint-plugin-node": "^11.1.0", 63 | 64 | 65 | "eslint-plugin-prettier": "^3.4.0", 66 | 67 | 68 | "eslint-plugin-promise": "^5.1.0", 69 | 70 | 71 | "ethereum-waffle": "^3.0.0", 72 | 73 | 74 | "ethers": "^5.0.0", 75 | 76 | 77 | "hardhat": "^2.8.3", 78 | 79 | 80 | "hardhat-gas-reporter": "^1.0.4", 81 | 82 | 83 | "prettier": "^2.3.2", 84 | 85 | 86 | "prettier-plugin-solidity": "^1.0.0-beta.13", 87 | 88 | 89 | "solhint": "^3.3.6", 90 | 91 | 92 | "solidity-coverage": "^0.7.16", 93 | 94 | 95 | "ts-node": "^10.1.0", 96 | 97 | 98 | "typechain": "^5.1.2", 99 | 100 | 101 | "typescript": "^4.5.2" 102 | 103 | }, 104 | 105 | "dependencies": { 106 | 107 | 108 | "@openzeppelin/contracts": "^4.5.0" 109 | 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /backend/scripts/deploy.ts: -------------------------------------------------------------------------------- 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 16 | 17 | 18 | -------------------------------------------------------------------------------- /frontend/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node' 5 | } 6 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-react-typescript-tailwind-starter", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "tsc && vite build", 7 | "serve": "vite preview --port 3000", 8 | "lint": "eslint . --ext .ts,.tsx,.js", 9 | "test": "jest" 10 | }, 11 | "dependencies": { 12 | "ethers": "^5.6.0", 13 | "ipfs-http-client": "^56.0.1", 14 | "react": "^17.0.2", 15 | "react-dom": "^17.0.2" 16 | }, 17 | "devDependencies": { 18 | "@commitlint/cli": "^15.0.0", 19 | "@commitlint/config-conventional": "^15.0.0", 20 | "@heroicons/react": "^1.0.5", 21 | "@tailwindcss/forms": "^0.4.0", 22 | "@typechain/ethers-v5": "^9.0.0", 23 | "@typechain/hardhat": "^5.0.0", 24 | "@types/jest": "^27.0.3", 25 | "@types/node": "16", 26 | "@types/react": "^17.0.38", 27 | "@types/react-dom": "^17.0.11", 28 | "@typescript-eslint/eslint-plugin": "^5.8.0", 29 | "@typescript-eslint/parser": "^5.8.0", 30 | "@vitejs/plugin-react": "^1.1.3", 31 | "autoprefixer": "^10.4.0", 32 | "eslint": "^8.5.0", 33 | "eslint-config-prettier": "^8.3.0", 34 | "eslint-plugin-import": "^2.25.3", 35 | "eslint-plugin-prettier": "^4.0.0", 36 | "eslint-watch": "^8.0.0", 37 | "jest": "^27.4.5", 38 | "postcss": "^8.4.5", 39 | "prettier": "^2.5.1", 40 | "tailwindcss": "^3.0.7", 41 | "ts-jest": "^27.1.2", 42 | "typechain": "^7.0.1", 43 | "typescript": "^4.5.4", 44 | "vite": "^2.7.6" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import logo from "./logo.svg"; 3 | // if you have successfuly compiled the smart contract in the backend folder, typechain should have created an interface that we can use here 4 | // import {ExampleContract} from '../../backend/typechain/ExampleContract'; 5 | import getContract from "./utils/useGetContract"; 6 | 7 | function App() { 8 | /*-----------STATES---------------*/ 9 | // const [typedContract, setTypedContract] = useState() 10 | const [contract, setContract] = useState(undefined) 11 | const contractAddress = "0x5FbDB2315678afecb367f032d93F642f64180aa3" 12 | 13 | /*-----------SIDE EFFECTS---------------*/ 14 | useEffect(() => { 15 | // wild wild west version of setting contract 16 | setContract(getContract(contractAddress)) 17 | 18 | // gentlemen version of setting contract 19 | //setTypedContract( (getContract(contractAddress) as ExampleContract)) 20 | }, []) 21 | 22 | /*-----------FUNCTIONS---------------*/ 23 | async function addAccount() { 24 | // if you want to use the typed contract 25 | 26 | // await (await typedContract?.addAccount("XamHans")); 27 | // typedContract?.on("accountCreatedEvent", async function (event:any) { 28 | // console.log('Recieved event from smart contract ',event) 29 | // }) 30 | 31 | await contract.addAccount("XamHans"); 32 | contract?.on("accountCreatedEvent", async function (event:any) { 33 | console.log('Recieved event from smart contract ',event) 34 | }) 35 | } 36 | 37 | return ( 38 |
39 | 40 |
41 | logo 42 |

Vite + React + Solidity + Typescript = 🌞

43 |

44 | 50 |

51 |

52 | 58 | Learn React 59 | 60 | {" | "} 61 | 67 | Vite Docs 68 | 69 | {" | "} 70 | 76 | Solidity Docs 77 | 78 |

79 |
80 |
81 | ); 82 | } 83 | 84 | export default App; -------------------------------------------------------------------------------- /frontend/src/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /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/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import './App.css' 4 | import App from './App' 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ) 12 | -------------------------------------------------------------------------------- /frontend/src/utils/useGetContract.ts: -------------------------------------------------------------------------------- 1 | import { Contract, ethers } from "ethers"; 2 | import ExampleContract from '../../../backend/artifacts/contracts/ExampleContract.sol/ExampleContract.json' 3 | 4 | export default function getContract(contractAddress: string): any { 5 | const provider = new ethers.providers.Web3Provider( (window as any).ethereum); 6 | const signer = provider.getSigner(); 7 | const contract = new ethers.Contract( 8 | contractAddress, 9 | ExampleContract.abi, 10 | signer 11 | ); 12 | return contract; 13 | } -------------------------------------------------------------------------------- /frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ['./dist/**/*.html', './src/**/*.{js,jsx,ts,tsx}', './*.html'], 3 | plugins: [require('@tailwindcss/forms')], 4 | variants: { 5 | extend: { 6 | opacity: ['disabled'] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": false, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["./src"] 20 | } 21 | -------------------------------------------------------------------------------- /frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()] 7 | }) 8 | --------------------------------------------------------------------------------