Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
46bb276
feat(trezor): add hidden-wallet passphrase selection and on-chain wat…
coreyphillips Jun 1, 2026
5953f53
fix(trezor): make event watcher usable without a connected device
coreyphillips Jun 1, 2026
9283ad9
Merge branch 'master' into feat/trezor-hidden-wallet-and-watcher
ovitrif Jun 8, 2026
d44202a
fix(trezor): align watcher follow-up behavior
ovitrif Jun 9, 2026
6772be8
chore: add trezor changelog fragment
ovitrif Jun 9, 2026
00da2de
refactor(trezor): reuse shared UI components in dev screens
coreyphillips Jun 10, 2026
ba10de0
fix(trezor): stop watchers on dashboard dismiss and key tx rows by txid
coreyphillips Jun 10, 2026
34ac7a4
Merge branch 'master' into feat/trezor-hidden-wallet-and-watcher
coreyphillips Jun 10, 2026
9505677
fix(trezor): reset watcher input on dismiss and restart watcher on ac…
coreyphillips Jun 11, 2026
ac289c6
fix(trezor): treat watcher stop during native startup as cancellatio…
coreyphillips Jun 11, 2026
04b2305
fix(trezor): keep watcher alive across connect/disconnect and quarant…
coreyphillips Jun 12, 2026
9880030
Merge branch 'master' into feat/trezor-hidden-wallet-and-watcher
ovitrif Jun 15, 2026
95512e3
Merge branch 'master' into feat/trezor-hidden-wallet-and-watcher
ovitrif Jun 16, 2026
71d1a11
Merge branch 'master' into feat/trezor-hidden-wallet-and-watcher
ovitrif Jun 17, 2026
0177a8b
fix(trezor): clear stale wallet state on disconnect failures
ovitrif Jun 17, 2026
b7ac927
Merge branch 'master' into feat/trezor-hidden-wallet-and-watcher
ovitrif Jun 19, 2026
e1a50ba
Merge branch 'master' into feat/trezor-hidden-wallet-and-watcher
ovitrif Jun 19, 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.1.64;
version = 0.1.66;
};
};
96E20CD22CB6D91A00C24149 /* XCRemoteSwiftPackageReference "CodeScanner" */ = {
Expand Down

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

18 changes: 18 additions & 0 deletions Bitkit/Components/Trezor/TrezorAccountTypeSelector.swift
Comment thread
jvsena42 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import SwiftUI

struct TrezorAccountTypeSelector: View {
@Binding var selection: TrezorAccountTypeSelection
var title: String = "Account Type"

var body: some View {
Comment thread
jvsena42 marked this conversation as resolved.
VStack(alignment: .leading, spacing: 8) {
CaptionMText(title)

SegmentedControl(selectedTab: $selection, tabs: TrezorAccountTypeSelection.allCases)

FootnoteText(selection.subtitle)
}
.frame(maxWidth: .infinity, alignment: .leading)
.accessibilityIdentifier("TrezorAccountTypeSelector")
}
}
37 changes: 29 additions & 8 deletions Bitkit/Components/Trezor/TrezorPinPad.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ struct TrezorPinPad: View {
/// Current PIN being entered
@Binding var pin: String

/// Maximum PIN length
var maxLength: Int = 9
/// Maximum PIN length. Trezor PINs can be up to 50 digits.
var maxLength: Int = 50

/// Number of entered-digit dots to render per row before wrapping.
private let dotsPerRow = 9

/// PIN pad layout (positions map to device keypad)
/// The Trezor shows scrambled numbers, we show only position dots
Expand All @@ -19,12 +22,30 @@ struct TrezorPinPad: View {

var body: some View {
VStack(spacing: 16) {
// PIN display
HStack(spacing: 12) {
ForEach(0 ..< maxLength, id: \.self) { index in
Circle()
.fill(index < pin.count ? Color.white : Color.white.opacity(0.3))
.frame(width: 12, height: 12)
// PIN display — one dot per entered digit, wrapping across rows so long
// PINs (Trezor allows up to 50 digits) don't overflow a single line.
VStack(spacing: 8) {
if pin.isEmpty {
// Placeholder row so the layout doesn't collapse before entry.
HStack(spacing: 12) {
ForEach(0 ..< dotsPerRow, id: \.self) { _ in
Circle()
.fill(Color.white.opacity(0.3))
.frame(width: 12, height: 12)
}
}
} else {
let rowCount = (pin.count + dotsPerRow - 1) / dotsPerRow
ForEach(0 ..< rowCount, id: \.self) { row in
let dotsInRow = min(dotsPerRow, pin.count - row * dotsPerRow)
HStack(spacing: 12) {
ForEach(0 ..< dotsInRow, id: \.self) { _ in
Circle()
.fill(Color.white)
.frame(width: 12, height: 12)
}
}
}
}
}
.padding(.bottom, 24)
Expand Down
39 changes: 39 additions & 0 deletions Bitkit/Services/Trezor/TrezorEventListener.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import BitkitCore
import Foundation

/// Bridges bitkit-core's `EventListener` callback (invoked on a background thread by the
/// Rust watcher loop) onto the main actor so the ViewModel can update `@Observable` state.
///
/// Mirrors bitkit-android's `eventBridge` in `TrezorRepo`.
final class TrezorEventListener: EventListener, @unchecked Sendable {
/// Forwards `(watcherId, event)` to a consumer on the main actor.
private let onEventHandler: @MainActor (String, WatcherEvent) -> Void

init(onEvent: @escaping @MainActor (String, WatcherEvent) -> Void) {
onEventHandler = onEvent
}

func onEvent(watcherId: String, event: WatcherEvent) {
let handler = onEventHandler
Task { @MainActor in
TrezorDebugLog.shared.log("[WATCHER] [\(watcherId)] \(event.logLabel)")
handler(watcherId, event)
}
}
}

extension WatcherEvent {
/// Short label for the debug log.
var logLabel: String {
switch self {
case .transactionsChanged:
return "transactionsChanged"
case let .error(message):
return "error: \(message)"
case let .disconnected(message):
return "disconnected: \(message)"
case .reconnected:
return "reconnected"
}
}
}
43 changes: 39 additions & 4 deletions Bitkit/Services/Trezor/TrezorService.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import BitkitCore
import Foundation

/// Watcher-related service calls, extracted as a protocol so unit tests can
/// substitute a mock (mirrors bitkit-android's mocked TrezorRepo in TrezorViewModelTest).
protocol TrezorWatcherServicing {
func startWatcher(params: WatcherParams, listener: EventListener) async throws
func stopWatcher(watcherId: String) throws
func stopAllWatchers()
}

extension TrezorService: TrezorWatcherServicing {}

/// Service layer wrapper for Trezor FFI functions
/// All operations run on ServiceQueue.background(.core) to ensure thread safety
class TrezorService {
Expand Down Expand Up @@ -65,13 +75,17 @@ class TrezorService {

// MARK: - Connection Management

/// Connect to a Trezor device by its ID
/// - Parameter deviceId: The device identifier (path)
/// Connect to a Trezor device by its ID, opening the wallet given by `selection`.
/// On THP devices (Safe 5/7) the passphrase is bound to the session at creation, so
/// it is supplied per-connect rather than cached between calls.
/// - Parameters:
/// - deviceId: The device identifier (path)
/// - selection: Which wallet to open (standard / hidden / on-device passphrase)
/// - Returns: Device features after successful connection
func connect(deviceId: String) async throws -> TrezorFeatures {
func connect(deviceId: String, selection: WalletSelection) async throws -> TrezorFeatures {
try await ServiceQueue.background(.core) { [self] in
ensureCallbacksRegistered()
return try await trezorConnect(deviceId: deviceId, selection: .standard)
return try await trezorConnect(deviceId: deviceId, selection: selection)
}
}

Expand Down Expand Up @@ -268,6 +282,27 @@ class TrezorService {
}
}

// MARK: - Event Watcher (No Device Required)

/// Start watching an extended public key for on-chain transaction activity.
/// Events are delivered to `listener` until the watcher is stopped.
/// Does NOT require a connected Trezor device — it subscribes to Electrum directly.
func startWatcher(params: WatcherParams, listener: EventListener) async throws {
try await ServiceQueue.background(.core) {
try await onchainStartWatcher(params: params, listener: listener)
}
}

/// Stop a specific watcher by its id.
func stopWatcher(watcherId: String) throws {
try onchainStopWatcher(watcherId: watcherId)
}

/// Stop all active watchers.
func stopAllWatchers() {
Comment thread
jvsena42 marked this conversation as resolved.
onchainStopAllWatchers()
}

// MARK: - Helpers

/// Convert TrezorCoinType to the Network enum used by onchain FFI functions
Expand Down
Loading
Loading