Skip to main content

Nethereum.Mud.Contracts

NuGet: Nethereum.Mud.Contracts | Source: src/Nethereum.Mud.Contracts/ Nethereum.Mud.Contracts provides contract definitions and services for interacting with MUD (Onchain Engine) World and Store contracts. It enables you to call MUD systems, query tables, and process Store events.

Features​

  • World Service - Interact with MUD World contract
  • Store Services - Query and subscribe to Store events
  • Table Services - Type-safe table query and mutation
  • System Services - Call MUD system functions
  • Event Processing - Process Store_SetRecord, Store_DeleteRecord, Store_SpliceStaticData, Store_SpliceDynamicData events
  • Batch Calls - Efficient multi-call execution
  • Access Control - Manage World permissions

Installation​

dotnet add package Nethereum.Mud.Contracts

Dependencies​

  • Nethereum.Mud
  • Nethereum.Web3

Key Concepts​

World Contract​

The World contract is the central registry for all MUD resources:

  • Contains all namespaces, tables, and systems
  • Provides call and callFrom for system execution
  • Manages access control via grantAccess and revokeAccess
  • Emits Store events for all state changes

Store Contract​

The Store is embedded in the World and manages table data:

  • Stores all table records as key-value pairs
  • Emits events when data changes (Store_SetRecord, Store_DeleteRecord)
  • Handles dynamic data via Store_SpliceStaticData and Store_SpliceDynamicData
  • Provides schema registration via Tables table

Store Events​

MUD emits events for all state changes:

Store_SetRecord

  • Emitted when a table record is created or updated
  • Contains: tableId, keyTuple, staticData, encodedLengths, dynamicData

Store_DeleteRecord

  • Emitted when a table record is deleted
  • Contains: tableId, keyTuple

Store_SpliceStaticData

  • Emitted when static data is partially updated
  • Contains: tableId, keyTuple, start, data

Store_SpliceDynamicData

  • Emitted when dynamic data (arrays, strings) is partially updated
  • Contains: tableId, keyTuple, dynamicFieldIndex, start, deleteCount, encodedLengths, data

Table Services​

Generated table services provide type-safe access to MUD tables:

  • Query records by key
  • Query all records
  • Decode table schemas
  • Built on top of World contract

System Services​

Generated system services wrap MUD system functions:

  • Type-safe function calls
  • Automatic ABI encoding
  • Integration with World's call or direct system calls

World Contract Interaction​

WorldService​

The WorldService provides methods for interacting with the World contract:

public class WorldService
{
// Call a system
Task<TransactionReceipt> CallRequestAndWaitForReceiptAsync(
byte[] systemId,
byte[] callData
);

// Call a system with delegator
Task<TransactionReceipt> CallFromRequestAndWaitForReceiptAsync(
string delegator,
byte[] systemId,
byte[] callData
);
}

// Access control (AccessManagementSystemService)
public class AccessManagementSystemService
{
Task<TransactionReceipt> GrantAccessRequestAndWaitForReceiptAsync(
byte[] resourceId,
string grantee
);

Task<TransactionReceipt> RevokeAccessRequestAndWaitForReceiptAsync(
byte[] resourceId,
string grantee
);
}

// Batch calls (BatchCallSystemService)
public class BatchCallSystemService
{
Task<TransactionReceipt> BatchCallRequestAndWaitForReceiptAsync(
List<SystemCallData> systemCalls
);
}

Usage Examples​

Example 1: Setup World Service​

using Nethereum.Web3;
using Nethereum.Mud.Contracts.World;

var web3 = new Web3("https://rpc.mud.game");
var worldAddress = "0xWorldContractAddress";

var worldService = new WorldService(web3, worldAddress);

Console.WriteLine($"Connected to World at {worldAddress}");

Example 2: Call a MUD System​

using Nethereum.Mud;
using Nethereum.Mud.Contracts.World;
using Nethereum.Mud.EncodingDecoding;
using Nethereum.Contracts;

// Create resource ID for the system
var systemId = ResourceEncoder.EncodeSystem("Game", "MoveSystem");

