Writing Your First System
Now that you understand MUD's architecture, let's write a real system. We'll build a simple visitor counter that tracks how many times a Smart Assembly has been interacted with.
Step 1: Define the Table
First, define a table to store visitor counts per assembly:
// mud.config.ts
import { mudConfig } from "@latticexyz/world/src/mudConfig";
export default mudConfig({
tables: {
VisitorCount: {
keySchema: {
smartObjectId: "uint256",
},
valueSchema: {
count: "uint32",
lastVisitor: "address",
lastVisitTime: "uint256",
},
},
},
});After updating the config, run code generation:
pnpm mud tablegenStep 2: Write the System
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import { System } from "@latticexyz/world/src/System.sol";
import { VisitorCount } from "../codegen/tables/VisitorCount.sol";
contract VisitorSystem is System {
error AlreadyVisitedRecently(uint256 smartObjectId, address visitor);
function recordVisit(uint256 smartObjectId) public {
address visitor = _msgSender();
// Read current state
uint32 currentCount = VisitorCount.getCount(smartObjectId);
address lastVisitor = VisitorCount.getLastVisitor(smartObjectId);
uint256 lastTime = VisitorCount.getLastVisitTime(smartObjectId);
// Prevent spam: same visitor must wait 60 seconds
if (lastVisitor == visitor && block.timestamp - lastTime < 60) {
revert AlreadyVisitedRecently(smartObjectId, visitor);
}
// Update the table
VisitorCount.set(
smartObjectId,
currentCount + 1,
visitor,
block.timestamp
);
}
function getVisitorCount(uint256 smartObjectId) public view returns (uint32) {
return VisitorCount.getCount(smartObjectId);
}
}Step 3: Understanding the Code
Let's break down the key patterns:
Reading State
uint32 currentCount = VisitorCount.getCount(smartObjectId);MUD generates getter functions for each column. The key (smartObjectId) identifies which row to read.
Writing State
VisitorCount.set(smartObjectId, currentCount + 1, visitor, block.timestamp);The set function writes all columns at once. Order matches valueSchema in the config.
Access Control
address visitor = _msgSender();The _msgSender() function (from MUD's System base class) returns the actual caller, even through delegatecall chains.
Step 4: Local Testing
Write a test using Foundry:
// test/VisitorSystem.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "forge-std/Test.sol";
import { VisitorSystem } from "../src/systems/VisitorSystem.sol";
contract VisitorSystemTest is Test {
function test_recordVisit_incrementsCount() public {
// Setup and test will be covered in a later chapter
}
}In the next chapter, we'll explore reading on-chain tables from a frontend.
Sign in to track your progress.