Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
</PropertyGroup>
<ItemGroup>
<!-- central versions -->
<PackageVersion Include="Nethereum.Accounts" Version="5.8.0" />
<PackageVersion Include="Nethereum.Web3" Version="5.8.0" />
<PackageVersion Include="Nethereum.Signer" Version="5.8.0" />
<PackageVersion Include="Nethereum.HdWallet" Version="5.8.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" />
<PackageVersion Include="Aspire.StackExchange.Redis.DistributedCaching" Version="13.1.0" />
<PackageVersion Include="NUnit" Version="4.4.0" />
<PackageVersion Include="NUnit3TestAdapter" Version="6.1.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageVersion Include="Nethereum.Accounts" Version="6.0.4" />
<PackageVersion Include="Nethereum.Web3" Version="6.0.4" />
<PackageVersion Include="Nethereum.Signer" Version="6.0.4" />
<PackageVersion Include="Nethereum.HdWallet" Version="6.0.4" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.5" />
<PackageVersion Include="Aspire.StackExchange.Redis.DistributedCaching" Version="13.1.3" />
<PackageVersion Include="NUnit" Version="4.5.1" />
<PackageVersion Include="NUnit3TestAdapter" Version="6.2.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageVersion Include="NSubstitute" Version="5.3.0" />
<PackageVersion Include="Testcontainers" Version="4.10.0" />
<PackageVersion Include="Testcontainers" Version="4.11.0" />
<PackageVersion Include="Tricksfor.DistributedNonce" Version="10.0.2" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
</ItemGroup>
Expand Down
80 changes: 74 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ A .NET 9 library that provides comprehensive utilities and services for working
- **HD Wallet Management**: Generate and restore hierarchical deterministic wallets
- **Balance Services**: Query native token and ERC20/ERC721 token balances
- **Transaction Services**: Send and track blockchain transactions
- **Transaction Status**: Detect the correct lifecycle state of a transaction (pending, confirmed, reverted, replaced, dropped)
- **Gas Estimation**: Estimate gas costs for various transaction types
- **Transfer Services**: Simplified token transfer operations (native, ERC20, ERC721)
- **Block Services**: Query blockchain block information
- **Network Health**: Check blockchain network and RPC endpoint health before sending transactions
- **Web3 Provider**: Easy Web3 instance creation with various configurations

## Prerequisites
Expand Down Expand Up @@ -272,6 +274,64 @@ var blockService = serviceProvider.GetRequiredService<IBlockService>();
var latestBlock = await blockService.GetLastBlockAsync(web3, cancellationToken);
```

#### Transaction Status Service

Detect the lifecycle state of a transaction:

```csharp
using BlockChainTools.Interfaces;
using BlockChainTools.DataTransferObjects;

var txStatusService = serviceProvider.GetRequiredService<ITransactionStatusService>();

// Check the state of a transaction by hash
var stateInfo = await txStatusService.GetTransactionStateAsync(
web3,
"0xTransactionHash...",
cancellationToken
);

// stateInfo.State can be:
// TransactionState.Pending – known to node but not mined
// TransactionState.ConfirmedSuccess – receipt exists, status == 1
// TransactionState.ConfirmedReverted– receipt exists, status == 0 (on-chain revert)
// TransactionState.Replaced – another tx with the same nonce was mined
// TransactionState.StalePending – node no longer knows the tx (likely dropped)

if (stateInfo.State == TransactionState.ConfirmedSuccess)
{
Console.WriteLine($"Mined in block {stateInfo.BlockNumber}");
}
```

#### Network Health Service

Check blockchain network health before sending transactions:

```csharp
using BlockChainTools.Interfaces;
using Nethereum.Signer;

var healthService = serviceProvider.GetRequiredService<INetworkHealthService>();

// Full health check with expected chain and freshness threshold
var health = await healthService.CheckNetworkHealthAsync(
web3,
expectedChain: Chain.Polygon,
maxBlockAgeSeconds: 120,
cancellationToken
);

if (!health.IsHealthy)
{
foreach (var issue in health.Issues)
Console.WriteLine($"Issue: {issue}");
}

