├── .gitattributes ├── .github └── stale.yml ├── .gitignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── client ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.jsx │ ├── components │ │ ├── Demo │ │ │ ├── Contract.jsx │ │ │ ├── ContractBtns.jsx │ │ │ ├── Cta.jsx │ │ │ ├── Desc.jsx │ │ │ ├── NoticeNoArtifact.jsx │ │ │ ├── NoticeWrongNetwork.jsx │ │ │ ├── Title.jsx │ │ │ └── index.jsx │ │ ├── Footer.jsx │ │ ├── Intro │ │ │ ├── Desc.jsx │ │ │ ├── Tree.jsx │ │ │ ├── Welcome.jsx │ │ │ └── index.jsx │ │ └── Setup.jsx │ ├── contexts │ │ └── EthContext │ │ │ ├── EthContext.js │ │ │ ├── EthProvider.jsx │ │ │ ├── index.js │ │ │ ├── state.js │ │ │ └── useEth.js │ ├── index.jsx │ └── styles.css └── webpack.config.js ├── truffle-box.json └── truffle ├── contracts └── SimpleStorage.sol ├── migrations └── 1_deploy_simple_storage.js ├── package-lock.json ├── package.json ├── scripts └── increment.js ├── test ├── SimpleStorageTest.sol └── simplestorage.js └── truffle-config.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: > 18 | This issue has been closed, but can be re-opened if further comments 19 | indicate that the problem persists. Feel free to tag maintainers if there 20 | is no reply to further comments. 21 | -------------------------------------------------------------------------------- /.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 | # Production 9 | build 10 | client/src/contracts 11 | client/*/src/contracts 12 | 13 | # Testing 14 | coverage 15 | 16 | # Env 17 | .env 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | # Editor 24 | .vscode 25 | 26 | # Misc. 27 | .DS_Store 28 | 29 | npm-debug.log* 30 | yarn-debug.log* 31 | yarn-error.log* 32 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Truffle 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Truffle Box 2 | 3 | This box comes with everything you need to start using Truffle to write, compile, test, and deploy smart contracts, and interact with them from a React app. 4 | 5 | ## Installation 6 | 7 | First ensure you are in an empty directory. 8 | 9 | Run the `unbox` command using 1 of 2 ways. 10 | 11 | ```sh 12 | # Install Truffle globally and run `truffle unbox` 13 | $ npm install -g truffle 14 | $ truffle unbox react 15 | ``` 16 | 17 | ```sh 18 | # Alternatively, run `truffle unbox` via npx 19 | $ npx truffle unbox react 20 | ``` 21 | 22 | Start the react dev server. 23 | 24 | ```sh 25 | $ cd client 26 | $ npm start 27 | ``` 28 | 29 | From there, follow the instructions on the hosted React app. It will walk you through using Truffle and Ganache to deploy the `SimpleStorage` contract, making calls to it, and sending transactions to change the contract's state. 30 | 31 | ## FAQ 32 | 33 | - __How do I use this with Ganache (or any other network)?__ 34 | 35 | The Truffle project is set to deploy to Ganache by default. If you'd like to change this, it's as easy as modifying the Truffle config file! Check out [our documentation on adding network configurations](https://trufflesuite.com/docs/truffle/reference/configuration/#networks). From there, you can run `truffle migrate` pointed to another network, restart the React dev server, and see the change take place. 36 | 37 | - __Where can I find more resources?__ 38 | 39 | This Box is a sweet combo of [Truffle](https://trufflesuite.com) and [Webpack](https://webpack.js.org). Either one would be a great place to start! 40 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # React client 2 | 3 | This react project is unopinionated with only `web3.js` as an added dependency, so nothing stands in your way. 4 | 5 | ## Getting started 6 | 7 | Run `npm start` to start the dev server, and `npm build` to create a production build. 8 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "truffle-client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "webpack", 7 | "start": "webpack serve" 8 | }, 9 | "dependencies": { 10 | "react": "^18.2.0", 11 | "react-dom": "^18.2.0", 12 | "web3": "^1.8.2" 13 | }, 14 | "devDependencies": { 15 | "@babel/preset-react": "^7.18.6", 16 | "babel-loader": "^9.1.2", 17 | "css-loader": "^6.7.3", 18 | "eslint": "^8.34.0", 19 | "eslint-config-react-app": "^7.0.1", 20 | "eslint-webpack-plugin": "^4.0.0", 21 | "html-webpack-plugin": "^5.5.0", 22 | "style-loader": "^3.3.1", 23 | "webpack": "^5.75.0", 24 | "webpack-cli": "^5.0.1", 25 | "webpack-dev-server": "^4.11.1" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app" 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truffle-box/react-box/bcfeb00e8acb9ec0a213c9a8f031725ab0f7c817/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | React Truffle Box 8 | 9 | 10 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /client/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { EthProvider } from "./contexts/EthContext"; 2 | import Intro from "./components/Intro/"; 3 | import Setup from "./components/Setup"; 4 | import Demo from "./components/Demo"; 5 | import Footer from "./components/Footer"; 6 | 7 | function App() { 8 | return ( 9 | 10 |
11 |
12 | 13 |
14 | 15 |
16 | 17 |
18 |
20 |
21 |
22 | ); 23 | } 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /client/src/components/Demo/Contract.jsx: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect } from "react"; 2 | 3 | function Contract({ value }) { 4 | const spanEle = useRef(null); 5 | 6 | useEffect(() => { 7 | spanEle.current.classList.add("flash"); 8 | const flash = setTimeout(() => { 9 | spanEle.current.classList.remove("flash"); 10 | }, 300); 11 | return () => { 12 | clearTimeout(flash); 13 | }; 14 | }, [value]); 15 | 16 | return ( 17 | 18 | {`contract SimpleStorage { 19 | uint256 value = `} 20 | 21 | 22 | {value} 23 | 24 | 25 | {`; 26 | 27 | function read() public view returns (uint256) { 28 | return value; 29 | } 30 | 31 | function write(uint256 newValue) public { 32 | value = newValue; 33 | } 34 | }`} 35 | 36 | ); 37 | } 38 | 39 | export default Contract; 40 | -------------------------------------------------------------------------------- /client/src/components/Demo/ContractBtns.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import useEth from "../../contexts/EthContext/useEth"; 3 | 4 | function ContractBtns({ setValue }) { 5 | const { state: { contract, accounts } } = useEth(); 6 | const [inputValue, setInputValue] = useState(""); 7 | 8 | const handleInputChange = e => { 9 | if (/^\d+$|^$/.test(e.target.value)) { 10 | setInputValue(e.target.value); 11 | } 12 | }; 13 | 14 | const read = async () => { 15 | const value = await contract.methods.read().call({ from: accounts[0] }); 16 | setValue(value); 17 | }; 18 | 19 | const write = async e => { 20 | if (e.target.tagName === "INPUT") { 21 | return; 22 | } 23 | if (inputValue === "") { 24 | alert("Please enter a value to write."); 25 | return; 26 | } 27 | const newValue = parseInt(inputValue); 28 | await contract.methods.write(newValue).send({ from: accounts[0] }); 29 | }; 30 | 31 | return ( 32 |
33 | 34 | 37 | 38 |
39 | write() 45 |
46 | 47 |
48 | ); 49 | } 50 | 51 | export default ContractBtns; 52 | -------------------------------------------------------------------------------- /client/src/components/Demo/Cta.jsx: -------------------------------------------------------------------------------- 1 | function Cta() { 2 | return ( 3 |

4 | Try changing  5 | value 6 |  in  7 | SimpleStorage. 8 |

9 | ); 10 | } 11 | 12 | export default Cta; 13 | -------------------------------------------------------------------------------- /client/src/components/Demo/Desc.jsx: -------------------------------------------------------------------------------- 1 | function Desc() { 2 | return ( 3 | <> 4 |

5 | Take a look at client/src/contexts/EthContext. 6 | This context maintains a global state and provides web3.js functionalities 7 | to the rest of the app. 8 |

9 |

10 | Feel free to remove any component or styling that you don't need, and 11 | extend EthContext to your dapp's needs. 12 |

13 |

14 | Happy hacking! 15 |

16 | 17 | ); 18 | } 19 | 20 | export default Desc; 21 | -------------------------------------------------------------------------------- /client/src/components/Demo/NoticeNoArtifact.jsx: -------------------------------------------------------------------------------- 1 | function NoticeNoArtifact() { 2 | return ( 3 |

4 | ⚠️ Cannot find SimpleStorage contract artifact. 5 | Please complete the above preparation first, then restart the react dev server. 6 |

7 | ); 8 | } 9 | 10 | export default NoticeNoArtifact; 11 | -------------------------------------------------------------------------------- /client/src/components/Demo/NoticeWrongNetwork.jsx: -------------------------------------------------------------------------------- 1 | function NoticeWrongNetwork() { 2 | return ( 3 |

4 | ⚠️ MetaMask is not connected to the same network as the one you deployed to. 5 |

6 | ); 7 | } 8 | 9 | export default NoticeWrongNetwork; 10 | -------------------------------------------------------------------------------- /client/src/components/Demo/Title.jsx: -------------------------------------------------------------------------------- 1 | function Title() { 2 | return

See it in action

; 3 | } 4 | 5 | export default Title; 6 | -------------------------------------------------------------------------------- /client/src/components/Demo/index.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import useEth from "../../contexts/EthContext/useEth"; 3 | import Title from "./Title"; 4 | import Cta from "./Cta"; 5 | import Contract from "./Contract"; 6 | import ContractBtns from "./ContractBtns"; 7 | import Desc from "./Desc"; 8 | import NoticeNoArtifact from "./NoticeNoArtifact"; 9 | import NoticeWrongNetwork from "./NoticeWrongNetwork"; 10 | 11 | function Demo() { 12 | const { state } = useEth(); 13 | const [value, setValue] = useState("?"); 14 | 15 | const demo = 16 | <> 17 | 18 |
19 | 20 | 21 |
22 | 23 | ; 24 | 25 | return ( 26 |
27 | 28 | { 29 | !state.artifact ? <NoticeNoArtifact /> : 30 | !state.contract ? <NoticeWrongNetwork /> : 31 | demo 32 | } 33 | </div> 34 | ); 35 | } 36 | 37 | export default Demo; 38 | -------------------------------------------------------------------------------- /client/src/components/Footer.jsx: -------------------------------------------------------------------------------- 1 | function Link({ uri, text }) { 2 | return <a href={uri} target="_blank" rel="noreferrer">{text}</a>; 3 | } 4 | 5 | function Footer() { 6 | return ( 7 | <footer> 8 | <h2>More resources</h2> 9 | <Link uri={"https://trufflesuite.com"} text={"Truffle"} /> 10 | <Link uri={"https://reactjs.org"} text={"React"} /> 11 | <Link uri={"https://soliditylang.org"} text={"Solidity"} /> 12 | <Link uri={"https://ethereum.org"} text={"Ethereum"} /> 13 | </footer > 14 | ); 15 | } 16 | 17 | export default Footer; 18 | -------------------------------------------------------------------------------- /client/src/components/Intro/Desc.jsx: -------------------------------------------------------------------------------- 1 | function Desc() { 2 | return ( 3 | <p> 4 | This particular Box uses  5 | <a href="https://github.com/ChainSafe/web3.js" target="_blank" rel="noreferrer"> 6 | web3.js 7 | </a> 8 | , a popular Ethereum library. 9 | </p> 10 | ); 11 | } 12 | 13 | export default Desc; 14 | -------------------------------------------------------------------------------- /client/src/components/Intro/Tree.jsx: -------------------------------------------------------------------------------- 1 | function Tree() { 2 | return ( 3 | <code> 4 | {`.\n`} 5 | {`├── client`} 6 | <span className="primary-color"> 7 | {` # React project (create-react-app)\n`} 8 | </span> 9 | {`└── truffle`} 10 | <span className="primary-color"> 11 | {` # Truffle project`} 12 | </span> 13 | </code> 14 | ); 15 | } 16 | 17 | export default Tree; 18 | -------------------------------------------------------------------------------- /client/src/components/Intro/Welcome.jsx: -------------------------------------------------------------------------------- 1 | function Welcome() { 2 | return ( 3 | <div className="welcome"> 4 | <h1>👋 Welcome to the Truffle + React Box!</h1> 5 | <p> 6 | This is everything you need to start using Truffle to write, 7 | compile, test, and deploy smart contracts, and interact with 8 | them from a React app. 9 | </p> 10 | </div> 11 | ); 12 | } 13 | 14 | export default Welcome; 15 | -------------------------------------------------------------------------------- /client/src/components/Intro/index.jsx: -------------------------------------------------------------------------------- 1 | import Welcome from "./Welcome"; 2 | import Tree from "./Tree"; 3 | import Desc from "./Desc"; 4 | 5 | function Intro() { 6 | return ( 7 | <> 8 | <Welcome /> 9 | <Tree /> 10 | <Desc /> 11 | </> 12 | ); 13 | } 14 | 15 | export default Intro; 16 | -------------------------------------------------------------------------------- /client/src/components/Setup.jsx: -------------------------------------------------------------------------------- 1 | function Setup() { 2 | 3 | return ( 4 | <> 5 | <h2>Preparation</h2> 6 | 7 | <details> 8 | <summary>Install</summary> 9 | <p>Install Truffle and Ganache globally.</p> 10 | <code>$ npm install -g truffle ganache</code> 11 | </details> 12 | 13 | <details> 14 | <summary>Ganache and MetaMask</summary> 15 | <p> 16 | Open a terminal and run Ganache, a simulated Ethereum blockchain on your machine. 17 | </p> 18 | <code>$ ganache</code> 19 | <p>From the list of generated private keys, import the first one to MetaMask.</p> 20 | </details> 21 | 22 | <details> 23 | <summary>Truffle</summary> 24 | <p> 25 | Keep Ganache running and open another terminal. Let's compile and deploy our 26 | contracts to Ganache. 27 | </p> 28 | <code> 29 | {`$ cd truffle\n`} 30 | {`$ truffle migrate --network development\n`} 31 | <span className="dim-color"> 32 | # The `development` network points to Ganache, it's configured in 33 | truffle/truffle-config.js on line 45. 34 | </span> 35 | </code> 36 | </details> 37 | </> 38 | ); 39 | } 40 | 41 | export default Setup; 42 | -------------------------------------------------------------------------------- /client/src/contexts/EthContext/EthContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | const EthContext = createContext(); 4 | 5 | export default EthContext; 6 | -------------------------------------------------------------------------------- /client/src/contexts/EthContext/EthProvider.jsx: -------------------------------------------------------------------------------- 1 | import React, { useReducer, useCallback, useEffect } from "react"; 2 | import Web3 from "web3"; 3 | import EthContext from "./EthContext"; 4 | import { reducer, actions, initialState } from "./state"; 5 | 6 | function EthProvider({ children }) { 7 | const [state, dispatch] = useReducer(reducer, initialState); 8 | 9 | const init = useCallback( 10 | async artifact => { 11 | if (artifact) { 12 | const web3 = new Web3(Web3.givenProvider || "ws://localhost:8545"); 13 | const accounts = await web3.eth.requestAccounts(); 14 | const networkID = await web3.eth.net.getId(); 15 | const { abi } = artifact; 16 | let address, contract; 17 | try { 18 | address = artifact.networks[networkID].address; 19 | contract = new web3.eth.Contract(abi, address); 20 | } catch (err) { 21 | console.error(err); 22 | } 23 | dispatch({ 24 | type: actions.init, 25 | data: { artifact, web3, accounts, networkID, contract } 26 | }); 27 | } 28 | }, []); 29 | 30 | useEffect(() => { 31 | const tryInit = async () => { 32 | try { 33 | const artifact = require("../../contracts/SimpleStorage.json"); 34 | init(artifact); 35 | } catch (err) { 36 | console.error(err); 37 | } 38 | }; 39 | 40 | tryInit(); 41 | }, [init]); 42 | 43 | useEffect(() => { 44 | const events = ["chainChanged", "accountsChanged"]; 45 | const handleChange = () => { 46 | init(state.artifact); 47 | }; 48 | 49 | events.forEach(e => window.ethereum.on(e, handleChange)); 50 | return () => { 51 | events.forEach(e => window.ethereum.removeListener(e, handleChange)); 52 | }; 53 | }, [init, state.artifact]); 54 | 55 | return ( 56 | <EthContext.Provider value={{ 57 | state, 58 | dispatch 59 | }}> 60 | {children} 61 | </EthContext.Provider> 62 | ); 63 | } 64 | 65 | export default EthProvider; 66 | -------------------------------------------------------------------------------- /client/src/contexts/EthContext/index.js: -------------------------------------------------------------------------------- 1 | export { default as EthContext } from "./EthContext"; 2 | export { default as EthProvider } from "./EthProvider"; 3 | export { default as useEth } from "./useEth"; 4 | export * from "./state"; 5 | -------------------------------------------------------------------------------- /client/src/contexts/EthContext/state.js: -------------------------------------------------------------------------------- 1 | const actions = { 2 | init: "INIT" 3 | }; 4 | 5 | const initialState = { 6 | artifact: null, 7 | web3: null, 8 | accounts: null, 9 | networkID: null, 10 | contract: null 11 | }; 12 | 13 | const reducer = (state, action) => { 14 | const { type, data } = action; 15 | switch (type) { 16 | case actions.init: 17 | return { ...state, ...data }; 18 | default: 19 | throw new Error("Undefined reducer action type"); 20 | } 21 | }; 22 | 23 | export { actions, initialState, reducer }; 24 | -------------------------------------------------------------------------------- /client/src/contexts/EthContext/useEth.js: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import EthContext from "./EthContext"; 3 | 4 | const useEth = () => useContext(EthContext); 5 | 6 | export default useEth; 7 | -------------------------------------------------------------------------------- /client/src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | import "./styles.css"; 5 | 6 | const root = ReactDOM.createRoot(document.getElementById("root")); 7 | root.render( 8 | <React.StrictMode> 9 | <App /> 10 | </React.StrictMode> 11 | ); 12 | -------------------------------------------------------------------------------- /client/src/styles.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 10px; 3 | font-family: sans-serif; 4 | } 5 | 6 | * { 7 | margin: 0; 8 | padding: 0; 9 | } 10 | 11 | #App { 12 | width: 100%; 13 | height: 100%; 14 | display: flex; 15 | align-items: center; 16 | justify-content: center; 17 | font-size: 1.6rem; 18 | --primary-color: #1266cc; 19 | --secondary-color: #f96400; 20 | --flash-color: rgb(89, 255, 0); 21 | --gray: #999; 22 | --light-gray: #e0e0e0; 23 | --dark-gray: #777; 24 | } 25 | 26 | #App .container { 27 | width: 40%; 28 | min-width: 60rem; 29 | max-width: 75rem; 30 | height: 100%; 31 | } 32 | 33 | code { 34 | display: block; 35 | white-space: pre-wrap; 36 | padding: 1.2rem 2rem; 37 | border-radius: 1rem; 38 | background-color: var(--light-gray); 39 | font-size: 1.4rem; 40 | } 41 | 42 | code span { 43 | transition: all 0.3s; 44 | } 45 | 46 | code span.primary-color { 47 | color: var(--primary-color); 48 | } 49 | 50 | code span.secondary-color { 51 | color: var(--secondary-color); 52 | } 53 | 54 | code span.dim-color { 55 | color: var(--gray); 56 | } 57 | 58 | code span.flash { 59 | background-color: var(--flash-color); 60 | } 61 | 62 | details { 63 | margin: 1.6rem 0; 64 | padding: 0.5rem 2rem 0.5rem 0; 65 | border: 1px solid var(--light-gray); 66 | border-radius: 1rem; 67 | } 68 | 69 | details :not(summary, span) { 70 | margin-left: 2.55rem; 71 | } 72 | 73 | details summary { 74 | padding: 1.2rem; 75 | cursor: pointer; 76 | } 77 | 78 | p, 79 | details code { 80 | margin-top: 1rem; 81 | margin-bottom: 1.4rem; 82 | } 83 | 84 | p { 85 | line-height: 2.6rem; 86 | } 87 | 88 | p span.code { 89 | font-family: monospace; 90 | color: var(--gray); 91 | } 92 | 93 | hr { 94 | margin: 4rem 0; 95 | } 96 | 97 | #App .container .welcome h1 { 98 | padding-top: 7.5rem; 99 | font-size: 2.6rem; 100 | } 101 | 102 | #App .container .demo .contract-container { 103 | display: flex; 104 | } 105 | 106 | #App .container .demo .contract-container code { 107 | min-width: 42rem; 108 | border-radius: 1rem 0 0 1rem; 109 | } 110 | 111 | #App .container .demo .contract-container .btns { 112 | width: 100%; 113 | display: flex; 114 | flex-direction: column; 115 | align-items: center; 116 | justify-content: space-evenly; 117 | padding: 0 1rem; 118 | background-color: var(--gray); 119 | border-radius: 0 1rem 1rem 0; 120 | } 121 | 122 | #App .container .demo .contract-container .btns > * { 123 | width: 12rem; 124 | display: flex; 125 | align-items: center; 126 | justify-content: center; 127 | padding: 1rem 0; 128 | background-color: var(--light-gray); 129 | border: none; 130 | font-size: 1.2rem; 131 | font-family: monospace; 132 | cursor: pointer; 133 | } 134 | 135 | #App .container .demo .contract-container .btns > *:hover { 136 | background-color: var(--dark-gray); 137 | } 138 | 139 | #App .container .demo .contract-container .btns .input-btn input { 140 | width: 4rem; 141 | text-align: center; 142 | font-family: monospace; 143 | font-size: 1.2rem; 144 | } 145 | 146 | #App .container footer { 147 | padding-bottom: 7.5rem; 148 | } 149 | 150 | #App .container footer a { 151 | display: inline-block; 152 | margin: 2rem 1rem 0 0; 153 | } 154 | -------------------------------------------------------------------------------- /client/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 3 | const EslintWebpackPlugin = require("eslint-webpack-plugin"); 4 | 5 | const extensions = [".js", ".jsx"]; 6 | 7 | module.exports = { 8 | mode: process.env.NODE_ENV === "production" ? "production" : "development", 9 | entry: "./src/index.jsx", 10 | output: { 11 | path: path.resolve(__dirname, "build"), 12 | }, 13 | resolve: { extensions }, 14 | devServer: { 15 | client: { 16 | overlay: false, 17 | }, 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.jsx?$/i, 23 | use: [ 24 | { 25 | loader: "babel-loader", 26 | options: { 27 | presets: [["@babel/preset-react", { runtime: "automatic" }]], 28 | }, 29 | }, 30 | ], 31 | exclude: /node_modules/, 32 | }, 33 | { 34 | test: /\.css$/i, 35 | use: ["style-loader", "css-loader"], 36 | }, 37 | ], 38 | }, 39 | plugins: [ 40 | new EslintWebpackPlugin({ extensions }), 41 | new HtmlWebpackPlugin({ 42 | template: "./public/index.html", 43 | favicon: "./public/favicon.ico", 44 | }), 45 | ], 46 | stats: "minimal", 47 | }; 48 | -------------------------------------------------------------------------------- /truffle-box.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": [ 3 | ".github", 4 | ".prettierrc.json" 5 | ], 6 | "commands": { 7 | "Contracts: Compile": "cd truffle && truffle compile", 8 | "Contracts: Test": "cd truffle && truffle test", 9 | "Contracts: Migrate": "cd truffle && truffle migrate", 10 | "Dapp: Run dev server": "cd client && npm start", 11 | "Dapp: Test": "cd client && npm test", 12 | "Dapp: Build for production": "cd client && npm run build" 13 | }, 14 | "hooks": { 15 | "post-unpack": "cd client && npm ci" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /truffle/contracts/SimpleStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.4.22 <0.9.0; 3 | 4 | contract SimpleStorage { 5 | uint256 value; 6 | 7 | function read() public view returns (uint256) { 8 | return value; 9 | } 10 | 11 | function write(uint256 newValue) public { 12 | value = newValue; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /truffle/migrations/1_deploy_simple_storage.js: -------------------------------------------------------------------------------- 1 | const SimpleStorage = artifacts.require("SimpleStorage"); 2 | 3 | module.exports = function (deployer) { 4 | deployer.deploy(SimpleStorage); 5 | }; 6 | -------------------------------------------------------------------------------- /truffle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "truffle-project", 3 | "version": "0.1.0", 4 | "description": "Truffle project with SimpleStorage contract", 5 | "scripts": { 6 | "test": "truffle test" 7 | }, 8 | "dependencies": { 9 | "@truffle/hdwallet-provider": "^2.1.7" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /truffle/scripts/increment.js: -------------------------------------------------------------------------------- 1 | /* 2 | Try `truffle exec scripts/increment.js`, you should `truffle migrate` first. 3 | 4 | Learn more about Truffle external scripts: 5 | https://trufflesuite.com/docs/truffle/getting-started/writing-external-scripts 6 | */ 7 | 8 | const SimpleStorage = artifacts.require("SimpleStorage"); 9 | 10 | module.exports = async function (callback) { 11 | const deployed = await SimpleStorage.deployed(); 12 | 13 | const currentValue = (await deployed.read()).toNumber(); 14 | console.log(`Current SimpleStorage value: ${currentValue}`); 15 | 16 | const { tx } = await deployed.write(currentValue + 1); 17 | console.log(`Confirmed transaction ${tx}`); 18 | 19 | const updatedValue = (await deployed.read()).toNumber(); 20 | console.log(`Updated SimpleStorage value: ${updatedValue}`); 21 | 22 | callback(); 23 | }; 24 | -------------------------------------------------------------------------------- /truffle/test/SimpleStorageTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.4.22 <0.9.0; 3 | 4 | import "../contracts/SimpleStorage.sol"; 5 | // These files are dynamically created at test time 6 | import "truffle/Assert.sol"; 7 | import "truffle/DeployedAddresses.sol"; 8 | 9 | contract SimpleStorageTest { 10 | 11 | function testWriteValue() public { 12 | SimpleStorage simpleStorage = SimpleStorage(DeployedAddresses.SimpleStorage()); 13 | 14 | Assert.equal(simpleStorage.read(), 0, "Contract should have 0 stored"); 15 | simpleStorage.write(1); 16 | Assert.equal(simpleStorage.read(), 1, "Contract should have 1 stored"); 17 | simpleStorage.write(2); 18 | Assert.equal(simpleStorage.read(), 2, "Contract should have 2 stored"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /truffle/test/simplestorage.js: -------------------------------------------------------------------------------- 1 | const SimpleStorage = artifacts.require("SimpleStorage"); 2 | 3 | contract('SimpleStorage', () => { 4 | it('should read newly written values', async() => { 5 | const simpleStorageInstance = await SimpleStorage.deployed(); 6 | var value = (await simpleStorageInstance.read()).toNumber(); 7 | 8 | assert.equal(value, 0, "0 wasn't the initial value"); 9 | 10 | await simpleStorageInstance.write(1); 11 | value = (await simpleStorageInstance.read()).toNumber(); 12 | assert.equal(value, 1, "1 was not written"); 13 | 14 | await simpleStorageInstance.write(2); 15 | value = (await simpleStorageInstance.read()).toNumber(); 16 | assert.equal(value, 2, "2 was not written"); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /truffle/truffle-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Use this file to configure your truffle project. It's seeded with some 3 | * common settings for different networks and features like migrations, 4 | * compilation, and testing. Uncomment the ones you need or modify 5 | * them to suit your project as necessary. 6 | * 7 | * More information about configuration can be found at: 8 | * 9 | * https://trufflesuite.com/docs/truffle/reference/configuration 10 | * 11 | * Hands-off deployment with Infura 12 | * -------------------------------- 13 | * 14 | * Do you have a complex application that requires lots of transactions to deploy? 15 | * Use this approach to make deployment a breeze 🏖️: 16 | * 17 | * Infura deployment needs a wallet provider (like @truffle/hdwallet-provider) 18 | * to sign transactions before they're sent to a remote public node. 19 | * Infura accounts are available for free at 🔍: https://infura.io/register 20 | * 21 | * You'll need a mnemonic - the twelve word phrase the wallet uses to generate 22 | * public/private key pairs. You can store your secrets 🤐 in a .env file. 23 | * In your project root, run `$ npm install dotenv`. 24 | * Create .env (which should be .gitignored) and declare your MNEMONIC 25 | * and Infura PROJECT_ID variables inside. 26 | * For example, your .env file will have the following structure: 27 | * 28 | * MNEMONIC = <Your 12 phrase mnemonic> 29 | * PROJECT_ID = <Your Infura project id> 30 | * 31 | * Deployment with Truffle Dashboard (Recommended for best security practice) 32 | * -------------------------------------------------------------------------- 33 | * 34 | * Are you concerned about security and minimizing rekt status 🤔? 35 | * Use this method for best security: 36 | * 37 | * Truffle Dashboard lets you review transactions in detail, and leverages 38 | * MetaMask for signing, so there's no need to copy-paste your mnemonic. 39 | * More details can be found at 🔎: 40 | * 41 | * https://trufflesuite.com/docs/truffle/getting-started/using-the-truffle-dashboard/ 42 | */ 43 | 44 | // require('dotenv').config(); 45 | // const { MNEMONIC, PROJECT_ID } = process.env; 46 | 47 | // const HDWalletProvider = require('@truffle/hdwallet-provider'); 48 | 49 | module.exports = { 50 | /** 51 | * Networks define how you connect to your ethereum client and let you set the 52 | * defaults web3 uses to send transactions. If you don't specify one truffle 53 | * will spin up a managed Ganache instance for you on port 9545 when you 54 | * run `develop` or `test`. You can ask a truffle command to use a specific 55 | * network from the command line, e.g 56 | * 57 | * $ truffle test --network <network-name> 58 | */ 59 | 60 | contracts_build_directory: "../client/src/contracts", 61 | networks: { 62 | // Useful for testing. The `development` name is special - truffle uses it by default 63 | // if it's defined here and no other network is specified at the command line. 64 | // You should run a client (like ganache, geth, or parity) in a separate terminal 65 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 66 | // options below to some value. 67 | // 68 | // development: { 69 | // host: "127.0.0.1", // Localhost (default: none) 70 | // port: 8545, // Standard Ethereum port (default: none) 71 | // network_id: "*", // Any network (default: none) 72 | // }, 73 | // 74 | // An additional network, but with some advanced options… 75 | // advanced: { 76 | // port: 8777, // Custom port 77 | // network_id: 1342, // Custom network 78 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000) 79 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 80 | // from: <address>, // Account to send transactions from (default: accounts[0]) 81 | // websocket: true // Enable EventEmitter interface for web3 (default: false) 82 | // }, 83 | // 84 | // Useful for deploying to a public network. 85 | // Note: It's important to wrap the provider as a function to ensure truffle uses a new provider every time. 86 | // goerli: { 87 | // provider: () => new HDWalletProvider(MNEMONIC, `https://goerli.infura.io/v3/${PROJECT_ID}`), 88 | // network_id: 5, // Goerli's id 89 | // confirmations: 2, // # of confirmations to wait between deployments. (default: 0) 90 | // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) 91 | // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) 92 | // }, 93 | // 94 | // Useful for private networks 95 | // private: { 96 | // provider: () => new HDWalletProvider(MNEMONIC, `https://network.io`), 97 | // network_id: 2111, // This network is yours, in the cloud. 98 | // production: true // Treats this network as if it was a public net. (default: false) 99 | // } 100 | }, 101 | 102 | // Set default mocha options here, use special reporters, etc. 103 | mocha: { 104 | // timeout: 100000 105 | }, 106 | 107 | // Configure your compilers 108 | compilers: { 109 | solc: { 110 | version: "0.8.18", // Fetch exact version from solc-bin (default: truffle's version) 111 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 112 | // settings: { // See the solidity docs for advice about optimization and evmVersion 113 | // optimizer: { 114 | // enabled: false, 115 | // runs: 200 116 | // }, 117 | // evmVersion: "byzantium" 118 | // } 119 | } 120 | }, 121 | 122 | // Truffle DB is currently disabled by default; to enable it, change enabled: 123 | // false to enabled: true. The default storage location can also be 124 | // overridden by specifying the adapter settings, as shown in the commented code below. 125 | // 126 | // NOTE: It is not possible to migrate your contracts to truffle DB and you should 127 | // make a backup of your artifacts to a safe location before enabling this feature. 128 | // 129 | // After you backed up your artifacts you can utilize db by running migrate as follows: 130 | // $ truffle migrate --reset --compile-all 131 | // 132 | // db: { 133 | // enabled: false, 134 | // host: "127.0.0.1", 135 | // adapter: { 136 | // name: "indexeddb", 137 | // settings: { 138 | // directory: ".db" 139 | // } 140 | // } 141 | // } 142 | }; 143 | --------------------------------------------------------------------------------