Skip to main content

Nethereum.JsonRpc.IpcClient

NuGet: Nethereum.JsonRpc.IpcClient | Source: src/Nethereum.JsonRpc.IpcClient/

Nethereum.JsonRpc.IpcClient

High-performance IPC (Inter-Process Communication) JSON-RPC client for local Ethereum node communication.

Overview​

Nethereum.JsonRpc.IpcClient provides IPC transport implementations for communicating with local Ethereum nodes via Named Pipes (Windows) and Unix Domain Sockets (Linux/macOS). IPC bypasses the HTTP stack, which typically lowers latency for a node on the same machine.

Key Features:

  • Named Pipes support (Windows)
  • Unix Domain Sockets support (Linux, macOS)
  • Typically lower latency than HTTP for a co-located node
  • Reconnects lazily on the next request after a connection error
  • Requests are serialised with an internal lock (one connection per client)
  • Compatible with Geth, Erigon, Besu IPC endpoints

Use Cases:

  • Local node communication (same machine)
  • High-frequency trading / MEV bots
  • Low-latency blockchain indexers
  • Real-time event processing
  • Production node operators
  • Development and testing with local nodes

Installation​

dotnet add package Nethereum.JsonRpc.IpcClient

Platform Support:

  • Windows: Named Pipes (IpcClient)
  • Linux/macOS: Unix Domain Sockets (UnixIpcClient)

Dependencies​

Nethereum:

  • Nethereum.JsonRpc.Client - Core RPC abstraction (provides JSON serialization and logging support)

External:

  • System.IO.Pipes (v4.3.0) - Named pipes support

Quick Start​

Windows (Named Pipes)​

using Nethereum.JsonRpc.IpcClient;
using Nethereum.RPC.Eth.Blocks;

// Connect to Geth IPC endpoint (Windows)
var client = new IpcClient(@"\\.\pipe\geth.ipc");

// Query blockchain over IPC
var ethBlockNumber = new EthBlockNumber(client);
var blockNumber = await ethBlockNumber.SendRequestAsync();

Console.WriteLine($"Current block: {blockNumber.Value}");
// Typically lower latency than HTTP for a co-located node

Linux/macOS (Unix Domain Sockets)​

using Nethereum.JsonRpc.IpcClient;
using Nethereum.RPC.Eth;

// Connect to Geth IPC endpoint (Linux/macOS)
var client = new UnixIpcClient("/home/user/.ethereum/geth.ipc");

// Query blockchain
var ethChainId = new EthChainId(client);
var chainId = await ethChainId.SendRequestAsync();

Console.WriteLine($"Chain ID: {chainId.Value}");

Usage Examples​

Example 1: Connecting to Geth IPC Endpoints​

using Nethereum.JsonRpc.Client;
using Nethereum.JsonRpc.IpcClient;
using Nethereum.RPC.Eth.Blocks;
using System.Runtime.InteropServices;

// Platform-specific IPC path detection
IClient client;

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Windows: Named pipe
client = new IpcClient(@"\\.\pipe\geth.ipc");
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
// Linux: Unix socket
client = new UnixIpcClient("/home/user/.ethereum/geth.ipc");
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
// macOS: Unix socket
client = new UnixIpcClient("/Users/user/Library/Ethereum/geth.ipc");
}
else
{
throw new PlatformNotSupportedException();
}

// Use with RPC services
var ethBlockNumber = new EthBlockNumber(client);
var blockNumber = await ethBlockNumber.SendRequestAsync();

Console.WriteLine($"Block: {blockNumber.Value}");

Example 2: Custom Connection Timeout​

ConnectionTimeout is static on ClientBase (ClientBase.cs:10) - it applies to every client in the process and defaults to 20 seconds, not 120:

using Nethereum.JsonRpc.Client;
using Nethereum.JsonRpc.IpcClient;
using Nethereum.RPC.Eth;

var client = new IpcClient(@"\\.\pipe\geth.ipc");

// Default timeout is 20 seconds, shared process-wide
Console.WriteLine($"Default timeout: {ClientBase.ConnectionTimeout.TotalSeconds}s");

// Set custom timeout - affects ALL clients, not just this instance
ClientBase.ConnectionTimeout = TimeSpan.FromSeconds(10);

