Nethereum is the go-to .NET library for talking to OP Mainnet. It targets .NET Standard 2.0+, so the same code runs across Unity3D games, Xamarin mobile apps, ASP.NET Core services, and Blazor front ends. Because OP Mainnet is an OP Stack rollup that stays byte-for-byte EVM-equivalent with Ethereum L1, the standard JSON-RPC surface Nethereum speaks works unchanged — you simply point it at https://optimism.therpc.io/YOUR_API_KEY (chain ID 10, ETH as the gas token). Add it to a project with dotnet add package Nethereum.Web3.
Nethereum builds against .NET Standard 2.0+, which keeps it compatible with Unity3D, Xamarin, ASP.NET Core, and Blazor — one dependency covers desktop, mobile, web, and game runtimes that need OP Mainnet access. Pull it in from NuGet with dotnet add package Nethereum.Web3.
Nethereum
Create a client by passing the OP Mainnet endpoint URL to new Web3(url) — that single object exposes the full Eth API surface. Every method that touches the network returns a Task, so always await it. With OP Mainnet producing blocks roughly every 2 seconds, calls return quickly, but you should still avoid blocking on .Result or .Wait() in UI or ASP.NET request threads, since that pattern deadlocks the synchronization context.
using Nethereum.Web3;
using Nethereum.Web3.Accounts;
using Nethereum.Util;
using Nethereum.Hex.HexTypes;
public class EthereumService
{
private readonly Web3 _web3;
public EthereumService(string url)
{
_web3= new Web3(url);
}
public async Task<decimal> GetBalanceAsync(string address)
{
var balance = await _web3.Eth.GetBalance.SendRequestAsync(address);
return Web3.Convert.FromWei(balance.Value);
}
public async Task<string> SendTransactionAsync(
string privateKey,
string toAddress,
decimal etherAmount)
{
var account = new Account(privateKey);
var web3= new Web3(account, _web3.Client.Url);
var transaction = await web3.Eth.GetEtherTransferService()
Key features: full OP Mainnet JSON-RPC integration, smart contract deployment and interaction, HD wallet support, Unity3D and Xamarin runtimes, and IPC / RPC / WebSocket transports
Smart Contract Integration
To work with a deployed contract — say a Velodrome pool or a Synthetix module on OP Mainnet — get a handle from _web3.Eth.GetContract(abi, contractAddress). From there, grab a function with GetFunction(name), then call CallAsync<T>(...) for read-only views (no gas, no signature) and SendTransactionAsync(...) for state-changing writes that submit an ETH-paid transaction to the network.
public class SmartContractService
{
private readonly Web3 _web3;
private readonly Contract _contract;
public SmartContractService(string url, string contractAddress, string abi)
In an ASP.NET Core app, register EthereumService in the dependency-injection container in Program.cs (or Startup) and let the framework resolve it into your controllers — that keeps one configured OP Mainnet client shared across requests. Wrap each await in a try/catch so a dropped connection or a reverted call surfaces as a clean BadRequest rather than an unhandled 500.
public EthereumController(EthereumService ethereumService)
{
_ethereumService = ethereumService;
}
[HttpGet("balance/{address}")]
public async Task<ActionResult<decimal>> GetBalance(string address)
{
try
{
var balance = await _ethereumService.GetBalanceAsync(address);
return Ok(balance);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
}
Event Handling
To watch contract events on OP Mainnet, model the event as a DTO and use GetEvent<TEventDTO>(contractAddress) together with CreateFilterInput() to define which logs to pull. Polling GetFilterChanges returns new matching events as they land — handy for tracking, say, swap or transfer events emitted by a Superchain dApp.
public class EventMonitorService
{
private readonly Web3 _web3;
public async Task MonitorEventsAsync(string contractAddress, string eventName)
{
var filterAll = _web3.Eth.GetEvent<YourEventDTO>(contractAddress)
.CreateFilterInput();
var subscription = _web3.Eth.GetEvent<YourEventDTO>(contractAddress)
.GetFilterChanges(filterAll);
subscription.Subscribe(evt =>
{
Console.WriteLine($"New event: {evt.Event}");
});
}
}
Unity3D Integration
For a Unity3D game integrating OP Mainnet, build the Web3 client once in Start() or Awake() against https://optimism.therpc.io/YOUR_API_KEY and reuse it. Be careful with async void: it is only acceptable on top-level MonoBehaviour entry points like Start() because exceptions inside it cannot be awaited or caught upstream. Keep the real work in Task-returning helper methods and call them from those entry points so errors stay catchable.
public class EthereumUnityManager : MonoBehaviour
{
private Web3 _web3;
private async void Start()
{
_web3= new Web3("https://optimism.therpc.io/YOUR_API_KEY");
await InitializeWalletAsync();
}
private async Task InitializeWalletAsync()
{
try
{
var balance = await _web3.Eth.GetBalance.SendRequestAsync("YOUR_ADDRESS");