Skip to main content

Nethereum.JsonRpc.RpcClient

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

Nethereum.JsonRpc.RpcClient

Production-ready HTTP/HTTPS JSON-RPC client for Ethereum node communication.

Overview​

Nethereum.JsonRpc.RpcClient provides the standard HTTP/HTTPS transport implementation for communicating with Ethereum nodes via JSON-RPC. This is the most commonly used RPC client in Nethereum, offering connection pooling, HttpClient rotation on older frameworks, authentication support and configurable timeouts.

Key Features:

  • HTTP/HTTPS transport with HttpClient
  • Connection pooling and lifecycle management
  • Basic authentication support (username/password)
  • Configurable timeouts
  • Automatic HttpClient rotation (for older .NET versions)
  • Thread-safe concurrent requests
  • Production-tested reliability
  • Support for custom HttpClientHandler

Use Cases:

  • Connecting to Ethereum nodes (Geth, Erigon, Besu, Nethermind)
  • Querying blockchain data
  • Sending transactions
  • Contract interactions
  • Load balancer/proxy integration
  • Production dApp backends

Installation​

dotnet add package Nethereum.JsonRpc.RpcClient

This is typically the default RPC client used by Nethereum.Web3:

dotnet add package Nethereum.Web3

Dependencies​

Nethereum:

  • Nethereum.JsonRpc.Client - Core RPC abstraction (which provides JSON serialization support via Newtonsoft.Json or System.Text.Json)

Framework:

  • System.Net.Http - HTTP/HTTPS communication (built-in .NET library)

Quick Start​

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

// Connect to local node
var client = new RpcClient(new Uri("http://localhost:8545"));

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

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

Usage Examples​

Example 1: Basic Connection to Ethereum Node​

using Nethereum.JsonRpc.Client;
using Nethereum.RPC.Eth;
using Nethereum.Hex.HexTypes;

// Local Geth/Erigon/Besu node
var client = new RpcClient(new Uri("http://localhost:8545"));

// Infura
var infuraClient = new RpcClient(
new Uri("https://mainnet.infura.io/v3/YOUR_PROJECT_ID")
);

// Alchemy
var alchemyClient = new RpcClient(
new Uri("https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY")
);

// Use with any RPC service
var ethChainId = new EthChainId(client);
HexBigInteger chainId = await ethChainId.SendRequestAsync();

Console.WriteLine($"Chain ID: {chainId.Value}");
// Output: Chain ID: 1 (mainnet)

Example 2: HTTP Basic Authentication​

using Nethereum.JsonRpc.Client;
using System.Net.Http.Headers;
using System.Text;

// Option 1: URL-based authentication (simplest)
var client = new RpcClient(
new Uri("http://username:password@localhost:8545")
);

// Option 2: Explicit AuthenticationHeaderValue
var credentials = Convert.ToBase64String(
Encoding.UTF8.GetBytes("admin:secretpassword")
);
var authHeader = new AuthenticationHeaderValue("Basic", credentials);

var authenticatedClient = new RpcClient(
new Uri("http://localhost:8545"),
authHeaderValue: authHeader
);

// Use authenticated client
var ethAccounts = new EthAccounts(authenticatedClient);
var accounts = await ethAccounts.SendRequestAsync();

Console.WriteLine($"Accounts: {string.Join(", ", accounts)}");

Example 3: Custom Connection Timeout​

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

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

var client = new RpcClient(new Uri("http://localhost:8545"));

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

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

try
{
var ethGasPrice = new EthGasPrice(client);
var gasPrice = await ethGasPrice.SendRequestAsync();
Console.WriteLine($"Gas price: {gasPrice.Value} wei");
}
catch (RpcClientTimeoutException ex)
{
Console.WriteLine($"Request timed out after 30 seconds: {ex.Message}");
}

Example 4: Using Custom HttpClient for Advanced Configuration​

using Nethereum.JsonRpc.Client;
using Nethereum.RPC.Eth.Blocks;
using System.Net.Http;

// Create custom HttpClient with specific settings
var httpClient = new HttpClient(new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(15),
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(10),
MaxConnectionsPerServer = 50,
EnableMultipleHttp2Connections = true
})
{
Timeout = TimeSpan.FromSeconds(60)
};

// Create RpcClient with custom HttpClient
var client = new RpcClient(
new Uri("http://localhost:8545"),
httpClient: httpClient
);

var ethBlockNumber = new EthBlockNumber(client);
var blockNumber = await ethBlockNumber.SendRequestAsync();

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

Example 5: Custom HttpClientHandler for Proxy Support​

using Nethereum.JsonRpc.Client;
using System.Net;
using System.Net.Http;

// Configure proxy
var handler = new HttpClientHandler
{
Proxy = new WebProxy("http://proxy.example.com:8080")
{
Credentials = new NetworkCredential("proxyuser", "proxypass")
},
UseProxy = true,
MaxConnectionsPerServer = 20
};