// Quick check without chain ID validation
var quickHealth = await healthService.CheckNetworkHealthAsync(web3);
```

## Available Services

### IWeb3ProviderService
Expand Down Expand Up @@ -308,9 +368,15 @@ var latestBlock = await blockService.GetLastBlockAsync(web3, cancellationToken);
### ITransactionService
- `GetTransactionByHashAsync(Web3, string hash)` - Get transaction details

### ITransactionStatusService
- `GetTransactionStateAsync(Web3, string transactionHash)` - Determine the lifecycle state of a transaction (Pending, ConfirmedSuccess, ConfirmedReverted, Replaced, StalePending)

### IBlockService
- `GetLastBlockAsync(Web3)` - Get latest block number

### INetworkHealthService
- `CheckNetworkHealthAsync(Web3, Chain? expectedChain, int maxBlockAgeSeconds)` - Check network health (block freshness, gas price availability, chain ID match)

## Project Structure

```
Expand All @@ -323,7 +389,9 @@ BlockChainTools/
│ │ ├── BlockService.cs
│ │ ├── GasService.cs
│ │ ├── HdWalletService.cs
│ │ ├── NetworkHealthService.cs
│ │ ├── TransactionService.cs
│ │ ├── TransactionStatusService.cs
│ │ ├── TransferService.cs
│ │ └── Web3ProviderService.cs
│ ├── Interfaces/ # Service contracts
Expand All @@ -334,12 +402,12 @@ BlockChainTools/

## Dependencies

- **Nethereum.Web3** (5.0.0) - Ethereum Web3 implementation
- **Nethereum.Accounts** (5.0.0) - Account management
- **Nethereum.Signer** (5.0.0) - Transaction signing
- **Nethereum.HdWallet** (5.0.0) - HD wallet support
- **Microsoft.Extensions.DependencyInjection** (9.0.10) - DI support
- **Tricksfor.DistributedNonce** (9.0.0) - Distributed nonce management
- **Nethereum.Web3** (6.0.4) - Ethereum Web3 implementation
- **Nethereum.Accounts** (6.0.4) - Account management
- **Nethereum.Signer** (6.0.4) - Transaction signing
- **Nethereum.HdWallet** (6.0.4) - HD wallet support
- **Microsoft.Extensions.DependencyInjection** (10.0.5) - DI support
- **Tricksfor.DistributedNonce** (10.0.2) - Distributed nonce management

## Testing

Expand Down
2 changes: 2 additions & 0 deletions src/BlockChainTools/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public static void AddBlockChainTools(this IServiceCollection services)
services.AddScoped<ITransactionService, TransactionService>();
services.AddScoped<IBlockService, BlockService>();
services.AddScoped<ITransferService, TransferService>();
services.AddScoped<ITransactionStatusService, TransactionStatusService>();
services.AddScoped<INetworkHealthService, NetworkHealthService>();

services.AddDistributedNonce();
}
Expand Down
56 changes: 56 additions & 0 deletions src/BlockChainTools/DataTransferObjects/NetworkHealthInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System.Numerics;

namespace BlockChainTools.DataTransferObjects;

/// <summary>
/// Contains the results of a blockchain network health check.
/// </summary>
public class NetworkHealthInfo
{
/// <summary>
/// Whether the network is considered healthy overall.
/// True when blocks are advancing, timestamps are fresh, gas estimates succeed, and chain ID matches.
/// </summary>
public required bool IsHealthy { get; init; }

/// <summary>
/// The latest block number reported by the RPC endpoint.
/// </summary>
public BigInteger? LatestBlockNumber { get; init; }

/// <summary>
/// The timestamp of the latest block as a UTC DateTime, if available.
/// </summary>
public DateTime? LatestBlockTimestamp { get; init; }

/// <summary>
/// Whether the latest block timestamp is within the acceptable freshness threshold.
/// </summary>
public bool IsBlockTimestampFresh { get; init; }

/// <summary>
/// The current gas price returned by the node, if available.
/// </summary>
public BigInteger? GasPrice { get; init; }

/// <summary>
/// Whether a gas price was successfully retrieved and is non-zero.
/// </summary>
public bool IsGasPriceAvailable { get; init; }

/// <summary>
/// The chain ID returned by the RPC endpoint, if available.
/// </summary>
public BigInteger? ChainId { get; init; }

/// <summary>
/// Whether the returned chain ID matches the expected chain ID (when an expected value was provided).
/// </summary>
public bool ChainIdMatches { get; init; }

/// <summary>
/// Human-readable summary of any issues detected during the health check.
/// Empty when fully healthy.
/// </summary>
public required IReadOnlyList<string> Issues { get; init; }
}
35 changes: 35 additions & 0 deletions src/BlockChainTools/DataTransferObjects/TransactionState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
namespace BlockChainTools.DataTransferObjects;

/// <summary>
/// Represents the lifecycle state of an EVM transaction.
/// </summary>
public enum TransactionState
{
/// <summary>
/// The transaction is known to the node (returned by eth_getTransactionByHash)
/// but has not yet been mined (no receipt).
/// </summary>
Pending,

/// <summary>
/// A receipt exists and receipt.Status == 1 — the transaction executed successfully on-chain.
/// </summary>
ConfirmedSuccess,

/// <summary>
/// A receipt exists and receipt.Status == 0 — the transaction was mined but reverted on-chain.
/// </summary>
ConfirmedReverted,

/// <summary>
/// Another transaction with the same nonce from the same sender has been mined,
/// meaning this transaction was replaced or cancelled.
/// </summary>
Replaced,

/// <summary>
/// No receipt exists and the node no longer returns the transaction,
/// indicating it was likely dropped from the mempool.
/// </summary>
StalePending
}
39 changes: 39 additions & 0 deletions src/BlockChainTools/DataTransferObjects/TransactionStateInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.Numerics;

