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
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ extension CbfClient {
let id = ObjectIdentifier(self)

let task = Task { [self] in
var hasEstablishedConnection = false
while true {
if Task.isCancelled { break }
do {
Expand All @@ -63,25 +62,32 @@ extension CbfClient {
object: nil,
userInfo: ["height": height]
)
if !hasEstablishedConnection {
hasEstablishedConnection = true
NotificationCenter.default.post(
name: NSNotification.Name("KyotoConnectionUpdate"),
object: nil,
userInfo: ["connected": true]
)
}
NotificationCenter.default.post(
name: NSNotification.Name("KyotoConnectionUpdate"),
object: nil,
userInfo: ["connected": true]
)
}
case .stateUpdate(let nodeState):
await MainActor.run {
NotificationCenter.default.post(
name: NSNotification.Name("KyotoStateUpdate"),
object: nil,
userInfo: ["state": nodeState]
)
NotificationCenter.default.post(
name: NSNotification.Name("KyotoConnectionUpdate"),
object: nil,
userInfo: ["connected": true]
)
}
case .connectionsMet, .successfulHandshake:
await MainActor.run {
if !hasEstablishedConnection {
hasEstablishedConnection = true
NotificationCenter.default.post(
name: NSNotification.Name("KyotoConnectionUpdate"),
object: nil,
userInfo: ["connected": true]
)
}
NotificationCenter.default.post(
name: NSNotification.Name("KyotoConnectionUpdate"),
object: nil,
userInfo: ["connected": true]
)
}
default:
break
Expand Down
10 changes: 10 additions & 0 deletions BDKSwiftExampleWallet/Service/BDK Service/BDKService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,16 @@ private class BDKService {
if isSigned {
let transaction = try psbt.extractTx()
try self.blockchainClient.broadcast(transaction)

if self.clientType == .kyoto {
let lastSeen = UInt64(Date().timeIntervalSince1970)
let unconfirmedTx = UnconfirmedTx(tx: transaction, lastSeen: lastSeen)
wallet.applyUnconfirmedTxs(unconfirmedTxs: [unconfirmedTx])
guard let persister = self.persister else {
throw WalletError.dbNotFound
}
let _ = try wallet.persist(persister: persister)
}
} else {
throw WalletError.notSigned
}
Expand Down
31 changes: 25 additions & 6 deletions BDKSwiftExampleWallet/View Model/WalletViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class WalletViewModel {
}
var isKyotoConnected: Bool = false
var currentBlockHeight: UInt32 = 0
var kyotoNodeState: NodeState?

private var updateProgress: @Sendable (UInt64, UInt64) -> Void {
{ [weak self] inspected, total in
Expand All @@ -62,12 +63,13 @@ class WalletViewModel {
}

private var updateKyotoProgress: @Sendable (Float) -> Void {
{ [weak self] progress in
DispatchQueue.main.async {
self?.progress = progress
let progressPercent = UInt64(progress)
self?.inspectedScripts = progressPercent
self?.totalScripts = 100
{ [weak self] rawProgress in
DispatchQueue.main.async { [weak self] in
guard let self else { return }
let sanitized = rawProgress.isFinite ? min(max(rawProgress, 0), 100) : 0
self.progress = sanitized
self.inspectedScripts = UInt64(sanitized)
self.totalScripts = 100
}
}
}
Expand Down Expand Up @@ -160,6 +162,23 @@ class WalletViewModel {
}
}
}

NotificationCenter.default.addObserver(
forName: NSNotification.Name("KyotoStateUpdate"),
object: nil,
queue: .main
) { [weak self] notification in
guard let self else { return }
if self.bdkClient.getClientType() != .kyoto { return }
if let nodeState = notification.userInfo?["state"] as? NodeState {
self.kyotoNodeState = nodeState
if nodeState == .transactionsSynced {
self.walletSyncState = .synced
} else {
self.walletSyncState = .syncing
}
}
}
}

private func fullScanWithProgress() async {
Expand Down
35 changes: 34 additions & 1 deletion BDKSwiftExampleWallet/View/Home/ActivityHomeHeaderView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Created by Rubens Machion on 24/04/25.
//

import BitcoinDevKit
import SwiftUI

struct ActivityHomeHeaderView: View {
Expand All @@ -17,6 +18,7 @@ struct ActivityHomeHeaderView: View {
let isKyotoClient: Bool
let isKyotoConnected: Bool
let currentBlockHeight: UInt32
let kyotoNodeState: NodeState?

let showAllTransactions: () -> Void

Expand All @@ -40,7 +42,13 @@ struct ActivityHomeHeaderView: View {
} else if walletSyncState == .syncing {
HStack {
if isKyotoClient {
if progress < 100.0 { // Kyoto progress is percent
if let status = kyotoStatusText {
Text(status)
.padding(.trailing, -5.0)
.fontWeight(.semibold)
.contentTransition(.opacity)
.transition(.opacity)
} else if progress < 100.0 { // Kyoto progress is percent
if currentBlockHeight > 0 {
Text("Block \(currentBlockHeight)")
.padding(.trailing, -5.0)
Expand Down Expand Up @@ -198,3 +206,28 @@ struct ActivityHomeHeaderView: View {
}
}
}

extension ActivityHomeHeaderView {
fileprivate var kyotoStatusText: String? {
guard isKyotoClient, let kyotoNodeState else { return nil }
// Kyoto's NodeState reflects the next stage it will enter, so describe upcoming work.
switch kyotoNodeState {
case .behind:
// Still acquiring header tips, so call out the header sync explicitly.
return "Getting headers..."
case .headersSynced:
// Kyoto reports this once headers are already finished, so surface the next
// actionable phase the node is entering rather than the completed step.
return "Preparing filters..."
case .filterHeadersSynced:
// Filter headers are ready; actual filter scanning starts next.
return "Scanning filters..."
case .filtersSynced:
// Filters are exhausted; the node now gossips for matching blocks/txs.
return "Fetching matches..."
case .transactionsSynced:
// No further phases—fall back to showing percent + standard synced UI.
return nil
}
}
}
3 changes: 2 additions & 1 deletion BDKSwiftExampleWallet/View/WalletView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ struct WalletView: View {
needsFullScan: viewModel.needsFullScan,
isKyotoClient: viewModel.isKyotoClient,
isKyotoConnected: viewModel.isKyotoConnected,
currentBlockHeight: viewModel.currentBlockHeight
currentBlockHeight: viewModel.currentBlockHeight,
kyotoNodeState: viewModel.kyotoNodeState
) {
showAllTransactions = true
}
Expand Down