Getting Started with Task Manager
This guide walks you through the essentials of using the Task Manager to schedule on-chain tasks.
Prerequisites
To start using the Task Manager, you'll need:
- Access to the Monad Testnet
- MON tokens for:
- Gas fees for all transactions
- Task execution payment (paid directly or through bonded shMONAD)
- The AddressHub contract address:
0xC9f0cDE8316AbC5Efc8C3f5A6b571e815C021B51
(Testnet)
For a complete working example, check the Task Manager Demo Repository
Quickstart
1. Setup Environment
Create a .env
file:
ADDRESS_HUB=0xC9f0cDE8316AbC5Efc8C3f5A6b571e815C021B51
RPC_URL=https://testnet-rpc.monad.xyz
DEPLOYER_PRIVATE_KEY=your_private_key_here_without_0x_prefix
2. Install Dependencies
You can use either viem (recommended) or ethers.js:
# Using viem
npm install viem dotenv
3. Basic Task Scheduling Example
Here's a simplified example using viem:
import {
encodeFunctionData,
createPublicClient,
createWalletClient,
http,
encodePacked,
formatUnits
} from "viem";
import { TaskManagerHelper } from "./utils/taskManager"; // See demo repo
import { AddressHubHelper } from "./utils/addressHub"; // See demo repo
import dotenv from "dotenv";
dotenv.config();
async function main() {
// Setup clients
const publicClient = createPublicClient({
chain: { id: 10143, name: 'Monad Testnet' },
transport: http(process.env.RPC_URL)
});
const account = { address: "0x...", privateKey: `0x${process.env.DEPLOYER_PRIVATE_KEY}` };
const walletClient = createWalletClient({
account,
chain: { id: 10143, name: 'Monad Testnet' },
transport: http(process.env.RPC_URL)
});
// Get contract addresses from hub
const addressHub = new AddressHubHelper(
process.env.ADDRESS_HUB as `0x${string}`,
publicClient
);
const taskManagerAddress = await addressHub.getTaskManagerAddress();
// Create Task Manager helper
const taskManager = new TaskManagerHelper(
taskManagerAddress,
publicClient,
walletClient
);
// 1. Define your target contract call
const targetAddress = "0x..." as `0x${string}`; // Your target contract
const targetCall = encodeFunctionData({
abi: [{
type: 'function',
name: 'yourFunction',
inputs: [{ type: 'uint256', name: 'value' }],
outputs: [],
stateMutability: 'nonpayable'
}],
functionName: 'yourFunction',
args: [123]
});
// 2. Pack the target and calldata for execution environment
const packedData = encodePacked(
['address', 'bytes'],
[targetAddress, targetCall]
);
// 3. Get execution environment address
const executionEnv = await taskManager.contract.read.EXECUTION_ENV_TEMPLATE();
// 4. Schedule task parameters
const currentBlock = await publicClient.getBlockNumber();
const targetBlock = currentBlock + 10n; // Schedule 10 blocks ahead
const taskGasLimit = 100000n; // Small task (≤100k gas)
// 5. Calculate required payment
const estimatedCost = await taskManager.estimateTaskCost(targetBlock, taskGasLimit);
console.log(`Estimated cost: ${formatUnits(estimatedCost, 18)} MON`);
// 6. Schedule the task (with direct MON payment)
const txHash = await taskManager.scheduleTaskRaw(
executionEnv,
taskGasLimit,
targetBlock,
estimatedCost,
packedData,
);
console.log(`Task scheduled! Transaction hash: ${txHash}`);
console.log(`Task will execute around block ${targetBlock}`);
}
main().catch(console.error);
Understanding Task Data Encoding
Proper encoding is crucial for task execution. Here's a visual breakdown:
┌─────────────────────────────────────────┐
│ 1. Target Function Call │
│ encodeFunctionData(yourFunction, []) │
└───────────────┬─────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 2. Pack Target + Calldata │
│ encodePacked([address, bytes], │
│ [targetAddress, call]) │
└───────────────┬─────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 3. Schedule With Execution Environment │
│ taskManager.scheduleTask( │
│ executionEnv, │
│ gasLimit, │
│ targetBlock, │
│ payment, │
│ packedData │
│ ) │
└─────────────────────────────────────────┘
Task Execution
After scheduling, tasks are executed in two ways:
-
Automatic Execution: A network of executors monitors pending tasks and executes them at the target block.
-
Manual Execution: You can execute tasks yourself:
// Using the TaskManagerHelper from the demo
const feesEarned = await taskManager.executeTasks(walletClient.account.address);
console.log(`Earned ${formatUnits(feesEarned, 18)} MON in fees`);
Bonding vs. Direct Payment
-
Direct Payment: Send MON with your transaction (simpler for one-time tasks)
// The value parameter sends MON for payment
await taskManager.scheduleTask(...args, { value: estimatedCost }); -
Bonding: Use shMONAD tokens (required for recurring tasks)
// First deposit and bond if needed
await shmonad.depositAndBond(policyId, account.address, depositAmount);
// Then schedule with the bond
await taskManager.scheduleWithBond(...args);
Troubleshooting
Common issues:
- Insufficient MON: Ensure you have enough MON for gas fees and task payment
- Encoding Errors: Verify your ABI is correct for the target function
- Task Execution Failures: Check if your task's gas limit is sufficient
Next Steps
For more details:
- ITaskManager Interface - Complete API reference
- Task Manager Demo Repository - Full examples
- Contract Addresses - Deployed contract reference