Implementing Access Control
The heart of any Smart Gate is its access control system. In this chapter, we'll build a flexible access control system that supports public, whitelist, and tribe-based access.
The Whitelist Table
First, add a table for whitelisted addresses:
typescript
// Add to mud.config.ts
GateWhitelist: {
keySchema: {
gateId: "uint256",
player: "address",
},
valueSchema: {
isAllowed: "bool",
addedAt: "uint256",
},
},Access Control System
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import { System } from "@latticexyz/world/src/System.sol";
import { GateConfig } from "../codegen/tables/GateConfig.sol";
import { GateWhitelist } from "../codegen/tables/GateWhitelist.sol";
contract GateAccessSystem is System {
error NotGateOwner(uint256 gateId, address caller);
error InvalidAccessMode(uint8 mode);
uint8 constant ACCESS_PUBLIC = 0;
uint8 constant ACCESS_WHITELIST = 1;
uint8 constant ACCESS_TRIBE = 2;
function setAccessMode(uint256 gateId, uint8 mode) public {
_requireOwner(gateId);
if (mode > ACCESS_TRIBE) {
revert InvalidAccessMode(mode);
}
GateConfig.setAccessMode(gateId, mode);
}
function addToWhitelist(uint256 gateId, address player) public {
_requireOwner(gateId);
GateWhitelist.set(gateId, player, true, block.timestamp);
}
function removeFromWhitelist(uint256 gateId, address player) public {
_requireOwner(gateId);
GateWhitelist.set(gateId, player, false, 0);
}
function checkAccess(uint256 gateId, address player) public view returns (bool) {
uint8 mode = GateConfig.getAccessMode(gateId);
if (mode == ACCESS_PUBLIC) {
return true;
}
if (mode == ACCESS_WHITELIST) {
return GateWhitelist.getIsAllowed(gateId, player);
}
if (mode == ACCESS_TRIBE) {
return _isTribeMember(gateId, player);
}
return false;
}
function _requireOwner(uint256 gateId) internal view {
address owner = GateConfig.getOwner(gateId);
if (_msgSender() != owner) {
revert NotGateOwner(gateId, _msgSender());
}
}
function _isTribeMember(uint256 gateId, address player) internal view returns (bool) {
// In production, this would check the tribe registry
// For now, we'll use a simplified check
address owner = GateConfig.getOwner(gateId);
return player == owner; // Placeholder
}
}Key Design Decisions
Why Separate Systems?
We split gate usage and access control into separate systems because:
Why Onchain Access Control?
Checking access on-chain guarantees enforcement. A client-side check could be bypassed, but a revert in Solidity is final.
In the next chapter, we'll add toll collection and fee management.
Sign in to track your progress.