Skip to content

Commit 95e2b64

Browse files
committed
ui: list recent transactions and list utxos
1 parent 2429360 commit 95e2b64

15 files changed

+445
-25
lines changed

BDKSwiftExampleWallet.xcodeproj/project.pbxproj

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
AE18E9382A9528200019D2A4 /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE18E9372A9528200019D2A4 /* Bundle+Extensions.swift */; };
1919
AE18E93A2A9532CB0019D2A4 /* BDKSwiftExampleWalletBundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE18E9392A9532CB0019D2A4 /* BDKSwiftExampleWalletBundle+Extensions.swift */; };
2020
AE1C34242A424456008F807A /* ReceiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE1C34232A424456008F807A /* ReceiveView.swift */; };
21+
AE2381AD2C60578500F6B00C /* AllTransactionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE2381AC2C60578500F6B00C /* AllTransactionsView.swift */; };
22+
AE2381AF2C605B1D00F6B00C /* AllTransactionsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE2381AE2C605B1D00F6B00C /* AllTransactionsViewModel.swift */; };
23+
AE2381B12C60690900F6B00C /* LocalOutput+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE2381B02C60690900F6B00C /* LocalOutput+Extensions.swift */; };
24+
AE2381B32C60877600F6B00C /* UTXOListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE2381B22C60877600F6B00C /* UTXOListView.swift */; };
25+
AE2381B52C60878E00F6B00C /* UTXOListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE2381B42C60878E00F6B00C /* UTXOListItemView.swift */; };
2126
AE287E772C0F6D200036A748 /* Array+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE287E762C0F6D200036A748 /* Array+Extensions.swift */; };
2227
AE29ED0F2BBE2E7100EB9C4F /* BitcoinDevKit in Frameworks */ = {isa = PBXBuildFile; productRef = AE29ED0E2BBE2E7100EB9C4F /* BitcoinDevKit */; };
2328
AE29ED112BBE318A00EB9C4F /* WalletTransactionsListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE29ED102BBE318A00EB9C4F /* WalletTransactionsListItemView.swift */; };
@@ -105,6 +110,11 @@
105110
AE18E9372A9528200019D2A4 /* Bundle+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Extensions.swift"; sourceTree = "<group>"; };
106111
AE18E9392A9532CB0019D2A4 /* BDKSwiftExampleWalletBundle+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BDKSwiftExampleWalletBundle+Extensions.swift"; sourceTree = "<group>"; };
107112
AE1C34232A424456008F807A /* ReceiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiveView.swift; sourceTree = "<group>"; };
113+
AE2381AC2C60578500F6B00C /* AllTransactionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllTransactionsView.swift; sourceTree = "<group>"; };
114+
AE2381AE2C605B1D00F6B00C /* AllTransactionsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllTransactionsViewModel.swift; sourceTree = "<group>"; };
115+
AE2381B02C60690900F6B00C /* LocalOutput+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LocalOutput+Extensions.swift"; sourceTree = "<group>"; };
116+
AE2381B22C60877600F6B00C /* UTXOListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTXOListView.swift; sourceTree = "<group>"; };
117+
AE2381B42C60878E00F6B00C /* UTXOListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTXOListItemView.swift; sourceTree = "<group>"; };
108118
AE287E762C0F6D200036A748 /* Array+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Extensions.swift"; sourceTree = "<group>"; };
109119
AE29ED102BBE318A00EB9C4F /* WalletTransactionsListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletTransactionsListItemView.swift; sourceTree = "<group>"; };
110120
AE29ED142BBE36C500EB9C4F /* WalletTransactionsListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletTransactionsListViewModel.swift; sourceTree = "<group>"; };
@@ -203,6 +213,7 @@
203213
AE0C30F82A804B65008F1EAE /* OnboardingViewModel.swift */,
204214
AE2ADD752B61EFEB00C2A823 /* TabHomeViewModel.swift */,
205215
AE0C30FA2A804B95008F1EAE /* WalletViewModel.swift */,
216+
AE2381AE2C605B1D00F6B00C /* AllTransactionsViewModel.swift */,
206217
AE29ED142BBE36C500EB9C4F /* WalletTransactionsListViewModel.swift */,
207218
AEB6C9D02B7E8529003AD704 /* TransactionDetailsViewModel.swift */,
208219
AE0C30FC2A804BC1008F1EAE /* ReceiveViewModel.swift */,
@@ -235,6 +246,9 @@
235246
AED4CC0B2A1D3A9400CE1831 /* OnboardingView.swift */,
236247
AE3902A32A3B4CD900BEC318 /* TabHomeView.swift */,
237248
AED4CC0F2A1D522100CE1831 /* WalletView.swift */,
249+
AE2381AC2C60578500F6B00C /* AllTransactionsView.swift */,
250+
AE2381B22C60877600F6B00C /* UTXOListView.swift */,
251+
AE2381B42C60878E00F6B00C /* UTXOListItemView.swift */,
238252
AE0C30F62A804A2D008F1EAE /* WalletTransactionListView.swift */,
239253
AE29ED102BBE318A00EB9C4F /* WalletTransactionsListItemView.swift */,
240254
AEB130C82A44E4850087785B /* TransactionDetailsView.swift */,
@@ -424,6 +438,7 @@
424438
isa = PBXGroup;
425439
children = (
426440
AEE6C74B2ABCB3E200442ADD /* Transaction+Extensions.swift */,
441+
AE2381B02C60690900F6B00C /* LocalOutput+Extensions.swift */,
427442
AE6F34D72AA6C1800087E700 /* Network+Extensions.swift */,
428443
AE6F34D92AA6C1E00087E700 /* Balance+Extensions.swift */,
429444
AE184EFB2BFE52C800374362 /* Amount+Extensions.swift */,
@@ -567,6 +582,7 @@
567582
AEAB03132ABDDBF4000C9528 /* AmountViewModel.swift in Sources */,
568583
AE7953902A2D5B4400CCB277 /* BDKSwiftExampleWalletError.swift in Sources */,
569584
AE91CEEF2C0FDBC7000AAD20 /* CanonicalTx+Extensions.swift in Sources */,
585+
AE2381B52C60878E00F6B00C /* UTXOListItemView.swift in Sources */,
570586
AEA9BEB32AA8081800C523CA /* BDKError+Extensions.swift in Sources */,
571587
AE3646262BEDB01200B04E25 /* FileManager+Extensions.swift in Sources */,
572588
AEB6C9D12B7E8529003AD704 /* TransactionDetailsViewModel.swift in Sources */,
@@ -581,12 +597,14 @@
581597
AE6715FD2A9AC056005C193F /* PriceServiceError.swift in Sources */,
582598
AE34DDAC2B6B31ED00F04AD4 /* SeedView.swift in Sources */,
583599
AE2ADD742B61E8F500C2A823 /* SettingsView.swift in Sources */,
600+
AE2381AF2C605B1D00F6B00C /* AllTransactionsViewModel.swift in Sources */,
584601
AE6F34D82AA6C1800087E700 /* Network+Extensions.swift in Sources */,
585602
AE1390C72A7DB0AF0098127A /* KeyService.swift in Sources */,
586603
AED4CC0A2A1D297600CE1831 /* BDKService.swift in Sources */,
587604
AED4CC102A1D522100CE1831 /* WalletView.swift in Sources */,
588605
AE7F67092A7451AA00CED561 /* Price.swift in Sources */,
589606
AE184EFC2BFE52C800374362 /* Amount+Extensions.swift in Sources */,
607+
AE2381B12C60690900F6B00C /* LocalOutput+Extensions.swift in Sources */,
590608
AE7F67072A744CE200CED561 /* Double+Extensions.swift in Sources */,
591609
A73F7A362A3B778E00B87FC6 /* Int+Extensions.swift in Sources */,
592610
AE7839FD2AB4E18D005F0CBA /* AmountView.swift in Sources */,
@@ -597,6 +615,8 @@
597615
AE0C30FB2A804B95008F1EAE /* WalletViewModel.swift in Sources */,
598616
AE49847C2A1BBBD6009951E2 /* BDKSwiftExampleWalletApp.swift in Sources */,
599617
AE6715FF2A9AC066005C193F /* FeeServiceError.swift in Sources */,
618+
AE2381AD2C60578500F6B00C /* AllTransactionsView.swift in Sources */,
619+
AE2381B32C60877600F6B00C /* UTXOListView.swift in Sources */,
600620
AE783A052AB4F51F005F0CBA /* String+Extensions.swift in Sources */,
601621
AE29ED112BBE318A00EB9C4F /* WalletTransactionsListItemView.swift in Sources */,
602622
AE34DDAE2B6B320F00F04AD4 /* SeedViewModel.swift in Sources */,
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// LocalOutput+Extensions.swift
3+
// BDKSwiftExampleWallet
4+
//
5+
// Created by Matthew Ramsden on 8/4/24.
6+
//
7+
8+
import BitcoinDevKit
9+
10+
//#if DEBUG
11+
extension LocalOutput {
12+
static var mock = LocalOutput(
13+
outpoint: OutPoint(
14+
txid: "txid",
15+
vout: UInt32(1)
16+
),
17+
txout: TxOut(
18+
value: UInt64(1),
19+
scriptPubkey: Script(rawOutputScript: [UInt8(1)])
20+
),
21+
keychain: .external,
22+
isSpent: false
23+
)
24+
}
25+
//#endif

BDKSwiftExampleWallet/Resources/Localizable.xcstrings

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,18 +80,31 @@
8080
"%@ sats fee" : {
8181

8282
},
83-
"%lld %@" : {
83+
"%lld Transaction%@" : {
8484
"localizations" : {
8585
"en" : {
8686
"stringUnit" : {
8787
"state" : "new",
88-
"value" : "%1$lld %2$@"
88+
"value" : "%1$lld Transaction%2$@"
89+
}
90+
}
91+
}
92+
},
93+
"%lld UTXO%@" : {
94+
"localizations" : {
95+
"en" : {
96+
"stringUnit" : {
97+
"state" : "new",
98+
"value" : "%1$lld UTXO%2$@"
8999
}
90100
}
91101
}
92102
},
93103
"%llu" : {
94104

105+
},
106+
"%llu sats" : {
107+
95108
},
96109
"%llu sats fee" : {
97110
"extractionState" : "stale",
@@ -150,6 +163,7 @@
150163
}
151164
},
152165
"Activity" : {
166+
"extractionState" : "stale",
153167
"localizations" : {
154168
"fr" : {
155169
"stringUnit" : {
@@ -357,6 +371,9 @@
357371
},
358372
"Full Scan" : {
359373

374+
},
375+
"Navigation Title" : {
376+
360377
},
361378
"Network" : {
362379
"localizations" : {
@@ -410,6 +427,9 @@
410427
}
411428
}
412429
}
430+
},
431+
"No UTXOs" : {
432+
413433
},
414434
"OK" : {
415435
"localizations" : {
@@ -460,6 +480,9 @@
460480
}
461481
}
462482
}
483+
},
484+
"Recent" : {
485+
463486
},
464487
"Regtest" : {
465488
"localizations" : {
@@ -524,6 +547,9 @@
524547
}
525548
}
526549
}
550+
},
551+
"Show All" : {
552+
527553
},
528554
"Show Seed" : {
529555
"localizations" : {
@@ -649,6 +675,12 @@
649675
}
650676
}
651677
}
678+
},
679+
"UTXOs" : {
680+
681+
},
682+
"Vout: %u" : {
683+
652684
},
653685
"Wallet" : {
654686

BDKSwiftExampleWallet/Service/BDK Service/BDKService.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ private class BDKService {
5757
return transactions
5858
}
5959

60+
func listUnspent() throws -> [LocalOutput] {
61+
guard let wallet = self.wallet else {
62+
throw WalletError.walletNotFound
63+
}
64+
let values = wallet.listUnspent()
65+
return values
66+
}
67+
6068
func createWallet(words: String?) throws {
6169

6270
let baseUrl =
@@ -276,6 +284,7 @@ struct BDKClient {
276284
let createWallet: (String?) throws -> Void
277285
let getBalance: () throws -> Balance
278286
let transactions: () throws -> [CanonicalTx]
287+
let listUnspent: () throws -> [LocalOutput]
279288
let syncWithInspector: (SyncScriptInspector) async throws -> Void
280289
let fullScanWithInspector: (FullScanScriptInspector) async throws -> Void
281290
let getAddress: () throws -> String
@@ -296,6 +305,7 @@ extension BDKClient {
296305
createWallet: { words in try BDKService.shared.createWallet(words: words) },
297306
getBalance: { try BDKService.shared.getBalance() },
298307
transactions: { try BDKService.shared.transactions() },
308+
listUnspent: { try BDKService.shared.listUnspent() },
299309
syncWithInspector: { inspector in
300310
try await BDKService.shared.syncWithInspector(inspector: inspector)
301311
},
@@ -336,6 +346,11 @@ extension BDKClient {
336346
.mock
337347
]
338348
},
349+
listUnspent: {
350+
return [
351+
.mock
352+
]
353+
},
339354
syncWithInspector: { _ in },
340355
fullScanWithInspector: { _ in },
341356
getAddress: { "tb1pd8jmenqpe7rz2mavfdx7uc8pj7vskxv4rl6avxlqsw2u8u7d4gfs97durt" },
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
//
2+
// AllTransactionsViewModel.swift
3+
// BDKSwiftExampleWallet
4+
//
5+
// Created by Matthew Ramsden on 8/4/24.
6+
//
7+
8+
import BitcoinDevKit
9+
import Foundation
10+
11+
@MainActor
12+
@Observable
13+
class AllTransactionsViewModel {
14+
let bdkClient: BDKClient
15+
var walletSyncState: WalletSyncState
16+
var transactions: [CanonicalTx]
17+
var walletViewError: AppError?
18+
var showingWalletViewErrorAlert = false
19+
var progress: Float = 0.0
20+
var inspectedScripts: UInt64 = 0
21+
var totalScripts: UInt64 = 0
22+
var utxos: [LocalOutput] = []
23+
var displayMode: DisplayMode = .transactions
24+
25+
enum DisplayMode {
26+
case transactions
27+
case utxos
28+
}
29+
30+
init(
31+
bdkClient: BDKClient = .live,
32+
walletSyncState: WalletSyncState = .notStarted,
33+
transactions: [CanonicalTx] = []
34+
) {
35+
self.bdkClient = bdkClient
36+
self.walletSyncState = walletSyncState
37+
self.transactions = transactions
38+
}
39+
40+
func getUTXOs() {
41+
do {
42+
self.utxos = try bdkClient.listUnspent()
43+
} catch let error as WalletError {
44+
self.walletViewError = .generic(message: error.localizedDescription)
45+
self.showingWalletViewErrorAlert = true
46+
} catch {
47+
self.walletViewError = .generic(message: error.localizedDescription)
48+
self.showingWalletViewErrorAlert = true
49+
}
50+
}
51+
52+
func getTransactions() {
53+
do {
54+
let transactionDetails = try bdkClient.transactions()
55+
self.transactions = transactionDetails
56+
} catch let error as WalletError {
57+
self.walletViewError = .generic(message: error.localizedDescription)
58+
self.showingWalletViewErrorAlert = true
59+
} catch {
60+
self.walletViewError = .generic(message: error.localizedDescription)
61+
self.showingWalletViewErrorAlert = true
62+
}
63+
}
64+
65+
private func startSyncWithProgress() async {
66+
self.walletSyncState = .syncing
67+
do {
68+
let inspector = WalletSyncScriptInspector(updateProgress: updateProgress)
69+
try await bdkClient.syncWithInspector(inspector)
70+
self.walletSyncState = .synced
71+
} catch let error as CannotConnectError {
72+
self.walletViewError = .generic(message: error.localizedDescription)
73+
self.showingWalletViewErrorAlert = true
74+
} catch let error as EsploraError {
75+
self.walletViewError = .generic(message: error.localizedDescription)
76+
self.showingWalletViewErrorAlert = true
77+
} catch let error as InspectError {
78+
self.walletViewError = .generic(message: error.localizedDescription)
79+
self.showingWalletViewErrorAlert = true
80+
} catch let error as PersistenceError {
81+
self.walletViewError = .generic(message: error.localizedDescription)
82+
self.showingWalletViewErrorAlert = true
83+
} catch {
84+
self.walletSyncState = .error(error)
85+
self.showingWalletViewErrorAlert = true
86+
}
87+
}
88+
89+
func syncOrFullScan() async {
90+
await startSyncWithProgress()
91+
}
92+
93+
private func updateProgress(inspected: UInt64, total: UInt64) {
94+
DispatchQueue.main.async {
95+
self.totalScripts = total
96+
self.inspectedScripts = inspected
97+
self.progress = total > 0 ? Float(inspected) / Float(total) : 0
98+
}
99+
}
100+
101+
}

BDKSwiftExampleWallet/View Model/TransactionDetailsViewModel.swift

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,23 @@
77

88
import BitcoinDevKit
99
import Foundation
10+
import Observation
1011

11-
class TransactionDetailsViewModel: ObservableObject {
12+
@MainActor
13+
@Observable
14+
class TransactionDetailsViewModel {
1215
let bdkClient: BDKClient
1316
let keyClient: KeyClient
1417

15-
@Published var network: String?
16-
@Published var esploraURL: String?
18+
var network: String?
19+
var esploraURL: String?
1720

18-
@Published var esploraError: EsploraError?
19-
@Published var calculateFeeError: CalculateFeeError?
20-
@Published var transactionDetailsError: AppError?
21+
var esploraError: EsploraError?
22+
var calculateFeeError: CalculateFeeError?
23+
var transactionDetailsError: AppError?
2124

22-
@Published var showingTransactionDetailsViewErrorAlert = false
23-
@Published var calculateFee: String?
25+
var showingTransactionDetailsViewErrorAlert = false
26+
var calculateFee: String?
2427

2528
init(
2629
bdkClient: BDKClient = .live,

BDKSwiftExampleWallet/View Model/WalletViewModel.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ class WalletViewModel {
3131
var inspectedScripts: UInt64 = 0
3232
var totalScripts: UInt64 = 0
3333

34+
var recentTransactions: [CanonicalTx] {
35+
Array(transactions.prefix(5))
36+
}
37+
3438
init(
3539
priceClient: PriceClient = .live,
3640
bdkClient: BDKClient = .live,

0 commit comments

Comments
 (0)