try
{
var ethAccounts = new EthAccounts(client);
var accounts = await ethAccounts.SendRequestAsync();
Console.WriteLine($"Accounts: {string.Join(", ", accounts)}");
}
catch (RpcClientTimeoutException ex)
{
Console.WriteLine($"IPC connection timed out: {ex.Message}");
}

Example 3: Logging with Microsoft.Extensions.Logging​

using Nethereum.JsonRpc.IpcClient;
using Microsoft.Extensions.Logging;
using Nethereum.RPC.Eth;

// Create logger
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Trace);
});

var logger = loggerFactory.CreateLogger<IpcClient>();

// Create client with logging
var client = new UnixIpcClient(
"/home/user/.ethereum/geth.ipc",
jsonSerializerSettings: null,
log: logger
);

// All requests are logged
var ethGasPrice = new EthGasPrice(client);
var gasPrice = await ethGasPrice.SendRequestAsync();
// Console output: RPC Request: {"jsonrpc":"2.0","method":"eth_gasPrice",...}
// Console output: RPC Response: 0x...

Example 4: Using with Nethereum.Web3​

using Nethereum.Web3;
using Nethereum.JsonRpc.Client;
using Nethereum.JsonRpc.IpcClient;
using System.Runtime.InteropServices;

// Create IPC client
IClient ipcClient = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? new IpcClient(@"\\.\pipe\geth.ipc")
: new UnixIpcClient("/home/user/.ethereum/geth.ipc") as IClient;

// Use with Web3
var web3 = new Web3(ipcClient);

// Local queries
var balance = await web3.Eth.GetBalance.SendRequestAsync(
"0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"
);

var blockNumber = await web3.Eth.Blocks.GetBlockNumber.SendRequestAsync();

Console.WriteLine($"Balance: {Web3.Convert.FromWei(balance)} ETH");
Console.WriteLine($"Block: {blockNumber.Value}");

Example 5: High-Frequency Request Pattern (MEV Bot)​

using Nethereum.JsonRpc.Client;
using Nethereum.JsonRpc.IpcClient;
using Nethereum.RPC.Eth.Blocks;
using System.Diagnostics;
using System.Numerics;

var client = new UnixIpcClient("/home/user/.ethereum/geth.ipc");
ClientBase.ConnectionTimeout = TimeSpan.FromSeconds(5); // static - applies process-wide

// High-frequency block monitoring with minimal latency
var ethBlockNumber = new EthBlockNumber(client);

var lastBlock = BigInteger.Zero;
while (true)
{
var sw = Stopwatch.StartNew();
var currentBlock = await ethBlockNumber.SendRequestAsync();
sw.Stop();

if (currentBlock.Value > lastBlock)
{
Console.WriteLine($"New block {currentBlock.Value} detected in {sw.ElapsedMilliseconds}ms");
lastBlock = currentBlock.Value;

// Execute time-sensitive logic here (MEV, arbitrage, etc.)
}

await Task.Delay(100); // Poll every 100ms
}
// Typically lower latency than HTTP for a co-located node

Example 6: Connection Error Handling and Retry​

Note: Polly is not a dependency of Nethereum.JsonRpc.IpcClient - add Polly to your own project to use this pattern. IpcPath is protected on IpcClientBase (IpcClientBase.cs:15), so it is not readable from outside the client - track the path yourself if you need to log it.

using Nethereum.JsonRpc.Client;
using Nethereum.JsonRpc.IpcClient;
using Nethereum.RPC.Eth.Blocks;
using Polly;

var ipcPath = @"\\.\pipe\geth.ipc";
var client = new IpcClient(ipcPath);
ClientBase.ConnectionTimeout = TimeSpan.FromSeconds(10); // static - applies process-wide

// Define retry policy for IPC connection failures
var retryPolicy = Policy
.Handle<RpcClientTimeoutException>()
.Or<RpcClientUnknownException>()
.Or<IOException>()
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)),
onRetry: (exception, timeSpan, retryCount, context) =>
{
Console.WriteLine($"IPC retry {retryCount} after {timeSpan.TotalSeconds}s: {exception.Message}");
}
);

