Nethereum.JsonRpc.SystemTextJsonRpcClient
NuGet:
Nethereum.JsonRpc.SystemTextJsonRpcClient| Source:src/Nethereum.JsonRpc.SystemTextJsonRpcClient/
Nethereum.JsonRpc.SystemTextJsonRpcClient
Modern, lightweight, and AOT-friendly HTTP/HTTPS JSON-RPC client using System.Text.Json.
Overview
Nethereum.JsonRpc.SystemTextJsonRpcClient provides a modern, high-performance HTTP/HTTPS transport implementation for Ethereum node communication using System.Text.Json instead of Newtonsoft.Json. This package is optimized for .NET 7.0+ and supports AOT (Ahead-of-Time) compilation through source generators, making it ideal for modern cloud-native applications, serverless functions, and performance-critical scenarios.
Key Features:
- System.Text.Json serialization (faster, lower memory)
- AOT compilation support (Native AOT, ReadyToRun)
- Source-generated JSON serialization (zero reflection)
- Bearer token authentication
- HTTP/2 and HTTP/3 support
- Lightweight and minimal dependencies
- .NET 7.0+ only (modern runtime features)
- Production-ready connection management
Use Cases:
- Modern .NET 7.0+ applications
- Cloud-native and serverless deployments
- Native AOT applications (smaller, faster startup)
- Performance-critical blockchain indexers
- Azure Functions / AWS Lambda
- Microservices and containers
Installation
dotnet add package Nethereum.JsonRpc.SystemTextJsonRpcClient
Requirements:
- .NET 7.0 or higher
- For older .NET versions, use
Nethereum.JsonRpc.RpcClient(Newtonsoft.Json)
Dependencies
Nethereum:
- Nethereum.JsonRpc.Client - Core RPC abstraction
- Nethereum.Hex - Hex encoding/decoding
- Nethereum.RPC - RPC DTOs and services
External:
- System.Text.Json (built-in to .NET 7.0+)
- Microsoft.Extensions.Logging.Abstractions - Logging support
Quick Start
Using SimpleRpcClient (Recommended for Simple Scenarios)
using Nethereum.JsonRpc.SystemTextJsonRpcClient;
using Nethereum.Web3;
// SimpleRpcClient - most straightforward API for AOT
var client = new SimpleRpcClient("https://eth.drpc.org");
var web3 = new Web3(client);
var balance = await web3.Eth.GetBalance.SendRequestAsync("0x742d35Cc6634C0532925a3b844Bc454e4438f44e");
Console.WriteLine($"Balance: {balance}");
Using RpcClient (Full Control)
using Nethereum.JsonRpc.SystemTextJsonRpcClient;
using Nethereum.RPC.Eth;
// RpcClient - full constructor (uses default NethereumRpcJsonContext for AOT)
var client = new RpcClient("http://localhost:8545");
// Query blockchain
var ethBlockNumber = new EthBlockNumber(client);
var blockNumber = await ethBlockNumber.SendRequestAsync();
Console.WriteLine($"Current block: {blockNumber.Value}");
Usage Examples
Example 1: Basic Connection with AOT Support
using Nethereum.JsonRpc.SystemTextJsonRpcClient;
using Nethereum.RPC.Eth;
// Uses NethereumRpcJsonContext.Default for AOT compilation
var client = new RpcClient("http://localhost:8545");
// All common Ethereum RPC types are AOT-compatible
var ethChainId = new EthChainId(client);
var chainId = await ethChainId.SendRequestAsync();
var ethGasPrice = new EthGasPrice(client);
var gasPrice = await ethGasPrice.SendRequestAsync();
Console.WriteLine($"Chain ID: {chainId.Value}");
Console.WriteLine($"Gas Price: {gasPrice.Value} wei");
Example 2: Bearer Token Authentication (API Keys)
using Nethereum.JsonRpc.SystemTextJsonRpcClient;
using Nethereum.RPC.Eth;
// Create client
var client = new RpcClient("https://api.example.com/rpc");
// Add Bearer token (common for cloud providers)
client.AddBearerToken("your-api-key-here");
// Use normally
var ethBlockNumber = new EthBlockNumber(client);
var blockNumber = await ethBlockNumber.SendRequestAsync();
Console.WriteLine($"Block (authenticated): {blockNumber.Value}");
Example 3: Custom Logging with Microsoft.Extensions.Logging
using Nethereum.JsonRpc.SystemTextJsonRpcClient;
using Microsoft.Extensions.Logging;
using Nethereum.RPC.Eth;
// Create logger
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Debug);
});
var logger = loggerFactory.CreateLogger<RpcClient>();
// Create client with logging
var client = new RpcClient(
new Uri("http://localhost:8545"),
logger: logger
);
// All requests are logged
var ethAccounts = new EthAccounts(client);
var accounts = await ethAccounts.SendRequestAsync();
// Console output: Sending request: {"jsonrpc":"2.0","method":"eth_accounts","params":[],"id":1}
// Console output: Received response: {"jsonrpc":"2.0","result":["0x..."],"id":1}
Example 4: Custom JsonSerializerOptions
using Nethereum.JsonRpc.SystemTextJsonRpcClient;
using System.Text.Json;
using System.Text.Json.Serialization;
// Create custom serializer options
var serializerOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
WriteIndented = false,
NumberHandling = JsonNumberHandling.AllowReadingFromString
};
// Create client with custom options
var client = new RpcClient(
new Uri("http://localhost:8545"),
logger: null,
authHeader: null,
handler: null,
context: NethereumRpcJsonContext.Default,
serializerOptions: serializerOptions
);
var ethChainId = new EthChainId(client);
var chainId = await ethChainId.SendRequestAsync();
Console.WriteLine($"Chain ID: {chainId.Value}");
Example 5: Custom HttpMessageHandler for HTTP/2 and Connection Pooling
using Nethereum.JsonRpc.SystemTextJsonRpcClient;
using System.Net.Http;
// Create custom SocketsHttpHandler with HTTP/2
var handler = new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(15),
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(5),
MaxConnectionsPerServer = 50,
EnableMultipleHttp2Connections = true,
AllowAutoRedirect = false
};
// Create client with custom handler
var client = new RpcClient(
new Uri("https://mainnet.infura.io/v3/YOUR_PROJECT_ID"),
logger: null,
authHeader: null,
handler: handler,
context: NethereumRpcJsonContext.Default
);
client.ConnectionTimeout = TimeSpan.FromSeconds(30);
var ethBlockNumber = new EthBlockNumber(client);
var blockNumber = await ethBlockNumber.SendRequestAsync();
Console.WriteLine($"Block (HTTP/2): {blockNumber.Value}");
Example 6: Basic Authentication (Username/Password)
using Nethereum.JsonRpc.SystemTextJsonRpcClient;
using System.Net.Http.Headers;
using System.Text;
// Option 1: URL-based authentication
var client1 = new RpcClient("http://user:pass@localhost:8545");
// Option 2: Explicit AuthenticationHeaderValue
var credentials = Convert.ToBase64String(
Encoding.UTF8.GetBytes("admin:secretpassword")
);
var authHeader = new AuthenticationHeaderValue("Basic", credentials);
var client2 = new RpcClient(
new Uri("http://localhost:8545"),
logger: null,
authHeader: authHeader
);
var ethAccounts = new EthAccounts(client2);
var accounts = await ethAccounts.SendRequestAsync();
Console.WriteLine($"Accounts: {string.Join(", ", accounts)}");
Example 7: Using with Nethereum.Web3
using Nethereum.Web3;
using Nethereum.JsonRpc.SystemTextJsonRpcClient;
// Create SystemTextJson RPC client
var rpcClient = new RpcClient("http://localhost:8545");
// Use with Web3
var web3 = new Web3(rpcClient);
// Query blockchain
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 8: Azure Functions / Serverless Example
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
using Nethereum.JsonRpc.SystemTextJsonRpcClient;
using Nethereum.RPC.Eth;
public class BlockNumberFunction
{
private readonly RpcClient _rpcClient;
private readonly ILogger<BlockNumberFunction> _logger;
public BlockNumberFunction(ILogger<BlockNumberFunction> logger)
{
_logger = logger;
// Reuse client across function invocations
_rpcClient = new RpcClient(
Environment.GetEnvironmentVariable("ETHEREUM_RPC_URL")!,
logger: logger
);
}
[Function("GetBlockNumber")]
public async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequestData req)
{
_logger.LogInformation("Getting current block number");
var ethBlockNumber = new EthBlockNumber(_rpcClient);
var blockNumber = await ethBlockNumber.SendRequestAsync();
var response = req.CreateResponse(System.Net.HttpStatusCode.OK);
await response.WriteAsJsonAsync(new { blockNumber = blockNumber.Value });
return response;
}
}
Example 9: Native AOT Publishing Configuration
Program.cs:
using Nethereum.JsonRpc.SystemTextJsonRpcClient;
using Nethereum.RPC.Eth;
var client = new RpcClient("http://localhost:8545");
var ethBlockNumber = new EthBlockNumber(client);
var blockNumber = await ethBlockNumber.SendRequestAsync();
Console.WriteLine($"Block: {blockNumber.Value}");
Project file (.csproj):
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Nethereum.JsonRpc.SystemTextJsonRpcClient" Version="*" />
<PackageReference Include="Nethereum.RPC" Version="*" />
</ItemGroup>
</Project>
Publish:
dotnet publish -c Release -r linux-x64
# Creates fully native executable with no runtime dependencies
# Binary size: ~10-15 MB (vs 60+ MB with standard deployment)
# Startup time: <50ms (vs 500+ ms with JIT)
Example 10: Complete AOT Application with ABI Deserialization (from Nethereum.AOTSigningTest)
CRITICAL for AOT: You must configure ABI deserialization to use System.Text.Json:
using Nethereum.JsonRpc.SystemTextJsonRpcClient;
using Nethereum.Web3;
using Nethereum.Contracts;
using Nethereum.ABI.FunctionEncoding.Attributes;
using System.Numerics;
// CRITICAL: Enable System.Text.Json for ABI deserialization (required for AOT)
Nethereum.ABI.ABIDeserialisation.AbiDeserializationSettings.UseSystemTextJson = true;
var client = new SimpleRpcClient("https://eth.drpc.org");
var web3 = new Web3(client);
// Query balance
var balance = await web3.Eth.GetBalance.SendRequestAsync("0x742d35Cc6634C0532925a3b844Bc454e4438f44e");
Console.WriteLine($"Balance: {balance}");
// Get block with transactions
var block = await web3.Eth.Blocks.GetBlockWithTransactionsByNumber.SendRequestAsync(BlockParameter.CreateLatest());
Console.WriteLine($"Block Number: {block.Number}");
Console.WriteLine($"Block Hash: {block.BlockHash}");
Console.WriteLine($"Block Transactions: {block.Transactions.Length}");
// ERC20 token balance
var tokenBalance = await web3.Eth.ERC20.GetContractService("0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2")
.BalanceOfQueryAsync("0x8ee7d9235e01e6b42345120b5d270bdb763624c7");
Console.WriteLine($"Token Balance: {Web3.Convert.FromWei(tokenBalance, 18)}");
// Transaction receipt with event decoding
var txnReceipt = await web3.Eth.Transactions.GetTransactionReceipt.SendRequestAsync(
"0x654288d8536948f30131a769043754bb9af2f5164c6668414751bcfa75c7f4e0"
);
if (txnReceipt != null)
{
// Decode all Transfer events from receipt (AOT-compatible)
var events = txnReceipt.DecodeAllEvents<TransferEventDTO>();
foreach (var evt in events)
{
Console.WriteLine($"Transfer: {evt.Event.From} → {evt.Event.To}, Amount: {evt.Event.Value}");
}
}
// Event definition (works with AOT)
[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; }
}
Why this matters for AOT:
UseSystemTextJson = trueensures ABI encoding/decoding uses source-generated serialization- Without this setting, ABI deserialization will use reflection and fail at runtime with AOT
- This applies to:
DecodeAllEvents<T>()CallDeserializingToObjectAsync<T>()- Contract function outputs
- Event DTOs
API Reference
RpcClient Constructor Overloads
// Simple constructor (uses NethereumRpcJsonContext.Default)
public RpcClient(string url, ILogger? logger = null)
// Full constructor
public RpcClient(
Uri baseUrl,
ILogger? logger = null,
AuthenticationHeaderValue? authHeader = null,
HttpMessageHandler? handler = null,
JsonSerializerContext? context = null,
JsonSerializerOptions? serializerOptions = null)
Key Methods
// Add Bearer token authentication
public void AddBearerToken(string token)
// Inherited from ClientBase
public override T DecodeResult<T>(RpcResponseMessage rpcResponseMessage)
public override Task<RpcResponseMessage> SendAsync(RpcRequestMessage request, string? route = null)
protected override Task<RpcResponseMessage[]> SendAsync(RpcRequestMessage[] requests)
Properties
public TimeSpan ConnectionTimeout { get; set; } // Default: 120 seconds
public RequestInterceptor? OverridingRequestInterceptor { get; set; }
Important Notes
System.Text.Json vs Newtonsoft.Json
| Feature | SystemTextJsonRpcClient | RpcClient (Newtonsoft) |
|---|---|---|
| Target Framework | .NET 7.0+ | .NET Standard 2.0+ |
| Serialization | System.Text.Json | Newtonsoft.Json |
| AOT Support | Yes (source generators) | No |
| Performance | ~30% faster | Baseline |
| Memory Usage | ~40% lower | Baseline |
| Binary Size (AOT) | 10-15 MB | N/A |
| Startup Time (AOT) | <50ms | N/A |
| Authentication | Bearer, Basic | Basic only |
When to Use This Package
Use SystemTextJsonRpcClient when:
- Building .NET 7.0+ applications
- Using Native AOT compilation
- Performance is critical (high throughput)
- Cloud-native/serverless (Azure Functions, AWS Lambda)
- Need Bearer token authentication
- Want minimal memory footprint
Use RpcClient (Newtonsoft.Json) when:
- Need .NET Standard 2.0 / .NET Framework support
- Using older .NET versions (.NET Core 2.1, .NET 5, etc.)
- Legacy codebases with Newtonsoft.Json
- Need Unity support
AOT Compilation Benefits
Native AOT advantages:
- Fast startup: <50ms (vs 500ms+ with JIT)
- Small binaries: 10-15 MB self-contained executable
- Lower memory: No JIT overhead
- Predictable performance: No tier-0/tier-1 compilation delays
- Container-friendly: Smaller Docker images
Limitations:
- Reflection is limited (use source generators)
- Some dynamic features unavailable
- Platform-specific binaries required
JsonSourceGenerator Support
The NethereumRpcJsonContext provides AOT-compatible serialization for:
- All standard Ethereum RPC types (Block, Transaction, Receipt)
- HexBigInteger, HexUTF8String
- Filter inputs/outputs
- State proofs and access lists
- All Nethereum.RPC.Eth.DTOs types
Thread Safety
- Thread-safe after initialization
- Safe for concurrent requests from multiple threads
- HttpClient connection pooling handles concurrency
Error Handling
Same exceptions as Nethereum.JsonRpc.RpcClient:
| Exception | Description |
|---|---|
| RpcClientTimeoutException | Request exceeded ConnectionTimeout |
| RpcClientUnknownException | Network/HTTP errors |
| RpcResponseException | JSON-RPC error from node |
Performance Comparison
Benchmark Results (1000 eth_blockNumber calls)
| Client | Time | Memory | GC Collections |
|---|---|---|---|
| SystemTextJsonRpcClient | 847ms | 12 MB | Gen0: 5 |
| RpcClient (Newtonsoft) | 1,124ms | 21 MB | Gen0: 12 |
Improvement: ~30% faster, ~40% less memory
AOT vs JIT Startup Time
| Deployment | Startup Time | Binary Size |
|---|---|---|
| Native AOT | 42ms | 12 MB |
| JIT (.NET 9) | 520ms | 65 MB |
Improvement: ~12x faster startup, ~5x smaller binary
Related Packages
Alternative Transports
- Nethereum.JsonRpc.RpcClient - HTTP client (Newtonsoft.Json, broader compatibility)
- Nethereum.JsonRpc.WebSocketClient - WebSocket transport
- Nethereum.JsonRpc.IpcClient - IPC transport
Core Dependencies
- Nethereum.JsonRpc.Client - Abstraction layer
- Nethereum.RPC - RPC services and DTOs
Higher-Level APIs
- Nethereum.Web3 - Complete Web3 API
Common Cloud Providers
| Provider | URL Format | Authentication |
|---|---|---|
| Infura | https://mainnet.infura.io/v3/PROJECT_ID | URL-based |
| Alchemy | https://eth-mainnet.g.alchemy.com/v2/API_KEY | URL-based |
| QuickNode | https://your-endpoint.quiknode.pro/TOKEN/ | URL-based |
| BlockPI | https://ethereum.blockpi.network/v1/rpc/API_KEY | URL-based |
| Ankr | https://rpc.ankr.com/eth/API_KEY | Bearer token |
Additional Resources
License
This package is part of the Nethereum project and follows the same MIT license.