// Encode function call (e.g., move(direction))
var moveFunction = new MoveFunction { Direction = 1 }; // Assuming generated function DTO
var callData = moveFunction.GetCallData(); // byte[]

// Call the system via World
var receipt = await worldService.CallRequestAndWaitForReceiptAsync(
systemId,
callData
);

Console.WriteLine($"System called successfully. Tx: {receipt.TransactionHash}");

Example 3: Query MUD Table​

using Nethereum.Mud;
using Nethereum.Mud.Contracts.Store;

// Assuming generated PlayerTableService
var playerTableService = new PlayerTableService(web3, worldAddress);

// Query single record by key
var playerId = 42;
var playerRecord = await playerTableService.GetTableRecordAsync(playerId);

Console.WriteLine($"Player: {playerRecord.Values.Name}");
Console.WriteLine($"Level: {playerRecord.Values.Level}");
Console.WriteLine($"Health: {playerRecord.Values.Health}");

Example 4: Query Store Events​

using Nethereum.Mud.Contracts.Core.StoreEvents;
using Nethereum.Mud.EncodingDecoding;

var storeEventsService = new StoreEventsLogProcessingService(web3, worldAddress);

// Get all SetRecord events
var setRecordEvents = await storeEventsService.GetAllSetRecord(
fromBlockNumber: 0,
toBlockNumber: null,
cancellationToken: CancellationToken.None
);

foreach (var log in setRecordEvents)
{
var evt = log.Event;
Console.WriteLine($"Record updated:");
Console.WriteLine($" TableId: {evt.TableId.ToHex()}");
Console.WriteLine($" Keys: {string.Join(", ", evt.KeyTuple.Select(k => k.ToHex()))}");
Console.WriteLine($" Block: {log.Log.BlockNumber.Value}");
}

// Get SetRecord events for a specific table
var playerTableId = ResourceEncoder.EncodeTable("Game", "Player");
var playerEvents = await storeEventsService.GetAllSetRecordForTable(
playerTableId,
fromBlockNumber: 0,
toBlockNumber: null,
cancellationToken: CancellationToken.None
);

Console.WriteLine($"Found {playerEvents.Count} player table events");

Example 5: Process Store Events to Repository​

using Nethereum.Mud.Contracts.Core.StoreEvents;
using Nethereum.Mud.TableRepository;

// Create table repository
var playerRepository = new InMemoryTableRepository();

// Setup event processing
var storeEventsService = new StoreEventsLogProcessingService(web3, worldAddress);
var progressRepository = new InMemoryBlockchainProgressRepository();

var processor = storeEventsService.CreateProcessor(
playerRepository,
progressRepository,
log: null,
numberOfBlocksPerRequest: 1000,
retryWeight: 50,
minimumNumberOfConfirmations: 12
);

// Start processing from block 0
await processor.ExecuteAsync(
startAtBlockNumberIfNotProcessed: 0,
cancellationToken: CancellationToken.None
);

Console.WriteLine("Store events processed to repository");

Example 6: Batch System Calls​

using Nethereum.Mud.Contracts.World;
using Nethereum.Mud.Contracts.World.Systems.BatchCallSystem;
using Nethereum.Mud.EncodingDecoding;

// Prepare multiple system calls
var systemCalls = new List<SystemCallData>
{
new SystemCallData
{
SystemId = ResourceEncoder.EncodeSystem("Game", "MoveSystem"),
CallData = EncodeMoveCall(direction: 1)
},
new SystemCallData
{
SystemId = ResourceEncoder.EncodeSystem("Game", "AttackSystem"),
CallData = EncodeAttackCall(targetId: 5)
},
new SystemCallData
{
SystemId = ResourceEncoder.EncodeSystem("Game", "CraftSystem"),
CallData = EncodeCraftCall(itemId: 10, quantity: 1)
}
};

// Execute all calls in one transaction via BatchCallSystemService
var batchCallService = new BatchCallSystemService(web3, worldAddress);
var receipt = await batchCallService.BatchCallRequestAndWaitForReceiptAsync(systemCalls);

Console.WriteLine($"Batch call successful. Gas used: {receipt.GasUsed.Value}");

Example 7: Grant and Revoke Access​

using Nethereum.Mud;
using Nethereum.Mud.Contracts.World.Systems.AccessManagementSystem;
using Nethereum.Mud.EncodingDecoding;

// Create resource ID for a table
var tableId = ResourceEncoder.EncodeTable("Game", "Player");

// Use AccessManagementSystemService for access control
var accessService = new AccessManagementSystemService(web3, worldAddress);

// Grant access to an address
var granteeAddress = "0xGranteeAddress";
var grantReceipt = await accessService.GrantAccessRequestAndWaitForReceiptAsync(
tableId,
granteeAddress
);

Console.WriteLine($"Access granted to {granteeAddress}");

// Revoke access
var revokeReceipt = await accessService.RevokeAccessRequestAndWaitForReceiptAsync(
tableId,
granteeAddress
);

Console.WriteLine($"Access revoked from {granteeAddress}");

Example 8: Deploy MUD World​

using Nethereum.Mud.Contracts.World.ContractDefinition;
using Nethereum.Web3.Accounts;

var account = new Account("0xPrivateKey", chainId: 1);
var web3 = new Web3(account, "https://rpc.mud.game");

// Deploy World contract
var deploymentMessage = new WorldDeployment();
var deploymentReceipt = await WorldService.DeployContractAndWaitForReceiptAsync(
web3,
deploymentMessage
);

var worldAddress = deploymentReceipt.ContractAddress;
Console.WriteLine($"World deployed at: {worldAddress}");

Example 9: Register Custom Table​

using System.Collections.Generic;
using Nethereum.Mud;
using Nethereum.Mud.EncodingDecoding;

// Define table schema
var playerTableId = ResourceEncoder.EncodeTable("Game", "Player");
var schema = SchemaEncoder.GetSchemaEncoded<PlayerKey, PlayerValue>(
playerTableId
);

// Register table via RegistrationSystemService
var registrationService = new RegistrationSystemService(web3, worldAddress);
var registerTableReceipt = await registrationService.RegisterTableRequestAndWaitForReceiptAsync(
tableId: playerTableId,
fieldLayout: schema.FieldLayout,
keySchema: schema.KeySchema,
valueSchema: schema.ValueSchema,
keyNames: new List<string> { "playerId" },
fieldNames: new List<string> { "name", "level", "health" }
);

Console.WriteLine("Table registered: Game:Player");

Example 10: Read Table Schema from Chain​

using Nethereum.Mud.Contracts.Store.Tables;
using Nethereum.Mud.EncodingDecoding;

// TablesTableService queries the on-chain Tables table
var tablesService = new TablesTableService(web3, worldAddress);

// Get schema for a specific table
var playerTableId = ResourceEncoder.EncodeTable("Game", "Player");
var tableRecord = await tablesService.GetTableRecordAsync(playerTableId);

var schema = tableRecord.GetTableSchema();
Console.WriteLine($"Table: {schema.Name}");
Console.WriteLine($"Namespace: {schema.Namespace}");
Console.WriteLine($"Keys: {string.Join(", ", schema.SchemaKeys.Select(k => k.Name))}");
Console.WriteLine($"Values: {string.Join(", ", schema.SchemaValues.Select(v => v.Name))}");

Core Classes​

WorldService​

public class WorldService : ContractWeb3ServiceBase
{
public WorldService(IWeb3 web3, string contractAddress);

// System calls
Task<TransactionReceipt> CallRequestAndWaitForReceiptAsync(
byte[] systemId,
byte[] callData
);

Task<TransactionReceipt> CallFromRequestAndWaitForReceiptAsync(
string delegator,
byte[] systemId,
byte[] callData
);
}

AccessManagementSystemService​

public class AccessManagementSystemService
{
Task<TransactionReceipt> GrantAccessRequestAndWaitForReceiptAsync(
byte[] resourceId,
string grantee
);

Task<TransactionReceipt> RevokeAccessRequestAndWaitForReceiptAsync(
byte[] resourceId,
string grantee
);
}

BatchCallSystemService​

public class BatchCallSystemService
{
Task<TransactionReceipt> BatchCallRequestAndWaitForReceiptAsync(
List<SystemCallData> systemCalls
);
}

RegistrationSystemService​

public class RegistrationSystemService
{
Task<TransactionReceipt> RegisterTableRequestAndWaitForReceiptAsync(
byte[] tableId,
byte[] fieldLayout,
byte[] keySchema,
byte[] valueSchema,
List<string> keyNames,
List<string> fieldNames
);
}

StoreEventsLogProcessingService​

public class StoreEventsLogProcessingService
{
public StoreEventsLogProcessingService(IWeb3 web3, string contractAddress);

// Query all SetRecord events
Task<List<EventLog<StoreSetRecordEventDTO>>> GetAllSetRecord(...);
Task<List<EventLog<StoreSetRecordEventDTO>>> GetAllSetRecordForTable(byte[] tableId, ...);

// Query all SpliceStaticData events
Task<List<EventLog<StoreSpliceStaticDataEventDTO>>> GetAllSpliceStaticData(...);
Task<List<EventLog<StoreSpliceStaticDataEventDTO>>> GetAllSpliceStaticDataForTable(byte[] tableId, ...);

// Process all store changes (SetRecord, DeleteRecord, SpliceStaticData, SpliceDynamicData)
Task ProcessAllStoreChangesAsync(ITableRepository tableRepository, ...);
Task ProcessAllStoreChangesAsync(ITableRepository tableRepository, byte[] tableId, ...);
Task ProcessAllStoreChangesAsync(ITableRepository tableRepository, string nameSpace, string tableName, ...);
}

Generated Table Service​

Example of a generated table service:

Generated table services derive from TableService<TTableRecord, TKey, TValue>, inheriting its read/write methods and adding a typed convenience overload built from the table's key fields:

public partial class PlayerTableService
: TableService<PlayerTableRecord, PlayerKey, PlayerValue>
{
public PlayerTableService(IWeb3 web3, string contractAddress);

// Generated convenience overload: builds the PlayerKey from the key fields
Task<PlayerTableRecord> GetTableRecordAsync(
BigInteger playerId,
BlockParameter blockParameter = null
);

// --- Inherited from TableService base ---

// Query a single record by its typed key
// Task<PlayerTableRecord> GetTableRecordAsync(PlayerKey key, BlockParameter blockParameter = null);

// Query multiple records by key in a single multicall
// Task<List<PlayerTableRecord>> GetTableRecordsMulticallRpcAsync(List<PlayerKey> key, BlockParameter blockParameter = null);

// Read all records previously indexed into a table repository
// Task<IEnumerable<PlayerTableRecord>> GetTableRecordsAsync(ITableRepository tableRepository);

// Set / delete records through the World
// Task<TransactionReceipt> SetRecordRequestAndWaitForReceiptAsync(PlayerKey key, PlayerValue value);
// Task<TransactionReceipt> DeleteRecordRequestAndWaitForReceiptAsync(PlayerKey key);
}

Store Event DTOs​

[Event("Store_SetRecord")]
public class StoreSetRecordEventDTO : IEventDTO
{
[Parameter("bytes32", "tableId", 1, true)]
public byte[] TableId { get; set; }

[Parameter("bytes32[]", "keyTuple", 2, false)]
public List<byte[]> KeyTuple { get; set; }

[Parameter("bytes", "staticData", 3, false)]
public byte[] StaticData { get; set; }

[Parameter("bytes32", "encodedLengths", 4, false)]
public byte[] EncodedLengths { get; set; }

[Parameter("bytes", "dynamicData", 5, false)]
public byte[] DynamicData { get; set; }
}

[Event("Store_DeleteRecord")]
public class StoreDeleteRecordEventDTO : IEventDTO
{
[Parameter("bytes32", "tableId", 1, true)]
public byte[] TableId { get; set; }

[Parameter("bytes32[]", "keyTuple", 2, false)]
public List<byte[]> KeyTuple { get; set; }
}

Code Generation​

Generate table and system services using .nethereum-gen.multisettings:

Configuration​

