Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0eb1267
feat: add hw funding account lookup
ovitrif Jun 22, 2026
42ce34b
feat: fund spending from hardware wallet
ovitrif Jun 22, 2026
130a25e
feat: add spending amount hw screen
ovitrif Jun 22, 2026
06c602f
feat: add hw sign and signed screens
ovitrif Jun 22, 2026
be20234
feat: wire hw transfer flow
ovitrif Jun 22, 2026
8ddcc39
test: cover hw transfer view model
ovitrif Jun 22, 2026
3c4dcdf
test: add hw transfer journey
ovitrif Jun 22, 2026
7c1e138
chore: add hw transfer changelog
ovitrif Jun 22, 2026
79c970a
chore: rename changelog fragment
ovitrif Jun 22, 2026
455eb68
fix: track pending hw transfers
ovitrif Jun 22, 2026
c0a9e1c
fix: use configured trezor electrum
ovitrif Jun 22, 2026
526d53a
Merge branch 'master' into feat/hw-transfer-to-spending
ovitrif Jun 23, 2026
4dad88c
chore: group hw transfer screens
ovitrif Jun 23, 2026
5af487e
fix: match hw transfer figma visuals
ovitrif Jun 23, 2026
249ba1a
fix: reconnect known ble trezors
ovitrif Jun 23, 2026
ad7dc7a
fix: match hw reconnect copy
ovitrif Jun 23, 2026
dac6850
fix: refresh hw signing session
ovitrif Jun 23, 2026
d36cea3
chore: fix previews text wrap
ovitrif Jun 23, 2026
26ba2a2
chore: code style cleanup
ovitrif Jun 23, 2026
2ecf4d1
fix: harden hardware transfer flow
ovitrif Jun 23, 2026
d0865f6
refactor: move hw funding models
ovitrif Jun 23, 2026
cb1dc29
fix: harden trezor bridge signing
ovitrif Jun 23, 2026
4673701
refactor: aggregate hw wallet models
ovitrif Jun 23, 2026
56aa8f1
fix: harden hw transfer edge cases
ovitrif Jun 24, 2026
0ee223d
refactor: reuse fee fallback constant
ovitrif Jun 24, 2026
5778f44
refactor: move fee fallback default
ovitrif Jun 24, 2026
d9a5314
fix: show hw spending intro
ovitrif Jun 24, 2026
e8a5039
fix: handle hw signed effect
ovitrif Jun 24, 2026
a5d4dcb
refactor: simplify spending navigation
ovitrif Jun 24, 2026
7583336
fix: preserve reconnect cancellation
ovitrif Jun 24, 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 app/src/main/java/to/bitkit/data/HwWalletStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import to.bitkit.data.serializers.HwWalletDataSerializer
import to.bitkit.di.IoDispatcher
import to.bitkit.repositories.KnownDevice
import to.bitkit.models.KnownDevice
import javax.inject.Inject
import javax.inject.Singleton

Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/to/bitkit/data/dao/TransferDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.Flow
import to.bitkit.data.entities.TransferEntity

