An Interest In:
Web News this Week
- April 17, 2024
- April 16, 2024
- April 15, 2024
- April 14, 2024
- April 13, 2024
- April 12, 2024
- April 11, 2024
Building a Decentralized Todo List Application on Ethereum
Welcome to the exciting world of decentralized applications (dApps) on the Ethereum blockchain! In this step-by-step guide, we'll walk through the process of creating a decentralized todo list application using the Hardhat development framework.
We will cover interesting topics like setting up your development environment, writing a Solidity smart contract, testing it, and deploying it to the Sepolia testnet. Code along for better understanding!
Prerequisites
Before we dive in, ensure you have the following tools and prerequisites in place:
Node
Hardhat: JavaScript framework to interact with the Blockchain.
Metamask: Install Metamaks and obtain your private key. Configure Metamask to connect to the Sepolia network.
Alchemy: Get your alchemy HTTP endpoint for the Sepolia testnet. Here is a guide on how to set it up.
Test Sepolia ETH: Request for some Sepolia ETH from the faucet.
Setting up our environment
Now that we've gathered our tools, it's time to set up our development environment. Here's a step-by-step guide:
- Create a new project directory for your application todolist.
mkdir todolistcd todolistnpm init -ynpm install --save-dev hardhat
- Initialize your Hardhat project by running:
npx hardhat init
Choose the option to create a JavaScript project, and accept the default options. Hardhat will generate a sample project and install the necessary dependencies for you.
- Open your project folder in your preferred code editor. If you use Visual Studio Code, simply run:
code .
To keep sensitive information like your Metamask private key and Alchemy RPC URL secure, create a .env file in your project directory and store your keys there in the format below:
Install the
dotenv
package, which will help us work with environment variables:
npm i dotenv
- Modify your Hardhat configuration file (usually named hardhat.config.js) to recognize the keys from your
.env
file:
require("@nomicfoundation/hardhat-toolbox");require("dotenv").config();module.exports = { solidity: "0.8.19", networks: { sepolia: { url: process.env.ALCHEMY_API_KEY_URL, accounts: [process.env.PRIVATE_KEY], }, },};
Your environment is now ready to perform some magic on the Ethereum blockchain!
Building our Contract
Let's delve into the heart of our todo list application by writing a Solidity smart contract. In the contracts folder, you will find a default 'Lock.sol' file. Begin by locating the 'Lock.sol' file in the 'contracts' folder and rename it to 'TodoList.sol' to align with the name of our contract.
Below is the TodoList contract, along with comments to explain what each block of code is doing:
// SPDX-License-Identifier: MIT// Solidity Versionpragma solidity 0.8.19;contract TodoList { // Struct to represent a task struct Task { uint id; // Unique task identifier uint date; // Timestamp of task creation string name; // Task name string description; // Task description bool isCompleted; // Flag indicating task completion status address owner; // Address of the task owner } // Array to store all tasks Task[] public tasks; // Mapping to associate user addresses with their task IDs mapping(address => uint[]) private userTasks; // Constructor function constructor() {} // Event emitted when a task is created event TaskCreated( uint id, uint date, string name, string description, bool isCompleted, address owner ); // Event emitted when a task is marked as completed event TaskCompleted(uint id, address owner); // Event emitted when a task is deleted event TaskDeleted(uint id, address owner); // Function to create a new task function createTask(string memory name, string memory description) public { uint taskId = tasks.length; // Calculate the new task ID tasks.push( Task(taskId, block.timestamp, name, description, false, msg.sender) ); // Create and add the new task to the array userTasks[msg.sender].push(taskId); // Update the userTasks mapping emit TaskCreated( taskId, block.timestamp, name, description, false, msg.sender ); // Emit a TaskCreated event } // Function to retrieve task details by ID function getTask( uint id ) public view returns (uint, uint, string memory, string memory, bool, address) { // Ensure the task ID is valid require(id < tasks.length, "Task ID does not exist"); Task storage task = tasks[id]; // Retrieve the task from storage return ( task.id, task.date, task.name, task.description, task.isCompleted, task.owner ); // Return task details } // Function to mark a task as completed function markTaskCompleted(uint id) public { // Ensure the task ID is valid require(id < tasks.length, "Task ID does not exist"); Task storage task = tasks[id]; // Retrieve the task from storage require( task.owner == msg.sender, "Only the owner can complete the task" ); // Ensure the task is not already completed require(!task.isCompleted, "Task is already completed"); task.isCompleted = true; // Mark the task as completed emit TaskCompleted(id, msg.sender); // Emit a TaskCompleted event } // Function to delete a task function deleteTask(uint id) public { // Ensure the task ID is valid require(id < tasks.length, "Task ID does not exist"); Task storage task = tasks[id]; // Retrieve the task from storage // Ensure only the owner can delete the task require(task.owner == msg.sender, "Only the owner can delete the task"); emit TaskDeleted(id, msg.sender); // Emit a TaskDeleted event // Delete the task by replacing it with the last task in the array // and reducing the array size uint lastIndex = tasks.length - 1; if (id != lastIndex) { Task storage lastTask = tasks[lastIndex]; tasks[id] = lastTask; userTasks[msg.sender][id] = lastIndex; } tasks.pop(); userTasks[msg.sender].pop(); } // Function to retrieve all task IDs belonging to the caller function getUserTasks() public view returns (uint[] memory) { // Return the task IDs associated with the caller's address return userTasks[msg.sender]; }}
Testing our Contract
Testing our contract is essential to guarantee its reliability and functionality to ensure it performs as intended. In an industry prone to hacks and exploits, writing tests is very necessary to make sure we don't leave our contract vulnerable to exploits.
Writing our Test in Mocha
We'll use Mocha for testing, so let's set up our tests. Inside the test folder, rename the Lock.js file to test.js and replace the code with the following:
const { expect } = require("chai");const { ethers } = require("hardhat");describe("TodoList contract", function () { let TodoList; let todolist; let owner; before(async function () { [owner] = await ethers.getSigners(); // Deploy the TodoList contract todolist = await ethers.deployContract("TodoList"); // await TodoList.waitForDeployment(); }); it("should create a new task", async function () { const taskName = "Sample Task"; const taskDescription = "This is a sample task description"; // Create a new task await todolist.createTask(taskName, taskDescription); // Retrieve the task details const [id, date, name, description, isCompleted, taskOwner] = await todolist.getTask(0); expect(id).to.equal(0); expect(name).to.equal(taskName); expect(description).to.equal(taskDescription); expect(isCompleted).to.equal(false); expect(taskOwner).to.equal(owner.address); }); it("should mark a task as completed", async function () { // Mark the task at index 0 as completed await todolist.markTaskCompleted(0); // Retrieve the task details const [, , , , isCompleted] = await todolist.getTask(0); expect(isCompleted).to.equal(true); }); it("should delete a task", async function () { // Create a new task await todolist.createTask( "Task to be deleted", "This task will be deleted" ); // Delete the task at index 1 await todolist.deleteTask(1); // Attempt to retrieve the deleted task (should throw an error) let errorOccurred = false; try { await todolist.getTask(1); } catch (error) { errorOccurred = true; } expect(errorOccurred).to.equal(true); }); it("should retrieve the user's tasks", async function () { // Create a new task await todolist.createTask("User's Task", "This is the user's task"); // Retrieve the user's tasks const userTasks = await todolist.getUserTasks(); // Expect that there is at least one task expect(userTasks.length).to.be.at.least(1); });});
To test our contract, we run the common:
npx hardhat test
The response should look like this:
Deploying our Contract
Now, the thrilling part - deploying our smart contract to the Sepolia network. We'll write a deployment script to make this happen.
Writing our Deployment Script
In the scripts folder, you will find a deploy.js file with some sample code. Replace the JavaScript code with the following:
// Import the ethers library from the Hardhat frameworkconst { ethers } = require("hardhat");// Define an asynchronous main function for contract deploymentasync function main() { // Deploy the contract const TodoList = await ethers.deployContract("TodoList"); // Log message to show deployment in progress console.log("Deploying contract....."); // Wait for the deployment of the contract to complete await TodoList.waitForDeployment(); // Log the deployment target (contract address) to the console console.log(`TodoList deployed to ${TodoList.target}`);}// Execute the main function, and handle any errors that may occurmain().catch((error) => { console.error(error); process.exitCode = 1;});
To deploy our contract to the Sepolia network, use the command:
npx hardhat run scripts/deploy.js --network sepolia
NB: If you intend to deploy your smart contract to a different network you can easily replace
sepolia
with the network of your choice.
This should take some seconds as we are deploying to a testnet. You'll receive confirmation of the contract deployment, along with the contract's address.
Now you can experience the excitement of your decentralized todo list coming to life! Go ahead and copy your contract address and verify its presence on the Sepolia Testnet Explorer just like you can do on the Ethereum mainnet. Super Interesting!
Conclusion
You have successfully built and deployed your first dApp to the Ethereum Blockchain. As a next step, I highly recommend the following resources:
Lumos Academy: Lumos Academy by Lumos Labs is a platform dedicated to (aspiring) Web3 developers who are learning Web3 development
Ethereum Development Tutorial: This curated list of community tutorials covers a wide range of Ethereum development topics
Hope you enjoyed the article! If you have any questions or comments, feel free to drop them below or reach out to me on Twitter!
Original Link: https://dev.to/tosynthegeek/building-a-decentralized-todo-list-application-on-ethereum-26f2
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To