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 @@ -8,7 +8,7 @@
import BitcoinDevKit
import Foundation

extension OutPoint: Hashable {
extension OutPoint: @retroactive Hashable {
public static func == (lhs: OutPoint, rhs: OutPoint) -> Bool {
lhs.txid == rhs.txid && lhs.vout == rhs.vout
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import Foundation

extension Notification.Name {
static let transactionSent = Notification.Name("TransactionSent")
static let walletCreated = Notification.Name("walletCreated")
static let walletDidUpdate = Notification.Name("walletDidUpdate")
}
10 changes: 4 additions & 6 deletions BDKSwiftExampleWallet/Service/BDK Service/BDKService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -571,9 +571,9 @@ final class BDKService {

var sweptTxids: [Txid] = []
var lastWIFOperationError: Error?

var destinationScript: Script?

for descriptorString in candidates {
guard
let descriptor = try? Descriptor(
Expand Down Expand Up @@ -855,7 +855,7 @@ struct BDKClient {
let syncWithInspector: (SyncScriptInspector) async throws -> Void
let fullScanWithInspector: (FullScanScriptInspector) async throws -> Void
let getAddress: () throws -> String
let send: (String, UInt64, UInt64) throws -> Void
let send: (String, UInt64, UInt64) async throws -> Void
let sweepWif: (String, UInt64) async throws -> [Txid]
let calculateFee: (Transaction) throws -> Amount
let calculateFeeRate: (Transaction) throws -> UInt64
Expand Down Expand Up @@ -897,9 +897,7 @@ extension BDKClient {
},
getAddress: { try BDKService.shared.getAddress() },
send: { (address, amount, feeRate) in
Task {
try await BDKService.shared.send(address: address, amount: amount, feeRate: feeRate)
}
try await BDKService.shared.send(address: address, amount: amount, feeRate: feeRate)
},
sweepWif: { (wif, feeRate) in
try await BDKService.shared.sweepWif(wif: wif, feeRate: feeRate)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class ActivityListViewModel {

private var updateProgress: @Sendable (UInt64, UInt64) -> Void {
{ [weak self] inspected, total in
DispatchQueue.main.async {
Task { @MainActor [weak self] in
self?.totalScripts = total
self?.inspectedScripts = inspected
self?.progress = total > 0 ? Float(inspected) / Float(total) : 0
Expand Down
33 changes: 16 additions & 17 deletions BDKSwiftExampleWallet/View Model/OnboardingViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,53 +128,52 @@ class OnboardingViewModel: ObservableObject {

func createWallet() {
// Check if wallet already exists
if let existingBackup = try? bdkClient.getBackupInfo() {
DispatchQueue.main.async {
self.isOnboarding = false
}
if (try? bdkClient.getBackupInfo()) != nil {
self.isOnboarding = false
return
}

guard !isCreatingWallet else {
return
}

DispatchQueue.main.async {
self.isCreatingWallet = true
}
self.isCreatingWallet = true
let words = self.words
let isDescriptor = self.isDescriptor
let isXPub = self.isXPub

Task {
do {
if WifParser.extract(from: self.words) != nil {
if WifParser.extract(from: words) != nil {
throw AppError.generic(
message:
"WIF is for sweep, not wallet creation. Open an existing wallet and use Send > Scan/Paste to sweep it."
)
}
if self.isDescriptor {
try self.bdkClient.createWalletFromDescriptor(self.words)
} else if self.isXPub {
try self.bdkClient.createWalletFromXPub(self.words)
if isDescriptor {
try self.bdkClient.createWalletFromDescriptor(words)
} else if isXPub {
try self.bdkClient.createWalletFromXPub(words)
} else {
try self.bdkClient.createWalletFromSeed(self.words)
try self.bdkClient.createWalletFromSeed(words)
}
DispatchQueue.main.async {
await MainActor.run {
self.isCreatingWallet = false
self.isOnboarding = false
NotificationCenter.default.post(name: .walletCreated, object: nil)
}
} catch let error as CreateWithPersistError {
DispatchQueue.main.async {
await MainActor.run {
self.isCreatingWallet = false
self.createWithPersistError = error
}
} catch let error as AppError {
DispatchQueue.main.async {
await MainActor.run {
self.isCreatingWallet = false
self.onboardingViewError = error
}
} catch {
DispatchQueue.main.async {
await MainActor.run {
self.isCreatingWallet = false
self.onboardingViewError = .generic(message: error.localizedDescription)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ extension ReceiveViewModel {
return
}

DispatchQueue.main.async {
Task { @MainActor in
self.receiveViewError = .generic(message: error.localizedDescription)
self.showingReceiveViewErrorAlert = true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ class BuildTransactionViewModel {
}
}

func send(address: String, amount: UInt64, feeRate: UInt64) {
func send(address: String, amount: UInt64, feeRate: UInt64) async {
do {
try bdkClient.send(address, amount, feeRate)
try await bdkClient.send(address, amount, feeRate)
NotificationCenter.default.post(
name: Notification.Name("TransactionSent"),
name: .transactionSent,
object: nil
)
} catch let error as EsploraError {
Expand Down
42 changes: 15 additions & 27 deletions BDKSwiftExampleWallet/View Model/Settings/SettingsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class SettingsViewModel: ObservableObject {

private var updateProgressFullScan: @Sendable (UInt64) -> Void {
{ [weak self] inspected in
DispatchQueue.main.async {
Task { @MainActor [weak self] in
self?.inspectedScripts = inspected
}
}
Expand All @@ -46,9 +46,7 @@ class SettingsViewModel: ObservableObject {
}

func getAddressType() {
DispatchQueue.main.async {
self.addressType = self.bdkClient.getAddressType()
}
self.addressType = self.bdkClient.getAddressType()
}

func delete() {
Expand All @@ -66,33 +64,23 @@ class SettingsViewModel: ObservableObject {
do {
let inspector = WalletFullScanScriptInspector(updateProgress: updateProgressFullScan)
try await bdkClient.fullScanWithInspector(inspector)
DispatchQueue.main.async {
NotificationCenter.default.post(
name: Notification.Name("TransactionSent"),
object: nil
)
self.walletSyncState = .synced
}
NotificationCenter.default.post(
name: .transactionSent,
object: nil
)
self.walletSyncState = .synced
} catch let error as CannotConnectError {
DispatchQueue.main.async {
self.settingsError = .generic(message: error.localizedDescription)
self.showingSettingsViewErrorAlert = true
}
self.settingsError = .generic(message: error.localizedDescription)
self.showingSettingsViewErrorAlert = true
} catch let error as EsploraError {
DispatchQueue.main.async {
self.settingsError = .generic(message: error.localizedDescription)
self.showingSettingsViewErrorAlert = true
}
self.settingsError = .generic(message: error.localizedDescription)
self.showingSettingsViewErrorAlert = true
} catch let error as PersistenceError {
DispatchQueue.main.async {
self.settingsError = .generic(message: error.localizedDescription)
self.showingSettingsViewErrorAlert = true
}
self.settingsError = .generic(message: error.localizedDescription)
self.showingSettingsViewErrorAlert = true
} catch {
DispatchQueue.main.async {
self.walletSyncState = .error(error)
self.showingSettingsViewErrorAlert = true
}
self.walletSyncState = .error(error)
self.showingSettingsViewErrorAlert = true
}
}

Expand Down
99 changes: 52 additions & 47 deletions BDKSwiftExampleWallet/View Model/WalletViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class WalletViewModel {

private var updateProgress: @Sendable (UInt64, UInt64) -> Void {
{ [weak self] inspected, total in
DispatchQueue.main.async {
Task { @MainActor [weak self] in
// When using Kyoto, progress is provided separately as percent
if self?.isKyotoClient == true { return }
self?.totalScripts = total
Expand All @@ -63,7 +63,7 @@ class WalletViewModel {

private var updateKyotoProgress: @Sendable (Float) -> Void {
{ [weak self] rawProgress in
DispatchQueue.main.async { [weak self] in
Task { @MainActor [weak self] in
guard let self else { return }
let sanitized = rawProgress.isFinite ? min(max(rawProgress, 0), 100) : 0
self.progress = sanitized
Expand All @@ -75,7 +75,7 @@ class WalletViewModel {

private var updateProgressFullScan: @Sendable (UInt64) -> Void {
{ [weak self] inspected in
DispatchQueue.main.async {
Task { @MainActor [weak self] in
self?.inspectedScripts = inspected
}
}
Expand All @@ -99,25 +99,27 @@ class WalletViewModel {
object: nil,
queue: .main
) { [weak self] notification in
guard let self else { return }
// Ignore Kyoto updates unless client type is Kyoto
if self.bdkClient.getClientType() != .kyoto { return }
if let progress = notification.userInfo?["progress"] as? Float {
self.updateKyotoProgress(progress)
if let height = notification.userInfo?["height"] as? Int {
self.currentBlockHeight = UInt32(max(height, 0))
}
// Consider any progress update as evidence of an active connection
// so the UI does not falsely show a red disconnected indicator while syncing.
if progress > 0 {
self.isKyotoConnected = true
}
Task { @MainActor [weak self] in
guard let self else { return }
// Ignore Kyoto updates unless client type is Kyoto
if self.bdkClient.getClientType() != .kyoto { return }
if let progress = notification.userInfo?["progress"] as? Float {
self.updateKyotoProgress(progress)
if let height = notification.userInfo?["height"] as? Int {
self.currentBlockHeight = UInt32(max(height, 0))
}
// Consider any progress update as evidence of an active connection
// so the UI does not falsely show a red disconnected indicator while syncing.
if progress > 0 {
self.isKyotoConnected = true
}

// Update sync state based on Kyoto progress
if progress >= 100 {
self.walletSyncState = .synced
} else if progress > 0 {
self.walletSyncState = .syncing
// Update sync state based on Kyoto progress
if progress >= 100 {
self.walletSyncState = .synced
} else if progress > 0 {
self.walletSyncState = .syncing
}
}
}
}
Expand All @@ -127,16 +129,19 @@ class WalletViewModel {
object: nil,
queue: .main
) { [weak self] notification in
if let connected = notification.userInfo?["connected"] as? Bool {
self?.isKyotoConnected = connected
Task { @MainActor [weak self] in
guard let self else { return }
if let connected = notification.userInfo?["connected"] as? Bool {
self.isKyotoConnected = connected

// When Kyoto connects, update sync state if needed
if connected && self?.walletSyncState == .notStarted {
// Check current progress to determine state
if let progress = self?.progress, progress >= 100 {
self?.walletSyncState = .synced
} else {
self?.walletSyncState = .syncing
// When Kyoto connects, update sync state if needed
if connected && self.walletSyncState == .notStarted {
// Check current progress to determine state
if self.progress >= 100 {
self.walletSyncState = .synced
} else {
self.walletSyncState = .syncing
}
}
}
}
Expand All @@ -147,19 +152,19 @@ class WalletViewModel {
object: nil,
queue: .main
) { [weak self] notification in
guard let self else { return }
// Ignore Kyoto updates unless client type is Kyoto
if self.bdkClient.getClientType() != .kyoto { return }
if let height = notification.userInfo?["height"] as? Int {
self.currentBlockHeight = UInt32(max(height, 0))
// Receiving chain height implies we have peer connectivity
self.isKyotoConnected = true
// Ensure UI reflects syncing as soon as we see chain activity
if self.walletSyncState == .notStarted { self.walletSyncState = .syncing }
// Auto-refresh wallet data when Kyoto receives new blocks
self.getBalance()
self.getTransactions()
Task {
Task { @MainActor [weak self] in
guard let self else { return }
// Ignore Kyoto updates unless client type is Kyoto
if self.bdkClient.getClientType() != .kyoto { return }
if let height = notification.userInfo?["height"] as? Int {
self.currentBlockHeight = UInt32(max(height, 0))
// Receiving chain height implies we have peer connectivity
self.isKyotoConnected = true
// Ensure UI reflects syncing as soon as we see chain activity
if self.walletSyncState == .notStarted { self.walletSyncState = .syncing }
// Auto-refresh wallet data when Kyoto receives new blocks
self.getBalance()
self.getTransactions()
await self.getPrices()
}
}
Expand All @@ -170,10 +175,10 @@ class WalletViewModel {
object: nil,
queue: .main
) { [weak self] _ in
guard let self else { return }
self.getBalance()
self.getTransactions()
Task {
Task { @MainActor [weak self] in
guard let self else { return }
self.getBalance()
self.getTransactions()
await self.getPrices()
}
}
Expand Down
2 changes: 1 addition & 1 deletion BDKSwiftExampleWallet/View/Send/AddressView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ extension AddressView {
alertMessage = "Sweep broadcasted: \(txidText)"
isShowingAlert = true
NotificationCenter.default.post(
name: Notification.Name("TransactionSent"),
name: .transactionSent,
object: nil
)
}
Expand Down
Loading