Skip to content

Commit 52b8658

Browse files
committed
fix(send): propagate async broadcast failures
1 parent cb2e818 commit 52b8658

File tree

4 files changed

+101
-23
lines changed

4 files changed

+101
-23
lines changed

BDKSwiftExampleWallet/Service/BDK Service/BDKService.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -571,9 +571,9 @@ final class BDKService {
571571

572572
var sweptTxids: [Txid] = []
573573
var lastWIFOperationError: Error?
574-
574+
575575
var destinationScript: Script?
576-
576+
577577
for descriptorString in candidates {
578578
guard
579579
let descriptor = try? Descriptor(
@@ -855,7 +855,7 @@ struct BDKClient {
855855
let syncWithInspector: (SyncScriptInspector) async throws -> Void
856856
let fullScanWithInspector: (FullScanScriptInspector) async throws -> Void
857857
let getAddress: () throws -> String
858-
let send: (String, UInt64, UInt64) throws -> Void
858+
let send: (String, UInt64, UInt64) async throws -> Void
859859
let sweepWif: (String, UInt64) async throws -> [Txid]
860860
let calculateFee: (Transaction) throws -> Amount
861861
let calculateFeeRate: (Transaction) throws -> UInt64
@@ -897,9 +897,7 @@ extension BDKClient {
897897
},
898898
getAddress: { try BDKService.shared.getAddress() },
899899
send: { (address, amount, feeRate) in
900-
Task {
901-
try await BDKService.shared.send(address: address, amount: amount, feeRate: feeRate)
902-
}
900+
try await BDKService.shared.send(address: address, amount: amount, feeRate: feeRate)
903901
},
904902
sweepWif: { (wif, feeRate) in
905903
try await BDKService.shared.sweepWif(wif: wif, feeRate: feeRate)

BDKSwiftExampleWallet/View Model/Send/BuildTransactionViewModel.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ class BuildTransactionViewModel {
6868
}
6969
}
7070

71-
func send(address: String, amount: UInt64, feeRate: UInt64) {
71+
func send(address: String, amount: UInt64, feeRate: UInt64) async {
7272
do {
73-
try bdkClient.send(address, amount, feeRate)
73+
try await bdkClient.send(address, amount, feeRate)
7474
NotificationCenter.default.post(
7575
name: .transactionSent,
7676
object: nil

BDKSwiftExampleWallet/View/Send/BuildTransactionView.swift

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -120,22 +120,26 @@ struct BuildTransactionView: View {
120120
if let amt = UInt64(amount) {
121121
viewModel.buildTransactionViewError = nil
122122
isError = false
123-
viewModel.send(
124-
address: address,
125-
amount: amt,
126-
feeRate: UInt64(fee)
127-
)
128-
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
129-
if self.viewModel.buildTransactionViewError == nil {
130-
self.isSent = true
131-
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
132-
self.navigationPath.removeLast(
133-
self.navigationPath.count
134-
)
123+
Task { @MainActor in
124+
await viewModel.send(
125+
address: address,
126+
amount: amt,
127+
feeRate: UInt64(fee)
128+
)
129+
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
130+
if self.viewModel.buildTransactionViewError == nil {
131+
self.isSent = true
132+
DispatchQueue.main.asyncAfter(
133+
deadline: .now() + 1.5
134+
) {
135+
self.navigationPath.removeLast(
136+
self.navigationPath.count
137+
)
138+
}
139+
} else {
140+
self.isSent = false
141+
self.isError = true
135142
}
136-
} else {
137-
self.isSent = false
138-
self.isError = true
139143
}
140144
}
141145
} else {

BDKSwiftExampleWalletTests/View Model/BDKSwiftExampleWalletSendViewModelTests.swift

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,51 @@
55
// Created by Matthew Ramsden on 8/24/23.
66
//
77

8+
import BitcoinDevKit
89
import XCTest
910

1011
@testable import BDKSwiftExampleWallet
1112

1213
final class BDKSwiftExampleWalletSendViewModelTests: XCTestCase {
14+
private enum TestSendError: Error {
15+
case failed
16+
}
17+
18+
private func makeBDKClient(
19+
send: @escaping (String, UInt64, UInt64) async throws -> Void
20+
) -> BDKClient {
21+
BDKClient(
22+
loadWallet: BDKClient.mock.loadWallet,
23+
deleteWallet: BDKClient.mock.deleteWallet,
24+
createWalletFromSeed: BDKClient.mock.createWalletFromSeed,
25+
createWalletFromDescriptor: BDKClient.mock.createWalletFromDescriptor,
26+
createWalletFromXPub: BDKClient.mock.createWalletFromXPub,
27+
getBalance: BDKClient.mock.getBalance,
28+
transactions: BDKClient.mock.transactions,
29+
listUnspent: BDKClient.mock.listUnspent,
30+
syncWithInspector: BDKClient.mock.syncWithInspector,
31+
fullScanWithInspector: BDKClient.mock.fullScanWithInspector,
32+
getAddress: BDKClient.mock.getAddress,
33+
send: send,
34+
sweepWif: BDKClient.mock.sweepWif,
35+
calculateFee: BDKClient.mock.calculateFee,
36+
calculateFeeRate: BDKClient.mock.calculateFeeRate,
37+
sentAndReceived: BDKClient.mock.sentAndReceived,
38+
txDetails: BDKClient.mock.txDetails,
39+
buildTransaction: BDKClient.mock.buildTransaction,
40+
getBackupInfo: BDKClient.mock.getBackupInfo,
41+
needsFullScan: BDKClient.mock.needsFullScan,
42+
setNeedsFullScan: BDKClient.mock.setNeedsFullScan,
43+
getNetwork: BDKClient.mock.getNetwork,
44+
getEsploraURL: BDKClient.mock.getEsploraURL,
45+
updateNetwork: BDKClient.mock.updateNetwork,
46+
updateEsploraURL: BDKClient.mock.updateEsploraURL,
47+
getAddressType: BDKClient.mock.getAddressType,
48+
updateAddressType: BDKClient.mock.updateAddressType,
49+
getClientType: BDKClient.mock.getClientType,
50+
updateClientType: BDKClient.mock.updateClientType
51+
)
52+
}
1353

1454
@MainActor
1555
func testAmountViewModel() async {
@@ -52,4 +92,40 @@ final class BDKSwiftExampleWalletSendViewModelTests: XCTestCase {
5292
)
5393
}
5494

95+
@MainActor
96+
func testBuildTransactionViewModelSendPostsTransactionSentNotification() async {
97+
let viewModel = BuildTransactionViewModel(
98+
bdkClient: makeBDKClient(send: { _, _, _ in })
99+
)
100+
let expectation = XCTNSNotificationExpectation(name: .transactionSent)
101+
102+
await viewModel.send(
103+
address: "tb1pxg0lakl0x4jee73f38m334qsma7mn2yv764x9an5ylht6tx8ccdsxtktrt",
104+
amount: 100_000,
105+
feeRate: 17
106+
)
107+
108+
await fulfillment(of: [expectation], timeout: 1.0)
109+
XCTAssertNil(viewModel.buildTransactionViewError)
110+
XCTAssertFalse(viewModel.showingBuildTransactionViewErrorAlert)
111+
}
112+
113+
@MainActor
114+
func testBuildTransactionViewModelSendSurfacesAsyncFailure() async {
115+
let viewModel = BuildTransactionViewModel(
116+
bdkClient: makeBDKClient(send: { _, _, _ in
117+
throw TestSendError.failed
118+
})
119+
)
120+
121+
await viewModel.send(
122+
address: "tb1pxg0lakl0x4jee73f38m334qsma7mn2yv764x9an5ylht6tx8ccdsxtktrt",
123+
amount: 100_000,
124+
feeRate: 17
125+
)
126+
127+
XCTAssertNotNil(viewModel.buildTransactionViewError)
128+
XCTAssertTrue(viewModel.showingBuildTransactionViewErrorAlert)
129+
}
130+
55131
}

0 commit comments

Comments
 (0)