Skip to main content

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:

  1. Access to the Monad Testnet
  2. MON tokens for:
    • Gas fees for all transactions
    • Task execution payment (paid directly or through bonded shMONAD)
  3. The AddressHub contract address: 0xC9f0cDE8316AbC5Efc8C3f5A6b571e815C021B51 (Testnet)
tip

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:

  1. Automatic Execution: A network of executors monitors pending tasks and executes them at the target block.

  2. 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:

  1. Insufficient MON: Ensure you have enough MON for gas fees and task payment
  2. Encoding Errors: Verify your ABI is correct for the target function
  3. Task Execution Failures: Check if your task's gas limit is sufficient

Next Steps

For more details: