Smart Contract Events
NuGet: Nethereum.Web3
dotnet add package Nethereum.Web3
Define an Event DTO
Map a Solidity event to a C# class using [Event] and [Parameter] attributes. The fourth argument (true) marks indexed (topic) parameters.
using System.Numerics;
using Nethereum.ABI.FunctionEncoding.Attributes;
using Nethereum.Contracts;
[Event("Transfer")]
public class TransferEventDTO : IEventDTO
{
[Parameter("address", "_from", 1, true)]
public string From { get; set; }
[Parameter("address", "_to", 2, true)]
public string To { get; set; }
[Parameter("uint256", "_value", 3, false)]
public BigInteger Value { get; set; }
}
Nethereum ships TransferEventDTO and ApprovalEventDTO for ERC-20 in Nethereum.Contracts.Standards.ERC20.ContractDefinition.
Get an Event Handler
var web3 = new Web3("https://mainnet.infura.io/v3/YOUR_KEY");
var contractAddress = "0xYourContractAddress";
// From web3
var transferEvent = web3.Eth.GetEvent<TransferEventDTO>(contractAddress);
// From contract handler
var contractHandler = web3.Eth.GetContractHandler(contractAddress);
var transferEvent = contractHandler.GetEvent<TransferEventDTO>();
Query All Historical Events
var filterInput = transferEvent.CreateFilterInput(
BlockParameter.CreateEarliest(),
BlockParameter.CreateLatest());
var allTransfers = await transferEvent.GetAllChangesAsync(filterInput);
foreach (var transfer in allTransfers)
{
Console.WriteLine(
$"From: {transfer.Event.From} To: {transfer.Event.To} Value: {transfer.Event.Value}");
}
Filter by Indexed Parameters
Indexed parameters become topics the node filters server-side.
By sender (first indexed param)
var filterByFrom = transferEvent.CreateFilterInput(fromAddress);
var results = await transferEvent.GetAllChangesAsync(filterByFrom);
By recipient (second indexed param)
Pass null for the first topic to skip it:
var filterByTo = transferEvent.CreateFilterInput<string, string>(null, toAddress);
var results = await transferEvent.GetAllChangesAsync(filterByTo);
By both sender and recipient
var filterBoth = transferEvent.CreateFilterInput(fromAddress, toAddress);
var results = await transferEvent.GetAllChangesAsync(filterBoth);
By arrays of addresses
var filterFromArray = transferEvent.CreateFilterInput(new[] { addr1, addr2 });
var filterToArray = transferEvent.CreateFilterInput(null, new[] { recipA, recipB });
var filterBothArrays = transferEvent.CreateFilterInput(
new[] { addr1, addr2 },
new[] { recipA, recipB });
Live Filter (Poll for New Events)
Install a filter and poll for changes instead of querying history:
var contractHandler = web3.Eth.GetContractHandler(contractAddress);
var eventFilter = contractHandler.GetEvent<TransferEventDTO>();
var filterId = await eventFilter.CreateFilterAsync();
// ... transactions happen ...
var newEvents = await eventFilter.GetFilterChangesAsync(filterId);
foreach (var evt in newEvents)
{
Console.WriteLine($"New transfer: {evt.Event.Value}");
}
Decode Events from Transaction Receipt
var receipt = await web3.Eth.GetTransactionReceipt
.SendRequestAsync(transactionHash);
var transfers = receipt.DecodeAllEvents<TransferEventDTO>();
foreach (var transfer in transfers)
{
Console.WriteLine(
$"From: {transfer.Event.From} To: {transfer.Event.To} Value: {transfer.Event.Value}");
}
From a JArray of logs:
var decoded = receipt.Logs.DecodeAllEvents<TransferEventDTO>();
Custom Event DTO
[Event("ItemCreated")]
public class ItemCreatedEventDTO : IEventDTO
{
[Parameter("uint256", "itemId", 1, true)]
public BigInteger ItemId { get; set; }
[Parameter("address", "result", 2, false)]
public string Result { get; set; }
}
var items = receipt.DecodeAllEvents<ItemCreatedEventDTO>();
Query Events Across All Contracts
// No contract address -- matches all contracts
var eventForAny = new Event<TransferEventDTO>(web3.Client);
var filterInput = eventForAny.CreateFilterInput();
var allTransfers = await eventForAny.GetAllChangesAsync(filterInput);
// Scoped to one contract
var eventForOne = new Event<TransferEventDTO>(web3.Client, contractAddress);
var results = await eventForOne.GetAllChangesAsync(eventForOne.CreateFilterInput());
Built-in ERC-20 Event DTOs
using Nethereum.Contracts.Standards.ERC20.ContractDefinition;
// TransferEventDTO - Transfer(address indexed _from, address indexed _to, uint256 _value)
// ApprovalEventDTO - Approval(address indexed _owner, address indexed _spender, uint256 _value)
var transfers = receipt.DecodeAllEvents<TransferEventDTO>();
var approvals = receipt.DecodeAllEvents<ApprovalEventDTO>();
Typed Topic Filtering with FilterInputBuilder
Fluent, lambda-based API for building event filters -- reference indexed properties by name with compile-time checking:
// Filter by sender
var filter = new FilterInputBuilder<TransferEventDTO>()
.AddTopic(t => t.From, "0xSenderAddress")
.Build(contractAddress);
var results = await transferEvent.GetAllChangesAsync(filter);
// OR matching
var filter = new FilterInputBuilder<TransferEventDTO>()
.AddTopic(t => t.From, new[] { "0xAddr1", "0xAddr2" })
.Build(contractAddress);
// Multiple indexed params + block range
var filter = new FilterInputBuilder<TransferEventDTO>()
.AddTopic(t => t.From, "0xFrom")
.AddTopic(t => t.To, "0xTo")
.Build(contractAddress, BlockParameter.CreateEarliest(), BlockParameter.CreateLatest());
// Multiple contracts
var filter = new FilterInputBuilder<TransferEventDTO>()
.AddTopic(t => t.From, "0xSender")
.Build(new[] { contract1, contract2 });
Key Types
Event<T>-- typed event handler, created from web3 or clientFilterInputBuilder<T>-- fluent lambda-based filter builder for indexed topicsIEventDTO-- marker interface for event DTOs[Event("Name")]-- maps DTO to Solidity event name[Parameter("type", "name", order, indexed)]-- maps DTO property to event parameterEventLog<T>-- wrapper with.Event(decoded DTO),.Log(raw FilterLog)NewFilterInput-- filter configuration with address, topics, block rangeBlockParameter.CreateEarliest()/BlockParameter.CreateLatest()-- block range bounds
Common Patterns
| Task | Method |
|---|---|
| Get event handler from web3 | web3.Eth.GetEvent<T>(address) |
| Get event handler from contract | contractHandler.GetEvent<T>() |
| Query historical events | event.GetAllChangesAsync(filterInput) |
| Install live filter | event.CreateFilterAsync() |
| Poll live filter | event.GetFilterChangesAsync(filterId) |
| Decode from receipt | receipt.DecodeAllEvents<T>() |
| Decode from JArray logs | receipt.Logs.DecodeAllEvents<T>() |
| Filter by first indexed param | event.CreateFilterInput(value) |
| Filter by second indexed param | event.CreateFilterInput<T1,T2>(null, value) |
| Filter by both indexed params | event.CreateFilterInput(val1, val2) |
| Filter by address arrays | event.CreateFilterInput(new[]{...}, new[]{...}) |
| Cross-contract query | new Event<T>(web3.Client) |