// Create client with proxy handler
var client = new RpcClient(
new Uri("http://localhost:8545"),
httpClientHandler: handler
);

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

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

Example 6: Multiple Concurrent Requests (Thread Safety)​

using Nethereum.JsonRpc.Client;
using Nethereum.RPC.Eth;
using Nethereum.RPC.Eth.Blocks;
using System.Threading.Tasks;

var client = new RpcClient(new Uri("http://localhost:8545"));

// RpcClient is thread-safe - can handle concurrent requests
var tasks = new List<Task>
{
Task.Run(async () =>
{
var ethBlockNumber = new EthBlockNumber(client);
var block = await ethBlockNumber.SendRequestAsync();
Console.WriteLine($"Task 1 - Block: {block.Value}");
}),

Task.Run(async () =>
{
var ethGasPrice = new EthGasPrice(client);
var gasPrice = await ethGasPrice.SendRequestAsync();
Console.WriteLine($"Task 2 - Gas Price: {gasPrice.Value}");
}),

Task.Run(async () =>
{
var ethChainId = new EthChainId(client);
var chainId = await ethChainId.SendRequestAsync();
Console.WriteLine($"Task 3 - Chain ID: {chainId.Value}");
})
};

await Task.WhenAll(tasks);
Console.WriteLine("All requests completed successfully");

Example 7: Error Handling and Retry Logic​

Note: Polly is not a dependency of Nethereum.JsonRpc.RpcClient - add Polly to your own project if you want to use this pattern.

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

var client = new RpcClient(new Uri("http://localhost:8545"));
ClientBase.ConnectionTimeout = TimeSpan.FromSeconds(10); // static, shared process-wide (default 20s)

