Skip to content
Draft
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
22 changes: 7 additions & 15 deletions BDKSwiftExampleWallet/Service/BDK Service/BDKService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -550,16 +550,7 @@ final class BDKService {
return backupInfo
}

func send(
address: String,
amount: UInt64,
feeRate: UInt64
) async throws {
let psbt = try buildTransaction(
address: address,
amount: amount,
feeRate: feeRate
)
func send(psbt: Psbt) async throws -> Txid {
try await signAndBroadcast(psbt: psbt)
}

Expand Down Expand Up @@ -667,7 +658,7 @@ final class BDKService {
return txBuilder
}

private func signAndBroadcast(psbt: Psbt) async throws {
private func signAndBroadcast(psbt: Psbt) async throws -> Txid {
guard let wallet = self.wallet else { throw WalletError.walletNotFound }
let isSigned = try wallet.sign(psbt: psbt)
if isSigned {
Expand All @@ -684,6 +675,7 @@ final class BDKService {
}
let _ = try wallet.persist(persister: persister)
}
return transaction.computeTxid()
} else {
throw WalletError.notSigned
}
Expand Down Expand Up @@ -863,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) async throws -> Void
let send: (Psbt) async throws -> Txid
let sweepWif: (String, UInt64) async throws -> [Txid]
let calculateFee: (Transaction) throws -> Amount
let calculateFeeRate: (Transaction) throws -> UInt64
Expand Down Expand Up @@ -904,8 +896,8 @@ extension BDKClient {
try await BDKService.shared.fullScanWithInspector(inspector: inspector)
},
getAddress: { try BDKService.shared.getAddress() },
send: { (address, amount, feeRate) in
try await BDKService.shared.send(address: address, amount: amount, feeRate: feeRate)
send: { psbt in
try await BDKService.shared.send(psbt: psbt)
},
sweepWif: { (wif, feeRate) in
try await BDKService.shared.sweepWif(wif: wif, feeRate: feeRate)
Expand Down Expand Up @@ -971,7 +963,7 @@ extension BDKClient {
syncWithInspector: { _ in },
fullScanWithInspector: { _ in },
getAddress: { "tb1pd8jmenqpe7rz2mavfdx7uc8pj7vskxv4rl6avxlqsw2u8u7d4gfs97durt" },
send: { _, _, _ in },
send: { _ in try Txid.fromString(hex: String.mockHashHex) },
sweepWif: { _, _ in [] },
calculateFee: { _ in Amount.mock },
calculateFeeRate: { _ in UInt64(6.15) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Foundation
class BuildTransactionViewModel {
let bdkClient: BDKClient

var broadcastTxid: Txid?
var buildTransactionViewError: AppError?
var calculateFee: String?
var psbt: Psbt?
Expand Down Expand Up @@ -68,9 +69,14 @@ class BuildTransactionViewModel {
}
}

func send(address: String, amount: UInt64, feeRate: UInt64) async {
func send() async {
guard let psbt = self.psbt else {
self.buildTransactionViewError = .generic(message: "No transaction to send")
self.showingBuildTransactionViewErrorAlert = true
return
}
do {
try await bdkClient.send(address, amount, feeRate)
self.broadcastTxid = try await bdkClient.send(psbt)
NotificationCenter.default.post(
name: .transactionSent,
object: nil
Expand Down
45 changes: 19 additions & 26 deletions BDKSwiftExampleWallet/View/Send/BuildTransactionView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,32 +117,25 @@ struct BuildTransactionView: View {

if !isSent {
Button {
if let amt = UInt64(amount) {
viewModel.buildTransactionViewError = nil
isError = false
Task { @MainActor in
await viewModel.send(
address: address,
amount: amt,
feeRate: UInt64(fee)
)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
if self.viewModel.buildTransactionViewError == nil {
self.isSent = true
DispatchQueue.main.asyncAfter(
deadline: .now() + 1.5
) {
self.navigationPath.removeLast(
self.navigationPath.count
)
}
} else {
self.isSent = false
self.isError = true
viewModel.buildTransactionViewError = nil
isError = false
Task { @MainActor in
await viewModel.send()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
if self.viewModel.buildTransactionViewError == nil {
self.isSent = true
DispatchQueue.main.asyncAfter(
deadline: .now() + 1.5
) {
self.navigationPath.removeLast(
self.navigationPath.count
)
}
} else {
self.isSent = false
self.isError = true
}
}
} else {
}
} label: {
Text("Send")
Expand Down Expand Up @@ -171,14 +164,14 @@ struct BuildTransactionView: View {
VStack {
Image(systemName: "checkmark")
.foregroundStyle(.green)
if let transaction = viewModel.extractTransaction() {
if let txid = viewModel.broadcastTxid {
HStack {
Text("\(transaction.computeTxid())")
Text("\(txid)")
.lineLimit(1)
.truncationMode(.middle)
Spacer()
Button {
UIPasteboard.general.string = "\(transaction.computeTxid())"
UIPasteboard.general.string = "\(txid)"
isCopied = true
showCheckmark = true
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ final class BDKSwiftExampleWalletSendViewModelTests: XCTestCase {
}

private func makeBDKClient(
send: @escaping (String, UInt64, UInt64) async throws -> Void
send: @escaping (Psbt) async throws -> Txid
) -> BDKClient {
BDKClient(
loadWallet: BDKClient.mock.loadWallet,
Expand Down Expand Up @@ -95,16 +95,17 @@ final class BDKSwiftExampleWalletSendViewModelTests: XCTestCase {
@MainActor
func testBuildTransactionViewModelSendPostsTransactionSentNotification() async {
let viewModel = BuildTransactionViewModel(
bdkClient: makeBDKClient(send: { _, _, _ in })
bdkClient: makeBDKClient(send: BDKClient.mock.send)
)
let expectation = XCTNSNotificationExpectation(name: .transactionSent)

await viewModel.send(
viewModel.buildTransaction(
address: "tb1pxg0lakl0x4jee73f38m334qsma7mn2yv764x9an5ylht6tx8ccdsxtktrt",
amount: 100_000,
feeRate: 17
)

await viewModel.send()

await fulfillment(of: [expectation], timeout: 1.0)
XCTAssertNil(viewModel.buildTransactionViewError)
XCTAssertFalse(viewModel.showingBuildTransactionViewErrorAlert)
Expand All @@ -113,19 +114,57 @@ final class BDKSwiftExampleWalletSendViewModelTests: XCTestCase {
@MainActor
func testBuildTransactionViewModelSendSurfacesAsyncFailure() async {
let viewModel = BuildTransactionViewModel(
bdkClient: makeBDKClient(send: { _, _, _ in
bdkClient: makeBDKClient(send: { _ in
throw TestSendError.failed
})
)
viewModel.buildTransaction(
address: "tb1pxg0lakl0x4jee73f38m334qsma7mn2yv764x9an5ylht6tx8ccdsxtktrt",
amount: 100_000,
feeRate: 17
)

await viewModel.send()

XCTAssertNotNil(viewModel.buildTransactionViewError)
XCTAssertTrue(viewModel.showingBuildTransactionViewErrorAlert)
XCTAssertNil(viewModel.broadcastTxid)
}

await viewModel.send(
@MainActor
func testBuildTransactionViewModelSendBroadcastsReviewedPsbtAndStoresReturnedTxid() async throws
{
let broadcastTxid = try Txid.fromString(hex: String.mockHashHex)
var sentTxid: String?
let viewModel = BuildTransactionViewModel(
bdkClient: makeBDKClient(send: { psbt in
sentTxid = try psbt.extractTx().transactionID
return broadcastTxid
})
)
viewModel.buildTransaction(
address: "tb1pxg0lakl0x4jee73f38m334qsma7mn2yv764x9an5ylht6tx8ccdsxtktrt",
amount: 100_000,
feeRate: 17
)
let reviewedTxid = try XCTUnwrap(viewModel.psbt).extractTx().transactionID

await viewModel.send()

XCTAssertEqual(sentTxid, reviewedTxid)
XCTAssertEqual(viewModel.broadcastTxid.map { "\($0)" }, "\(broadcastTxid)")
XCTAssertNil(viewModel.buildTransactionViewError)
}

@MainActor
func testBuildTransactionViewModelSendWithoutPsbtSurfacesError() async {
let viewModel = BuildTransactionViewModel(bdkClient: .mock)

await viewModel.send()

XCTAssertNotNil(viewModel.buildTransactionViewError)
XCTAssertTrue(viewModel.showingBuildTransactionViewErrorAlert)
XCTAssertNil(viewModel.broadcastTxid)
}

}
Loading