try
{
var blockNumber = await retryPolicy.ExecuteAsync(async () =>
{
var ethBlockNumber = new EthBlockNumber(client);
return await ethBlockNumber.SendRequestAsync();
});

Console.WriteLine($"Success! Block: {blockNumber.Value}");
}
catch (RpcClientTimeoutException ex)
{
Console.WriteLine($"IPC timeout after retries: {ex.Message}");
Console.WriteLine("Is Geth running? Check IPC path.");
}
catch (RpcClientUnknownException ex)
{
Console.WriteLine($"IPC connection error: {ex.Message}");
Console.WriteLine($"IPC path: {ipcPath}");
}

Example 7: Erigon IPC Connection​

using Nethereum.JsonRpc.Client;
using Nethereum.JsonRpc.IpcClient;
using Nethereum.RPC.Eth.Blocks;
using System.Runtime.InteropServices;

// Erigon default IPC paths
var client = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? new IpcClient(@"\\.\pipe\erigon.ipc")
: new UnixIpcClient("/home/user/.local/share/erigon/erigon.ipc") as IClient;

// Erigon-specific RPC methods work over IPC
var ethBlockNumber = new EthBlockNumber(client);
var blockNumber = await ethBlockNumber.SendRequestAsync();

Console.WriteLine($"Erigon block: {blockNumber.Value}");

Example 8: Custom JsonSerializerSettings​

using Nethereum.JsonRpc.IpcClient;
using Newtonsoft.Json;
using Nethereum.RPC.Eth;

// Create custom serializer settings
var settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
Formatting = Formatting.None,
DateTimeZoneHandling = DateTimeZoneHandling.Utc
};

// Create client with custom settings
var client = new IpcClient(
@"\\.\pipe\geth.ipc",
jsonSerializerSettings: settings
);

var ethChainId = new EthChainId(client);
var chainId = await ethChainId.SendRequestAsync();

Console.WriteLine($"Chain ID: {chainId.Value}");

Example 9: Proper Disposal Pattern​

using Nethereum.JsonRpc.IpcClient;
using Nethereum.RPC.Eth.Blocks;

// IpcClient implements IDisposable (inherited from IpcClientBase) - always dispose properly
using (var client = new UnixIpcClient("/home/user/.ethereum/geth.ipc"))
{
var ethBlockNumber = new EthBlockNumber(client);
var blockNumber = await ethBlockNumber.SendRequestAsync();

Console.WriteLine($"Block: {blockNumber.Value}");

// Client automatically disposed and connection closed
}

// For long-running applications, reuse the client
var persistentClient = new IpcClient(@"\\.\pipe\geth.ipc");
try
{
// Use throughout application lifetime
while (true)
{
var ethBlockNumber = new EthBlockNumber(persistentClient);
var block = await ethBlockNumber.SendRequestAsync();
await Task.Delay(1000);
}
}
finally
{
persistentClient.Dispose();
}

API Reference​

IpcClient (Windows - Named Pipes)​

IDisposable is implemented by the base class, not declared again on IpcClient itself:

public class IpcClient : IpcClientBase
{
public IpcClient(string ipcPath,
JsonSerializerSettings jsonSerializerSettings = null,
ILogger log = null)
}

Parameters:

  • ipcPath: Named pipe path (e.g., \\.\pipe\geth.ipc)
  • jsonSerializerSettings: Optional custom JSON settings
  • log: Optional logger instance

UnixIpcClient (Linux/macOS - Unix Domain Sockets)​

public class UnixIpcClient : IpcClientBase
{
public UnixIpcClient(string ipcPath,
JsonSerializerSettings jsonSerializerSettings = null,
ILogger log = null)
}

Parameters:

  • ipcPath: Unix socket path (e.g., /home/user/.ethereum/geth.ipc)
  • jsonSerializerSettings: Optional custom JSON settings
  • log: Optional logger instance

SimpleIpcClient​

A lighter-weight alternative that opens a new NamedPipeClientStream per request instead of holding a persistent connection open (SimpleIpcClient.cs:16-20):

public class SimpleIpcClient : ClientBase
{
public SimpleIpcClient(string ipcPath,
JsonSerializerSettings jsonSerializerSettings = null,
ILogger log = null)

public JsonSerializerSettings JsonSerializerSettings { get; set; }
}

Properties​

