Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
April 17, 2024 09:52 am GMT

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

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To