TaskMaster
A gamified task management application built with ao-forge, demonstrating basic AO process integration with Next.js. This example shows the default project structure generated by ao-forge init.
Overview
TaskMaster is a decentralized task management system that includes:
- Gamified Task Management - Earn points for creating and completing tasks
- AO Process Integration - Lua-based smart contract for task storage
- Modern UI - Next.js with TypeScript and Tailwind CSS
- Wallet Integration - ArConnect wallet support
Project Structure
The default ao-forge project includes:
my-taskmaster/
├── README.md
├── package.json
├── tsconfig.json
├── next.config.ts
├── postcss.config.mjs
├── eslint.config.mjs
├── ao/
│ ├── README.md
│ └── task-process.lua # Main AO process
├── app/
│ ├── components/
│ │ ├── CreateTask.tsx # Task creation form
│ │ ├── TaskList.tsx # Task display component
│ │ ├── PointsDisplay.tsx # Points counter
│ │ ├── Leaderboard.tsx # Global leaderboard
│ │ ├── Navbar.tsx # Navigation
│ │ └── WalletConnect.tsx # Wallet connection
│ ├── lib/
│ │ └── aoconnect.ts # AO client library
│ ├── page.tsx # Main page
│ ├── layout.tsx # Root layout
│ ├── globals.css # Global styles
│ └── favicon.ico
└── public/
Key Features
🎮 Gamification
- +10 points for creating tasks
- +25 points for completing tasks
- Global leaderboard with top 10 users
- Priority levels (high, medium, low)
🔗 AO Integration
- Decentralized storage on Arweave
- Lua smart contracts for task logic
- Message-based communication with AO processes
- Wallet authentication via ArConnect
AO Process (task-process.lua)
The core AO process handles all task operations:
-- TaskMaster AO Process
local json = require("json")
-- Initialize state
local Tasks = {}
local Points = {}
local TaskCounter = 0
-- Helper functions
local function safeDecode(data)
local ok, result = pcall(json.decode, data)
if ok then return result else return nil end
end
local function getUserPoints(userId)
return Points[userId] or 0
end
local function awardPoints(userId, amount)
Points[userId] = getUserPoints(userId) + amount
return Points[userId]
end
-- Create Task Handler
Handlers.add(
"CreateTask",
Handlers.utils.hasMatchingTag("Action", "CreateTask"),
function(msg)
local taskData = safeDecode(msg.Data)
if not taskData then
return msg.reply({
success = false,
message = "Invalid JSON data"
})
end
TaskCounter = TaskCounter + 1
local taskId = tostring(TaskCounter)
local task = {
id = taskId,
title = taskData.title or "Untitled Task",
description = taskData.description or "",
completed = false,
created = os.time(),
owner = msg.From,
priority = taskData.priority or "medium"
}
Tasks[taskId] = task
local newPoints = awardPoints(msg.From, 10)
msg.reply({
success = true,
message = "Task created successfully! +10 points",
data = {
task = task,
points = newPoints
}
})
end
)
-- Complete Task Handler
Handlers.add(
"CompleteTask",
Handlers.utils.hasMatchingTag("Action", "CompleteTask"),
function(msg)
local taskData = safeDecode(msg.Data)
local taskId = taskData and taskData.taskId
if not taskId then
return msg.reply({
success = false,
message = "TaskId not provided"
})
end
local task = Tasks[taskId]
if not task or task.owner ~= msg.From then
return msg.reply({
success = false,
message = "Task not found or not owned by you"
})
end
if task.completed then
return msg.reply({
success = false,
message = "Task already completed"
})
end
task.completed = true
task.completedAt = os.time()
local newPoints = awardPoints(msg.From, 25)
msg.reply({
success = true,
message = "Task completed! +25 points",
data = {
task = task,
points = newPoints
}
})
end
)
-- Get Tasks Handler
Handlers.add(
"GetTasks",
Handlers.utils.hasMatchingTag("Action", "GetTasks"),
function(msg)
local userTasks = {}
for _, task in pairs(Tasks) do
if task.owner == msg.From then
table.insert(userTasks, task)
end
end
table.sort(userTasks, function(a, b) return a.created > b.created end)
msg.reply({
success = true,
data = {
tasks = userTasks,
count = #userTasks
}
})
end
)
-- Get Points Handler
Handlers.add(
"GetPoints",
Handlers.utils.hasMatchingTag("Action", "GetPoints"),
function(msg)
local userPoints = getUserPoints(msg.From)
msg.reply({
success = true,
data = {
points = userPoints,
userId = msg.From
}
})
end
)
-- Get Leaderboard Handler
Handlers.add(
"GetLeaderboard",
Handlers.utils.hasMatchingTag("Action", "GetLeaderboard"),
function(msg)
local leaderboard = {}
for userId, points in pairs(Points) do
table.insert(leaderboard, { userId = userId, points = points })
end
table.sort(leaderboard, function(a, b) return a.points > b.points end)
local top10 = {}
for i = 1, math.min(10, #leaderboard) do
table.insert(top10, leaderboard[i])
end
msg.reply({
success = true,
data = {
leaderboard = top10
}
})
end
)
-- Delete Task Handler
Handlers.add(
"DeleteTask",
Handlers.utils.hasMatchingTag("Action", "DeleteTask"),
function(msg)
local taskData = safeDecode(msg.Data)
local taskId = taskData and taskData.taskId
if not taskId then
return msg.reply({
success = false,
message = "TaskId not provided"
})
end
local task = Tasks[taskId]
if not task or task.owner ~= msg.From then
return msg.reply({
success = false,
message = "Task not found or not owned by you"
})
end
Tasks[taskId] = nil
msg.reply({
success = true,
message = "Task deleted successfully"
})
end
)
Frontend Integration
AO Client (app/lib/aoconnect.ts)
import { message, result, createSigner } from '@permaweb/aoconnect';
export interface Task {
id: string;
title: string;
description: string;
completed: boolean;
created: number;
owner: string;
priority: 'high' | 'medium' | 'low';
completedAt?: number;
}
export class AOClient {
public signer: any;
public processId: string;
constructor(processId: string) {
this.signer = createSigner(window.arweaveWallet);
this.processId = processId;
}
async sendMessage(
action: string,
data: Record<string, any> = {},
extraTags: Record<string, string> = {}
): Promise<AOResponse> {
try {
const tags = [{ name: 'Action', value: action }];
for (const key in extraTags) {
tags.push({ name: key, value: extraTags[key] || '' });
}
const msgTxId = await message({
process: this.processId,
tags,
data: JSON.stringify(data),
signer: this.signer
});
const response = await result({
message: msgTxId,
process: this.processId
});
if (response.Error) {
return { success: false, error: response.Error };
}
return { success: true, data: response };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
// Task methods
async createTask(taskData: { title: string; description?: string; priority?: 'high' | 'medium' | 'low' }) {
return this.sendMessage('CreateTask', taskData);
}
async completeTask(taskId: string) {
return this.sendMessage('CompleteTask', { taskId: taskId });
}
async getTasks() {
return this.sendMessage('GetTasks', {});
}
async getPoints() {
return this.sendMessage('GetPoints', {});
}
async getLeaderboard() {
return this.sendMessage('GetLeaderboard', {});
}
async deleteTask(taskId: string) {
return this.sendMessage('DeleteTask', { taskId: taskId });
}
}
Getting Started
1. Create the Project
# Create new TaskMaster project
ao-forge init my-taskmaster --framework nextjs
cd my-taskmaster
2. Install Dependencies
# Install dependencies
pnpm install
# or
npm install
# or
yarn install
3. Start Development
# Start the development server
ao-forge dev
# In another terminal, start the AO process
ao-forge process start -n taskmaster-process
4. Deploy AO Process
# Deploy the AO process using AOS CLI
aos taskmaster-process --load ./ao/task-process.lua --wallet ./wallet.json
5. Use the Application
- Open your browser to
http://localhost:3000 - Connect your Arweave wallet (ArConnect)
- Enter your AO process ID
- Start creating and managing tasks!
Key Components
CreateTask Component
- Form for creating new tasks
- Priority selection (high, medium, low)
- Real-time points updates
TaskList Component
- Display all user tasks
- Mark tasks as complete
- Delete tasks
- Priority-based styling
PointsDisplay Component
- Show current user points
- Real-time updates
- Points history
Leaderboard Component
- Global top 10 users
- Points rankings
- User identification
Configuration
The project uses standard Next.js configuration with:
- TypeScript for type safety
- Tailwind CSS for styling
- ESLint for code quality
- PostCSS for CSS processing
Dependencies
{
"dependencies": {
"@permaweb/aoconnect": "^0.0.90",
"next": "15.5.4",
"react": "19.1.0",
"react-dom": "19.1.0"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "15.5.4",
"tailwindcss": "^4",
"typescript": "^5"
}
}
Features Demonstrated
AO Development Patterns
- Message Handlers - Using
Handlers.add()for message processing - State Management - Local variables for task and points storage
- Error Handling - Proper error responses and validation
- User Authentication - Owner-based task access control
Frontend Integration
- Wallet Connection - ArConnect integration
- Real-time Updates - Live task and points updates
- Component Architecture - Modular React components
- TypeScript Integration - Full type safety
Modern Web Development
- Next.js App Router - Modern routing system
- Responsive Design - Mobile-first approach
- Dark Theme - Modern UI with glassmorphism effects
- Interactive Elements - Smooth animations and transitions
This example demonstrates the complete integration between AO processes and modern web frameworks, showing how to build decentralized applications with ao-forge.
