Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
05143f3
feat: add string keys
jvsena42 Jul 2, 2026
f8b5995
feat: add assets
jvsena42 Jul 2, 2026
75930b0
feat: connection flow logic
jvsena42 Jul 2, 2026
4cc2b54
feat: HW step views
jvsena42 Jul 2, 2026
3cf5ed8
feat: convert intro sheet into a wizard for connection flow
jvsena42 Jul 2, 2026
fcc8428
feat: dont dismiss wizard sheet for displaying the hardware pairing
jvsena42 Jul 2, 2026
7e628b8
test: connection flow tests
jvsena42 Jul 2, 2026
cd214b2
doc: changelog
jvsena42 Jul 2, 2026
8804dd8
Merge branch 'master' into feat/hw-wallet-connect
jvsena42 Jul 2, 2026
a2d7707
doc: rename changelog
jvsena42 Jul 2, 2026
8abff20
feat: screen transitions
jvsena42 Jul 2, 2026
2c11fbd
fix: use uncropped trezor image in found step; rename cropped asset
jvsena42 Jul 2, 2026
3324fd7
fix: match andrpid speed animation
jvsena42 Jul 2, 2026
384e65c
fix: navigate to home after add the wallet
jvsena42 Jul 3, 2026
28384c8
fix: headline two lines
jvsena42 Jul 3, 2026
01e9c21
fix: headline line break and move image to screen bottom
jvsena42 Jul 3, 2026
152f9cd
refactor: rename intro sheets to connect sheet
jvsena42 Jul 3, 2026
8df7fa8
refactor: comments cleanup
jvsena42 Jul 3, 2026
d366be6
refactor: comments cleanup
jvsena42 Jul 3, 2026
e009ec9
fix: disconnect device if task is cancelled mid connection
jvsena42 Jul 3, 2026
de88a03
fix: make the scan-loop sleep propagate cancellation so dismissing th…
jvsena42 Jul 3, 2026
9b51de1
fix: require the connected device to be the one we requested, so a li…
jvsena42 Jul 3, 2026
4fddfd0
fix: suppress the global sheet only while the wizard is actively pair…
jvsena42 Jul 3, 2026
57c53f4
fix: cleanup stale semaphore sates
jvsena42 Jul 6, 2026
1c65282
fix: propagate connection error
jvsena42 Jul 6, 2026
d0a2431
fix: add error pin and device busy branch errors
jvsena42 Jul 6, 2026
da14967
chore: bump bitkit core to 0.3.9
jvsena42 Jul 6, 2026
0dff778
test: cover new eror cases
jvsena42 Jul 6, 2026
16293c2
fix: error parsing
jvsena42 Jul 6, 2026
a5b5919
fix: clear stale showPairingCode state from previous sessions
jvsena42 Jul 6, 2026
922f691
fix: handle stable showPairingCode acoss retries
jvsena42 Jul 6, 2026
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
2 changes: 1 addition & 1 deletion Bitkit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1201,7 +1201,7 @@
repositoryURL = "https://github.com/synonymdev/bitkit-core";
requirement = {
kind = exactVersion;
version = 0.3.6;
version = 0.3.9;
};
};
96E20CD22CB6D91A00C24149 /* XCRemoteSwiftPackageReference "CodeScanner" */ = {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "coin-stack-3.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "hw-searching-arrows.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "hw-searching-ring-inner.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "hw-searching-ring.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"images" : [
{
"filename" : "trezor.png",
"filename" : "trezor-cropped.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "trezor@2x.png",
"filename" : "trezor-cropped@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
Expand Down
2 changes: 1 addition & 1 deletion Bitkit/Components/Trezor/HwDeviceIllustrations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ struct HwDeviceIllustrations: View {
let staggerY = width * Layout.proportionalStagger

ZStack {
Image("trezor")
Image("trezor-cropped")
.resizable()
.scaledToFit()
.frame(width: width * Layout.trezorProportionalWidth, height: imageHeight)
Expand Down
2 changes: 1 addition & 1 deletion Bitkit/Components/Widgets/Suggestions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ struct Suggestions: View {
case .support:
route = .support
case .hardware:
sheets.showSheet(.hardwareIntro)
sheets.showSheet(.hardwareConnect)
case .transferToSpending:
route = app.hasSeenTransferIntro ? .fundingOptions : .transferIntro
}
Expand Down
5 changes: 3 additions & 2 deletions Bitkit/MainNavView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,12 @@ struct MainNavView: View {
config in WidgetsSheet(config: config)
}
.sheet(
item: $sheets.hardwareIntroSheetItem,
item: $sheets.hardwareConnectSheetItem,
onDismiss: {
sheets.hideSheet()
}
) {
config in HardwareIntroSheet(config: config)
config in HardwareConnectSheet(config: config)
}
.sheet(
item: $sheets.hardwarePairingSheetItem,
Expand All @@ -216,6 +216,7 @@ struct MainNavView: View {
// A hardware device asked for its one-time pairing code (e.g. during reconnect);
// surface the app-wide Pair Device sheet. Hidden again once submitted/cancelled.
if needsCode {
guard !sheets.hardwareConnectHandlesPairing else { return }
sheets.showSheet(.hardwarePairing)
} else {
sheets.hideSheetIfActive(.hardwarePairing, reason: "Pairing code resolved")
Expand Down
18 changes: 18 additions & 0 deletions Bitkit/Managers/TrezorManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ final class TrezorManager {

var showPairingCode: Bool = false

private(set) var pairingCodeRequestID: Int = 0

var showConfirmOnDevice: Bool = false

var confirmMessage: String = ""
Expand Down Expand Up @@ -115,6 +117,7 @@ final class TrezorManager {
.receive(on: DispatchQueue.main)
.sink { [weak self] in
self?.showPairingCode = true
self?.pairingCodeRequestID &+= 1
}
.store(in: &cancellables)

Expand Down Expand Up @@ -170,6 +173,7 @@ final class TrezorManager {
showPinEntry = false
showPassphraseEntry = false
showConfirmOnDevice = false
showPairingCode = false
showWalletModeChooser = false
uiHandler.setWalletMode(.standard)
walletMode = .standard
Expand Down Expand Up @@ -263,6 +267,7 @@ final class TrezorManager {
func connect(device: TrezorDeviceInfo) async {
error = nil
suppressNextAutoReconnect = false
showPairingCode = false

// Explicit user-initiated connect always opens the standard wallet — a
// passphrase/on-device selection left over from a previously connected device
Expand All @@ -274,6 +279,13 @@ final class TrezorManager {

do {
let features = try await trezorService.connect(deviceId: device.path, selection: uiHandler.currentSelection())

if Task.isCancelled {
try? await trezorService.disconnect()
trezorLog("Connect cancelled before pairing; disconnected \(device.path)")
return
}

connectedDevice = device
deviceFeatures = features
showConfirmOnDevice = false
Expand Down Expand Up @@ -710,6 +722,12 @@ final class TrezorManager {
if message.contains("Pairing required") {
return "Bluetooth pairing required. Please put your Trezor in pairing mode."
}
if message.contains("Code verification failed") || message.contains("verification failed") {
return t("hardware__pairing_code_invalid")
}
if message.contains("DeviceBusy") || message.contains("Device is busy") || message.contains("DeviceLocked") {
return t("hardware__device_busy")
}
if message.contains("Pairing failed") || message.contains("Invalid credentials") {
return "Pairing failed. Please try putting your Trezor back in pairing mode."
}
Expand Down
25 changes: 25 additions & 0 deletions Bitkit/Resources/Localization/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,31 @@
"hardware__intro_title" = "Hardware Wallet";
"hardware__intro_header" = "Add your <accent>hardware wallet</accent>";
"hardware__intro_text" = "Connect your hardware device to watch or manage your long-term funds.";
"hardware__connect_title" = "Connect Device";
"hardware__connect_header" = "Searching for <accent>devices</accent>";
"hardware__connect_text" = "Please connect your hardware wallet now via Bluetooth.";
"hardware__connect_error" = "Could not connect to your Trezor. Check that it is unlocked and try again.";
"hardware__search_error" = "Could not search for hardware wallets. Check your connection and try again.";
"hardware__pairing_code_invalid" = "Incorrect pairing code. Put your Trezor back in pairing mode and try again.";
"hardware__device_busy" = "Your Trezor is busy. Unlock it on the device, then try again.";
"hardware__device_model_trezor" = "Trezor";
"hardware__bluetooth_open_settings" = "Open Settings";
"hardware__bluetooth_off_title" = "Bluetooth is Off";
"hardware__bluetooth_off_text" = "Please enable Bluetooth in Settings to connect to your Trezor.";
"hardware__bluetooth_unauthorized_title" = "Bluetooth Unauthorized";
"hardware__bluetooth_unauthorized_text" = "Bitkit needs Bluetooth permission to connect to your Trezor. Please enable it in Settings.";
"hardware__bluetooth_unsupported_title" = "Bluetooth Unsupported";
"hardware__bluetooth_unsupported_text" = "This device does not support Bluetooth Low Energy.";
"hardware__bluetooth_unavailable_title" = "Bluetooth Unavailable";
"hardware__bluetooth_unavailable_text" = "Bluetooth is not available. Please check your device settings.";
"hardware__found_title" = "Found Device";
"hardware__found_header" = "Found\n<accent>Trezor</accent>";
"hardware__found_text" = "Would you like to securely pair this {model} with Bitkit?";
"hardware__paired_title" = "Device Connected";
"hardware__paired_header" = "Paired\n<accent>Trezor</accent>";
"hardware__paired_text" = "Bitkit found funds on your device and added these to your balance.";
"hardware__paired_label" = "Label Funds";
"hardware__paired_finish" = "Finish";
"hardware__pairing_title" = "Pair Device";
"hardware__pairing_text" = "Enter the 6-digit code shown on your hardware device.";
"hardware__remove_button" = "Remove {name}";
Expand Down
4 changes: 4 additions & 0 deletions Bitkit/Services/Trezor/TrezorTransport.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import BitkitCore
import Combine

Check warning on line 2 in Bitkit/Services/Trezor/TrezorTransport.swift

View workflow job for this annotation

GitHub Actions / Run Tests

add '@preconcurrency' to suppress 'Sendable'-related warnings from module 'Combine'

Check warning on line 2 in Bitkit/Services/Trezor/TrezorTransport.swift

View workflow job for this annotation

GitHub Actions / Run Integration Tests

add '@preconcurrency' to suppress 'Sendable'-related warnings from module 'Combine'
import CoreBluetooth
import Foundation

Expand All @@ -8,15 +8,15 @@
final class TrezorTransport: TrezorTransportCallback {
static let shared = TrezorTransport()

private let bleManager = TrezorBLEManager.shared

Check warning on line 11 in Bitkit/Services/Trezor/TrezorTransport.swift

View workflow job for this annotation

GitHub Actions / Run Tests

stored property 'bleManager' of 'Sendable'-conforming class 'TrezorTransport' has non-sendable type 'TrezorBLEManager'; this is an error in the Swift 6 language mode

Check warning on line 11 in Bitkit/Services/Trezor/TrezorTransport.swift

View workflow job for this annotation

GitHub Actions / Run Integration Tests

stored property 'bleManager' of 'Sendable'-conforming class 'TrezorTransport' has non-sendable type 'TrezorBLEManager'; this is an error in the Swift 6 language mode
private let bridgeTransport = TrezorBridgeTransport.shared

Check warning on line 12 in Bitkit/Services/Trezor/TrezorTransport.swift

View workflow job for this annotation

GitHub Actions / Run Tests

stored property 'bridgeTransport' of 'Sendable'-conforming class 'TrezorTransport' has non-sendable type 'TrezorBridgeTransport'; this is an error in the Swift 6 language mode

Check warning on line 12 in Bitkit/Services/Trezor/TrezorTransport.swift

View workflow job for this annotation

GitHub Actions / Run Integration Tests

stored property 'bridgeTransport' of 'Sendable'-conforming class 'TrezorTransport' has non-sendable type 'TrezorBridgeTransport'; this is an error in the Swift 6 language mode

// MARK: - Pairing Code Handling

/// Subject to notify UI when pairing code is needed
let needsPairingCodePublisher = PassthroughSubject<Void, Never>()

Check warning on line 17 in Bitkit/Services/Trezor/TrezorTransport.swift

View workflow job for this annotation

GitHub Actions / Run Tests

stored property 'needsPairingCodePublisher' of 'Sendable'-conforming class 'TrezorTransport' has non-sendable type 'PassthroughSubject<Void, Never>'; this is an error in the Swift 6 language mode

Check warning on line 17 in Bitkit/Services/Trezor/TrezorTransport.swift

View workflow job for this annotation

GitHub Actions / Run Integration Tests

stored property 'needsPairingCodePublisher' of 'Sendable'-conforming class 'TrezorTransport' has non-sendable type 'PassthroughSubject<Void, Never>'; this is an error in the Swift 6 language mode

private var submittedPairingCode: String = ""

Check warning on line 19 in Bitkit/Services/Trezor/TrezorTransport.swift

View workflow job for this annotation

GitHub Actions / Run Tests

stored property 'submittedPairingCode' of 'Sendable'-conforming class 'TrezorTransport' is mutable; this is an error in the Swift 6 language mode

Check warning on line 19 in Bitkit/Services/Trezor/TrezorTransport.swift

View workflow job for this annotation

GitHub Actions / Run Integration Tests

stored property 'submittedPairingCode' of 'Sendable'-conforming class 'TrezorTransport' is mutable; this is an error in the Swift 6 language mode
private let pairingCodeLock = NSLock()

/// Timeout for pairing code entry (2 minutes)
Expand Down Expand Up @@ -198,6 +198,10 @@
submittedPairingCode = ""
pairingCodeLock.unlock()

// Drain any leftover signal from a prior (cancelled/submitted) pairing so this request
// doesn't return an empty code instantly and abort the handshake mid-pairing.
while pairingCodeSemaphore.wait(timeout: DispatchTime.now()) == .success {}

// Notify UI to show pairing code dialog
DispatchQueue.main.async {
self.needsPairingCodePublisher.send()
Expand Down
9 changes: 5 additions & 4 deletions Bitkit/ViewModels/SheetViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ enum SheetID: String, CaseIterable {
case tagFilter
case dateRangeSelector
case widgets
case hardwareIntro
case hardwareConnect
case hardwarePairing
case renameHardwareWallet
}
Expand All @@ -36,6 +36,7 @@ struct SheetConfiguration {

class SheetViewModel: ObservableObject {
@Published var activeSheetConfiguration: SheetConfiguration? = nil
@Published var hardwareConnectHandlesPairing = false

func showSheet(_ id: SheetID, data: Any? = nil) {
if isAnySheetOpen {
Expand Down Expand Up @@ -304,10 +305,10 @@ class SheetViewModel: ObservableObject {
}
}

var hardwareIntroSheetItem: HardwareIntroSheetItem? {
var hardwareConnectSheetItem: HardwareConnectSheetItem? {
get {
guard let config = activeSheetConfiguration, config.id == .hardwareIntro else { return nil }
return HardwareIntroSheetItem()
guard let config = activeSheetConfiguration, config.id == .hardwareConnect else { return nil }
return HardwareConnectSheetItem()
}
set {
if newValue == nil {
Expand Down
Loading
Loading