diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
index e054e6ac8bf07bd9ade9d0837d1c6e0408362348..0a019179b2d45f23a41e54ecb084ac7ec835fe2d 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
@@ -11,6 +11,7 @@ public struct HomeState: Equatable {
   public init(
     failure: String? = nil,
     isNetworkHealthy: Bool? = nil,
+    networkNodesReport: NodeRegistrationReport? = nil,
     isDeletingAccount: Bool = false,
     alert: AlertState<HomeAction>? = nil,
     register: RegisterState? = nil
@@ -24,6 +25,7 @@ public struct HomeState: Equatable {
 
   public var failure: String?
   public var isNetworkHealthy: Bool?
+  public var networkNodesReport: NodeRegistrationReport?
   public var isDeletingAccount: Bool
   public var alert: AlertState<HomeAction>?
   public var register: RegisterState?
@@ -41,6 +43,7 @@ public enum HomeAction: Equatable {
     case start
     case stop
     case health(Bool)
+    case nodes(NodeRegistrationReport)
   }
 
   public enum DeleteAccount: Equatable {
@@ -93,6 +96,7 @@ extension HomeEnvironment {
 public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
 { state, action, env in
   enum NetworkHealthEffectId {}
+  enum NetworkNodesEffectId {}
 
   switch action {
   case .messenger(.start):
@@ -135,26 +139,44 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
     return .none
 
   case .networkMonitor(.start):
-    return .run { subscriber in
-      let callback = HealthCallback { isHealthy in
-        subscriber.send(.networkMonitor(.health(isHealthy)))
+    return .merge(
+      Effect.run { subscriber in
+        let callback = HealthCallback { isHealthy in
+          subscriber.send(.networkMonitor(.health(isHealthy)))
+        }
+        let cancellable = env.messenger.cMix()?.addHealthCallback(callback)
+        return AnyCancellable { cancellable?.cancel() }
       }
-      let cancellable = env.messenger.cMix()?.addHealthCallback(callback)
-      return AnyCancellable { cancellable?.cancel() }
-    }
-    .cancellable(id: NetworkHealthEffectId.self, cancelInFlight: true)
+        .cancellable(id: NetworkHealthEffectId.self, cancelInFlight: true),
+      Effect.timer(
+        id: NetworkNodesEffectId.self,
+        every: .seconds(2),
+        on: env.bgQueue
+      )
+      .compactMap { _ in try? env.messenger.cMix()?.getNodeRegistrationStatus() }
+        .map { HomeAction.networkMonitor(.nodes($0)) }
+        .eraseToEffect()
+    )
     .subscribe(on: env.bgQueue)
     .receive(on: env.mainQueue)
     .eraseToEffect()
 
   case .networkMonitor(.stop):
     state.isNetworkHealthy = nil
-    return .cancel(id: NetworkHealthEffectId.self)
+    state.networkNodesReport = nil
+    return .merge(
+      .cancel(id: NetworkHealthEffectId.self),
+      .cancel(id: NetworkNodesEffectId.self)
+    )
 
   case .networkMonitor(.health(let isHealthy)):
     state.isNetworkHealthy = isHealthy
     return .none
 
+  case .networkMonitor(.nodes(let report)):
+    state.networkNodesReport = report
+    return .none
+
   case .deleteAccount(.buttonTapped):
     state.alert = .confirmAccountDeletion()
     return .none
diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
index d105e0bd6c400c18217d17cb6b791d1ab391359f..ce8aac42ab93d9578770f7cb606b7090a6fb6082 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
@@ -2,6 +2,7 @@ import ComposableArchitecture
 import ComposablePresentation
 import RegisterFeature
 import SwiftUI
+import XXClient
 
 public struct HomeView: View {
   public init(store: Store<HomeState, HomeAction>) {
@@ -13,12 +14,14 @@ public struct HomeView: View {
   struct ViewState: Equatable {
     var failure: String?
     var isNetworkHealthy: Bool?
+    var networkNodesReport: NodeRegistrationReport?
     var isDeletingAccount: Bool
 
     init(state: HomeState) {
       failure = state.failure
       isNetworkHealthy = state.isNetworkHealthy
       isDeletingAccount = state.isDeletingAccount
+      networkNodesReport = state.networkNodesReport
     }
   }
 
@@ -26,15 +29,17 @@ public struct HomeView: View {
     WithViewStore(store.scope(state: ViewState.init)) { viewStore in
       NavigationView {
         Form {
-          if let failure = viewStore.failure {
-            Section {
+          Section {
+            if let failure = viewStore.failure {
               Text(failure)
               Button {
                 viewStore.send(.messenger(.start))
               } label: {
                 Text("Retry")
               }
-            } header: {
+            }
+          } header: {
+            if viewStore.failure != nil {
               Text("Error")
             }
           }
@@ -57,6 +62,26 @@ public struct HomeView: View {
                   .foregroundColor(.gray)
               }
             }
+
+            ProgressView(
+              value: viewStore.networkNodesReport?.ratio ?? 0,
+              label: {
+                Text("Node registration")
+              },
+              currentValueLabel: {
+                if let report = viewStore.networkNodesReport {
+                  HStack {
+                    Text("\(Int((report.ratio * 100).rounded(.down)))%")
+                    Spacer()
+                    Text("\(report.registered) / \(report.total)")
+                  }
+                } else {
+                  Text("Unknown")
+                }
+              }
+            )
+            .tint((viewStore.networkNodesReport?.ratio ?? 0) >= 0.8 ? .green : .orange)
+            .animation(.default, value: viewStore.networkNodesReport?.ratio)
           } header: {
             Text("Network")
           }
diff --git a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
index 97ac8900fff0624421ed26fee9232fce36f939b9..c7168d5b4537564a69ac82145b433c6ab22c65ff 100644
--- a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
@@ -58,6 +58,10 @@ final class HomeFeatureTests: XCTestCase {
     store.environment.messenger.cMix.get = {
       var cMix: CMix = .unimplemented
       cMix.addHealthCallback.run = { _ in Cancellable {} }
+      cMix.getNodeRegistrationStatus.run = {
+        struct Unimplemented: Error {}
+        throw Unimplemented()
+      }
       return cMix
     }
 
@@ -96,6 +100,10 @@ final class HomeFeatureTests: XCTestCase {
     store.environment.messenger.cMix.get = {
       var cMix: CMix = .unimplemented
       cMix.addHealthCallback.run = { _ in Cancellable {} }
+      cMix.getNodeRegistrationStatus.run = {
+        struct Unimplemented: Error {}
+        throw Unimplemented()
+      }
       return cMix
     }
 
@@ -219,41 +227,82 @@ final class HomeFeatureTests: XCTestCase {
       environment: .unimplemented
     )
 
+    let bgQueue = DispatchQueue.test
+    let mainQueue = DispatchQueue.test
+
     var cMixDidAddHealthCallback: [HealthCallback] = []
     var healthCallbackDidCancel = 0
-
-    store.environment.bgQueue = .immediate
-    store.environment.mainQueue = .immediate
+    var nodeRegistrationStatusIndex = 0
+    let nodeRegistrationStatus: [NodeRegistrationReport] = [
+      .init(registered: 0, total: 10),
+      .init(registered: 1, total: 11),
+      .init(registered: 2, total: 12),
+    ]
+
+    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
     store.environment.messenger.cMix.get = {
       var cMix: CMix = .unimplemented
       cMix.addHealthCallback.run = { callback in
         cMixDidAddHealthCallback.append(callback)
         return Cancellable { healthCallbackDidCancel += 1 }
       }
+      cMix.getNodeRegistrationStatus.run = {
+        defer { nodeRegistrationStatusIndex += 1 }
+        return nodeRegistrationStatus[nodeRegistrationStatusIndex]
+      }
       return cMix
     }
 
     store.send(.networkMonitor(.start))
 
+    bgQueue.advance()
+
     XCTAssertNoDifference(cMixDidAddHealthCallback.count, 1)
 
     cMixDidAddHealthCallback.first?.handle(true)
+    mainQueue.advance()
 
     store.receive(.networkMonitor(.health(true))) {
       $0.isNetworkHealthy = true
     }
 
     cMixDidAddHealthCallback.first?.handle(false)
+    mainQueue.advance()
 
     store.receive(.networkMonitor(.health(false))) {
       $0.isNetworkHealthy = false
     }
 
+    bgQueue.advance(by: 2)
+    mainQueue.advance()
+
+    store.receive(.networkMonitor(.nodes(nodeRegistrationStatus[0]))) {
+      $0.networkNodesReport = nodeRegistrationStatus[0]
+    }
+
+    bgQueue.advance(by: 2)
+    mainQueue.advance()
+
+    store.receive(.networkMonitor(.nodes(nodeRegistrationStatus[1]))) {
+      $0.networkNodesReport = nodeRegistrationStatus[1]
+    }
+
+    bgQueue.advance(by: 2)
+    mainQueue.advance()
+
+    store.receive(.networkMonitor(.nodes(nodeRegistrationStatus[2]))) {
+      $0.networkNodesReport = nodeRegistrationStatus[2]
+    }
+
     store.send(.networkMonitor(.stop)) {
       $0.isNetworkHealthy = nil
+      $0.networkNodesReport = nil
     }
 
     XCTAssertNoDifference(healthCallbackDidCancel, 1)
+
+    mainQueue.advance()
   }
 
   func testAccountDeletion() {