Skip to content
Open
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
57 changes: 57 additions & 0 deletions app/src/main/java/to/bitkit/repositories/TrezorRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.synonym.bitkitcore.CoinSelection
import com.synonym.bitkitcore.ComposeOutput
import com.synonym.bitkitcore.ComposeParams
import com.synonym.bitkitcore.ComposeResult
import com.synonym.bitkitcore.EventListener
import com.synonym.bitkitcore.SingleAddressInfoResult
import com.synonym.bitkitcore.TransactionHistoryResult
import com.synonym.bitkitcore.TrezorAddressResponse
Expand All @@ -22,14 +23,19 @@ import com.synonym.bitkitcore.TrezorSignedTx
import com.synonym.bitkitcore.TrezorTransportType
import com.synonym.bitkitcore.WalletParams
import com.synonym.bitkitcore.WalletSelection
import com.synonym.bitkitcore.WatcherEvent
import com.synonym.bitkitcore.WatcherParams
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
Expand Down Expand Up @@ -73,6 +79,16 @@ class TrezorRepo @Inject constructor(
private val _state = MutableStateFlow(TrezorState())
val state = _state.asStateFlow()

private val _watcherEvents = MutableSharedFlow<Pair<String, WatcherEvent>>(extraBufferCapacity = 64)
val watcherEvents: SharedFlow<Pair<String, WatcherEvent>> = _watcherEvents.asSharedFlow()

private val eventBridge: EventListener = object : EventListener {
override fun onEvent(watcherId: String, event: WatcherEvent) {
TrezorDebugLog.log("WATCHER", "[$watcherId] ${event::class.simpleName}")
_watcherEvents.tryEmit(watcherId to event)
}
}

/**
* Flow indicating when a pairing code needs to be entered.
* UI should show a dialog when this emits true.
Expand Down Expand Up @@ -551,6 +567,47 @@ class TrezorRepo @Inject constructor(
}
}

suspend fun startWatcher(
watcherId: String,
extendedKey: String,
network: BitkitCoreNetwork,
gapLimit: UInt = 20u,
): Result<Unit> = withContext(ioDispatcher) {
runCatching {
val params = WatcherParams(
watcherId = watcherId,
extendedKey = extendedKey,
electrumUrl = electrumUrlForNetwork(network),
network = network,
accountType = null,
gapLimit = gapLimit,
)
trezorService.startWatcher(params, eventBridge)
TrezorDebugLog.log("WATCHER", "Started watcher '$watcherId' for '${extendedKey.take(12)}...'")
Logger.info("Started watcher '$watcherId'", context = TAG)
}.onFailure {
Logger.error("Start watcher failed", it, context = TAG)
_state.update { s -> s.copy(error = it.message) }
}
}

fun stopWatcher(watcherId: String): Result<Unit> = runCatching {
trezorService.stopWatcher(watcherId)
TrezorDebugLog.log("WATCHER", "Stopped watcher '$watcherId'")
Logger.info("Stopped watcher '$watcherId'", context = TAG)
}.onFailure {
Logger.error("Stop watcher failed", it, context = TAG)
_state.update { s -> s.copy(error = it.message) }
}

fun stopAllWatchers(): Result<Unit> = runCatching {
trezorService.stopAllWatchers()
TrezorDebugLog.log("WATCHER", "Stopped all watchers")
}.onFailure {
Logger.error("Stop all watchers failed", it, context = TAG)
_state.update { s -> s.copy(error = it.message) }
}

fun clearError() {
_state.update { it.copy(error = null) }
}
Expand Down
19 changes: 19 additions & 0 deletions app/src/main/java/to/bitkit/services/TrezorService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.synonym.bitkitcore.AccountInfoResult
import com.synonym.bitkitcore.AccountType
import com.synonym.bitkitcore.ComposeParams
import com.synonym.bitkitcore.ComposeResult
import com.synonym.bitkitcore.EventListener
import com.synonym.bitkitcore.SingleAddressInfoResult
import com.synonym.bitkitcore.TransactionHistoryResult
import com.synonym.bitkitcore.TrezorAddressResponse
Expand All @@ -19,11 +20,15 @@ import com.synonym.bitkitcore.TrezorSignedMessageResponse
import com.synonym.bitkitcore.TrezorSignedTx
import com.synonym.bitkitcore.TrezorVerifyMessageParams
import com.synonym.bitkitcore.WalletSelection
import com.synonym.bitkitcore.WatcherParams
import com.synonym.bitkitcore.onchainBroadcastRawTx
import com.synonym.bitkitcore.onchainComposeTransaction
import com.synonym.bitkitcore.onchainGetAccountInfo
import com.synonym.bitkitcore.onchainGetAddressInfo
import com.synonym.bitkitcore.onchainGetTransactionHistory
import com.synonym.bitkitcore.onchainStartWatcher
import com.synonym.bitkitcore.onchainStopAllWatchers
import com.synonym.bitkitcore.onchainStopWatcher
import com.synonym.bitkitcore.trezorClearCredentials
import com.synonym.bitkitcore.trezorConnect
import com.synonym.bitkitcore.trezorDisconnect
Expand Down Expand Up @@ -266,4 +271,18 @@ class TrezorService @Inject constructor(
)
}
}

suspend fun startWatcher(params: WatcherParams, listener: EventListener) {
ServiceQueue.CORE.background {
onchainStartWatcher(params = params, listener = listener)
}
}

fun stopWatcher(watcherId: String) {
onchainStopWatcher(watcherId = watcherId)
}

fun stopAllWatchers() {
onchainStopAllWatchers()
}
}
20 changes: 20 additions & 0 deletions app/src/main/java/to/bitkit/ui/screens/trezor/TrezorPreviewData.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import com.synonym.bitkitcore.TrezorSignedTx
import com.synonym.bitkitcore.TrezorTransportType
import com.synonym.bitkitcore.TxDirection
import com.synonym.bitkitcore.WalletBalance
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import to.bitkit.repositories.ConnectedTrezorDevice
import to.bitkit.repositories.KnownDevice
import to.bitkit.repositories.KnownDeviceTransportType
Expand Down Expand Up @@ -305,6 +307,24 @@ internal object TrezorPreviewData {
),
)

val uiStateWithActiveWatcher = TrezorUiState(
network = TrezorNetworkState(selectedNetwork = BitkitCoreNetwork.REGTEST),
watcher = TrezorWatcherState(
extendedKey = SAMPLE_XPUB,
activeWatcherId = "watcher-abc-123",
connectionStatus = WatcherConnectionStatus.CONNECTED,
balance = sampleWalletBalance,
transactions = sampleHistoryTransactions.toImmutableList(),
transactionCount = 2u,
blockHeight = 850_000u,
accountType = AccountType.NATIVE_SEGWIT,
events = persistentListOf(
"Watcher started: watcher-abc-123",
"TX update: 2 txs, balance=155000 sats",
),
),
)

val uiStateBroadcast = TrezorUiState(
network = TrezorNetworkState(selectedNetwork = BitkitCoreNetwork.REGTEST),
send = TrezorSendState(
Expand Down
26 changes: 24 additions & 2 deletions app/src/main/java/to/bitkit/ui/screens/trezor/TrezorScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@ private fun TrezorScreenContent(
onResetSend = viewModel::resetSendFlow,
onTxHistoryInputChange = viewModel::setTxHistoryInput,
onLookupTxHistory = viewModel::lookupTransactionHistory,
onWatcherExtendedKeyChange = viewModel::setWatcherExtendedKey,
onWatcherGapLimitChange = viewModel::setWatcherGapLimit,
onStartWatcher = viewModel::startWatcher,
onStopWatcher = viewModel::stopWatcher,
onPopulateWatcherFromXpub = viewModel::populateWatcherFromXpub,
permissionsGranted = permissionsState.allPermissionsGranted,
)
}
Expand Down Expand Up @@ -207,6 +212,11 @@ private fun Content(
onResetSend: () -> Unit = {},
onTxHistoryInputChange: (String) -> Unit = {},
onLookupTxHistory: () -> Unit = {},
onWatcherExtendedKeyChange: (String) -> Unit = {},
onWatcherGapLimitChange: (String) -> Unit = {},
onStartWatcher: () -> Unit = {},
onStopWatcher: () -> Unit = {},
onPopulateWatcherFromXpub: () -> Unit = {},
permissionsGranted: Boolean = true,
) {
Column(
Expand Down Expand Up @@ -419,14 +429,26 @@ private fun Content(
onResetSend = onResetSend,
)

// Transaction History (always visible, no device needed)
// Transaction History (one-shot snapshot, no device needed)
VerticalSpacer(32.dp)
TransactionHistorySection(
uiState = uiState,
onInputChange = onTxHistoryInputChange,
onLookup = onLookupTxHistory,
)

// Event Watcher (live subscription, no device needed)
VerticalSpacer(32.dp)
WatcherSection(
uiState = uiState,
trezorState = trezorState,
onExtendedKeyChange = onWatcherExtendedKeyChange,
onGapLimitChange = onWatcherGapLimitChange,
onStartWatcher = onStartWatcher,
onStopWatcher = onStopWatcher,
onPopulateFromXpub = onPopulateWatcherFromXpub,
)

// Debug Log Window
DebugLogSection()
}
Expand Down Expand Up @@ -668,7 +690,7 @@ private fun StatusRow(trezorState: TrezorState) {
}

@Composable
private fun StatusBadge(text: String, color: Color) {
internal fun StatusBadge(text: String, color: Color) {
Caption(
text = text,
color = color,
Expand Down
Loading
Loading