├── .gitignore ├── README.md ├── bs-config.json ├── client ├── app.js ├── index.html ├── main.css └── ui.js ├── contracts ├── Migrations.sol └── TasksContract.sol ├── migrations ├── 1_initial_migration.js └── 2_deploy_tasks.js ├── package-lock.json ├── package.json ├── screenshot.png ├── test ├── .gitkeep └── TasksContract.js └── truffle-config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DTasksApp 2 | Decentralize App is a simple Tasks' App to practice the creation of a blockchain applicaciont using Solidity and Javascript. 3 | 4 | ![](./screenshot.png) 5 | 6 | The stack and tools of this application is: 7 | * Truffle Framework 8 | * Ganache 9 | * Solidity 10 | * Nodejs 11 | * Bootstrap -------------------------------------------------------------------------------- /bs-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": { 3 | "baseDir": [ 4 | "./client", 5 | "./build/contracts" 6 | ], 7 | "routes": { 8 | "/libs": "./node_modules" 9 | } 10 | }, 11 | "port": "3001" 12 | } -------------------------------------------------------------------------------- /client/app.js: -------------------------------------------------------------------------------- 1 | App = { 2 | contracts: {}, 3 | init: async () => { 4 | await App.loadWeb3(); 5 | await App.loadAccount(); 6 | await App.loadContract(); 7 | await App.render(); 8 | await App.renderTasks(); 9 | }, 10 | loadWeb3: async () => { 11 | if (window.ethereum) { 12 | App.web3Provider = window.ethereum; 13 | await window.ethereum.request({ method: "eth_requestAccounts" }); 14 | } else if (web3) { 15 | web3 = new Web3(window.web3.currentProvider); 16 | } else { 17 | console.log( 18 | "No ethereum browser is installed. Try it installing MetaMask " 19 | ); 20 | } 21 | }, 22 | loadAccount: async () => { 23 | const accounts = await window.ethereum.request({ 24 | method: "eth_requestAccounts", 25 | }); 26 | App.account = accounts[0]; 27 | }, 28 | loadContract: async () => { 29 | try { 30 | const res = await fetch("TasksContract.json"); 31 | const tasksContractJSON = await res.json(); 32 | App.contracts.TasksContract = TruffleContract(tasksContractJSON); 33 | App.contracts.TasksContract.setProvider(App.web3Provider); 34 | 35 | App.tasksContract = await App.contracts.TasksContract.deployed(); 36 | } catch (error) { 37 | console.error(error); 38 | } 39 | }, 40 | render: async () => { 41 | document.getElementById("account").innerText = App.account; 42 | }, 43 | renderTasks: async () => { 44 | const tasksCounter = await App.tasksContract.tasksCounter(); 45 | const taskCounterNumber = tasksCounter.toNumber(); 46 | 47 | let html = ""; 48 | 49 | for (let i = 1; i <= taskCounterNumber; i++) { 50 | const task = await App.tasksContract.tasks(i); 51 | const taskId = task[0].toNumber(); 52 | const taskTitle = task[1]; 53 | const taskDescription = task[2]; 54 | const taskDone = task[3]; 55 | const taskCreatedAt = task[4]; 56 | 57 | // Creating a task Card 58 | let taskElement = `
59 |
60 | ${taskTitle} 61 |
62 | 65 |
66 |
67 |
68 | ${taskDescription} 69 | ${taskDone} 70 |

Task was created ${new Date( 71 | taskCreatedAt * 1000 72 | ).toLocaleString()}

