Skip to main content

Filter & Query Events

The Simple Way
var transferEvent = web3.Eth.GetEvent<TransferEventDTO>(contractAddress);
var filter = transferEvent.CreateFilterInput(
BlockParameter.CreateEarliest(), BlockParameter.CreateLatest());
var events = await transferEvent.GetAllChangesAsync(filter);

Define an event DTO, create a filter, query — no gas or signing needed.

Smart contracts emit events (logs) to signal that something happened on-chain. Nethereum lets you define strongly-typed DTOs for events, create filters with block ranges and indexed parameters, and decode logs from transaction receipts or historical queries.

dotnet add package Nethereum.Web3

Define an Event DTO

Use the [Event] attribute to map a Solidity event and [Parameter] to map each field. 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 already ships TransferEventDTO and ApprovalEventDTO for ERC-20 in Nethereum.Contracts.Standards.ERC20.ContractDefinition -- you can use those directly instead of defining your own.

Get an Event Handler

Obtain a typed event handler from a Web3 instance:

var web3 = new Web3("https://mainnet.infura.io/v3/YOUR_KEY");
var contractAddress = "0xYourContractAddress";

var transferEvent = web3.Eth.GetEvent<TransferEventDTO>(contractAddress);

You can also get the event handler from a contract handler:

var contractHandler = web3.Eth.GetContractHandler(contractAddress);
var transferEvent = contractHandler.GetEvent<TransferEventDTO>();

Query All Historical Events

Create a filter input with a block range and retrieve all matching logs:

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 (true in the [Parameter] attribute) become topics that the node can filter server-side.

Filter by sender (first indexed param)

var fromAddress = "0xSenderAddress";
var filterByFrom = transferEvent.CreateFilterInput(fromAddress);
var results = await transferEvent.GetAllChangesAsync(filterByFrom);

Filter by recipient (second indexed param)

Pass null for the first topic to skip it:

var toAddress = "0xRecipientAddress";
var filterByTo = transferEvent.CreateFilterInput<string, string>(null, toAddress);
var results = await transferEvent.GetAllChangesAsync(filterByTo);

Filter by both sender and recipient

var filterBoth = transferEvent.CreateFilterInput(fromAddress, toAddress);
var results = await transferEvent.GetAllChangesAsync(filterBoth);

Filter by multiple addresses (array)

var filterFromArray = transferEvent.CreateFilterInput(
new[] { address1, address2 });

var filterToArray = transferEvent.CreateFilterInput(
null,
new[] { recipientA, recipientB });

var filterBothArrays = transferEvent.CreateFilterInput(
new[] { address1, address2 },
new[] { recipientA, recipientB });

Use a Live Filter (Poll for New Events)

Instead of querying history, install a filter and poll for new events:

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 a Transaction Receipt

After sending a transaction, decode events directly from the receipt logs:

var receipt = await web3.Eth.GetTransactionReceipt
.SendRequestAsync(transactionHash);

// Decode all Transfer events from the receipt
var transfers = receipt.DecodeAllEvents<TransferEventDTO>();

foreach (var transfer in transfers)
{
Console.WriteLine(
$"From: {transfer.Event.From} To: {transfer.Event.To} Value: {transfer.Event.Value}");
}

You can also decode from a JArray of logs:

var decoded = receipt.Logs.DecodeAllEvents<TransferEventDTO>();

Decode Custom Events from Receipt

For custom events, the same pattern applies:

[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; }
}

// After a transaction that emits ItemCreated
var items = receipt.DecodeAllEvents<ItemCreatedEventDTO>();

Query Events Without a Specific Contract

You can query events across all contracts by constructing the Event<T> directly with just the client:

var eventForAnyContract = new Event<TransferEventDTO>(web3.Client);
var filterInput = eventForAnyContract.CreateFilterInput();
var allTransfers = await eventForAnyContract.GetAllChangesAsync(filterInput);

Or scope to a specific contract:

var eventForContract = new Event<TransferEventDTO>(web3.Client, contractAddress);
var filterInput = eventForContract.CreateFilterInput();
var results = await eventForContract.GetAllChangesAsync(filterInput);

Built-in ERC-20 Event DTOs

Nethereum provides pre-built DTOs for standard ERC-20 events:

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

FilterInputBuilder<T> provides a fluent, lambda-based API for building event filters. Instead of positional topic arguments, you reference indexed properties directly by name -- fully type-safe with compile-time checking.

using Nethereum.Contracts;

// Build a filter using lambda expressions on the DTO properties
var filter = new FilterInputBuilder<TransferEventDTO>()
.AddTopic(t => t.From, "0xSenderAddress")
.Build(contractAddress);

var results = await transferEvent.GetAllChangesAsync(filter);

Filter by multiple values (OR matching)

var filter = new FilterInputBuilder<TransferEventDTO>()
.AddTopic(t => t.From, new[] { "0xAddress1", "0xAddress2" })
.Build(contractAddress);

Filter by multiple indexed parameters

var filter = new FilterInputBuilder<TransferEventDTO>()
.AddTopic(t => t.From, "0xSenderAddress")
.AddTopic(t => t.To, "0xRecipientAddress")
.Build(contractAddress);

With block range

var filter = new FilterInputBuilder<TransferEventDTO>()
.AddTopic(t => t.To, "0xRecipientAddress")
.Build(contractAddress,
BlockParameter.CreateEarliest(),
BlockParameter.CreateLatest());

// Or using BlockRange
var filter = new FilterInputBuilder<TransferEventDTO>()
.AddTopic(t => t.From, "0xSenderAddress")
.Build(contractAddress, new BlockRange(fromBlock, toBlock));

Multiple contracts

var filter = new FilterInputBuilder<TransferEventDTO>()
.AddTopic(t => t.From, "0xSenderAddress")
.Build(new[] { contractAddress1, contractAddress2 });

The builder only includes topics for indexed parameters (those marked true in the [Parameter] attribute). Non-indexed parameters are ignored.

Playground Samples

Try interactive event examples in the Nethereum Playground:

  • Sample 1008 -- Querying events with filters and indexed parameters
  • Sample 1009 -- Subscribing to event logs

Next Steps