ConnectionTimeout is defined on ClientBase and is static (shared process-wide, ClientBase.cs:10). IpcPath is protected readonly on IpcClientBase (IpcClientBase.cs:15) - it is not accessible from outside the client. ForceCompleteReadTotalMiliseconds is also static (IpcClientBase.cs:16):

public static TimeSpan ConnectionTimeout { get; set; } // Default: 20 seconds, shared by every ClientBase-derived client
protected readonly string IpcPath;
public static int ForceCompleteReadTotalMiliseconds { get; set; } // Default: 2000
public JsonSerializerSettings JsonSerializerSettings { get; set; } // IpcClientBase.cs:26

Key Methods (Inherited from ClientBase / IpcClientBase)​

public override Task<RpcResponseMessage> SendAsync(RpcRequestMessage request, string route = null)
public void Dispose()

Important Notes​

Common IPC Paths​

Geth:

PlatformDefault IPC Path
Windows\\.\pipe\geth.ipc
Linux/home/user/.ethereum/geth.ipc
macOS/Users/user/Library/Ethereum/geth.ipc

Erigon:

PlatformDefault IPC Path
Windows\\.\pipe\erigon.ipc
Linux/home/user/.local/share/erigon/erigon.ipc
macOS/Users/user/Library/Erigon/erigon.ipc

Besu:

PlatformDefault IPC Path
WindowsNot officially supported
Linux/tmp/besu.ipc
macOS/tmp/besu.ipc

Performance Comparison​

TransportUse Case
IPCLocal node, high-frequency
HTTPLocal or remote node, standard
HTTPS (remote)Cloud providers

IPC typically has lower latency than HTTP for a co-located node.

Thread Safety​

  • Requests are serialised with an internal lock over a single connection
  • For concurrent requests, create multiple client instances
  • Each instance maintains its own IPC connection
  • Safe to use from single thread or with external synchronization

Batch Requests Are NOT Supported​

IpcClientBase.SendBatchRequestAsync overrides the base implementation and throws NotImplementedException (IpcClientBase.cs:96-99); the batch-only SendAsync(RpcRequestMessage[]) overload does the same. Do not call SendBatchRequestAsync on IpcClient/UnixIpcClient/SimpleIpcClient - send individual requests instead. This is not a significant loss: IPC has low overhead, so batching provides less benefit than it does over HTTP.

Error Handling​

ExceptionCauseSolution
RpcClientTimeoutExceptionConnection timeoutCheck node is running, verify IPC path
RpcClientUnknownExceptionIPC communication errorVerify IPC path, check permissions
IOExceptionPipe/socket errorRestart node, check file system

Limitations​

  • Single connection per client - use multiple instances for concurrency
  • No subscription support - use WebSocketClient for eth_subscribe
  • Local only - IPC cannot communicate with remote nodes
  • Platform-specific - Named Pipes (Windows) vs Unix Sockets (Linux/macOS)

When to Use IPC vs HTTP vs WebSocket​

Use IPC when:

  • Running on same machine as node
  • Lowest latency to a co-located node required
  • High-frequency requests (MEV, indexing)
  • Production node operator

Use HTTP when:

  • Connecting to remote node
  • Simple request/response pattern
  • Standard latency acceptable

Use WebSocket when:

  • Need real-time subscriptions (eth_subscribe)
  • Event streaming required
  • Push notifications from node

Alternative Transports​

  • Nethereum.JsonRpc.RpcClient - HTTP/HTTPS transport
  • Nethereum.JsonRpc.WebSocketClient - WebSocket transport (subscriptions)
  • Nethereum.JsonRpc.SystemTextJsonRpcClient - HTTP with System.Text.Json

Core Dependencies​

  • Nethereum.JsonRpc.Client - Abstraction layer

Higher-Level APIs​

  • Nethereum.Web3 - Complete Web3 API

Starting Geth/Erigon with IPC​

Geth​

# Linux/macOS
geth --ipcpath /home/user/.ethereum/geth.ipc

# Windows
geth --ipcpath \\.\pipe\geth.ipc

# Default IPC is enabled automatically
geth --http --http.api eth,net,web3

Erigon​

# Linux
erigon --private.api.addr /home/user/.local/share/erigon/erigon.ipc

# Default IPC is enabled
erigon

Additional Resources​