diff --git a/app/api/get-tutorial-list/route.ts b/app/api/get-tutorial-list/route.ts
index b4f0f71..5fb1f9d 100644
--- a/app/api/get-tutorial-list/route.ts
+++ b/app/api/get-tutorial-list/route.ts
@@ -46,6 +46,16 @@ export async function GET() {
lang: "C#",
langId: "csharp",
},
+ {
+ id: "todo",
+ img: "/todo.jpeg",
+ title: "ToDo Contract",
+ description: "A basic ToDo smart contract",
+ level: "Intermediate",
+ levelId: "intermediate",
+ lang: "C#",
+ langId: "csharp",
+ },
].filter((i) => (solidityEnabled ? true : i.langId !== "solidity"));
return Response.json(data);
diff --git a/app/tutorials/[id]/_content/todo/todo.mdx b/app/tutorials/[id]/_content/todo/todo.mdx
new file mode 100644
index 0000000..25d7f94
--- /dev/null
+++ b/app/tutorials/[id]/_content/todo/todo.mdx
@@ -0,0 +1,268 @@
+# ToDo
+
+**Description**: This contract is moderately complex, demonstrating task management functionalities such as creating, updating, and deleting tasks. It also includes features for task prioritization and user interaction.
+
+**Purpose**: To introduce you to task management systems in smart contracts, focusing on state management, user interactions, and contract updates for efficient handling of to-do tasks.
+
+**Difficulty Level**: Moderate
+
+## Step 1 - Develop Smart Contract
+
+### Adding Your Smart Contract Code
+
+First, generate the template: .
+
+Now that we have a template todo list project, we can customise the template to incorporate our own contract logic.
+Let's start by implementing methods to handle the basic functionality of creating, editing, listing, deleting, and marking tasks as complete within the contract state. ToDo dApp includes the below functionalities like:
+1. Create a task (Name, category, description, createAt, updatedAt)
+2. Mark task as completed
+3. Delete a task
+4. List all the tasks
+5. Edit a task
+
+#### Defining Methods and Messages
+
+The implementation of `todo_app.proto` file inside folder `src/Protobuf/contract/` is as follows:
+
+```csharp title="todo_app.proto"
+syntax = "proto3";
+import "aelf/options.proto";
+import "google/protobuf/empty.proto";
+import "google/protobuf/wrappers.proto";
+import "Protobuf/reference/acs12.proto";
+// The namespace of this class
+option csharp_namespace = "AElf.Contracts.ToDo";
+service ToDo {
+ // The name of the state class the smart contract is going to use to access blockchain state
+ option (aelf.csharp_state) = "AElf.Contracts.ToDo.ToDoState";
+ option (aelf.base) = "Protobuf/reference/acs12.proto";
+ rpc Initialize (google.protobuf.Empty) returns (google.protobuf.Empty) {
+ }
+ rpc CreateTask (TaskInput) returns (google.protobuf.StringValue) {
+ }
+ rpc UpdateTask (TaskUpdateInput) returns (google.protobuf.Empty) {
+ }
+ rpc DeleteTask (google.protobuf.StringValue) returns (google.protobuf.Empty) {
+ }
+ rpc ListTasks (google.protobuf.StringValue) returns (TaskList) {
+ option (aelf.is_view) = true;
+ }
+ rpc GetTask (google.protobuf.StringValue) returns (Task) {
+ option (aelf.is_view) = true;
+ }
+ rpc GetInitialStatus (google.protobuf.Empty) returns (google.protobuf.BoolValue) {
+ option (aelf.is_view) = true;
+ }
+}
+// A message to represent a task
+message Task {
+ string task_id = 1;
+ string name = 2;
+ string description = 3;
+ string category = 4;
+ string status = 5;
+ string owner = 6;
+ int64 created_at = 7;
+ int64 updated_at = 8;
+}
+// Input for creating a task
+message TaskInput {
+ string name = 1;
+ string description = 2;
+ string category = 3;
+}
+// Input for updating a task
+message TaskUpdateInput {
+ string task_id = 1;
+ string name = 2;
+ string description = 3;
+ string category = 4;
+ string status = 5;
+}
+// List of tasks
+message TaskList {
+ repeated Task tasks = 1;
+}
+```
+
+- `rpc` methods define the callable functions within the contract, allowing external systems to interact with the contract's logic.
+- `message` represent the structured data exchanged between the contract and external systems.
+
+#### Define Contract States
+
+The implementation of the ToDo app state inside file `src/ToDoAppState.cs` is as follows:
+
+```csharp title="src/ToDoAppState.cs"
+using AElf.Sdk.CSharp.State;
+using AElf.Types;
+
+namespace AElf.Contracts.ToDo
+{
+ public class ToDoState : ContractState
+ {
+ public BoolState Initialized { get; set; }
+ public SingletonState
Owner { get; set; }
+ public MappedState Tasks { get; set; } // Mapping of task ID to Task
+ public MappedState TaskExistence { get; set; } // Mapping to track task existence
+ public StringState TaskIds { get; set; } // Concatenated string of task IDs
+ public Int32State TaskCounter { get; set; } // Counter for generating unique IDs
+ }
+}
+```
+
+- The `State.cs` file in an aelf blockchain smart contract holds the variables that store the contract's data, making sure this data is saved and accessible whenever the contract needs it.
+
+#### Implement ToDo Smart Contract
+
+The implementation of the ToDo App smart contract inside file `src/ToDoApp.cs` is as follows:
+
+```csharp title="src/ToDoApp.cs"
+using Google.Protobuf.WellKnownTypes;
+using System.Collections.Generic;
+namespace AElf.Contracts.ToDo
+{
+ public class ToDo : ToDoContainer.ToDoBase
+ {
+ public override Empty Initialize(Empty input)
+ {
+ if (State.Initialized.Value)
+ {
+ return new Empty();
+ }
+ State.Initialized.Value = true;
+ State.Owner.Value = Context.Sender;
+ State.TaskIds.Value = "";
+ State.TaskCounter.Value = 0;
+ return new Empty();
+ }
+ public override StringValue CreateTask(TaskInput input)
+ {
+ if (!State.Initialized.Value)
+ {
+ return new StringValue { Value = "Contract not initialized." };
+ }
+ var taskId = (State.TaskCounter.Value + 1).ToString();
+ State.TaskCounter.Value++;
+ var timestamp = Context.CurrentBlockTime.Seconds;
+ // Create task dictionary entry directly in ToDo class
+ State.Tasks[taskId] = new Task
+ {
+ TaskId = taskId,
+ Name = input.Name,
+ Description = input.Description,
+ Category = input.Category,
+ Status = "pending",
+ CreatedAt = timestamp,
+ UpdatedAt = timestamp,
+ Owner = Context.Sender.ToString().Trim('"'),
+ };
+ State.TaskExistence[taskId] = true;
+ // Append task ID to the list of IDs
+ var existingTaskIds = State.TaskIds.Value;
+ existingTaskIds += string.IsNullOrEmpty(existingTaskIds) ? taskId : $",{taskId}";
+ State.TaskIds.Value = existingTaskIds;
+ return new StringValue { Value = taskId };
+ }
+ public override Empty UpdateTask(TaskUpdateInput input)
+ {
+ var task = State.Tasks[input.TaskId];
+ if (task == null)
+ {
+ return new Empty(); // Handle case if task doesn't exist
+ }
+ task.Name = input.Name ?? task.Name;
+ task.Description = input.Description ?? task.Description;
+ task.Category = input.Category ?? task.Category;
+ task.Status = input.Status ?? task.Status;
+ task.UpdatedAt = Context.CurrentBlockTime.Seconds;
+ State.Tasks[input.TaskId] = task;
+ return new Empty();
+ }
+ public override Empty DeleteTask(StringValue input)
+ {
+ State.Tasks.Remove(input.Value);
+ State.TaskExistence.Remove(input.Value);
+ // Remove task ID from the list of IDs
+ var existingTaskIds = State.TaskIds.Value.Split(',');
+ var newTaskIds = new List(existingTaskIds.Length);
+ foreach (var taskId in existingTaskIds)
+ {
+ if (taskId != input.Value)
+ {
+ newTaskIds.Add(taskId);
+ }
+ }
+ State.TaskIds.Value = string.Join(",", newTaskIds);
+ return new Empty();
+ }
+ public override TaskList ListTasks(StringValue input)
+ {
+ var owner = input.Value; // Get the owner value from the input
+ var taskList = new TaskList();
+ var taskIds = State.TaskIds.Value.Split(',');
+ foreach (var taskId in taskIds)
+ {
+ var task = State.Tasks[taskId];
+ if (task != null && task.Owner == owner) // Filter tasks by owner
+ {
+ taskList.Tasks.Add(task);
+ }
+ }
+ return taskList;
+ }
+ public override Task GetTask(StringValue input)
+ {
+ var task = State.Tasks[input.Value];
+ if (task == null)
+ {
+ return new Task { TaskId = input.Value, Name = "Task not found." };
+ }
+ return task;
+ }
+ public override BoolValue GetInitialStatus(Empty input)
+ {
+ return new BoolValue { Value = State.Initialized.Value };
+ }
+ }
+}
+```
+
+## Step 2 - Building Smart Contract
+
+Build the new code by clicking the Build button.
+
+## Step 3 - Deploy Smart Contract
+
+Deploy the new code by clicking the Deploy button.
+
+## 🎯 Conclusion
+
+🎉 Congratulations on successfully completing the **ToDo dApp** tutorial! 🎉 You've taken important steps in setting up your development environment, developing and deploying a smart contract on ToDo dApp, and building a fully functional ToDo decentralized application on the aelf blockchain. 🌟
+
+**📚 What You've Learned**
+
+Throughout this tutorial, you've mastered:
+
+ - **💻 Developing Your Smart Contract:** You created the foundation of your ToDo dApp by writing and building the smart contract that manages tasks, from creation to deletion.
+
+ - **🚀 Deploying the Smart Contract:** You deployed your smart contract to the aelf blockchain, enabling its functionalities to be used in a live environment.
+
+**🔍 Final Output**
+
+By now, you should have:
+
+ - 📜 A deployed smart contract that powers your ToDo dApp, managing tasks with functionalities for creation, updating, status management, and deletion.
+
+**➡️ What's Next?**
+
+With the basics under your belt, consider exploring more advanced topics:
+
+ - **📈 Enhancing Smart Contract Logic:** Introduce more complex features to your ToDo dApp, such as prioritization, deadlines, or collaboration tools.
+
+ - **🔒 Improving Security:** Ensure your dApp and smart contract are secure by implementing best practices and security measures.
+
+ - **🌍 Exploring Cross-Chain Features:** Expand your dApp's capabilities by exploring aelf's cross-chain interoperability, enabling interaction with other blockchains.
+
+The possibilities with blockchain technology and decentralized applications are endless. You're now well-equipped to take your ToDo dApp to the next level. Keep building, innovating, and exploring with aelf. 🚀
+
+Happy coding and expanding your **ToDo dApp! 😊**
diff --git a/components/tutorial/generate-template.tsx b/components/tutorial/generate-template.tsx
index ceec6b6..f203095 100644
--- a/components/tutorial/generate-template.tsx
+++ b/components/tutorial/generate-template.tsx
@@ -8,9 +8,11 @@ import { mutate } from "swr";
export default function GenerateTemplate({
name = "HelloWorld",
template = "aelf",
+ renameHelloWorldProto = "hello_world_contract",
}: {
name?: string;
template?: string;
+ renameHelloWorldProto?: string;
}) {
const pathname = usePathname();
@@ -29,9 +31,23 @@ export default function GenerateTemplate({
const res = await fetch(
`/api/get-template-data?id=${template}&name=${name}`
);
- const templateData: { path: string; contents: string }[] =
+ const _templateData: { path: string; contents: string }[] =
await res.json();
+ const templateData = _templateData.map((i) => {
+ if (i.path.includes("hello_world_contract.proto")) {
+ return {
+ ...i,
+ path: i.path.replace(
+ "hello_world_contract.proto",
+ `${renameHelloWorldProto}.proto`
+ ),
+ };
+ }
+
+ return i;
+ });
+
await db.files.bulkDelete(
(await db.files.toArray())
.map((i) => i.path)