Nethereum.Mud
NuGet:
Nethereum.Mud| Source:src/Nethereum.Mud/
Nethereum.Mud
Nethereum.Mud provides core infrastructure for interacting with MUD (Onchain Engine) applications. MUD is a framework for building ambitious Ethereum applications with on-chain state management using an Entity-Component-System (ECS) architecture.
What is MUD?
MUD (Onchain Engine) is a framework for building complex, composable applications on Ethereum. It provides:
- Standardized On-Chain Data Storage - All application state lives on-chain in structured tables
- Entity-Component-System Architecture - Organize game/app logic using ECS patterns
- Composability - Applications can read and extend each other's data permissionlessly
- Automatic Synchronization - Client-side state stays in sync with blockchain state
- Developer Experience - TypeScript + Solidity tooling for rapid development
Nethereum.Mud brings this power to .NET, enabling you to build MUD clients, indexers, and tools in C#.
Features
- Table Records - Strongly-typed representations of MUD tables (keys + values)
- Schema Encoding/Decoding - Automatic encoding/decoding of MUD schemas
- Resource Management - Resource identifiers for namespaces, tables, and systems
- TableRepository - Query interface with LINQ-like predicates
- In-Memory Storage - Local table record caching and change tracking
- REST API Client - Query remote table repositories via HTTP
Installation
dotnet add package Nethereum.Mud
Dependencies
- Nethereum.Web3
- Nethereum.Util.Rest
MUD Architecture
MUD organizes on-chain applications using several core concepts:
World Contract
The World is the central registry contract that contains:
- All namespaces, tables, and systems
- Access control and permissions
- Store logic for reading/writing data
Every MUD application has one World contract address.
Namespaces
Namespaces organize related tables and systems:
- Provide access control boundaries
- Group related functionality
- Example:
"Land","Inventory","Combat"
Tables
Tables store on-chain data in structured key-value format:
- Schema defines field types (keys + values)
- Keys identify records (e.g.,
playerId,itemId) - Values contain the actual data
- Stored in the World's Store contract
Example table structure:
Table: "Player"
Keys: [playerId: uint256]
Values: [name: string, level: uint8, health: uint16]
Systems
Systems are smart contracts containing application logic:
- Functions that read/write table data
- Registered in the World
- Can be called via World contract's delegation
- Example:
"MoveSystem","CraftingSystem","TradeSystem"
Resources
Resources are identified by namespace:name:
- Tables:
tbtype (e.g.,"Game:Player"→0x7462...) - Systems:
sytype (e.g.,"Game:MoveSystem"→0x7379...) - Encoded as
bytes32resource IDs
Composability
MUD's composability model allows:
- Reading other apps' data - Any application can query any MUD World's tables
- Extending applications - Deploy new systems that interact with existing tables
- Building on top - Create meta-applications using multiple Worlds as data sources
Code Generation
MUD tables are typically code-generated from mud.config.ts. Use .nethereum-gen.multisettings for C# generation:
Configuration File
Create .nethereum-gen.multisettings in your contracts directory:
{
"paths": ["mud.config.ts"],
"generatorConfigs": [
{
"baseNamespace": "MyGame.Tables",
"basePath": "../MyGame/Tables",
"generatorType": "MudTables"
},
{
"baseNamespace": "MyGame.Systems",
"basePath": "../MyGame/Systems",
"generatorType": "MudExtendedService"
}
]
}
Generator Types
1. MudTables - Generates table record classes:
- TableRecord classes with typed Keys and Values
- Automatic encoding/decoding methods
- Schema information
2. MudExtendedService - Generates system service classes:
- Service classes for calling system functions
- Typed parameters and return values
- Integration with World contract
Generated Table Record Example
From a MUD config defining a Player table:
// Auto-generated from mud.config.ts
public partial class PlayerTableRecord : TableRecord<PlayerKey, PlayerValue>
{
public PlayerTableRecord() : base("MyWorld", "Player") { }
public class PlayerKey
{
[Parameter("uint256", "playerId", 1)]
public BigInteger PlayerId { get; set; }
}
public class PlayerValue
{
[Parameter("string", "name", 1)]
public string Name { get; set; }
[Parameter("uint8", "level", 2)]
public byte Level { get; set; }
[Parameter("uint16", "health", 3)]
public ushort Health { get; set; }
}
}
Running Code Generation
# Using Nethereum.Generators.JavaScript
npm install -g nethereum-codegen
# Generate from mud.config.ts
nethereum-codegen generate
Or use the VS Code Solidity extension with multisettings support: https://github.com/juanfranblanco/vscode-solidity
Encoding & Decoding
MUD uses custom encoding for efficient on-chain storage:
Schema Encoding
Table schemas are encoded into bytes32:
- First 2 bytes: Static field types
- Next 2 bytes: Dynamic field types
- Remaining bytes: Field count metadata
using Nethereum.Mud.EncodingDecoding;
var schema = SchemaEncoder.GetSchemaEncoded<PlayerKey, PlayerValue>(resourceId);
Console.WriteLine($"Key schema: {schema.KeySchema.ToHex()}");
Console.WriteLine($"Value schema: {schema.ValueSchema.ToHex()}");
Console.WriteLine($"Static fields: {schema.NumStaticFields}");
Console.WriteLine($"Dynamic fields: {schema.NumDynamicFields}");
Key Encoding
Keys are encoded as fixed 32-byte chunks:
- Each key component padded to 32 bytes
- Concatenated together
- Used as the record identifier
var playerRecord = new PlayerTableRecord
{
Keys = new PlayerTableRecord.PlayerKey { PlayerId = 42 }
};
// Encoded as: 0x000000000000000000000000000000000000000000000000000000000000002a
var encodedKey = playerRecord.GetEncodedKey();
Value Encoding
Values are encoded in two parts:
1. Static Data - Fixed-size fields (uint256, address, bool, etc.):
// Static fields packed tightly
// e.g., uint8 + uint16 = 3 bytes total
2. Dynamic Data - Variable-size fields (string, bytes, arrays):
// Prefixed with EncodedLengths (packed field lengths)
// Then concatenated dynamic data
Example:
var playerRecord = new PlayerTableRecord
{
Keys = new PlayerTableRecord.PlayerKey { PlayerId = 1 },
Values = new PlayerTableRecord.PlayerValue
{
Name = "Alice",
Level = 5,
Health = 100
}
};
var encodedValues = playerRecord.GetEncodedValues();
Console.WriteLine($"Static data: {encodedValues.StaticData.ToHex()}");
Console.WriteLine($"Dynamic data: {encodedValues.DynamicData.ToHex()}");
Console.WriteLine($"Encoded lengths: {encodedValues.EncodedLengths.ToHex()}");
Decoding
Decode on-chain data back to typed records:
// Decode from on-chain bytes
playerRecord.DecodeKey(encodedKeyBytes);
playerRecord.DecodeValues(encodedValueBytes);
Console.WriteLine($"Player {playerRecord.Keys.PlayerId}: {playerRecord.Values.Name}");
Console.WriteLine($"Level {playerRecord.Values.Level}, Health {playerRecord.Values.Health}");
Usage Examples
Example 1: Working with Table Records
using Nethereum.Mud;
using Nethereum.Web3;
// Table record with key and values
var playerRecord = new PlayerTableRecord();
// Set key
playerRecord.Keys = new PlayerTableRecord.PlayerKey
{
PlayerId = 42
};
// Set values
playerRecord.Values = new PlayerTableRecord.PlayerValue
{
Name = "Alice",
Level = 10,
Health = 100
};
// Encode for on-chain storage
var encodedKey = playerRecord.GetEncodedKey();
var encodedValues = playerRecord.GetEncodedValues();
// Decode from on-chain data
playerRecord.DecodeKey(encodedKeyBytes);
playerRecord.DecodeValues(encodedValuesBytes);
Console.WriteLine($"Player {playerRecord.Keys.PlayerId}: {playerRecord.Values.Name}");
Example 2: In-Memory Table Repository
using Nethereum.Mud.TableRepository;
// Create in-memory repository
var repository = new InMemoryTableRepository<PlayerTableRecord>();
// Add records
var player1 = new PlayerTableRecord
{
Keys = new() { PlayerId = 1 },
Values = new() { Name = "Alice", Level = 10, Health = 100 }
};
var player2 = new PlayerTableRecord
{
Keys = new() { PlayerId = 2 },
Values = new() { Name = "Bob", Level = 5, Health = 50 }
};
await repository.UpsertAsync(player1);
await repository.UpsertAsync(player2);
// Query by key
var record = await repository.GetByKeyAsync(player1.GetEncodedKey());
Console.WriteLine($"Found player: {record.Values.Name}");
// Query all records
var allPlayers = await repository.GetAsync();
Console.WriteLine($"Total players: {allPlayers.Count}");
Example 3: Query with Predicates
using Nethereum.Mud.TableRepository;
var repository = new InMemoryTableRepository<PlayerTableRecord>();
// Add multiple records
// ...
// Query with predicate builder
var predicate = new TablePredicateBuilder<PlayerTableRecord>()
.Where(player => player.Values.Level > 5)
.And(player => player.Values.Health >= 50)
.Build();
var results = await repository.GetAsync(predicate);
foreach (var player in results)
{
Console.WriteLine($"Player {player.Keys.PlayerId}: Level {player.Values.Level}, Health {player.Values.Health}");
}
Example 4: Change Tracking Repository
using Nethereum.Mud.TableRepository;
// Repository with change tracking
var repository = new InMemoryChangeTrackerTableRepository<PlayerTableRecord>();
// Modify records
var player = await repository.GetByKeyAsync(encodedKey);
player.Values.Level += 1;
player.Values.Health -= 10;
await repository.UpsertAsync(player);
// Get all changes since last checkpoint
var changeSet = repository.GetChangeSet();
Console.WriteLine($"Added: {changeSet.AddedRecords.Count}");
Console.WriteLine($"Updated: {changeSet.UpdatedRecords.Count}");
Console.WriteLine($"Deleted: {changeSet.DeletedRecords.Count}");
// Clear change tracking
repository.ClearChangeSet();
Example 5: Resource Identifiers
using Nethereum.Mud;
// Create resource for a table
var playerTableResource = new Resource("Game", "Player");
// Get resource ID (bytes32)
var resourceId = playerTableResource.ResourceIdEncoded;
Console.WriteLine($"Resource ID: {resourceId.ToHex()}");
// Create system resource
var moveSystemResource = new SystemResource("Game", "MoveSystem");
// Namespace resource
var gameNamespace = new NamespaceResource("Game");
Example 6: Schema Encoding
using Nethereum.Mud.EncodingDecoding;
// Get schema for a table record type
var schema = SchemaEncoder.GetSchemaEncoded<PlayerKey, PlayerValue>(resourceId);
Console.WriteLine($"Key schema: {schema.KeySchema.ToHex()}");
Console.WriteLine($"Value schema: {schema.ValueSchema.ToHex()}");
Console.WriteLine($"Total static fields: {schema.NumStaticFields}");
Console.WriteLine($"Total dynamic fields: {schema.NumDynamicFields}");
Example 7: REST API Client
using Nethereum.Mud.TableRepository;
// Connect to remote table repository API
var apiClient = new StoredRecordRestApiClient("https://api.example.com/mud");
// Query specific table
var records = await apiClient.GetRecordsAsync<PlayerTableRecord>(
worldAddress: "0xWorldAddress",
tableId: playerTableResource.ResourceIdEncoded.ToHex()
);
foreach (var record in records)
{
Console.WriteLine($"Player {record.Keys.PlayerId}: {record.Values.Name}");
}
// Query with filters
var filteredRecords = await apiClient.GetRecordsAsync<PlayerTableRecord>(
worldAddress: "0xWorldAddress",
tableId: playerTableResource.ResourceIdEncoded.ToHex(),
filter: $"Level gt 5"
);
Example 8: Working with Stored Records
using Nethereum.Mud.TableRepository;
// StoredRecord is the persisted form of a table record
var storedRecord = new StoredRecord
{
WorldAddress = "0xWorldAddress",
TableId = playerTableResource.ResourceIdEncoded.ToHex(),
Key0 = encodedKey[0].ToHex(), // First key component
StaticData = encodedStaticData.ToHex(),
DynamicData = encodedDynamicData.ToHex(),
EncodedLengths = encodedLengths.ToHex(),
IsDeleted = false
};
// Convert to table record
var mapper = new StoredRecordDTOMapper<PlayerTableRecord>();
var tableRecord = mapper.MapFromStoredRecord(storedRecord);
Console.WriteLine($"Restored player {tableRecord.Keys.PlayerId}");
Example 9: Singleton Tables (No Keys)
Some MUD tables have no keys (configuration singletons):
using Nethereum.Mud;
// Singleton table record
public class ConfigTableRecord : TableRecordSingleton<ConfigValue>
{
public ConfigTableRecord() : base("MyWorld", "Config") { }
public class ConfigValue
{
[Parameter("uint256", "maxPlayers", 1)]
public BigInteger MaxPlayers { get; set; }
[Parameter("bool", "isPaused", 2)]
public bool IsPaused { get; set; }
}
}
// Usage
var config = new ConfigTableRecord();
config.Values = new ConfigTableRecord.ConfigValue
{
MaxPlayers = 100,
IsPaused = false
};
// Only has values, no keys
var encodedValues = config.GetEncodedValues();
Example 10: Production MUD Application Pattern
using Nethereum.Mud;
using Nethereum.Mud.TableRepository;
using Nethereum.Web3;
// Initialize repository with change tracking
var playerRepository = new InMemoryChangeTrackerTableRepository<PlayerTableRecord>();
var inventoryRepository = new InMemoryChangeTrackerTableRepository<InventoryTableRecord>();
// Load initial state from chain or database
// ...
// Application logic modifies records
var player = await playerRepository.GetByKeyAsync(encodedPlayerId);
player.Values.Level += 1;
player.Values.Health = 100;
await playerRepository.UpsertAsync(player);
var inventory = await inventoryRepository.GetByKeyAsync(encodedInventoryKey);
inventory.Values.Quantity -= 1;
await inventoryRepository.UpsertAsync(inventory);
// Get changes to sync with chain
var playerChanges = playerRepository.GetChangeSet();
var inventoryChanges = inventoryRepository.GetChangeSet();
// Batch changes for on-chain transaction
var allChanges = new List<TableRecordChangeSet>
{
playerChanges,
inventoryChanges
};
// Send to chain via MUD World contract
// (See Nethereum.Mud.Contracts for World interaction)
// Clear change tracking after sync
playerRepository.ClearChangeSet();
inventoryRepository.ClearChangeSet();
Core Classes
TableRecord<TKey, TValue>
Base class for MUD table records with keys.
public abstract class TableRecord<TKey, TValue> : ITableRecord
where TKey : class, new()
where TValue : class, new()
{
public TKey Keys { get; set; }
public TValue Values { get; set; }
public List<byte[]> GetEncodedKey();
public EncodedValues GetEncodedValues();
public void DecodeKey(List<byte[]> encodedKey);
public void DecodeValues(EncodedValues encodedValues);
}
TableRecordSingleton
Base class for tables without keys (singletons).
public abstract class TableRecordSingleton<TValue> : ITableRecordSingleton
where TValue : class, new()
{
public TValue Values { get; set; }
public EncodedValues GetEncodedValues();
public void DecodeValues(EncodedValues encodedValues);
}
ITableRepository
Interface for table record storage and querying.
public interface ITableRepository<TTableRecord> where TTableRecord : ITableRecord, new()
{
Task<TTableRecord> GetByKeyAsync(List<byte[]> key);
Task<List<TTableRecord>> GetAsync();
Task<List<TTableRecord>> GetAsync(TablePredicate<TTableRecord> predicate);
Task UpsertAsync(TTableRecord record);
Task DeleteAsync(List<byte[]> key);
}
Resource
Represents a MUD resource identifier.
public class Resource
{
public Resource(string nameSpace, string name);
public string NameSpace { get; }
public string Name { get; }
public byte[] ResourceIdEncoded { get; }
}
Advanced Topics
Custom Encoding
using Nethereum.Mud.EncodingDecoding;
// Custom key encoding
var customKeys = KeyEncoderDecoder.EncodeKey(new MyKey
{
PlayerId = 1,
ItemId = 42
});
// Custom value encoding
var customValues = ValueEncoderDecoder.EncodeValues(new MyValue
{
Quantity = 10,
IsActive = true
});
Field Layout
using Nethereum.Mud.EncodingDecoding;
// Get field layout for a schema
var fieldLayout = FieldLayoutEncoder.Encode(
staticFieldLengths: new List<byte> { 32, 32, 1 }, // uint256, uint256, bool
numDynamicFields: 2 // Two dynamic fields (bytes or arrays)
);
Resource Registry
using Nethereum.Mud;
// Register custom resource types
ResourceTypeRegistry.Register("CustomType", 0x1234);
// Get resource type
var tableType = ResourceTypeRegistry.GetResourceTypeId("Table"); // 0x7462...
var systemType = ResourceTypeRegistry.GetResourceTypeId("System"); // 0x7379...
Production Patterns
1. Local-First Architecture
Keep MUD table data in memory for fast reads, sync changes to chain:
// In-memory repositories for all tables
var repositories = new Dictionary<string, object>
{
["Player"] = new InMemoryChangeTrackerTableRepository<PlayerTableRecord>(),
["Inventory"] = new InMemoryChangeTrackerTableRepository<InventoryTableRecord>(),
// ...
};
// User interacts locally
// Changes tracked automatically
// Periodic sync to chain
await SyncAllChangesToChainAsync(repositories);
2. Offline Mode with REST API
// Load initial state from REST API
var apiClient = new StoredRecordRestApiClient("https://api.mud.game");
var records = await apiClient.GetRecordsAsync<PlayerTableRecord>(worldAddress, tableId);
var localRepo = new InMemoryTableRepository<PlayerTableRecord>();
foreach (var record in records)
{
await localRepo.UpsertAsync(record);
}
// Work offline
// ...
// Sync back when online
var changes = GetLocalChanges();
await SyncToChainAsync(changes);
3. Real-Time Updates
// Subscribe to on-chain table updates
// (See Nethereum.Mud.Contracts for event subscriptions)
void OnStoreSetRecordEvent(StoreSetRecordEventDTO evt)
{
var storedRecord = evt.ToStoredRecord();
var tableRecord = mapper.MapFromStoredRecord<PlayerTableRecord>(storedRecord);
// Update local repository
await repository.UpsertAsync(tableRecord);
// Notify UI
NotifyUIOfUpdate(tableRecord);
}
Why Use MUD?
Composability
MUD applications are inherently composable:
- Read any World's data - Query tables from other applications
- Extend existing apps - Deploy new systems that interact with existing tables
- Build meta-applications - Aggregate data from multiple Worlds
On-Chain Data Indexing
All state lives on-chain in structured tables:
- Queryable - Use Store events to index data
- Verifiable - All data is on-chain and cryptographically secure
- Persistent - Data survives as long as Ethereum does
Client Synchronization
MUD provides automatic state sync:
- Store Events -
Store_SetRecord,Store_DeleteRecord - Real-time updates - Clients stay in sync via event subscriptions
- Optimistic updates - Apply changes locally, sync to chain asynchronously
Complex Application Building
MUD enables complex on-chain applications:
- Games - Fully on-chain games with rich state
- Autonomous Worlds - Persistent, extensible virtual worlds
- DeFi Protocols - Complex multi-table financial logic
- Social Networks - On-chain social graphs and interactions
Related Packages
Dependencies
- Nethereum.Web3 - Ethereum interaction
- Nethereum.Util.Rest - REST API utilities
Used By
- Nethereum.Mud.Contracts - On-chain MUD World and Store contracts
- Nethereum.Mud.Repositories.EntityFramework - EF Core persistence
- Nethereum.Mud.Repositories.Postgres - PostgreSQL persistence