在以太坊上構建去中心化的待辦事項列表應用程序
歡迎來到以太坊區塊鏈上令人興奮的去中心化應用程序 (dApp) 世界!在本分步指南中,我們將逐步介紹使用 Hardhat 開發框架創建去中心化待辦事項列表應用程序的過程。
我們將涵蓋有趣的主題,例如設置開發環境、編寫 Solidity 智能合約、測試它以及將其部署到 Sepolia 測試網。一起編寫代碼以便更好地理解!
先決條件
在我們深入研究之前,請確保您擁有以下工具和先決條件:
- ? Node
- ? Hardhat:與區塊鏈交互的 JavaScript 框架。
- ? Metamask:安裝 Metamaks 并獲取您的私鑰。配置 Metamask 以連接到 Sepolia 網絡。
- ? Alchemy:獲取 Sepolia 測試網的 Alchemy HTTP 端點。這是有關如何設置的指南。[1]
- ? Test Sepolia ETH:從水龍頭[2]請求一些Sepolia ETH 。
設置我們的環境
現在我們已經收集了我們的工具,是時候設置我們的開發環境了。
這是分步指南:
? 為您的應用程序 todolist 創建一個新的項目目錄。
mkdir todolist
cd todolist
npm init -y
npm install --save-dev hardhat
? 通過運行以下命令來初始化您的 Hardhat 項目:
npx hardhat init
- 選擇創建 JavaScript 項目的選項,并接受默認選項。Hardhat 將生成一個示例項目并為您安裝必要的依賴項。
- ? 在您喜歡的代碼編輯器中打開您的項目文件夾。如果您使用 Visual Studio Code,只需運行:
code .
? 為了確保 Metamask 私鑰和 Alchemy RPC URL 等敏感信息的安全,請在項目目錄中創建一個 .env 文件,并按以下格式存儲密鑰:
圖片
? 安裝該dotenv軟件包,這將幫助我們使用環境變量:
npm i dotenv
? 修改 Hardhat 配置文件(通常名為 Hardhat.config.js)以識別文件中的密鑰.env:
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],
},
},
};
您的環境現在已準備好在以太坊區塊鏈上施展魔法!
建立我們的合同
讓我們通過編寫 Solidity 智能合約來深入研究待辦事項列表應用程序的核心。在合同文件夾中,您將找到默認的“Lock.sol”文件。首先在“contracts”文件夾中找到“Lock.sol”文件,并將其重命名為“TodoList.sol”以與我們的合約名稱保持一致。
下面是“TodoList”合約,以及解釋每個代碼塊的作用的注釋:
// SPDX-License-Identifier: MIT
// Solidity Version
pragma 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];
}
}
測試我們的合約
測試我們的合約對于保證其可靠性和功能性以及確保其按預期執行至關重要。在一個容易遭受黑客攻擊和漏洞利用的行業中,編寫測試非常有必要,以確保我們的合約不會容易受到漏洞利用。
用 Mocha 編寫我們的測試
我們將使用 Mocha 進行測試,所以讓我們設置我們的測試。在 test 文件夾中,將 Lock.js 文件重命名為 test.js 并將代碼替換為以下內容:
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);
});
});
為了測試我們的合約,我們運行通用的:
npx hardhat test
響應應如下所示:
圖片
部署我們的合約
現在,令人興奮的部分 - 將我們的智能合約部署到 Sepolia 網絡。我們將編寫一個部署腳本來實現這一點。
編寫我們的部署腳本
在腳本文件夾中,您將找到一個包含一些示例代碼的deploy.js 文件。將 JavaScript 代碼替換為以下內容:
// Import the ethers library from the Hardhat framework
const { ethers } = require("hardhat");
// Define an asynchronous main function for contract deployment
async 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 occur
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
要將我們的合約部署到 Sepolia 網絡,請使用以下命令:
npx hardhat run scripts/deploy.js --network sepolia
注意:如果您打算將智能合約部署到不同的網絡,您可以輕松替換 sepolia 為您選擇的網絡。
當我們部署到測試網時,這應該需要幾秒鐘的時間。您將收到合同部署的確認信息以及合同地址。
圖片
img
現在您可以體驗去中心化待辦事項列表變為現實的興奮!繼續復制您的合約地址并驗證其在Sepolia Testnet Explorer[3]上的存在,就像您在以太坊主網上所做的那樣。超級有趣!
結論
您已成功構建第一個 dApp 并將其部署到以太坊區塊鏈。作為下一步,我強烈推薦以下資源:
Lumos Academy[4]:Lumos Labs 的 Lumos Academy 是一個專門為正在學習 Web3 開發的(有抱負的)Web3 開發人員提供的平臺
以太坊開發教程[5]:這個精選的社區教程列表涵蓋了廣泛的以太坊開發主題
希望您喜歡這篇文章!如果您有任何問題或意見,請隨時在下面留言或在Twitter 上與我聯系![6]
原文:https://tosynthegeek.hashnode.dev/building-a-decentralized-todo-list-application-on-ethereum
引用鏈接
[2] 水龍頭: https://sepoliafaucet.com/
[3] Sepolia Testnet Explorer: https://sepolia.etherscan.io/
[4] Lumos Academy: https://academy.lumoslabs.co/
[5] 以太坊開發教程: https://ethereum.org/en/developers/tutorials/[6] Twitter 上與我聯系!: https://twitter.com/tosynthegeek