// Define retry policy with Polly
var retryPolicy = Policy
.Handle<RpcClientTimeoutException>()
.Or<RpcClientUnknownException>()
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)),
onRetry: (exception, timeSpan, retryCount, context) =>
{
Console.WriteLine($"Retry {retryCount} after {timeSpan.TotalSeconds}s due to: {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 (RpcResponseException ex)
{
Console.WriteLine($"RPC Error {ex.RpcError.Code}: {ex.RpcError.Message}");
}
catch (RpcClientTimeoutException ex)
{
Console.WriteLine($"Timeout after retries: {ex.Message}");
}
catch (RpcClientUnknownException ex)
{
Console.WriteLine($"Network error: {ex.Message}");
}

Example 8: Load Balancing Across Multiple Nodes​

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

public class LoadBalancedRpcClient
{
private readonly List<RpcClient> _clients;
private int _currentIndex = 0;
private readonly object _lock = new object();

public LoadBalancedRpcClient(params string[] nodeUrls)
{
_clients = nodeUrls.Select(url => new RpcClient(new Uri(url))).ToList();
}

public RpcClient GetNextClient()
{
lock (_lock)
{
var client = _clients[_currentIndex];
_currentIndex = (_currentIndex + 1) % _clients.Count;
return client;
}
}
}

// Usage
var loadBalancer = new LoadBalancedRpcClient(
"http://node1.example.com:8545",
"http://node2.example.com:8545",
"http://node3.example.com:8545"
);

// Round-robin requests
for (int i = 0; i < 10; i++)
{
var client = loadBalancer.GetNextClient();
var ethBlockNumber = new EthBlockNumber(client);
var block = await ethBlockNumber.SendRequestAsync();
Console.WriteLine($"Request {i + 1} - Block: {block.Value}");
}

Example 9: Using with Nethereum.Web3​

using Nethereum.Web3;
using Nethereum.JsonRpc.Client;

// Option 1: Web3 creates RpcClient internally (simplest)
var web3 = new Web3("https://mainnet.infura.io/v3/YOUR_PROJECT_ID");

// Option 2: Create custom RpcClient first
var client = new RpcClient(new Uri("http://localhost:8545"));
ClientBase.ConnectionTimeout = TimeSpan.FromSeconds(60); // static - applies process-wide

var web3WithCustomClient = new Web3(client);

// Use Web3 normally
var balance = await web3WithCustomClient.Eth.GetBalance.SendRequestAsync("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb");
Console.WriteLine($"Balance: {Web3.Convert.FromWei(balance)} ETH");

var blockNumber = await web3WithCustomClient.Eth.Blocks.GetBlockNumber.SendRequestAsync();
Console.WriteLine($"Block: {blockNumber.Value}");

API Reference​

RpcClient Constructor Overloads​

// Basic constructor
public RpcClient(Uri baseUrl,
AuthenticationHeaderValue authHeaderValue = null,
JsonSerializerSettings jsonSerializerSettings = null,
HttpClientHandler httpClientHandler = null,
ILogger log = null)

// With custom HttpClient
public RpcClient(Uri baseUrl,
HttpClient httpClient,
AuthenticationHeaderValue authHeaderValue = null,
JsonSerializerSettings jsonSerializerSettings = null,
ILogger log = null)

Properties​

public static int MaximumConnectionsPerServer { get; set; } = 20;
public static TimeSpan ConnectionTimeout { get; set; } // Default: 20 seconds, shared by every ClientBase-derived client
public RequestInterceptor OverridingRequestInterceptor { get; set; }

SimpleRpcClient​

A minimal alternative to RpcClient for callers that already own an HttpClient and don't need connection rotation, pooling tuning, or authentication header inference (SimpleRpcClient.cs):

public class SimpleRpcClient : ClientBase
{
public SimpleRpcClient(Uri baseUrl, HttpClient httpClient,
JsonSerializerSettings jsonSerializerSettings = null)
}
using Nethereum.JsonRpc.Client;
using System.Net.Http;

var httpClient = new HttpClient();
var client = new SimpleRpcClient(new Uri("http://localhost:8545"), httpClient);

Key Methods (Inherited from ClientBase)​

Task<T> SendRequestAsync<T>(RpcRequest request, string route = null);
Task<T> SendRequestAsync<T>(string method, string route = null, params object[] paramList);
Task<RpcRequestResponseBatch> SendBatchRequestAsync(RpcRequestResponseBatch batch);
Task<RpcResponseMessage> SendAsync(RpcRequestMessage request, string route = null);

Important Notes​

Connection Management​

HttpClient Rotation:

  • On older .NET Framework, RpcClient automatically rotates HttpClient instances every 60 seconds
  • On .NET Core 2.1+, uses SocketsHttpHandler with connection pooling
  • Connection lifetime: 10 minutes
  • Idle timeout: 5 minutes
  • Max connections per server: 20 (configurable via MaximumConnectionsPerServer)

Best Practices:

  • Reuse RpcClient instances (don't create per request)
  • Set appropriate timeouts based on network conditions
  • Use connection pooling for high-traffic applications

Thread Safety​

  • Thread-safe after initialization
  • Safe to call from multiple threads concurrently
  • Connection pooling handles concurrent requests efficiently
  • Lock-free for read operations

Performance​

Optimization Tips:

  • Enable HTTP/2 with EnableMultipleHttp2Connections
  • Use batch requests for multiple calls
  • Tune MaxConnectionsPerServer for high throughput
  • Consider WebSocket client for subscriptions

Error Handling​

RpcClient.SendAsync catches every exception raised while sending/receiving (RpcClient.cs:142-189). HttpRequestException (DNS failures, connection refused, etc.) is therefore never seen by the caller directly - it is always wrapped as the InnerException of an RpcClientUnknownException:

ExceptionCauseRetry?
RpcClientTimeoutExceptionRequest exceeded ConnectionTimeout (TaskCanceledException caught first)Yes (with backoff)
RpcClientUnknownExceptionAny other failure, including HttpRequestException (available via .InnerException)Yes (transient)
RpcResponseExceptionJSON-RPC error from node (single request)Depends on error code
RpcResponseBatchExceptionOne or more batch items failed and AcceptPartiallySuccessful was falseDepends on error code
RpcResponseFormatExceptionResponse could not be decoded into the requested typeNo

Authentication​

Supports HTTP Basic Authentication:

  • URL-based: http://user:pass@localhost:8545
  • Header-based: AuthenticationHeaderValue("Basic", base64Credentials)
  • Automatically extracted from URI if present

JSON Serialization​

Uses Newtonsoft.Json with default settings optimized for Ethereum:

  • Proper handling of 0x hex prefixes
  • BigInteger serialization
  • Block/transaction DTOs

For System.Text.Json, use Nethereum.JsonRpc.SystemTextJsonRpcClient instead.

Logging​

Supports Microsoft.Extensions.Logging:

var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
var logger = loggerFactory.CreateLogger<RpcClient>();

var client = new RpcClient(
new Uri("http://localhost:8545"),
log: logger
);

Logs include:

  • Request JSON payloads
  • Response JSON payloads
  • Exception details
  • Timeout and error details

Alternative Transports​

  • Nethereum.JsonRpc.SystemTextJsonRpcClient - HTTP with System.Text.Json
  • Nethereum.JsonRpc.WebSocketClient - WebSocket transport
  • Nethereum.JsonRpc.IpcClient - IPC transport (Unix sockets, named pipes)

Higher-Level APIs​

  • Nethereum.Web3 - Complete Web3 API (uses RpcClient internally)
  • Nethereum.RPC - Typed RPC services

Core Dependencies​

  • Nethereum.JsonRpc.Client - Abstraction layer

Common Ethereum Node Providers​

ProviderURL FormatNotes
Infurahttps://mainnet.infura.io/v3/PROJECT_IDFree tier available
Alchemyhttps://eth-mainnet.g.alchemy.com/v2/API_KEYEnhanced APIs
QuickNodehttps://your-endpoint.quiknode.pro/token/Global network
Local Gethhttp://localhost:8545Full node
Local Erigonhttp://localhost:8545Archive node

Additional Resources​