{
"paths": ["mud.config.ts"],
"generatorConfigs": [
{
"baseNamespace": "MyGame.Tables",
"basePath": "../MyGame/Tables",
"generatorType": "MudTables"
},
{
"baseNamespace": "MyGame.Systems",
"basePath": "../MyGame/Systems",
"generatorType": "MudExtendedService"
}
]
}

Generated Output​

MudTables generates:

  • PlayerTableRecord.gen.cs - Table record with Keys and Values
  • PlayerTableService.gen.cs - Service for querying the table

MudExtendedService generates:

  • MoveSystemService.gen.cs - Service for calling system functions
  • Function DTOs for all system methods

Running Generation​

# Install Nethereum code generator
dotnet tool install -g Nethereum.Generator.Console

# Generate C# code
Nethereum.Generator.Console generate from-config -cfg .nethereum-gen.multisettings

# Or use VS Code extension
# https://github.com/juanfranblanco/vscode-solidity

Event Processing Patterns​

Pattern 1: Query and Process Events​

var storeEventsService = new StoreEventsLogProcessingService(web3, worldAddress);

// Query all SetRecord events
var events = await storeEventsService.GetAllSetRecord(
fromBlockNumber: 0,
toBlockNumber: null,
cancellationToken: CancellationToken.None
);

foreach (var eventLog in events)
{
var evt = eventLog.Event;

// Update local repository
await UpdateRepositoryFromEvent(evt);

// Notify UI
await NotifyUIAsync(evt);
}

Pattern 2: Historical Event Processing​

var storeEventsService = new StoreEventsLogProcessingService(web3, worldAddress);
var progressRepo = new InMemoryBlockchainProgressRepository();
var tableRepo = new InMemoryTableRepository();

var processor = storeEventsService.CreateProcessor(
tableRepo,
progressRepo,
log: null,
numberOfBlocksPerRequest: 1000,
retryWeight: 50,
minimumNumberOfConfirmations: 12
);

// Process all historical events
await processor.ExecuteAsync(
startAtBlockNumberIfNotProcessed: 0,
cancellationToken: CancellationToken.None
);

Pattern 3: Process All Store Changes to Repository​

var storeEventsService = new StoreEventsLogProcessingService(web3, worldAddress);
var tableRepo = new InMemoryTableRepository();

// Process all store changes (SetRecord, DeleteRecord, SpliceStaticData, SpliceDynamicData)
await storeEventsService.ProcessAllStoreChangesAsync(
tableRepo,
fromBlockNumber: 0,
toBlockNumber: null,
cancellationToken: CancellationToken.None
);

// Or process changes for a specific table only
var playerTableId = ResourceEncoder.EncodeTable("Game", "Player");
await storeEventsService.ProcessAllStoreChangesAsync(
tableRepo,
playerTableId,
fromBlockNumber: 0,
toBlockNumber: null,
cancellationToken: CancellationToken.None
);

Advanced Topics​

Custom Event Handlers​

public class CustomEventHandler
{
public async Task HandleSetRecord(StoreSetRecordEventDTO evt)
{
// Custom logic for SetRecord
var tableId = evt.TableId.ToHex();

if (tableId == playerTableId)
{
await HandlePlayerUpdate(evt);
}
else if (tableId == inventoryTableId)
{
await HandleInventoryUpdate(evt);
}
}
}

Filtering Events by Table​

var playerTableId = ResourceEncoder.EncodeTable("Game", "Player");

// Use GetAllSetRecordForTable to query events for a specific table
var playerEvents = await storeEventsService.GetAllSetRecordForTable(
playerTableId,
fromBlockNumber: 0,
toBlockNumber: null,
cancellationToken: CancellationToken.None
);

foreach (var eventLog in playerEvents)
{
// Only receives events for Player table
var playerEvent = eventLog.Event;
await HandlePlayerUpdate(playerEvent);
}

Handling Splice Events​

// Query static data splice events
var spliceStaticEvents = await storeEventsService.GetAllSpliceStaticData(
fromBlockNumber: 0,
toBlockNumber: null,
cancellationToken: CancellationToken.None
);