@Dao
@Suppress("TooManyFunctions")
interface TransferDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(transfer: TransferEntity)
Expand All @@ -35,6 +36,9 @@ interface TransferDao {
@Query("SELECT * FROM transfers WHERE id = :id LIMIT 1")
suspend fun getById(id: String): TransferEntity?

@Query("SELECT * FROM transfers WHERE fundingTxId = :fundingTxId LIMIT 1")
suspend fun getByFundingTxId(fundingTxId: String): TransferEntity?

@Query("UPDATE transfers SET isSettled = 1, settledAt = :settledAt WHERE id = :id")
suspend fun markSettled(id: String, settledAt: Long)

Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/to/bitkit/env/Env.kt
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,9 @@ object Defaults {
/** Recommended transaction base fee in sats */
const val recommendedBaseFee = 256u

/** Fallback fee percentage used when fee estimates are temporarily unavailable. */
const val fallbackFeePercent = 0.1

/**
* Minimum value in sats for an output. Outputs below the dust limit may not be processed because the fees
* required to include them in a block would be greater than the value of the transaction itself.
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/java/to/bitkit/ext/TrezorTransportType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package to.bitkit.ext

import com.synonym.bitkitcore.TrezorTransportType
import to.bitkit.models.TransportType

fun TrezorTransportType.toTransportType(): TransportType = when (this) {
TrezorTransportType.BLUETOOTH -> TransportType.BLUETOOTH
TrezorTransportType.USB -> TransportType.USB
}
98 changes: 98 additions & 0 deletions app/src/main/java/to/bitkit/models/HardwareWallet.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package to.bitkit.models

import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import com.synonym.bitkitcore.AccountType
import com.synonym.bitkitcore.Activity
import com.synonym.bitkitcore.AddressType
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.persistentSetOf
import kotlinx.serialization.Serializable

/** A paired hardware wallet tracked as a watch-only balance. */
@Stable
data class HwWallet(
val id: String,
val name: String,
val model: String?,
val transportType: TransportType,
val isConnected: Boolean,
val balanceSats: ULong,
val activities: ImmutableList<Activity>,
val fundingBalanceSats: ULong = balanceSats,
val deviceIds: ImmutableSet<String> = persistentSetOf(id),
)

/** Serializable per-device balance snapshot carried by [BalanceState]. */
@Immutable
@Serializable
data class HwWalletBalance(
val id: String,
val sats: ULong,
)

/** A newly detected inbound transaction to a watched hardware wallet. */
@Immutable
data class HwWalletReceivedTx(
val txid: String,
val sats: ULong,
)

sealed interface HwFundingAccount {
val vendor: HwWalletVendor
val xpub: String
val addressType: HwFundingAddressType
val accountType: AccountType
val balanceSats: ULong

data class Trezor(
override val xpub: String,
override val addressType: HwFundingAddressType,
override val balanceSats: ULong,
) : HwFundingAccount {
override val vendor: HwWalletVendor = HwWalletVendor.TREZOR
override val accountType: AccountType
get() = addressType.accountType
}
}

data class HwFundingTransaction(
val psbt: String,
val miningFeeSats: ULong,
val feeRate: Float,
val totalSpent: ULong,
val satsPerVByte: ULong,
)

data class HwFundingBroadcastResult(
val txId: String,
val miningFeeSats: ULong,
val feeRate: ULong,
val totalSpent: ULong,
Comment thread
ovitrif marked this conversation as resolved.
)

enum class HwWalletVendor {
TREZOR,
}
Comment thread
ovitrif marked this conversation as resolved.

enum class HwFundingAddressType(
val addressType: AddressType,
) {
LEGACY(AddressType.P2PKH),
NESTED_SEGWIT(AddressType.P2SH),
NATIVE_SEGWIT(AddressType.P2WPKH),
TAPROOT(AddressType.P2TR);

val settingsKey: String
get() = addressType.toSettingsString()

val accountType: AccountType
get() = addressType.toAccountType()

companion object {
val DEFAULT: HwFundingAddressType = entries.first { it.addressType == DEFAULT_ADDRESS_TYPE }
}
}

fun HwWallet.toBalance() = HwWalletBalance(id = id, sats = balanceSats)
39 changes: 0 additions & 39 deletions app/src/main/java/to/bitkit/models/HwWallet.kt

This file was deleted.

22 changes: 22 additions & 0 deletions app/src/main/java/to/bitkit/models/KnownDevice.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package to.bitkit.models

import androidx.compose.runtime.Immutable
import kotlinx.serialization.Serializable

@Serializable
@Immutable
data class KnownDevice(
val id: String,
val name: String?,
val path: String,
val transportType: TransportType,
val label: String?,
val model: String?,
val lastConnectedAt: Long,
/** Account-level extended public keys per address type. */
val xpubs: Map<String, String> = emptyMap(),
/** Bitkit-side funds label set by the user while pairing; null until renamed within Bitkit. */
val customLabel: String? = null,
/** Stable app-owned id for future wallet-scoped hardware activity metadata. */
val walletId: String = "",
)
20 changes: 20 additions & 0 deletions app/src/main/java/to/bitkit/repositories/ActivityRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,26 @@ class ActivityRepo @Inject constructor(
notifyActivitiesChanged()
}

suspend fun syncHardwareOnchainActivity(activity: OnchainActivity): Result<Unit> = withContext(bgDispatcher) {
runCatching {
val existing = coreService.activity.getOnchainActivityByTxId(activity.txId) ?: return@runCatching
val confirmTimestamp = existing.confirmTimestamp ?: activity.confirmTimestamp ?: activity.timestamp
.takeIf { activity.confirmed }
val updated = existing.copy(
confirmed = existing.confirmed || activity.confirmed,
confirmTimestamp = confirmTimestamp,
doesExist = if (activity.confirmed) true else existing.doesExist,
fee = if (existing.fee == 0uL && activity.fee > 0uL) activity.fee else existing.fee,
updatedAt = maxOf(existing.updatedAt ?: 0uL, activity.updatedAt ?: activity.timestamp),
)
if (updated == existing) return@runCatching
coreService.activity.update(existing.id, Activity.Onchain(updated))
notifyActivitiesChanged()
}.onFailure {
Logger.error("Failed to sync hardware activity '${activity.txId}'", it, context = TAG)
}
}

suspend fun handleOnchainTransactionReplaced(txid: String, conflicts: List<String>) {
coreService.activity.handleOnchainTransactionReplaced(txid, conflicts)
notifyActivitiesChanged()
Expand Down
Loading
Loading