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
16 changes: 8 additions & 8 deletions BDKSwiftExampleWallet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
AE2B8C1D2A9678C900815B2F /* FeeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE2B8C1C2A9678C900815B2F /* FeeService.swift */; };
AE2B8C1F2A96797300815B2F /* RecommendedFees.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE2B8C1E2A96797300815B2F /* RecommendedFees.swift */; };
AE2F255D2BED0BFB002A9AC6 /* AppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE2F255C2BED0BFB002A9AC6 /* AppError.swift */; };
AE34DDAC2B6B31ED00F04AD4 /* SeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE34DDAB2B6B31ED00F04AD4 /* SeedView.swift */; };
AE34DDAE2B6B320F00F04AD4 /* SeedViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE34DDAD2B6B320F00F04AD4 /* SeedViewModel.swift */; };
AE34DDAC2B6B31ED00F04AD4 /* WalletRecoveryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE34DDAB2B6B31ED00F04AD4 /* WalletRecoveryView.swift */; };
AE34DDAE2B6B320F00F04AD4 /* WalletRecoveryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE34DDAD2B6B320F00F04AD4 /* WalletRecoveryViewModel.swift */; };
AE3646262BEDB01200B04E25 /* FileManager+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE3646252BEDB01200B04E25 /* FileManager+Extensions.swift */; };
AE3902A42A3B4CD900BEC318 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE3902A32A3B4CD900BEC318 /* HomeView.swift */; };
AE49847C2A1BBBD6009951E2 /* BDKSwiftExampleWalletApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE49847B2A1BBBD6009951E2 /* BDKSwiftExampleWalletApp.swift */; };
Expand Down Expand Up @@ -126,8 +126,8 @@
AE2B8C1C2A9678C900815B2F /* FeeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeeService.swift; sourceTree = "<group>"; };
AE2B8C1E2A96797300815B2F /* RecommendedFees.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendedFees.swift; sourceTree = "<group>"; };
AE2F255C2BED0BFB002A9AC6 /* AppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppError.swift; sourceTree = "<group>"; };
AE34DDAB2B6B31ED00F04AD4 /* SeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedView.swift; sourceTree = "<group>"; };
AE34DDAD2B6B320F00F04AD4 /* SeedViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedViewModel.swift; sourceTree = "<group>"; };
AE34DDAB2B6B31ED00F04AD4 /* WalletRecoveryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletRecoveryView.swift; sourceTree = "<group>"; };
AE34DDAD2B6B320F00F04AD4 /* WalletRecoveryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletRecoveryViewModel.swift; sourceTree = "<group>"; };
AE3646252BEDB01200B04E25 /* FileManager+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+Extensions.swift"; sourceTree = "<group>"; };
AE3902A32A3B4CD900BEC318 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
AE4984782A1BBBD6009951E2 /* BDKSwiftExampleWallet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BDKSwiftExampleWallet.app; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -303,7 +303,7 @@
isa = PBXGroup;
children = (
AE2ADD732B61E8F500C2A823 /* SettingsView.swift */,
AE34DDAB2B6B31ED00F04AD4 /* SeedView.swift */,
AE34DDAB2B6B31ED00F04AD4 /* WalletRecoveryView.swift */,
);
path = Settings;
sourceTree = "<group>";
Expand Down Expand Up @@ -359,7 +359,7 @@
isa = PBXGroup;
children = (
AE2ADD772B61EFFE00C2A823 /* SettingsViewModel.swift */,
AE34DDAD2B6B320F00F04AD4 /* SeedViewModel.swift */,
AE34DDAD2B6B320F00F04AD4 /* WalletRecoveryViewModel.swift */,
);
path = Settings;
sourceTree = "<group>";
Expand Down Expand Up @@ -665,7 +665,7 @@
AEB130C92A44E4850087785B /* TransactionDetailView.swift in Sources */,
AE287E772C0F6D200036A748 /* Array+Extensions.swift in Sources */,
AE6715FD2A9AC056005C193F /* PriceServiceError.swift in Sources */,
AE34DDAC2B6B31ED00F04AD4 /* SeedView.swift in Sources */,
AE34DDAC2B6B31ED00F04AD4 /* WalletRecoveryView.swift in Sources */,
AE2ADD742B61E8F500C2A823 /* SettingsView.swift in Sources */,
AE2381AF2C605B1D00F6B00C /* ActivityListViewModel.swift in Sources */,
AE6F34D82AA6C1800087E700 /* Network+Extensions.swift in Sources */,
Expand All @@ -689,7 +689,7 @@
AE2381B32C60877600F6B00C /* LocalOutputListView.swift in Sources */,
AE783A052AB4F51F005F0CBA /* String+Extensions.swift in Sources */,
AE29ED112BBE318A00EB9C4F /* TransactionItemView.swift in Sources */,
AE34DDAE2B6B320F00F04AD4 /* SeedViewModel.swift in Sources */,
AE34DDAE2B6B320F00F04AD4 /* WalletRecoveryViewModel.swift in Sources */,
AE0C30F92A804B65008F1EAE /* OnboardingViewModel.swift in Sources */,
AE3902A42A3B4CD900BEC318 /* HomeView.swift in Sources */,
AE0C30FD2A804BC1008F1EAE /* ReceiveViewModel.swift in Sources */,
Expand Down
14 changes: 11 additions & 3 deletions BDKSwiftExampleWallet/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,9 @@
}
}
}
},
"Backup is not synced across devices." : {

},
"BDK Wallet" : {
"extractionState" : "stale",
Expand Down Expand Up @@ -320,6 +323,7 @@
}
},
"Delete Seed" : {
"extractionState" : "stale",
"localizations" : {
"fr" : {
"stringUnit" : {
Expand All @@ -328,6 +332,9 @@
}
}
}
},
"Delete Wallet" : {

},
"Descriptors" : {

Expand Down Expand Up @@ -586,9 +593,6 @@
},
"Seed" : {

},
"Seed is not synced across devices." : {

},
"Select Bitcoin Network" : {

Expand Down Expand Up @@ -640,6 +644,7 @@
}
},
"Show Seed" : {
"extractionState" : "stale",
"localizations" : {
"fr" : {
"stringUnit" : {
Expand All @@ -648,6 +653,9 @@
}
}
}
},
"Show Wallet" : {

},
"Showing Seed Error" : {
"localizations" : {
Expand Down
109 changes: 106 additions & 3 deletions BDKSwiftExampleWallet/Service/BDK Service/BDKService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,99 @@ private class BDKService {
self.wallet = wallet
}

func createWallet(descriptor: String?) throws {
let documentsDirectoryURL = URL.documentsDirectory
let walletDataDirectoryURL = documentsDirectoryURL.appendingPathComponent("wallet_data")

if FileManager.default.fileExists(atPath: walletDataDirectoryURL.path) {
try FileManager.default.removeItem(at: walletDataDirectoryURL)
} else {
}

let baseUrl =
try keyClient.getEsploraURL() ?? Constants.Config.EsploraServerURLNetwork.Signet.mutiny

guard let descriptorString = descriptor, !descriptorString.isEmpty else {
throw WalletError.walletNotFound
}

let cleanDescriptor =
descriptorString.split(separator: "#").first.map(String.init) ?? descriptorString
let descriptor = try Descriptor(descriptor: cleanDescriptor, network: network)
let changeDescriptorString = cleanDescriptor.replacingOccurrences(of: "/0/*", with: "/1/*")
let changeDescriptor = try Descriptor(descriptor: changeDescriptorString, network: network)

let backupInfo = BackupInfo(
mnemonic: "",
descriptor: descriptor.toStringWithSecret(),
changeDescriptor: changeDescriptor.toStringWithSecret()
)

try keyClient.saveBackupInfo(backupInfo)
try keyClient.saveNetwork(self.network.description)
try keyClient.saveEsploraURL(baseUrl)

try FileManager.default.ensureDirectoryExists(at: walletDataDirectoryURL)
try FileManager.default.removeOldFlatFileIfNeeded(at: documentsDirectoryURL)
let persistenceBackendPath = walletDataDirectoryURL.appendingPathComponent("wallet.sqlite")
.path
let connection = try Connection(path: persistenceBackendPath)
self.connection = connection
let wallet = try Wallet(
descriptor: descriptor,
changeDescriptor: changeDescriptor,
network: network,
connection: connection
)
self.wallet = wallet
}

func createWallet(xpub: String?) throws {
let documentsDirectoryURL = URL.documentsDirectory
let walletDataDirectoryURL = documentsDirectoryURL.appendingPathComponent("wallet_data")

if FileManager.default.fileExists(atPath: walletDataDirectoryURL.path) {
try FileManager.default.removeItem(at: walletDataDirectoryURL)
} else {
}

let baseUrl =
try keyClient.getEsploraURL() ?? Constants.Config.EsploraServerURLNetwork.Signet.mutiny

guard let xpubString = xpub, !xpubString.isEmpty else {
throw WalletError.walletNotFound
}

let descriptorString = "tr(\(xpubString)/0/*)"
let changeDescriptorString = "tr(\(xpubString)/1/*)"
let descriptor = try Descriptor(descriptor: descriptorString, network: network)
let changeDescriptor = try Descriptor(descriptor: changeDescriptorString, network: network)

let backupInfo = BackupInfo(
mnemonic: "",
descriptor: descriptor.toStringWithSecret(),
changeDescriptor: changeDescriptor.toStringWithSecret()
)

try keyClient.saveBackupInfo(backupInfo)
try keyClient.saveNetwork(self.network.description)
try keyClient.saveEsploraURL(baseUrl)

try FileManager.default.ensureDirectoryExists(at: walletDataDirectoryURL)
try FileManager.default.removeOldFlatFileIfNeeded(at: documentsDirectoryURL)
let persistenceBackendPath = walletDataDirectoryURL.appendingPathComponent("wallet.sqlite")
.path
let connection = try Connection(path: persistenceBackendPath)
self.connection = connection
let wallet = try Wallet(
descriptor: descriptor,
changeDescriptor: changeDescriptor,
network: network,
connection: connection
)
self.wallet = wallet
}

private func loadWallet(descriptor: Descriptor, changeDescriptor: Descriptor) throws {
let documentsDirectoryURL = URL.documentsDirectory
let walletDataDirectoryURL = documentsDirectoryURL.appendingPathComponent("wallet_data")
Expand Down Expand Up @@ -313,7 +406,9 @@ extension BDKService {
struct BDKClient {
let loadWallet: () throws -> Void
let deleteWallet: () throws -> Void
let createWallet: (String?) throws -> Void
let createWalletFromSeed: (String?) throws -> Void
let createWalletFromDescriptor: (String?) throws -> Void
let createWalletFromXPub: (String?) throws -> Void
let getBalance: () throws -> Balance
let transactions: () throws -> [CanonicalTx]
let listUnspent: () throws -> [LocalOutput]
Expand All @@ -338,7 +433,13 @@ extension BDKClient {
static let live = Self(
loadWallet: { try BDKService.shared.loadWalletFromBackup() },
deleteWallet: { try BDKService.shared.deleteWallet() },
createWallet: { words in try BDKService.shared.createWallet(words: words) },
createWalletFromSeed: { words in try BDKService.shared.createWallet(words: words) },
createWalletFromDescriptor: { descriptor in
try BDKService.shared.createWallet(descriptor: descriptor)
},
createWalletFromXPub: { xpub in
try BDKService.shared.createWallet(xpub: xpub)
},
getBalance: { try BDKService.shared.getBalance() },
transactions: { try BDKService.shared.transactions() },
listUnspent: { try BDKService.shared.listUnspent() },
Expand Down Expand Up @@ -387,7 +488,9 @@ extension BDKClient {
static let mock = Self(
loadWallet: {},
deleteWallet: {},
createWallet: { _ in },
createWalletFromSeed: { _ in },
createWalletFromDescriptor: { _ in },
createWalletFromXPub: { _ in },
getBalance: { .mock },
transactions: {
return [
Expand Down
30 changes: 20 additions & 10 deletions BDKSwiftExampleWallet/View Model/OnboardingViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ class OnboardingViewModel: ObservableObject {

@AppStorage("isOnboarding") var isOnboarding: Bool?
@Published var createWithPersistError: CreateWithPersistError?
var isDescriptor: Bool {
words.hasPrefix("tr(") || words.hasPrefix("wpkh(") || words.hasPrefix("wsh(")
|| words.hasPrefix("sh(")
}
var isXPub: Bool {
words.hasPrefix("xpub") || words.hasPrefix("tpub") || words.hasPrefix("vpub")
}
@Published var networkColor = Color.gray
@Published var onboardingViewError: AppError?
@Published var selectedNetwork: Network = .signet {
Expand All @@ -31,12 +38,14 @@ class OnboardingViewModel: ObservableObject {
bdkClient.updateEsploraURL(selectedURL)
}
}
@Published var words: String = "" {
didSet {
updateWordArray()
@Published var words: String = ""
var wordArray: [String] {
if words.hasPrefix("xpub") || words.hasPrefix("tpub") || words.hasPrefix("vpub") {
return []
}
let trimmedWords = words.trimmingCharacters(in: .whitespacesAndNewlines)
return trimmedWords.components(separatedBy: " ")
}
@Published var wordArray: [String] = []
var availableURLs: [String] {
switch selectedNetwork {
case .bitcoin:
Expand Down Expand Up @@ -72,7 +81,13 @@ class OnboardingViewModel: ObservableObject {

func createWallet() {
do {
try bdkClient.createWallet(words)
if isDescriptor {
try bdkClient.createWalletFromDescriptor(words)
} else if isXPub {
try bdkClient.createWalletFromXPub(words)
} else {
try bdkClient.createWalletFromSeed(words)
}
DispatchQueue.main.async {
self.isOnboarding = false
}
Expand All @@ -86,9 +101,4 @@ class OnboardingViewModel: ObservableObject {
}
}
}

private func updateWordArray() {
let trimmedWords = words.trimmingCharacters(in: .whitespacesAndNewlines)
wordArray = trimmedWords.split(separator: " ").map { String($0) }
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// SeedViewModel.swift
// WalletRecoveryViewModel.swift
// BDKSwiftExampleWallet
//
// Created by Matthew Ramsden on 1/31/24.
Expand All @@ -11,25 +11,25 @@ import SwiftUI

@Observable
@MainActor
class SeedViewModel {
class WalletRecoveryViewModel {
let bdkClient: BDKClient

var backupInfo: BackupInfo?
var publicDescriptor: Descriptor?
var publicChangeDescriptor: Descriptor?
var seedViewError: AppError?
var showingSeedViewErrorAlert: Bool
var walletRecoveryViewError: AppError?
var showingWalletRecoveryViewErrorAlert: Bool

init(
bdkClient: BDKClient = .live,
backupInfo: BackupInfo? = nil,
seedViewError: AppError? = nil,
showingSeedViewErrorAlert: Bool = false
walletRecoveryViewError: AppError? = nil,
showingWalletRecoveryViewErrorAlert: Bool = false
) {
self.bdkClient = bdkClient
self.backupInfo = backupInfo
self.seedViewError = seedViewError
self.showingSeedViewErrorAlert = showingSeedViewErrorAlert
self.walletRecoveryViewError = walletRecoveryViewError
self.showingWalletRecoveryViewErrorAlert = showingWalletRecoveryViewErrorAlert
}

func getNetwork() -> Network {
Expand All @@ -55,8 +55,8 @@ class SeedViewModel {

self.backupInfo = backupInfo
} catch {
self.seedViewError = .generic(message: error.localizedDescription)
self.showingSeedViewErrorAlert = true
self.walletRecoveryViewError = .generic(message: error.localizedDescription)
self.showingWalletRecoveryViewErrorAlert = true
}
}

Expand Down
7 changes: 7 additions & 0 deletions BDKSwiftExampleWallet/View Model/WalletViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ import Observation
@Observable
class WalletViewModel {
let bdkClient: BDKClient
let keyClient: KeyClient
let priceClient: PriceClient

var balanceTotal: UInt64 = 0
var canSend: Bool {
guard let backupInfo = try? keyClient.getBackupInfo() else { return false }
return backupInfo.descriptor.contains("tprv") || backupInfo.descriptor.contains("xprv")
}
var inspectedScripts: UInt64 = 0
var price: Double = 0.00
var progress: Float = 0.0
Expand All @@ -35,11 +40,13 @@ class WalletViewModel {

init(
bdkClient: BDKClient = .live,
keyClient: KeyClient = .live,
priceClient: PriceClient = .live,
transactions: [CanonicalTx] = [],
walletSyncState: WalletSyncState = .notStarted
) {
self.bdkClient = bdkClient
self.keyClient = keyClient
self.priceClient = priceClient
self.transactions = transactions
self.walletSyncState = walletSyncState
Expand Down
Loading
Loading