73 | 74 |
75 |
`; 76 | html += taskElement; 77 | } 78 | 79 | document.querySelector("#tasksList").innerHTML = html; 80 | }, 81 | createTask: async (title, description) => { 82 | try { 83 | const result = await App.tasksContract.createTask(title, description, { 84 | from: App.account, 85 | }); 86 | console.log(result.logs[0].args); 87 | window.location.reload(); 88 | } catch (error) { 89 | console.error(error); 90 | } 91 | }, 92 | toggleDone: async (element) => { 93 | const taskId = element.dataset.id; 94 | await App.tasksContract.toggleDone(taskId, { 95 | from: App.account, 96 | }); 97 | window.location.reload(); 98 | }, 99 | }; 100 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | TasksApp | DApp 8 | 9 | 10 | 11 | 15 | 16 | 17 |
18 |
19 |
20 |
21 |

22 | grid_view 23 | DTasksapp 24 |

25 |
Decentralize Tasks App
26 |
27 | account_balance_wallet 28 | Wallet 29 |
30 | 31 |
32 | 33 |
34 |

Save a Task

35 | 42 | 48 | 49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /client/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #0A192F; 3 | color: #fff; 4 | } -------------------------------------------------------------------------------- /client/ui.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded", () => { 2 | App.init(); 3 | }); 4 | 5 | /** 6 | * Task form 7 | */ 8 | const taskForm = document.querySelector("#taskForm"); 9 | 10 | taskForm.addEventListener("submit", (e) => { 11 | e.preventDefault(); 12 | const title = taskForm["title"].value; 13 | const description = taskForm["description"].value; 14 | App.createTask(title, description); 15 | }); 16 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.4.22 <0.9.0; 3 | 4 | contract Migrations { 5 | address public owner = msg.sender; 6 | uint public last_completed_migration; 7 | 8 | modifier restricted() { 9 | require( 10 | msg.sender == owner, 11 | "This function is restricted to the contract's owner" 12 | ); 13 | _; 14 | } 15 | 16 | function setCompleted(uint completed) public restricted { 17 | last_completed_migration = completed; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/TasksContract.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.6; 3 | 4 | contract TasksContract { 5 | uint256 public tasksCounter = 0; 6 | 7 | struct Task { 8 | uint256 id; 9 | string title; 10 | string description; 11 | bool done; 12 | uint256 createdAt; 13 | } 14 | 15 | event TaskCreated( 16 | uint256 id, 17 | string title, 18 | string description, 19 | bool done, 20 | uint256 createdAt 21 | ); 22 | event TaskToggledDone(uint256 id, bool done); 23 | 24 | mapping(uint256 => Task) public tasks; 25 | 26 | constructor() { 27 | createTask("my first task", "my first description"); 28 | } 29 | 30 | function createTask(string memory _title, string memory _description) 31 | public 32 | { 33 | tasksCounter++; 34 | tasks[tasksCounter] = Task( 35 | tasksCounter, 36 | _title, 37 | _description, 38 | false, 39 | block.timestamp 40 | ); 41 | emit TaskCreated( 42 | tasksCounter, 43 | _title, 44 | _description, 45 | false, 46 | block.timestamp 47 | ); 48 | } 49 | 50 | function toggleDone(uint256 _id) public { 51 | Task memory _task = tasks[_id]; 52 | _task.done = !_task.done; 53 | tasks[_id] = _task; 54 | emit TaskToggledDone(_id, _task.done); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function (deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/2_deploy_tasks.js: -------------------------------------------------------------------------------- 1 | const TasksContract = artifacts.require("TasksContract.sol"); 2 | 3 | module.exports = function (deployer) { 4 | deployer.deploy(TasksContract); 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ethereum-javascript-dapp", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "truffle-config.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "dev": "lite-server", 11 | "test": "truffle test" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "@truffle/contract": "^4.3.21", 18 | "bootstrap": "^5.0.2", 19 | "chai": "^4.3.4", 20 | "lite-server": "^2.6.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FaztWeb/solidity-javascript-dtasks/9abca207e7d0abfc8cf25d379adf415e594406b1/screenshot.png -------------------------------------------------------------------------------- /test/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FaztWeb/solidity-javascript-dtasks/9abca207e7d0abfc8cf25d379adf415e594406b1/test/.gitkeep -------------------------------------------------------------------------------- /test/TasksContract.js: -------------------------------------------------------------------------------- 1 | const TasksContract = artifacts.require("TasksContract"); 2 | 3 | contract("TasksContract", (accounts) => { 4 | before(async () => { 5 | this.tasksContract = await TasksContract.deployed(); 6 | }); 7 | 8 | it("migrate deployed successfully", async () => { 9 | const address = await this.tasksContract.address; 10 | 11 | assert.notEqual(address, null); 12 | assert.notEqual(address, undefined); 13 | assert.notEqual(address, 0x0); 14 | assert.notEqual(address, ""); 15 | }); 16 | 17 | it("get Tasks List", async () => { 18 | const tasksCounter = await this.tasksContract.tasksCounter(); 19 | const task = await this.tasksContract.tasks(tasksCounter); 20 | 21 | assert.equal(task.id.toNumber(), tasksCounter.toNumber()); 22 | assert.equal(task.title, "my first task"); 23 | assert.equal(task.description, "my first description"); 24 | assert.equal(task.done, false); 25 | assert.equal(tasksCounter, 1); 26 | }); 27 | 28 | it("task created successfully", async () => { 29 | const result = await this.tasksContract.createTask("some task two", "description two"); 30 | const taskEvent = result.logs[0].args; 31 | const tasksCounter = await this.tasksContract.tasksCounter(); 32 | 33 | assert.equal(tasksCounter, 2); 34 | assert.equal(taskEvent.id.toNumber(), 2); 35 | assert.equal(taskEvent.title, "some task two"); 36 | assert.equal(taskEvent.description, "description two"); 37 | assert.equal(taskEvent.done, false); 38 | }); 39 | 40 | it("task toggled done", async () => { 41 | const result = await this.tasksContract.toggleDone(1); 42 | const taskEvent = result.logs[0].args; 43 | const task = await this.tasksContract.tasks(1); 44 | 45 | assert.equal(task.done, true); 46 | assert.equal(taskEvent.id.toNumber(), 1); 47 | assert.equal(taskEvent.done, true); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /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 | * trufflesuite.com/docs/advanced/configuration 10 | * 11 | * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider) 12 | * to sign your transactions before they're sent to a remote public node. Infura accounts 13 | * are available for free at: infura.io/register. 14 | * 15 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 16 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 17 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 18 | * 19 | */ 20 | 21 | // const HDWalletProvider = require('@truffle/hdwallet-provider'); 22 | // const infuraKey = "fj4jll3k....."; 23 | // 24 | // const fs = require('fs'); 25 | // const mnemonic = fs.readFileSync(".secret").toString().trim(); 26 | 27 | module.exports = { 28 | /** 29 | * Networks define how you connect to your ethereum client and let you set the 30 | * defaults web3 uses to send transactions. If you don't specify one truffle 31 | * will spin up a development blockchain for you on port 9545 when you 32 | * run `develop` or `test`. You can ask a truffle command to use a specific 33 | * network from the command line, e.g 34 | * 35 | * $ truffle test --network 36 | */ 37 | 38 | networks: { 39 | // Useful for testing. The `development` name is special - truffle uses it by default 40 | // if it's defined here and no other network is specified at the command line. 41 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 42 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 43 | // options below to some value. 44 | // 45 | development: { 46 | host: "127.0.0.1", // Localhost (default: none) 47 | port: 7545, // Standard Ethereum port (default: none) 48 | network_id: "*", // Any network (default: none) 49 | }, 50 | // Another network with more advanced options... 51 | // advanced: { 52 | // port: 8777, // Custom port 53 | // network_id: 1342, // Custom network 54 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000) 55 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 56 | // from:
, // Account to send txs from (default: accounts[0]) 57 | // websocket: true // Enable EventEmitter interface for web3 (default: false) 58 | // }, 59 | // Useful for deploying to a public network. 60 | // NB: It's important to wrap the provider as a function. 61 | // ropsten: { 62 | // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), 63 | // network_id: 3, // Ropsten's id 64 | // gas: 5500000, // Ropsten has a lower block limit than mainnet 65 | // confirmations: 2, // # of confs to wait between deployments. (default: 0) 66 | // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) 67 | // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) 68 | // }, 69 | // Useful for private networks 70 | // private: { 71 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), 72 | // network_id: 2111, // This network is yours, in the cloud. 73 | // production: true // Treats this network as if it was a public net. (default: false) 74 | // } 75 | }, 76 | 77 | // Set default mocha options here, use special reporters etc. 78 | mocha: { 79 | // timeout: 100000 80 | }, 81 | 82 | // Configure your compilers 83 | compilers: { 84 | solc: { 85 | version: "0.8.6", // Fetch exact version from solc-bin (default: truffle's version) 86 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 87 | // settings: { // See the solidity docs for advice about optimization and evmVersion 88 | // optimizer: { 89 | // enabled: false, 90 | // runs: 200 91 | // }, 92 | // evmVersion: "byzantium" 93 | // } 94 | } 95 | }, 96 | 97 | // Truffle DB is currently disabled by default; to enable it, change enabled: false to enabled: true 98 | // 99 | // Note: if you migrated your contracts prior to enabling this field in your Truffle project and want 100 | // those previously migrated contracts available in the .db directory, you will need to run the following: 101 | // $ truffle migrate --reset --compile-all 102 | 103 | db: { 104 | enabled: false 105 | } 106 | }; 107 | --------------------------------------------------------------------------------