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
# Install the Nethereum code generator
dotnet tool install -g Nethereum.Generator.Console
# Generate from the multisettings file
Nethereum.Generator.Console generate from-config -cfg .nethereum-gen.multisettings
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 resourceId = ResourceEncoder.EncodeTable("Game", "Player");
var schema = SchemaEncoder.GetSchemaEncoded<PlayerTableRecord.PlayerKey, PlayerTableRecord.PlayerValue>(resourceId);
Console.WriteLine($"Key schema: {schema.KeySchema.ToHex()}");
Console.WriteLine($"Value schema: {schema.ValueSchema.ToHex()}");
Console.WriteLine($"Field layout: {schema.FieldLayout.ToHex()}");
Console.WriteLine($"Key names: {string.Join(", ", schema.KeyNames)}");
Console.WriteLine($"Field names: {string.Join(", ", schema.FieldNames)}");
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.GetEncodeValues();
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.GetEncodeValues();
// 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;
// The repository is not generic; a single instance stores records for any table
var repository = new InMemoryTableRepository();
// 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 }
};
// SetRecordAsync<T> resolves the table id and key from the record itself
await repository.SetRecordAsync(player1);
await repository.SetRecordAsync(player2);
// Read all records for a table, decoded to the strongly-typed record
var allPlayers = (await repository.GetTableRecordsAsync<PlayerTableRecord>()).ToList();
Console.WriteLine($"Total players: {allPlayers.Count}");
// Read a single raw stored record by encoded table id + combined key bytes
var keyBytes = TableRepositoryBase.ConvertKeyToCombinedHex(player1.GetEncodedKey()).HexToByteArray();
var stored = await repository.GetRecordAsync(player1.ResourceIdEncoded, keyBytes);
Example 3: Query with Predicates
using Nethereum.Mud.TableRepository;
// Predicates filter on KEY fields. The builder takes three type parameters
// (record, key, value) and the World address, and is finalized with Expand().
var predicate = new TablePredicateBuilder<PlayerTableRecord, PlayerTableRecord.PlayerKey, PlayerTableRecord.PlayerValue>("0xWorldAddress")
.AndEqual(key => key.PlayerId, 1) // AND key0 = 0x01
.OrEqual(key => key.PlayerId, 2) // OR key0 = 0x02
.AndNotEqual(key => key.PlayerId, 3) // AND key0 != 0x03
.Expand(); // Finalize the predicate
// Run the predicate against a repository that supports predicate queries.
// InMemoryTableRepository does not (query AllRecords with LINQ instead);
// the REST API client and the EF/Postgres repositories do.
var results = await apiClient.GetTableRecordsAsync<PlayerTableRecord>(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 (non-generic)
var repository = new InMemoryChangeTrackerTableRepository();
repository.StartTracking();
// Modify and upsert a record (tracked once tracking is enabled)
player.Values.Level += 1;
player.Values.Health -= 10;
await repository.SetRecordAsync(player);
// The accumulated changes: Upserted and Deleted are keyed by table id then key
var changeSet = repository.ChangeSet;
Console.WriteLine($"Tables with upserts: {changeSet.Upserted.Count}");
Console.WriteLine($"Tables with deletes: {changeSet.Deleted.Count}");
// Project the changes for a specific table to strongly-typed records
var playerChanges = changeSet.GetTableRecordChanges<PlayerTableRecord>();
Console.WriteLine($"Upserted: {playerChanges.Upserted.Count}, Deleted: {playerChanges.Deleted.Count}");
// Take a snapshot and clear the tracker in one step (or ClearChangeSet() to just clear)
var snapshot = repository.GetAndClearChangeSet();
Example 5: Resource Identifiers
using Nethereum.Mud.EncodingDecoding;
// Create resource ID for a table (returns bytes32)
var playerTableResourceId = ResourceEncoder.EncodeTable("Game", "Player");
Console.WriteLine($"Table Resource ID: {playerTableResourceId.ToHex()}");
// Create resource ID for a system
var moveSystemResourceId = ResourceEncoder.EncodeSystem("Game", "MoveSystem");
Console.WriteLine($"System Resource ID: {moveSystemResourceId.ToHex()}");
// Create resource ID for a namespace
var gameNamespaceId = ResourceEncoder.EncodeNamespace("Game");
Console.WriteLine($"Namespace Resource ID: {gameNamespaceId.ToHex()}");
// Decode a resource ID back to its components
var decoded = ResourceEncoder.Decode(playerTableResourceId);
Console.WriteLine($"Namespace: {decoded.Namespace}, Name: {decoded.Name}");
Example 6: Schema Encoding
using Nethereum.Mud.EncodingDecoding;
// Get schema for a table record type (pass the encoded table resource id)
var resourceId = ResourceEncoder.EncodeTable("Game", "Player");
var schema = SchemaEncoder.GetSchemaEncoded<PlayerTableRecord.PlayerKey, PlayerTableRecord.PlayerValue>(resourceId);
Console.WriteLine($"Key schema: {schema.KeySchema.ToHex()}");
Console.WriteLine($"Value schema: {schema.ValueSchema.ToHex()}");
Console.WriteLine($"Field layout: {schema.FieldLayout.ToHex()}");
Console.WriteLine($"Key names: {string.Join(", ", schema.KeyNames)}");
Console.WriteLine($"Field names: {string.Join(", ", schema.FieldNames)}");
Example 7: REST API Client
using Nethereum.Mud.TableRepository;
// Connect to a remote table repository API
// ctor: (IRestHttpHelper httpHelper, string baseUrl, string postPath = "storedrecords")
var httpHelper = new RestHttpHelper(new HttpClient());
var apiClient = new StoredRecordRestApiClient(httpHelper, "https://api.example.com/mud");
// Build a predicate (filters on key fields) and query typed records
var predicate = new TablePredicateBuilder<PlayerTableRecord, PlayerTableRecord.PlayerKey, PlayerTableRecord.PlayerValue>("0xWorldAddress")
.AndEqual(key => key.PlayerId, 1)
.Expand();
IEnumerable<PlayerTableRecord> records = await apiClient.GetTableRecordsAsync<PlayerTableRecord>(predicate);
foreach (var record in records)
{
Console.WriteLine($"Player {record.Keys.PlayerId}: {record.Values.Name}");
}
// Or fetch the raw stored records matching the predicate
List<StoredRecord> stored = await apiClient.GetRecordsAsync(predicate);
Example 8: Working with Stored Records
using Nethereum.Mud;
using Nethereum.Mud.TableRepository;
// StoredRecord is the persisted form of a table record.
// It derives from EncodedValues, so StaticData/DynamicData/EncodedLengths are byte[];
// the *Hex string setters (and TableId/Key0/Address) accept hex strings.
var playerTableResourceId = ResourceEncoder.EncodeTable("Game", "Player");
var encodedKey = playerRecord.GetEncodedKey();
var encodedValues = playerRecord.GetEncodeValues();
var storedRecord = new StoredRecord
{
Address = "0xWorldAddress", // the World address (property is Address)
TableId = playerTableResourceId.ToHex(true),
Key = TableRepositoryBase.ConvertKeyToCombinedHex(encodedKey), // combined key (hex string setter)
Key0 = encodedKey[0].ToHex(true), // first key component (hex string setter)
StaticDataHex = encodedValues.StaticData.ToHex(true),
DynamicDataHex = encodedValues.DynamicData.ToHex(true),
EncodedLengthsHex = encodedValues.EncodedLengths.ToHex(true),
IsDeleted = false
};
// Map to/from the transport DTO with the static extension methods
StoredRecordDTO dto = storedRecord.MapToStoredRecordDTO();
StoredRecord roundTripped = dto.MapToStoredRecord();
// Decode a stored record into a strongly-typed table record
var tableRecord = new PlayerTableRecord();
tableRecord.DecodeValues(roundTripped); // StoredRecord : EncodedValues
tableRecord.DecodeKey(KeyUtils.ConvertKeyFromCombinedHex(roundTripped.Key));
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.GetEncodeValues();
Example 10: Production MUD Application Pattern
using Nethereum.Mud;
using Nethereum.Mud.TableRepository;
using Nethereum.Web3;
// Initialize repositories with change tracking (non-generic; one stores many tables)
var playerRepository = new InMemoryChangeTrackerTableRepository();
var inventoryRepository = new InMemoryChangeTrackerTableRepository();
playerRepository.StartTracking();
inventoryRepository.StartTracking();
// Load initial state from chain or database
// ...
// Application logic modifies records, then upserts them
player.Values.Level += 1;
player.Values.Health = 100;
await playerRepository.SetRecordAsync(player);
inventory.Values.Quantity -= 1;
await inventoryRepository.SetRecordAsync(inventory);
// Snapshot the changes to sync with chain (GetAndClearChangeSet also clears the tracker)
InMemoryChangeSet playerChanges = playerRepository.GetAndClearChangeSet();
InMemoryChangeSet inventoryChanges = inventoryRepository.GetAndClearChangeSet();
// Project to strongly-typed changes per table when needed
TableRecordChangeSet<PlayerTableRecord> typedPlayerChanges =
playerChanges.GetTableRecordChanges<PlayerTableRecord>();
// Send to chain via MUD World contract
// (See Nethereum.Mud.Contracts for World interaction)
Core Classes
TableRecord<TKey, TValue>
Base class for MUD table records with keys.
public abstract class TableRecord<TKey, TValue> : TableRecordSingleton<TValue>, ITableRecord
where TKey : class, new()
where TValue : class, new()
{
public TKey Keys { get; set; }
// Values is inherited from TableRecordSingleton<TValue>
public virtual List<byte[]> GetEncodedKey();
public virtual void DecodeKey(List<byte[]> encodedKey);
public override SchemaEncoded GetSchemaEncoded();
// Inherited from TableRecordSingleton<TValue>:
// public virtual EncodedValues GetEncodeValues();
// 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 GetEncodeValues();
public void DecodeValues(EncodedValues encodedValues);
}
ITableRepository
Interface for table record storage and querying. It is not generic: a single repository stores records for any table, and the record type is supplied per call.
public interface ITableRepository : ITablePredicateQueryRepository
{
Task SetRecordAsync<TTableRecord>(TTableRecord record, string address = null,
BigInteger? blockNumber = null, int? logIndex = null) where TTableRecord : ITableRecord;
Task SetRecordsAsync<TTableRecord>(IEnumerable<TTableRecord> records, string address = null,
BigInteger? blockNumber = null, int? logIndex = null) where TTableRecord : ITableRecord;
Task SetRecordAsync(byte[] tableId, List<byte[]> key, EncodedValues encodedValues,
string address = null, BigInteger? blockNumber = null, int? logIndex = null);
Task<StoredRecord> GetRecordAsync(byte[] tableId, byte[] key);
Task<IEnumerable<EncodedTableRecord>> GetRecordsAsync(byte[] tableId);
Task<IEnumerable<TTableRecord>> GetTableRecordsAsync<TTableRecord>()
where TTableRecord : ITableRecordSingleton, new();
Task DeleteRecordAsync(byte[] tableId, List<byte[]> key, string address = null,
BigInteger? blockNumber = null, int? logIndex = null);
Task SetSpliceStaticDataAsync(byte[] tableId, List<byte[]> key, ulong start, byte[] newData,
string address = null, BigInteger? blockNumber = null, int? logIndex = null);
Task SetSpliceDynamicDataAsync(byte[] tableId, List<byte[]> key, ulong start, byte[] newData,
ulong deleteCount, byte[] encodedLengths, string address = null,
BigInteger? blockNumber = null, int? logIndex = null);
}
// Predicate queries are declared on the base interface:
public interface ITablePredicateQueryRepository
{
Task<IEnumerable<TTableRecord>> GetTableRecordsAsync<TTableRecord>(TablePredicate predicate)
where TTableRecord : ITableRecord, new();
Task<List<StoredRecord>> GetRecordsAsync(TablePredicate predicate);
}
ResourceEncoder
Static utility for creating and decoding MUD resource identifiers (bytes32).
public static class ResourceEncoder
{
public static byte[] EncodeTable(string @namespace, string name);
public static byte[] EncodeSystem(string @namespace, string name);
public static byte[] EncodeNamespace(string @namespace);
public static byte[] EncodeOffchainTable(string @namespace, string name);
public static byte[] EncodeRootTable(string name);
public static byte[] EncodeRootSystem(string name);
public static Resource Decode(byte[] resourceBytes);
}
Advanced Topics
Custom Encoding
using Nethereum.Mud.EncodingDecoding;
// Custom key encoding (EncodeKey<T> returns the key components as List<byte[]>)
var customKeys = KeyEncoderDecoder.EncodeKey(new MyKey
{
PlayerId = 1,
ItemId = 42
});
// Custom value encoding.
// EncodeValues takes a List<FieldValue>; to encode an object, use EncodedValues<T>
// (returns EncodedValues) or EncodeValuesAsyByteArray<T> (returns a single packed byte[]).
EncodedValues customValues = ValueEncoderDecoder.EncodedValues(new MyValue
{
Quantity = 10,
IsActive = true
});
byte[] packedValues = ValueEncoderDecoder.EncodeValuesAsyByteArray(new MyValue
{
Quantity = 10,
IsActive = true
});
Field Layout
using Nethereum.Mud.EncodingDecoding;
// Get field layout for a schema.
// EncodeFieldLayout takes the value fields (List<FieldInfo>) and derives the static
// field lengths and dynamic field count from them.
List<FieldInfo> valueFields = SchemaEncoder.GetFieldsFromType<PlayerTableRecord.PlayerValue>();
byte[] fieldLayout = FieldLayoutEncoder.EncodeFieldLayout(valueFields);
Resource Registry
using Nethereum.Mud;
// Map an encoded resource id (hex) to a .NET record type
var playerTableResourceId = ResourceEncoder.EncodeTable("Game", "Player");
ResourceTypeRegistry.RegisterType(playerTableResourceId.ToHex(true), typeof(PlayerTableRecord));
// Look the type back up by its encoded resource id
Type recordType = ResourceTypeRegistry.GetResourceType(playerTableResourceId.ToHex(true));
Production Patterns
1. Local-First Architecture
Keep MUD table data in memory for fast reads, sync changes to chain:
// A change-tracking repository is not generic and stores records for every table.
// Use one instance, or key several by concern if you prefer isolated change sets.
var repositories = new Dictionary<string, InMemoryChangeTrackerTableRepository>
{
["Player"] = new InMemoryChangeTrackerTableRepository(),
["Inventory"] = new InMemoryChangeTrackerTableRepository(),
// ...
};
foreach (var repo in repositories.Values)
repo.StartTracking();
// User interacts locally; upserts are tracked automatically
// Periodic sync to chain
await SyncAllChangesToChainAsync(repositories);
2. Offline Mode with REST API
// Load initial state from REST API
var httpHelper = new RestHttpHelper(new HttpClient());
var apiClient = new StoredRecordRestApiClient(httpHelper, "https://api.mud.game");
var predicate = new TablePredicateBuilder<PlayerTableRecord, PlayerTableRecord.PlayerKey, PlayerTableRecord.PlayerValue>(worldAddress)
.Expand();
var records = await apiClient.GetTableRecordsAsync<PlayerTableRecord>(predicate);
var localRepo = new InMemoryTableRepository();
foreach (var record in records)
{
await localRepo.SetRecordAsync(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 that yield StoredRecord instances)
async Task OnStoreSetRecord(StoredRecord storedRecord)
{
// Decode the stored record into a strongly-typed record
var tableRecord = new PlayerTableRecord();
tableRecord.DecodeValues(storedRecord); // StoredRecord : EncodedValues
tableRecord.DecodeKey(KeyUtils.ConvertKeyFromCombinedHex(storedRecord.Key));
// Update local repository
await repository.SetRecordAsync(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