foreach (var eventLog in spliceStaticEvents)
{
var evt = eventLog.Event;
Console.WriteLine($"Static data spliced:");
Console.WriteLine($" TableId: {evt.TableId.ToHex()}");
Console.WriteLine($" Start: {evt.Start}");
Console.WriteLine($" New data length: {evt.Data.Length}");
}

// Or use ProcessAllStoreChangesAsync to handle all event types
// (SetRecord, DeleteRecord, SpliceStaticData, SpliceDynamicData)
// and apply them to a table repository automatically
await storeEventsService.ProcessAllStoreChangesAsync(
tableRepository,
fromBlockNumber: 0,
toBlockNumber: null,
cancellationToken: CancellationToken.None
);

Production Patterns​

1. Decentralized Game Client​

// Initialize World connection
var worldService = new WorldService(web3, worldAddress);

// Load initial state by indexing Store events into a repository
var tableRepository = new InMemoryTableRepository();
var storeEventsService = new StoreEventsLogProcessingService(web3, worldAddress);
await storeEventsService.ProcessAllStoreChangesAsync(tableRepository);

var playerTable = new PlayerTableService(web3, worldAddress);
var players = await playerTable.GetTableRecordsAsync(tableRepository);

// Subscribe to updates
var subscription = await SubscribeToStoreEvents(worldAddress);

// User action: Move player
var moveSystemId = ResourceEncoder.EncodeSystem("Game", "MoveSystem");
await worldService.CallRequestAndWaitForReceiptAsync(
moveSystemId,
EncodeMoveCall(direction: 1)
);

2. MUD Indexer​

The Nethereum.Mud.Repositories.Postgres package ships a Postgres-backed indexer built on Entity Framework Core. Point MudPostgresStoreRecordsProcessingService at the World and RPC endpoint; it resumes from the last processed block on restart.

using Microsoft.EntityFrameworkCore;
using Nethereum.Mud.Repositories.Postgres;

// Configure the EF Core DbContext for Postgres
var options = new DbContextOptionsBuilder<MudPostgresStoreRecordsDbContext>()
.UseNpgsql(connectionString)
.UseLowerCaseNamingConvention()
.Options;

using var context = new MudPostgresStoreRecordsDbContext(options);
await context.Database.MigrateAsync();

// Index all Store events (SetRecord, DeleteRecord, Splice) into Postgres
var processingService = new MudPostgresStoreRecordsProcessingService(context, logger)
{
Address = worldAddress,
RpcUrl = rpcUrl,
StartAtBlockNumberIfNotProcessed = 0,
NumberOfBlocksToProcessPerRequest = 1000,
RetryWeight = 50,
MinimumNumberOfConfirmations = 12
};

await processingService.ExecuteAsync(cancellationToken);

For a hosted service that reads its configuration (connection string, World address, RPC URL) from IConfiguration, register the shipped background indexer instead:

services.AddMudPostgresProcessing(configuration, connectionString);

3. Multi-World Aggregator​

// Query data from multiple MUD Worlds
var world1Service = new WorldService(web3, world1Address);
var world2Service = new WorldService(web3, world2Address);

var player1Table = new PlayerTableService(web3, world1Address);
var player2Table = new PlayerTableService(web3, world2Address);

// Aggregate player data from both worlds (indexed into repositories first)
var repo1 = new InMemoryTableRepository();
var repo2 = new InMemoryTableRepository();
await new StoreEventsLogProcessingService(web3, world1Address).ProcessAllStoreChangesAsync(repo1);
await new StoreEventsLogProcessingService(web3, world2Address).ProcessAllStoreChangesAsync(repo2);

var players1 = await player1Table.GetTableRecordsAsync(repo1);
var players2 = await player2Table.GetTableRecordsAsync(repo2);

var allPlayers = players1.Concat(players2).ToList();

Dependencies​

  • Nethereum.Mud - Core MUD types and table records
  • Nethereum.Web3 - Ethereum client

Used By​

  • Nethereum.Mud.Repositories.EntityFramework - EF Core event processing
  • Nethereum.Mud.Repositories.Postgres - PostgreSQL event processing
  • Nethereum.Contracts - Contract interaction base

Additional Resources​

Support​