namespace BlockChainTools.DataTransferObjects;

/// <summary>
/// Contains the resolved state of a transaction along with supporting metadata.
/// </summary>
public class TransactionStateInfo
{
/// <summary>
/// The resolved lifecycle state of the transaction.
/// </summary>
public required TransactionState State { get; init; }

/// <summary>
/// The transaction hash that was queried.
/// </summary>
public required string TransactionHash { get; init; }

/// <summary>
/// The receipt status value (1 = success, 0 = revert) when a receipt exists; otherwise null.
/// </summary>
public BigInteger? ReceiptStatus { get; init; }

/// <summary>
/// The block number in which the transaction was mined, if available.
/// </summary>
public BigInteger? BlockNumber { get; init; }

/// <summary>
/// The nonce of the transaction, if the transaction data was available from the node.
/// </summary>
public BigInteger? Nonce { get; init; }

/// <summary>
/// The confirmed on-chain nonce for the sender address, if checked.
/// </summary>
public BigInteger? ConfirmedNonce { get; init; }
}
25 changes: 25 additions & 0 deletions src/BlockChainTools/Interfaces/INetworkHealthService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using BlockChainTools.DataTransferObjects;
using Nethereum.Signer;
using Nethereum.Web3;

namespace BlockChainTools.Interfaces;

public interface INetworkHealthService
{
/// <summary>
/// Performs a health check on the blockchain network via the given Web3 endpoint.
/// Verifies that blocks are being produced, timestamps are fresh, gas price is available,
/// and the chain ID matches expectations.
/// </summary>
/// <param name="web3">The Web3 instance to query against.</param>
/// <param name="expectedChain">
/// Optional expected chain. When provided, the health check verifies the endpoint returns the correct chain ID.
/// </param>
/// <param name="maxBlockAgeSeconds">
/// Maximum acceptable age (in seconds) of the latest block timestamp.
/// Defaults to 120 seconds. If the latest block is older than this, the network is considered unhealthy.
/// </param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A <see cref="NetworkHealthInfo"/> describing the health status of the network.</returns>
Task<NetworkHealthInfo> CheckNetworkHealthAsync(Web3 web3, Chain? expectedChain = null, int maxBlockAgeSeconds = 120, CancellationToken cancellationToken = default);
}
19 changes: 19 additions & 0 deletions src/BlockChainTools/Interfaces/ITransactionStatusService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using BlockChainTools.DataTransferObjects;
using Nethereum.Web3;

namespace BlockChainTools.Interfaces;

public interface ITransactionStatusService
{
/// <summary>
/// Determines the current lifecycle state of a transaction by checking its receipt and mempool presence.
/// Uses receipt status as the canonical success/failure signal when a receipt exists.
/// When no receipt is found, checks whether the transaction is still known to the node
/// and whether the sender's on-chain nonce has advanced past the transaction nonce (indicating replacement).
/// </summary>
/// <param name="web3">The Web3 instance to query against.</param>
/// <param name="transactionHash">The hash of the transaction to check.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A <see cref="TransactionStateInfo"/> describing the current state and metadata of the transaction.</returns>
Task<TransactionStateInfo> GetTransactionStateAsync(Web3 web3, string transactionHash, CancellationToken cancellationToken = default);
}
4 changes: 4 additions & 0 deletions src/BlockChainTools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ A .NET 9 library built on Nethereum that provides ready-to-use services for comm
- **Gas Estimation** - Estimate gas costs for various transaction types
- **Transfer Services** - Simplified transfers for native tokens, ERC20, and ERC721 (NFTs)
- **Transaction & Block Services** - Query blockchain data
- **Transaction Status** - Detect lifecycle state of transactions (pending, confirmed, reverted, replaced, dropped)
- **Network Health** - Check blockchain network and RPC endpoint health
- **Web3 Provider** - Easy Web3 instance creation with distributed nonce support

## Installation
Expand Down Expand Up @@ -87,7 +89,9 @@ var txHash = await transferService.TransferAsync(web3Tx, txInput);
- **ITransferService** - Transfer native tokens, ERC20, and ERC721 (NFTs)
- **IHdWalletService** - Generate/restore HD wallets and derive accounts
- **ITransactionService** - Query transaction details
- **ITransactionStatusService** - Detect transaction lifecycle state (pending, confirmed, reverted, replaced, dropped)
- **IBlockService** - Query blockchain block information
- **INetworkHealthService** - Check network health (block freshness, gas price, chain ID)

## Distributed Nonce Management

Expand Down
Loading
Loading