Decentralized applications (dApps) use a blockchain or on-chain smart contracts to store and reference data, rather than relying on traditional centralized databases. A common, simple dApp structure generally consists of a React.js or Vue.js front-end using Web3.js or Ethers.js to interact with smart contracts deployed to an EVM-compatible blockchain.
In this tutorial, you will learn how to create a task list where you can add tasks and mark them as complete or delete them in the CORE network.
- Git v2.44.0
- Node.js v20.11.1
- npm v10.2.4
- Hardhat v10.2.4
- MetaMask Web Wallet Extension
-
Download this repository
-
Install dependencies in the route /contract.
npm install
-
Install and configure MetaMask Chrome Extension to use with Core Testnet. Refer here for a detailed guide.
-
Create a secret.json file in the /contract folder and store the private key of your MetaMask wallet in it. Refer here for details on how to get MetaMask account's private key. Example:
{"PrivateKey":"ef1150b212a53b053a3dee265cb26cd010065b9340b4ac6cf5d895a7cf39c923"}
:::caution
Do not forget to add this file to the .gitignore
file in the root folder of your project so that you don't accidentally check your private keys/secret phrases into a public repository. Make sure you keep this file in an absolutely safe place!
:::
- Copy the following into your
hardhat.config.js
file in /contract
/**
* @type import('hardhat/config').HardhatUserConfig
*/
require('@nomiclabs/hardhat-ethers');
require("@nomiclabs/hardhat-waffle");
const { PrivateKey } = require('./secret.json');
module.exports = {
defaultNetwork: 'testnet',
networks: {
hardhat: {
},
testnet: {
url: 'https://rpc.test.btcs.network',
accounts: [PrivateKey],
chainId: 1115,
}
},
solidity: {
compilers: [
{
version: '0.8.24',
settings: {
evmVersion: 'paris',
optimizer: {
enabled: true,
runs: 200,
},
},
},
],
},
paths: {
sources: './contracts',
cache: './cache',
artifacts: './artifacts',
},
mocha: {
timeout: 20000,
},
};
- Inside the /contract/contracts folder is the TodoList.sol file which will contain the smart contract code to be used for this tutorial.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract TodoList {
struct Task {
uint id;
string content;
bool completed;
}
mapping(address => Task[]) private userTasks;
mapping(address => mapping(uint => uint)) private userTaskIndex; // Mapping from task ID to array index
mapping(address => uint) private taskCounts;
event TaskCreated(address indexed user, uint id, string content);
event TaskCompleted(address indexed user, uint id);
event TaskRemoved(address indexed user, uint id);
event TaskUpdated(address indexed user, uint id, string newContent);
function createTask(string calldata _content) external {
uint taskId = taskCounts[msg.sender]++;
userTasks[msg.sender].push(Task(taskId, _content, false));
userTaskIndex[msg.sender][taskId] = userTasks[msg.sender].length - 1; // Map task ID to its index
emit TaskCreated(msg.sender, taskId, _content);
}
function getTasks(uint _startIndex, uint _limit) external view returns (Task[] memory) {
Task[] memory allTasks = userTasks[msg.sender];
uint totalTasks = allTasks.length;
require(_startIndex < totalTasks, "Start index out of bounds");
uint endIndex = _startIndex + _limit > totalTasks ? totalTasks : _startIndex + _limit;
uint taskCount = endIndex - _startIndex;
Task[] memory paginatedTasks = new Task[](taskCount);
for (uint i = 0; i < taskCount; i++) {
paginatedTasks[i] = allTasks[totalTasks - 1 - (_startIndex + i)];
}
return paginatedTasks;
}
function completeTask(uint _taskId) external {
uint index = userTaskIndex[msg.sender][_taskId];
require(index < userTasks[msg.sender].length, "Task does not exist");
Task storage task = userTasks[msg.sender][index];
require(!task.completed, "Task already completed");
task.completed = true;
emit TaskCompleted(msg.sender, _taskId);
}
function removeTask(uint _taskId) external {
uint index = userTaskIndex[msg.sender][_taskId];
Task[] storage tasks = userTasks[msg.sender];
require(index < tasks.length, "Task does not exist");
// Remove the task by replacing it with the last one
tasks[index] = tasks[tasks.length - 1];
userTaskIndex[msg.sender][tasks[index].id] = index; // Update the index mapping for the moved task
tasks.pop();
// Remove the mapping for the deleted task
delete userTaskIndex[msg.sender][_taskId];
emit TaskRemoved(msg.sender, _taskId);
}
function updateTask(uint _taskId, string calldata _newContent) external {
uint index = userTaskIndex[msg.sender][_taskId];
Task[] storage tasks = userTasks[msg.sender];
require(index < tasks.length, "Task does not exist");
Task storage task = tasks[index];
task.content = _newContent;
emit TaskUpdated(msg.sender, _taskId, _newContent);
}
}
- To compile the
TodoList
smart contract defined in theTodoList.sol
, from the /contract directory run the following command. (Every time a change is made to the contract code we must recompile it).
npx hardhat compile
-
Before deploying your smart contract on the Core Chain, it is best adviced to first run a series of tests making sure that the smart contract is working as desired. Refer to the detailed guide here for more details.
-
Create a
scripts
folder in the /contract directory of your project. Inside this folder, create a filedeploy.js
; paste the following script into it.
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploy contract with the account:", deployer.address);
const TodoList = await ethers.getContractFactory("TodoList");
const todoList = await TodoList.deploy();
console.log("Contract Address:", todoList.address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
-
Make sure your MetaMask wallet has tCORE test tokens for the Core Testnet. Refer here for details on how to get tCORE tokens from Core Faucet.
-
Run the following command from the root directory of your project, to deploy your smart contract on the Core Chain.
npx hardhat run scripts/deploy.js
- In the root folder, install all the dependencies.
npm install
-
In the path src/contractABI we must copy the abi of our smart contract in the case of making modifications, this information will be obtained from contract/artifacts/contracts/TodoList.json.
-
Once the smart contract is deployed, it is necessary to copy the address and replace it in each of the components where we make calls to the contract, in this case in src/components/New.tsx and src/components/Get.tsx.
-
To test if things are working fine, run the application by using the following command. This will serve applciation with hot reload feature at http://localhost:5173
npm run dev
- To add a new task, you must first enter the text or description of the task.
- Once this is done, click on the Add Task button and accept the transaction in metamask.
- To mark a task as complete, you must first go to the "Task List" option in the menu.
- You must locate the task you want to mark as complete and then click on the "Complete" button.
- Finally, you will only have to accept the transaction in metamask and the task will be marked as complete.
- To remove a task from the task list, you must first go to the “Task List” option in the menu.
- You must locate the task you want to remove and then click on the “Remove” button.
- Finally, you will only have to accept the transaction in metamask and the task will be removed from the list.