Nethereum.MUD provides seamless integration between the Nethereum .NET Ethereum library and MUD (Multi-User Dungeon), enabling .NET developers to build complex applications and games on Ethereum using Unity, Blazor, and other .NET platforms.
Author: Juan Blanco (Creator of Nethereum and VSCode Solidity extension)
| Generator Type | Input | Output | Purpose |
|---|---|---|---|
ContractDefinition |
ABI JSON | Standard Service | Deployment, functions, events |
MudExtendedService |
ABI JSON | Extended Service | + Resource ID, Create2, registration |
MudTables |
mud.config.ts | TableRecord + TableService | Table schema and CRUD operations |
UnityRequest |
ABI JSON | Unity Requests | Unity-specific coroutine handlers |
MUD is a framework for building complex Ethereum applications and games. It provides:
- Data Model: Structured table-based storage supporting key-value and relational database formats
- Dynamic Schemas: Define schemas at runtime with flexibility to evolve post-deployment
- Typed Data Access: Auto-generated libraries for type-safe getters and setters with Solidity-compatible encoding
- Data Indexing: Events on all data changes, enabling real-time off-chain indexing of all on-chain data
- Scalability: Supports advanced data structures, ideal for complex logic in applications
- Flexibility: Adapts as needs evolve, allowing new data schemas without redeployment
- Upgradability: Upgrade or add new business logic without migrating data
- Interoperability: Data structures can be directly mapped to relational databases, easing off-chain integration
- Delegated Authority: Device wallets / Burner / Session wallets - users should not worry about their "main" account when using an application or a game
- Ever Evolving Worlds: New namespaces with new functionality and authority allows you or anyone to expand the "world"
To provide the tools, frameworks and guidance so any .NET developer and application can be created or integrated with Ethereum - bringing the love of Ethereum to .NET (and vice versa).
Nethereum supports integration across the entire .NET ecosystem:
- Mobile: iOS, Android, Tizen
- Desktop: Windows, macOS, Linux
- Gaming: Unity (AR/VR, Wearables, TV)
- Web: Blazor, ASP.NET, WebAssembly (WASM)
- Cloud: Azure, AWS
- IoT: Raspberry Pi, Windows IoT
- Enterprise: LOB applications, Windows Server
- Ethereum node (JSON-RPC)
- Smart contract integration
- RLP encoding
- EVM simulator
- Signing
- ABI Encoding
- Chain/log indexing
- L2 support
- MetaMask
- WalletConnect
- Reown AppKit (Blazor support)
- Azure Key Vault
- AWS Key Management
- Hardware wallets (Ledger, Trezor)
- Nethereum Playground
- Code generators
- Testing frameworks
- SIWE (Sign-In with Ethereum)
- Gnosis Safe
- ENS
- ERC20, ERC721, ERC1155
- EIP-712
- ERC6492
- Getting started templates (smart contracts)
- Blazor template
- SIWE template
- Web, Desktop, Mobile, Unity examples
Nethereum + MUD
│
├── Tables (Data Layer)
│ ├── TableRecord
│ ├── TableService
│ └── TableRepository (InMemory, EF, Postgres)
│
├── Systems (Business Logic)
│ ├── SystemService
│ └── SystemServiceResource
│
└── Namespaces (Organization)
├── NamespaceResource
└── TablesServices / SystemsServices
Nethereum's code generation extends the existing standard contract service generation to support MUD-specific components. The code generator produces:
- Standard Contract Services - Same as regular Nethereum generation
- MUD Extended Services - Systems with MUD-specific functionality (Resource IDs, Create2 deployment, function registration)
- MUD Tables - Table records and table services from
mud.config.ts
┌─────────────────────────────────────────────────────────────────────┐
│ MUD Code Generation │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ mud.config.ts ──────► MUD tablegen ──────► Solidity Tables │
│ │ │ │
│ │ ▼ │
│ │ Compile (forge/hardhat) │
│ │ │ │
│ ▼ ▼ │
│ .nethereum-gen.multisettings ABI JSON files │
│ │ │ │
│ └───────────────┬───────────────────────────┘ │
│ │ │
│ ▼ │
│ Nethereum Code Generator │
│ (VSCode Right-Click or CLI) │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ ▼ ▼ ▼ │
│ TableRecords TableServices SystemServices │
│ (from config) (from config) (from ABI + MudExtended) │
│ │
└─────────────────────────────────────────────────────────────────────┘
First, define your tables in mud.config.ts:
// mud.config.ts
import { defineWorld } from "@latticexyz/world";
export default defineWorld({
namespace: "myworld",
tables: {
CatalogueItem: {
schema: {
id: "uint32",
price: "uint32",
name: "string",
description: "string",
owner: "string",
},
key: ["id"],
},
Counter: {
schema: {
value: "uint32",
},
key: [],
},
},
});Generate Solidity tables using MUD tablegen:
# Note: On Windows, easier to use Git Bash or WSL2
pnpm mud tablegenCreate .nethereum-gen.multisettings file:
[
{
"paths": ["out/IncrementSystem.sol/IncrementSystem.json"],
"generatorConfigs": [
{
"baseNamespace": "MyProject.Contracts.MyWorld.Systems",
"basePath": "MyProject.Contracts/MyWorld/Systems",
"codeGenLang": 0,
"generatorType": "ContractDefinition"
},
{
"baseNamespace": "MyProject.Contracts.MyWorld.Systems",
"basePath": "MyProject.Contracts/MyWorld/Systems",
"codeGenLang": 0,
"generatorType": "MudExtendedService",
"mudNamespace": "myworld"
}
]
},
{
"paths": ["mud.config.ts"],
"generatorConfigs": [
{
"baseNamespace": "MyProject.Contracts.MyWorld.Tables",
"basePath": "MyProject.Contracts/MyWorld/Tables",
"generatorType": "MudTables",
"mudNamespace": "myworld"
}
]
}
]Using VSCode Solidity Extension:
- Right-click on the project
- Select "Nethereum: Generate Code from Settings"
Using CLI:
Nethereum.Generator.Console generate from-config
# Defaults to ".nethereum-gen.multisettings" in current folderThe code generator supports multiple configurations for generating client-side code for various platforms:
[
{
"paths": ["out/ERC20.sol/Standard_Token.json"],
"generatorConfigs": [
{
"baseNamespace": "MyProject.Contracts",
"basePath": "codeGenNodeTest/GeneratorSets/Example2/MyProject.Contracts",
"codeGenLang": 0,
"generatorType": "ContractDefinition"
},
{
"baseNamespace": "MyProject.Contracts",
"basePath": "codeGenNodeTest/GeneratorSets/Example2/MyProject.Contracts",
"codeGenLang": 0,
"generatorType": "UnityRequest"
}
]
},
{
"paths": ["out/IncrementSystem.sol/IncrementSystem.json"],
"generatorConfigs": [
{
"baseNamespace": "MyProject.Contracts.MyWorld1.Systems",
"basePath": "codeGenNodeTest/GeneratorSets/Example2/MyProject.Contracts/MyWorld1/Systems",
"codeGenLang": 0,
"generatorType": "ContractDefinition",
"mudNamespace": "myworld1"
},
{
"baseNamespace": "MyProject.Contracts.MyWorld1.Systems",
"basePath": "codeGenNodeTest/GeneratorSets/Example2/MyProject.Contracts/MyWorld1/Systems",
"codeGenLang": 0,
"generatorType": "MudExtendedService",
"mudNamespace": "myworld1"
}
]
},
{
"paths": ["mudMultipleNamespace/mud.config.ts"],
"generatorConfigs": [
{
"baseNamespace": "MyProject.Contracts.MyWorld1.Tables",
"basePath": "codeGenNodeTest/GeneratorSets/Example2/MyProject.Contracts/MyWorld1/Tables",
"generatorType": "MudTables",
"mudNamespace": "myworld1"
}
]
}
]| Type | Description |
|---|---|
ContractDefinition |
Standard Nethereum service, deployment, etc. |
MudExtendedService |
Extended functionality for MUD systems |
MudTables |
Table definitions from mud.config.ts |
UnityRequest |
Unity-specific request handlers |
When you use MudExtendedService, the generator creates a partial class that extends the standard ContractDefinition service with MUD-specific functionality.
1. Standard Contract Service (ContractDefinition):
// IncrementSystemService.cs - Standard Nethereum service
public partial class IncrementSystemService
{
public static Task<TransactionReceipt> DeployContractAndWaitForReceiptAsync(...)
public static Task<IncrementSystemService> DeployContractAndGetServiceAsync(...)
// All function handlers from ABI
public Task<string> IncrementRequestAsync(IncrementFunction incrementFunction) { ... }
public Task<TransactionReceipt> IncrementRequestAndWaitForReceiptAsync(...) { ... }
// Event handlers, error handlers, etc.
}2. MUD Extended Service (MudExtendedService) - Extends the above:
// IncrementSystemServiceMudExtended.cs - MUD-specific extensions
public class IncrementSystemServiceResource : SystemResource
{
public IncrementSystemServiceResource() : base("IncrementSystem") { }
}
public partial class IncrementSystemService : ISystemService<IncrementSystemServiceResource>
{
// System Resource identification
public IResource Resource => this.GetResource();
// System registration helper
public ISystemServiceResourceRegistration SystemServiceResourceRegistrator
{
get
{
return this.GetSystemServiceResourceRegistration<IncrementSystemServiceResource, IncrementSystemService>();
}
}
// Get all function ABIs for registration
public List<FunctionABI> GetSystemFunctionABIs()
{
return GetAllFunctionABIs();
}
// Create2 deployment support
public string CalculateCreate2Address(string deployerAddress, string salt, params ByteCodeLibrary[] byteCodeLibraries)
{
return new IncrementSystemDeployment().CalculateCreate2Address(deployerAddress, salt, byteCodeLibraries);
}
public Task<Create2ContractDeploymentTransactionResult> DeployCreate2ContractAsync(
string deployerAddress, string salt, params ByteCodeLibrary[] byteCodeLibraries)
{
var create2ProxyDeployerService = Web3.Eth.Create2DeterministicDeploymentProxyService;
var deployment = new IncrementSystemDeployment();
return create2ProxyDeployerService.DeployContractRequestAsync(deployment, deployerAddress, salt, byteCodeLibraries);
}
public Task<Create2ContractDeploymentTransactionReceiptResult> DeployCreate2ContractAndWaitForReceiptAsync(
string deployerAddress, string salt, ByteCodeLibrary[] byteCodeLibraries, CancellationToken cancellationToken = default)
{
var create2ProxyDeployerService = Web3.Eth.Create2DeterministicDeploymentProxyService;
var deployment = new IncrementSystemDeployment();
return create2ProxyDeployerService.DeployContractRequestAndWaitForReceiptAsync(
deployment, deployerAddress, salt, byteCodeLibraries, cancellationToken);
}
}| Feature | Description |
|---|---|
ISystemService<T> |
Interface marking this as a MUD system |
SystemResource |
Resource identifier for the system |
GetSystemFunctionABIs() |
Returns all functions to register with World |
SystemServiceResourceRegistrator |
Helper for registering the system |
CalculateCreate2Address() |
Deterministic address calculation |
DeployCreate2ContractAsync() |
Create2 deployment support |
When you use MudTables, the generator reads mud.config.ts and creates:
- TableRecord - Data structure matching the MUD schema
- TableService - Service for interacting with the table
1. TableRecord - Maps directly to mud.config.ts schema:
// CatalogueItemTableRecord.cs
public partial class CatalogueItemTableRecord : TableRecord<CatalogueItemTableRecord.CatalogueItemKey, CatalogueItemTableRecord.CatalogueItemValue>
{
// Constructor with table name and namespace
public CatalogueItemTableRecord() : base("myworld", "CatalogueItem")
{
}
// ========== Direct Property Accessors ==========
// Convenient access to key properties
public virtual uint Id => Keys.Id;
// Convenient access to value properties
public virtual uint Price => Values.Price;
public virtual string Name => Values.Name;
public virtual string Description => Values.Description;
public virtual string Owner => Values.Owner;
// ========== Key Class ==========
public partial class CatalogueItemKey
{
[Parameter("uint32", "id", 1)]
public virtual uint Id { get; set; }
}
// ========== Value Class ==========
public partial class CatalogueItemValue
{
[Parameter("uint32", "price", 1)]
public virtual uint Price { get; set; }
[Parameter("string", "name", 2)]
public virtual string Name { get; set; }
[Parameter("string", "description", 3)]
public virtual string Description { get; set; }
[Parameter("string", "owner", 4)]
public virtual string Owner { get; set; }
}
}2. TableService - CRUD operations for the table:
// CatalogueItemTableService.cs
public partial class CatalogueItemTableService : TableService<CatalogueItemTableRecord, CatalogueItemTableRecord.CatalogueItemKey, CatalogueItemTableRecord.CatalogueItemValue>
{
public CatalogueItemTableService(IWeb3 web3, string contractAddress) : base(web3, contractAddress) {}
// ========== GET by parameters ==========
public virtual Task<CatalogueItemTableRecord> GetTableRecordAsync(uint id, BlockParameter blockParameter = null)
{
var key = new CatalogueItemTableRecord.CatalogueItemKey();
key.Id = id;
return GetTableRecordAsync(key, blockParameter);
}
// ========== SET by parameters ==========
public virtual Task<string> SetRecordRequestAsync(uint id, uint price, string name, string description, string owner)
{
var key = new CatalogueItemTableRecord.CatalogueItemKey();
key.Id = id;
var values = new CatalogueItemTableRecord.CatalogueItemValue();
values.Price = price;
values.Name = name;
values.Description = description;
values.Owner = owner;
return SetRecordRequestAsync(key, values);
}
// ========== SET with receipt ==========
public virtual Task<TransactionReceipt> SetRecordRequestAndWaitForReceiptAsync(
uint id, uint price, string name, string description, string owner)
{
var key = new CatalogueItemTableRecord.CatalogueItemKey();
key.Id = id;
var values = new CatalogueItemTableRecord.CatalogueItemValue();
values.Price = price;
values.Name = name;
values.Description = description;
values.Owner = owner;
return SetRecordRequestAndWaitForReceiptAsync(key, values);
}
}The generated TableService inherits these methods from the base class:
public class TableService<TTableRecord, TKey, TValue>
{
// Register table schema in MUD
public Task<string> RegisterTableRequestAsync();
public Task<TransactionReceipt> RegisterTableRequestAndWaitForReceiptAsync();
// Set record using key/value objects
public Task<string> SetRecordRequestAsync(TKey key, TValue values);
public Task<TransactionReceipt> SetRecordRequestAndWaitForReceiptAsync(TKey key, TValue values);
// Get record from chain
public Task<TTableRecord> GetTableRecordAsync(TKey key, BlockParameter blockParameter = null);
// Get all records from logs
public Task<List<TTableRecord>> GetAllRecordsFromLogsAsync(BlockParameter fromBlock = null, BlockParameter toBlock = null);
// Query from repository
public Task<IEnumerable<TTableRecord>> GetRecordsFromRepositoryAsync(ITableRepository repository);
}Both TableRecord and TableService are generated as partial classes, allowing you to extend them:
// CatalogueItemTableService.Custom.cs - Your custom extensions
public partial class CatalogueItemTableService
{
// Add custom validation
public async Task<string> SetRecordWithValidationAsync(uint id, uint price, string name, string description, string owner)
{
if (price <= 0)
throw new ArgumentException("Price must be positive");
if (string.IsNullOrEmpty(name))
throw new ArgumentException("Name is required");
return await SetRecordRequestAsync(id, price, name, description, owner);
}
// Add custom queries
public async Task<List<CatalogueItemTableRecord>> GetItemsByOwnerAsync(string owner, ITableRepository repository)
{
var allRecords = await GetRecordsFromRepositoryAsync(repository);
return allRecords.Where(r => r.Owner == owner).ToList();
}
}After code generation, your project structure will look like:
MyProject.Contracts/
├── MyWorld/
│ ├── Tables/
│ │ ├── CatalogueItemTableRecord.cs # Generated from mud.config.ts
│ │ ├── CatalogueItemTableService.cs # Generated from mud.config.ts
│ │ ├── CounterTableRecord.cs # Generated from mud.config.ts
│ │ ├── CounterTableService.cs # Generated from mud.config.ts
│ │ └── MyWorldTableServices.cs # Container for all tables (manual)
│ │
│ ├── Systems/
│ │ ├── IncrementSystem/
│ │ │ ├── IncrementSystemService.cs # Standard ContractDefinition
│ │ │ ├── IncrementSystemServiceMudExtended.cs # MUD extensions
│ │ │ ├── ContractDefinition/
│ │ │ │ ├── IncrementFunction.cs
│ │ │ │ ├── IncrementSystemDeployment.cs
│ │ │ │ └── ...
│ │ │ └── ...
│ │ └── MyWorldSystemServices.cs # Container for all systems (manual)
│ │
│ ├── MyWorldNamespace.cs # Namespace container (manual)
│ └── MyWorldNamespaceResource.cs # Namespace resource (manual)
│
└── .nethereum-gen.multisettings # Generation configuration
MUD supports multiple namespaces, and so does Nethereum code generation:
[
{
"paths": ["mud.config.ts"],
"generatorConfigs": [
{
"baseNamespace": "MyProject.Contracts.World1.Tables",
"basePath": "MyProject.Contracts/World1/Tables",
"generatorType": "MudTables",
"mudNamespace": "world1"
}
]
},
{
"paths": ["mud.config.ts"],
"generatorConfigs": [
{
"baseNamespace": "MyProject.Contracts.World2.Tables",
"basePath": "MyProject.Contracts/World2/Tables",
"generatorType": "MudTables",
"mudNamespace": "world2"
}
]
}
]- Create a
.nethereum-gen.multisettingsfile with your configuration - Right-click in VSCode to generate code
- Different settings for different ABIs or mud.config.ts files
Note: When working on Windows, it is easier to install pnpm in Git Bash or WSL2
A TableRecord is mapped directly to the MUD config schema:
public partial class ItemTableRecord : TableRecord<ItemTableRecord.ItemKey, ItemTableRecord.ItemValue>
{
public ItemTableRecord() : base("MyWorld", "Item")
{
}
// Direct access to key properties
public virtual uint Id => Keys.Id;
// Direct access to value properties
public virtual uint Price => Values.Price;
public virtual string Name => Values.Name;
public virtual string Description => Values.Description;
public virtual string Owner => Values.Owner;
public partial class ItemKey
{
[Parameter("uint32", "id", 1)]
public virtual uint Id { get; set; }
}
public partial class ItemValue
{
[Parameter("uint32", "price", 1)]
public virtual uint Price { get; set; }
[Parameter("string", "name", 2)]
public virtual string Name { get; set; }
[Parameter("string", "description", 3)]
public virtual string Description { get; set; }
[Parameter("string", "owner", 4)]
public virtual string Owner { get; set; }
}
}- Initializes with both table name and namespace
- Separates Keys and Values
- Each property decorated with Parameter attribute (same as Nethereum Function Messages, Events)
- Uses MUD's packed encoder/decoder (different from standard ABI encoding)
MUD uses a packed encoding scheme that differs from standard Ethereum ABI encoding:
// Standard ABI encoding pads each value to 32 bytes
// MUD packed encoding is more efficient - values are tightly packed
// The Parameter attribute works the same way as standard Nethereum:
[Parameter("uint32", "id", 1)]
public virtual uint Id { get; set; }
// But internally, TableRecord uses PackedEncoder/PackedDecoder
// This is handled automatically by the generated codeThe TableService maps the table record, key, and value types to a service:
public partial class ItemTableService : TableService<ItemTableRecord, ItemTableRecord.ItemKey, ItemTableRecord.ItemValue>
{
public ItemTableService(IWeb3 web3, string contractAddress) : base(web3, contractAddress) {}
public virtual Task<ItemTableRecord> GetTableRecordAsync(uint id, BlockParameter blockParameter = null)
{
var _key = new ItemTableRecord.ItemKey();
_key.Id = id;
return GetTableRecordAsync(_key, blockParameter);
}
public virtual Task<string> SetRecordRequestAsync(uint id, uint price, string name, string description, string owner)
{
var _key = new ItemTableRecord.ItemKey();
_key.Id = id;
var _values = new ItemTableRecord.ItemValue();
_values.Price = price;
_values.Name = name;
_values.Description = description;
_values.Owner = owner;
return SetRecordRequestAsync(_key, _values);
}
public virtual Task<TransactionReceipt> SetRecordRequestAndWaitForReceiptAsync(uint id, uint price, string name, string description, string owner)
{
var _key = new ItemTableRecord.ItemKey();
_key.Id = id;
var _values = new ItemTableRecord.ItemValue();
_values.Price = price;
_values.Name = name;
_values.Description = description;
_values.Owner = owner;
return SetRecordRequestAndWaitForReceiptAsync(_key, _values);
}
}- Set records directly onto the table
- Get records from the chain by key
- Get all data related to a table using logs
- Register tables in MUD (so schema is accessible)
- Query table repositories, logs, or other providers using the table resource ID
- Partial class - can be extended with custom logic
A common abstraction to store all records from the MUD store:
var inMemoryStore = new InMemoryTableRepository();
var storeLogProcessingService = new StoreEventsLogProcessingService(web3, WorldAddress);
// Process all store changes
await storeLogProcessingService.ProcessAllStoreChangesAsync(inMemoryStore, null, null, CancellationToken.None);
// Query records
var results = await inMemoryStore.GetTableRecordsAsync<CounterTableRecord>(tableId);NuGet Packages:
Nethereum.Mud.Repositories.EFNethereum.Mud.Repositories.Postgres
Features:
- Log indexer using Nethereum Log Processor
- MUD-specific EF context (generic EF)
- Specialized Postgres implementation
- All data stored in
storedrecordstable (similar to MUD out-of-the-box indexer)
// Example: Postgres Log Processing
var connection = new NpgsqlConnection("Host=myserver;Username=mylogin;Password=mypass;Database=mydatabase");
var storeRecordsTableRepository = new MudPostgresStoreRecordsTableRepository(connection, logger);
var processingService = new MudPostgresNormaliserProcessingService(
storeRecordsTableRepository,
connection,
logger
)
{
RpcUrl = "https://localhost:8545",
Address = "0xYourContractAddress",
PageSize = 1000
};
await processingService.ExecuteAsync(cancellationToken);The Normalizer process creates tables based on the schema and decodes record data:
- Creates table structures based on MUD schemas
- Decodes encoded data into proper columns
- Once running, custom indexes, relationships, and optimizations can be added
- Enables creating reports, custom APIs, and complex queries
A fluent API to build predicates for querying MUD storage tables:
var predicateBuilder = new TablePredicateBuilder<ItemTableRecord, ItemTableRecord.ItemKey, ItemTableRecord.ItemValue>("0xABC123");
var predicate = predicateBuilder
.AndEqual(x => x.Id, 1) // AND key0 = '0x1'
.AndEqual(x => x.Id, 2) // AND key0 = '0x2'
.OrEqual(x => x.Id, 3) // OR key0 = '0x3'
.AndNotEqual(x => x.Id, 4) // AND key0 != '0x4'
.Expand(); // Finalize the predicate- Fluent API with chainable
.AndEqual(),.OrEqual(),.AndNotEqual()methods - Reusable across storage types (PostgreSQL, SQLite, etc.)
- REST API integration - predicates can be serialized to JSON
Note: SystemService classes are code-generated. See Generated Code: Systems for full details on what gets generated.
The same contract service created for standard Nethereum integration, extended with MUD functionality:
public partial class IncrementSystemService : ISystemService<IncrementSystemServiceResource>
{
public IResource Resource => this.GetResource();
public ISystemServiceResourceRegistration SystemServiceResourceRegistrator
{
get
{
return this.GetSystemServiceResourceRegistration<IncrementSystemServiceResource, IncrementSystemService>();
}
}
public List<FunctionABI> GetSystemFunctionABIs()
{
return GetAllFunctionABIs();
}
public string CalculateCreate2Address(string deployerAddress, string salt, params ByteCodeLibrary[] byteCodeLibraries)
{
return new IncrementSystemDeployment().CalculateCreate2Address(deployerAddress, salt, byteCodeLibraries);
}
public Task<Create2ContractDeploymentTransactionResult> DeployCreate2ContractAsync(string deployerAddress, string salt, params ByteCodeLibrary[] byteCodeLibraries)
{
var create2ProxyDeployerService = Web3.Eth.Create2DeterministicDeploymentProxyService;
var accessManagementSystemDeployment = new IncrementSystemDeployment();
return create2ProxyDeployerService.DeployContractRequestAsync(accessManagementSystemDeployment, deployerAddress, salt, byteCodeLibraries);
}
}- System Resource ID identification
- Registered function signatures
- Deployment helpers for Create2
- Helper functions for required registrations
public class MudTestNamespaceResource : NamespaceResource
{
public MudTestNamespaceResource() : base(String.Empty) { }
}
public class MudTestNamespace : NamespaceBase<MudTestNamespaceResource, MudTestSystemServices, MudTestTableServices>
{
public MudTestNamespace(IWeb3 web3, string contractAddress) : base(web3, contractAddress)
{
Tables = new MudTestTableServices(web3, contractAddress);
Systems = new MudTestSystemServices(web3, contractAddress);
}
}public class MudTestTableServices : TablesServices
{
public CounterTableService CounterTableService { get; protected set; }
public ItemTableService ItemTableService { get; protected set; }
public ConfigTableService ConfigTableService { get; protected set; }
public MudTestTableServices(IWeb3 web3, string contractAddress) : base(web3, contractAddress)
{
CounterTableService = new CounterTableService(web3, contractAddress);
ItemTableService = new ItemTableService(web3, contractAddress);
ConfigTableService = new ConfigTableService(web3, contractAddress);
TableServices = new List<ITableServiceBase> { CounterTableService, ItemTableService, ConfigTableService };
}
}public class MudTestSystemServices : SystemsServices
{
public IncrementSystemService IncrementSystemService { get; protected set; }
public MudTestSystemServices(IWeb3 web3, string contractAddress) : base(web3, contractAddress)
{
IncrementSystemService = new IncrementSystemService(web3, contractAddress);
SystemServices = new List<ISystemService> { IncrementSystemService };
}
}All tables and services belong to a namespace class that matches each namespace in MUD. The namespace provides:
- Deployment of all systems
- Registration of all tables
- Finding typed errors across all systems
- Registration of delegate authority
public class LocalState
{
public InMemoryTableRepository TableRepository { get; }
public List<SystemCallMulticallInput> UpdateLandOperations { get; }
// Model objects for changes before saving
}- Initial State: InMemoryTableRepository contains original game state (Land, Inventory, etc.)
- Play Phase: Changes stored in model objects, enabling continuation without automatic saving
- Action Tracking: Each action added to
UpdateLandOperationsfor batch submission - Save Phase: All collected actions executed and re-executed in order
// When "craft" action is performed:
// 1. Action performed against local state
// 2. Validated using similar logic to smart contracts
// 3. SystemCallMulticallInput created per action
// - Contains System Resource ID
// - Contains Nethereum Function Message to execute// Periodic or on-demand save:
// 1. Execute all collected actions on chain
// 2. Ensure transactions don't fail (logic must match)
// 3. Sync logs from transaction with InMemoryTableRepository
// 4. Recreate object model with chain-specific state changesChanges that can come from the chain include:
- Logic that prevents cheating/bots
- Placement time
- XP points changes
- Level progress
- Quests completed, rewards
- Deploy World Factory and then the World
- Register Namespace and Tables once World address obtained
- Register Systems and function selectors automatically in batch call
// As your world evolves:
// - Register or update systems individually
// - Register new tables
// - Add new namespaces with new functionalityNethereum includes a testing library in all templates and examples:
// Launch test chains automatically
// - Geth
// - Hardhat
// - Anvil
// Extension methods supported via Nethereum.RPC.Extensions[Fact]
public async Task ShouldGetAllChanges()
{
var web3 = GetWeb3();
var storeLogProcessingService = new StoreEventsLogProcessingService(web3, WorldAddress);
var inMemoryStore = new InMemoryTableRepository();
await storeLogProcessingService.ProcessAllStoreChangesAsync(inMemoryStore, null, null, CancellationToken.None);
var results = await inMemoryStore.GetTableRecordsAsync<CounterTableRecord>(tableId);
Assert.True(results.ToList()[0].Values.Value > 0);
}For constant saving, temporary accounts are needed (device accounts, session accounts, or burner accounts).
In games and applications, users shouldn't need to sign every transaction with their main wallet. MUD's delegated authority allows:
- Session keys: Temporary keys that can act on behalf of the user
- Burner wallets: Disposable wallets for gameplay
- Device wallets: Wallet per device without exposing main account
- Register Delegatee Account: Use namespace world systems
- Set Delegator in Namespace: Configure every system/service to use
MudCallFromContractHandler - Transaction Wrapping: Every transaction wrapped with
CallFromFunction
When delegated authority is configured, the MudCallFromContractHandler automatically wraps all contract calls:
// Without delegation - direct call
await incrementSystemService.IncrementRequestAsync();
// With delegation - automatically wrapped in CallFrom
// The handler intercepts the call and wraps it:
// World.callFrom(delegatorAddress, systemResourceId, encodedFunctionData)// 1. Register the delegatee (burner wallet) with the World
await worldService.RegisterDelegationRequestAsync(burnerAddress, delegationType, initCallData);
// 2. Configure the namespace to use CallFrom
namespace.SetDelegator(delegatorAddress, burnerAccount);
// 3. Now all system calls go through the delegator
// The MudCallFromContractHandler wraps every transaction
await namespace.Systems.IncrementSystemService.IncrementRequestAsync();
// ^ This is now executed as: CallFrom(delegatorAddress, systemId, data)The Contract Handler can be overridden and changed on ContractServices, allowing for specialization:
var privateKeySigner = "";
var privatekeySender = "";
var web3 = new Web3(new Nethereum.Web3.Accounts.Account(privateKey), "https://rpc.aboutcircles.com/");
var hubService = new HubService(web3, v2HubAddress);
// Change to Safe execution handler
hubService.ChangeContractHandlerToSafeExecTransaction(humanAddress1, privateKeySigner);- RPC Level: For MetaMask, WalletConnect, etc. - redirects messages to another provider
- ContractHandler Level: Sign as usual but embed contract call into another function message
| Package | Description |
|---|---|
Nethereum.Mud |
Core MUD integration |
Nethereum.Mud.Contracts |
MUD contract services |
Nethereum.Mud.Repositories.EF |
Entity Framework repository |
Nethereum.Mud.Repositories.Postgres |
PostgreSQL repository |
Nethereum.RPC.Extensions |
Hardhat/Anvil extensions |
- EIP-7702 support
- Multicall RPC batch support improvements
- .NET AOT Native support
- EVM Simulator updates (BASEFEE, BLOBHASH, BLOBBASEFEE, TLOAD, TSTORE)
- Code generator improvements for MUD tables
- Services now inherit from base class with virtual methods
- MUD Table Service extended with Set/Get methods using parameters
- Support for
.nethereum-gen.multisettingsin console
- Reown AppKit Blazor support
- .NET 9 target
- MUD Normaliser Postgres dbnull fix
- MUD Postgres Normaliser
- EF Stored records retrieval by BlockNumber and RowId
- TableSchema generic object from chain retrieval
- TablePredicateBuilder fluent API
- Mud.Repositories.EntityFramework project
- MUD Repository Postgres with EF integration
- REST API Integration examples
- Contract Handler override support
- Multiple configuration support in code generator
- Multiple Table registration support
- Contract Standards interface support
- ERC6492 support
- MUD multiquery RPC support
- Additional table service functionality
- Initial MUD Support
- Nethereum.Mud and Nethereum.Mud.Contracts packages
- Initial code generation support
- StoreEventsLogProcessingService
- InMemoryTableRepository
| Resource | URL |
|---|---|
| MUD Framework | https://mud.dev/ |
| Nethereum GitHub | https://github.com/Nethereum |
| Nethereum NuGet | https://www.nuget.org/profiles/nethereum |
| Nethereum Discord | https://discord.gg/u3Ej2BReNn |
| Nethereum.Unity | https://github.com/Nethereum/Nethereum.Unity |
| Unity Starter Template | https://github.com/Nethereum/Unity3dSampleTemplate |
| Nethereum Playground | http://playground.nethereum.com |
| VSCode Solidity Extension | https://marketplace.visualstudio.com/items?itemName=JuanBlanco.solidity |
| Cafe Cosmos (Example Game) | https://www.cafecosmos.io/ |
- Full Integration Tests: https://github.com/Nethereum/Nethereum/tree/master/tests/Nethereum.Mud.IntegrationTests
- Project Structure: https://github.com/Nethereum/Nethereum/tree/master/tests/Nethereum.Mud.IntegrationTests/MudTest
- Log Processing Example: https://github.com/Nethereum/Nethereum/tree/master/consoletests/NethereumMudLogProcessing
- REST API Example: https://github.com/Nethereum/Nethereum/tree/master/consoletests/NethereumMudStoredRecordsRestApi
- REST Client Example: https://github.com/Nethereum/Nethereum/tree/master/consoletests/NethereumMudStoreRestApiClient
The integration of Nethereum.MUD with Cafe Cosmos and early community feedback has allowed dogfooding the implementation, testing and refining it in a real-world scenario.