Interact with Ethereum smart contracts using Nethereum typed DTOs. Use this skill whenever the user asks about deploying contracts, calling contract functions, sending transactions to contracts, decoding events, querying historical state, estimating gas, offline signing, or non-type-safe ABI interaction in C#/.NET.
Nethereum uses typed C# classes (DTOs) to represent every part of a smart contract — deployment bytecode, function calls, events, and return values. This gives you compile-time safety: if a parameter name or type is wrong, you catch it before sending a transaction, not after.
NuGet: Nethereum.Web3
dotnet add package Nethereum.Web3
Every interaction starts with defining C# classes that map to the Solidity contract. These can be hand-written or auto-generated with Nethereum.Generator.Console.
A deployment message represents the contract constructor. It inherits from ContractDeploymentMessage and holds the compiled bytecode plus any constructor parameters:
public class StandardTokenDeployment : ContractDeploymentMessage
{
public static string BYTECODE = "0x60606040...";
public StandardTokenDeployment() : base(BYTECODE) { }
[Parameter("uint256", "totalSupply")]
public BigInteger TotalSupply { get; set; }
}
Each contract function maps to a class inheriting from FunctionMessage. The [Function] attribute takes the Solidity function name and return type:
[Function("balanceOf", "uint256")]
public class BalanceOfFunction : FunctionMessage
{
[Parameter("address", "_owner", 1)]
public string Owner { get; set; }
}
[Function("transfer", "bool")]
public class TransferFunction : FunctionMessage
{
[Parameter("address", "_to", 1)]
public string To { get; set; }
[Parameter("uint256", "_value", 2)]
public BigInteger TokenAmount { get; set; }
}
Events implement IEventDTO. The fourth argument in [Parameter] marks indexed parameters, which enable server-side filtering:
[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; }
}
For functions returning complex values, define an output DTO implementing IFunctionOutputDTO:
[FunctionOutput]
public class BalanceOfOutputDTO : IFunctionOutputDTO
{
[Parameter("uint256", "balance", 1)]
public BigInteger Balance { get; set; }
}
The deployment handler encodes constructor parameters into the bytecode, estimates gas, and waits for the receipt. The returned ContractAddress is where your contract lives:
var deploymentMessage = new StandardTokenDeployment { TotalSupply = 100000 };
var deploymentHandler = web3.Eth.GetContractDeploymentHandler<StandardTokenDeployment>();
var receipt = await deploymentHandler.SendRequestAndWaitForReceiptAsync(deploymentMessage);
var contractAddress = receipt.ContractAddress;
Gas, gas price, and nonce are estimated automatically. Set them on the message only when you need explicit control.
Use GetContractQueryHandler<T> to call view or pure functions. These are free — no gas, no transaction:
var balanceHandler = web3.Eth.GetContractQueryHandler<BalanceOfFunction>();
// Single return value
var balance = await balanceHandler.QueryAsync<BigInteger>(contractAddress,
new BalanceOfFunction { Owner = ownerAddress });
// Deserialize to output DTO (for multiple return values)
var output = await balanceHandler.QueryDeserializingToObjectAsync<BalanceOfOutputDTO>(
balanceOfMessage, contractAddress);
Use GetContractTransactionHandler<T> for functions that modify state. This sends a real transaction and costs gas:
var transferHandler = web3.Eth.GetContractTransactionHandler<TransferFunction>();
var transfer = new TransferFunction { To = receiverAddress, TokenAmount = 100 };
var receipt = await transferHandler.SendRequestAndWaitForReceiptAsync(contractAddress, transfer);
After a transaction, extract events from the receipt logs. This decodes all matching events — if the transaction triggered multiple transfers, you get them all:
var transferEvents = receipt.DecodeAllEvents<TransferEventDTO>();
var from = transferEvents[0].Event.From;
var to = transferEvents[0].Event.To;
var value = transferEvents[0].Event.Value;
All query methods accept BlockParameter to read contract state at a past block. The node must have archive state for the requested block:
var historicalBalance = await balanceHandler.QueryDeserializingToObjectAsync<BalanceOfOutputDTO>(
balanceOfMessage, contractAddress,
new BlockParameter(deployReceipt.BlockNumber));
Nethereum auto-estimates gas. For manual control — useful when you want to display costs or set a hard limit:
var estimate = await transferHandler.EstimateGasAsync(contractAddress, transfer);
transfer.Gas = estimate.Value;
transfer.GasPrice = Web3.Convert.ToWei(25, UnitConversion.EthUnit.Gwei);
If a Solidity function is payable, set AmountToSend to include ETH with the call:
transfer.AmountToSend = Web3.Convert.ToWei(1); // 1 ETH
Nethereum manages nonces automatically, including an in-memory counter for rapid submissions. Only set manually for specific ordering needs:
transfer.Nonce = 42;
Sign without broadcasting — useful for cold wallets or deferred submission. You must set Nonce, Gas, and GasPrice since there's no node to query:
transfer.Nonce = 2;
transfer.Gas = 60000;
transfer.GasPrice = Web3.Convert.ToWei(25, UnitConversion.EthUnit.Gwei);
var signedTx = await transferHandler.SignTransactionAsync(contractAddress, transfer);
When you have a raw ABI string and don't need typed classes. This approach is less safe — parameter mismatches fail at runtime, not compile time:
var contract = web3.Eth.GetContract(abi, contractAddress);
var balanceFunction = contract.GetFunction("balanceOf");
var transferFunction = contract.GetFunction("transfer");
var balance = await balanceFunction.CallAsync<BigInteger>(ownerAddress);
var gas = await transferFunction.EstimateGasAsync(senderAddress, null, null, toAddress, amount);
var txReceipt = await transferFunction.SendTransactionAndWaitForReceiptAsync(
senderAddress, gas, null, null, toAddress, amount);
| Handler | When | Gas Cost |
|---|---|---|
GetContractQueryHandler<T> | Read-only (view/pure functions) | Free |
GetContractTransactionHandler<T> | State-changing functions | Yes |
GetContractDeploymentHandler<T> | Deploying new contracts | Yes |
For standard tokens (ERC-20, ERC-721, ERC-1155), use the built-in services (web3.Eth.ERC20, web3.Eth.ERC721) instead of defining custom DTOs.
For full documentation, see: https://docs.nethereum.com/docs/smart-contracts/guide-smart-contract-interaction