Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ android {
buildConfigField("boolean", "TREZOR_BRIDGE", trezorBridgeEnv)
buildConfigField("String", "TREZOR_BRIDGE_URL", "\"$trezorBridgeUrlEnv\"")
buildConfigField("boolean", "GEO", System.getenv("GEO")?.toBoolean()?.toString() ?: "true")
buildConfigField("boolean", "PAYKIT_UI_DISABLED", System.getenv("PAYKIT_UI_DISABLED")?.toBoolean()?.toString() ?: "false")
Comment thread
ben-kaufman marked this conversation as resolved.
Outdated
buildConfigField("String", "LOCALES", "\"${bcp47Locales.joinToString(",")}\"")
}

Expand Down
34 changes: 33 additions & 1 deletion app/src/main/java/to/bitkit/data/SettingsStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import to.bitkit.models.SettingsBackupV1
import to.bitkit.models.Suggestion
import to.bitkit.models.TransactionSpeed
import to.bitkit.utils.Logger
import to.bitkit.utils.PaykitFeatureFlags
import javax.inject.Inject
import javax.inject.Singleton

Expand All @@ -38,7 +39,12 @@ class SettingsStore @Inject constructor(

suspend fun restoreFromBackup(payload: SettingsBackupV1) =
runCatching {
val data = payload.settings.resetPin()
val restored = payload.settings.resetPin()
val data = if (PaykitFeatureFlags.isUiEnabled(restored) || !restored.hasPaykitState()) {
restored
} else {
restored.paykitDisabled(markPublicCleanupPending = restored.hasPublicPaykitPublicationState())
}
store.updateData { data }

val monitored = data.addressTypesToMonitor
Expand Down Expand Up @@ -100,9 +106,11 @@ data class SettingsData(
val hasSeenShopIntro: Boolean = false,
val hasSeenProfileIntro: Boolean = false,
val hasSeenContactsIntro: Boolean = false,
val isPaykitEnabled: Boolean = false,
val hasConfirmedPublicPaykitEndpoints: Boolean = false,
val sharesPublicPaykitEndpoints: Boolean = false,
val sharesPrivatePaykitEndpoints: Boolean = false,
val publicPaykitCleanupPending: Boolean = false,
val publicPaykitLightningEnabled: Boolean = true,
val publicPaykitOnchainEnabled: Boolean = true,
val publicPaykitBolt11: String = "",
Expand Down Expand Up @@ -148,3 +156,27 @@ fun SettingsData.resetPin() = this.copy(
isPinForPaymentsEnabled = false,
isBiometricEnabled = false,
)

fun SettingsData.hasPublicPaykitPublicationState(): Boolean =
hasConfirmedPublicPaykitEndpoints ||
sharesPublicPaykitEndpoints ||
publicPaykitCleanupPending ||
publicPaykitBolt11.isNotBlank() ||
publicPaykitBolt11PaymentHash.isNotBlank() ||
publicPaykitBolt11ExpiresAtMillis > 0L

fun SettingsData.hasPaykitState(): Boolean =
isPaykitEnabled ||
hasPublicPaykitPublicationState() ||
sharesPrivatePaykitEndpoints

fun SettingsData.paykitDisabled(markPublicCleanupPending: Boolean = false) = copy(
isPaykitEnabled = false,
hasConfirmedPublicPaykitEndpoints = false,
sharesPublicPaykitEndpoints = false,
sharesPrivatePaykitEndpoints = false,
publicPaykitCleanupPending = publicPaykitCleanupPending || markPublicCleanupPending,
publicPaykitBolt11 = "",
publicPaykitBolt11PaymentHash = "",
publicPaykitBolt11ExpiresAtMillis = 0,
)
12 changes: 12 additions & 0 deletions app/src/main/java/to/bitkit/models/AddressType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,18 @@ fun AddressType.toDerivationPath(
}
}

fun AddressType.toAccountDerivationPath(network: Network = Env.network): String {
val coinType = if (network == Network.BITCOIN) 0 else 1

return when (this) {
AddressType.P2TR -> "m/86'/$coinType'/0'"
AddressType.P2WPKH -> "m/84'/$coinType'/0'"
AddressType.P2SH -> "m/49'/$coinType'/0'"
AddressType.P2PKH -> "m/44'/$coinType'/0'"
else -> ""
}
}

fun AddressType.toSettingsString(): String = when (this) {
AddressType.P2TR -> "taproot"
AddressType.P2WPKH -> "nativeSegwit"
Expand Down
26 changes: 11 additions & 15 deletions app/src/main/java/to/bitkit/repositories/BackupRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import to.bitkit.models.WidgetsBackupV1
import to.bitkit.services.LightningService
import to.bitkit.ui.shared.toast.ToastEventBus
import to.bitkit.utils.Logger
import to.bitkit.utils.PaykitFeatureFlags
import to.bitkit.utils.jsonLogOf
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
Expand Down Expand Up @@ -591,7 +592,7 @@ class BackupRepo @Inject constructor(
}
performRestore(BackupCategory.WALLET) { dataBytes ->
restoreWalletBackup(dataBytes)
}
}.getOrThrow()
performRestore(BackupCategory.BLOCKTANK) { dataBytes ->
val parsed = json.decodeFromString<BlocktankBackupV1>(String(dataBytes))
blocktankRepo.restoreFromBackup(parsed)
Expand Down Expand Up @@ -621,20 +622,15 @@ class BackupRepo @Inject constructor(
if (!parsed.privatePaykitHighestReservedReceiveIndexByAddressType.isNullOrEmpty()) {
cacheStore.update { it.copy(onchainAddress = "", bip21 = "") }
}
privatePaykitAddressReservationRepo.get()
.restoreBackup(parsed.privatePaykitHighestReservedReceiveIndexByAddressType)
.onFailure {
Logger.warn("Failed to restore private Paykit reservations", it, context = TAG)
}
privatePaykitRepo.get().restoreBackup(parsed.privatePaykitContactLinks)
.onFailure {
Logger.warn("Failed to restore private Paykit contact links", it, context = TAG)
}
privatePaykitAddressReservationRepo.get()
.reconcileReservedIndexesWithLdk()
.onFailure {
Logger.warn("Failed to reconcile restored private Paykit reservations", it, context = TAG)
}
val addressReservationRepo = privatePaykitAddressReservationRepo.get()
addressReservationRepo.restoreBackup(parsed.privatePaykitHighestReservedReceiveIndexByAddressType).getOrThrow()
val privateRepo = privatePaykitRepo.get()
privateRepo.restoreBackup(parsed.privatePaykitContactLinks).getOrThrow()
val isPaykitEnabled = PaykitFeatureFlags.isUiEnabled(settingsStore.data.first())
if (!isPaykitEnabled && !parsed.privatePaykitContactLinks.isNullOrEmpty()) {
privateRepo.setContactSharingCleanupPending(true).getOrThrow()
}
addressReservationRepo.reconcileReservedIndexesWithLdk().getOrThrow()
Logger.debug("Restored ${parsed.transfers.size} transfers", context = TAG)
return parsed.createdAt
}
Expand Down
14 changes: 9 additions & 5 deletions app/src/main/java/to/bitkit/repositories/PubkyRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
Expand All @@ -30,6 +31,7 @@ import kotlinx.coroutines.withContext
import to.bitkit.data.PrivatePaykitCacheStore
import to.bitkit.data.PubkyStore
import to.bitkit.data.SettingsStore
import to.bitkit.data.hasPublicPaykitPublicationState
import to.bitkit.data.keychain.Keychain
import to.bitkit.di.IoDispatcher
import to.bitkit.env.Env
Expand Down Expand Up @@ -983,7 +985,8 @@ class PubkyRepo @Inject constructor(
// region Sign out

suspend fun signOut(): Result<Unit> {
removeBitkitPaymentEndpoints()
val hadPublicPaykitState = settingsStore.data.first().hasPublicPaykitPublicationState()
val endpointCleanupResult = removeBitkitPaymentEndpoints()
.onFailure { Logger.warn("Failed to remove Bitkit payment endpoints", it, context = TAG) }

val result = runCatching {
Expand All @@ -993,7 +996,7 @@ class PubkyRepo @Inject constructor(
withContext(ioDispatcher) { pubkyService.forceSignOut() }
}

clearLocalState()
clearLocalState(publicPaykitCleanupPending = endpointCleanupResult.isFailure && hadPublicPaykitState)
return result
}

Expand Down Expand Up @@ -1074,23 +1077,24 @@ class PubkyRepo @Inject constructor(
_contactsLoadVersion.update { it + 1 }
}

private suspend fun clearLocalState() = withContext(ioDispatcher) {
private suspend fun clearLocalState(publicPaykitCleanupPending: Boolean = false) = withContext(ioDispatcher) {
runCatching { keychain.delete(Keychain.Key.PAYKIT_SESSION.name) }
runCatching { keychain.delete(Keychain.Key.PUBKY_SECRET_KEY.name) }
runCatching { clearPublicPaykitSharingState() }
runCatching { clearPublicPaykitSharingState(publicPaykitCleanupPending) }
.onFailure { Logger.warn("Failed to clear public Paykit sharing state", it, context = TAG) }
notifyBackupStateChanged()
clearAuthenticatedState()
}

private suspend fun clearPublicPaykitSharingState() {
private suspend fun clearPublicPaykitSharingState(publicPaykitCleanupPending: Boolean) {
settingsStore.update {
it.copy(
hasConfirmedPublicPaykitEndpoints = false,
sharesPublicPaykitEndpoints = false,
publicPaykitBolt11 = "",
publicPaykitBolt11PaymentHash = "",
publicPaykitBolt11ExpiresAtMillis = 0,
publicPaykitCleanupPending = publicPaykitCleanupPending,
)
}
}
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/to/bitkit/repositories/PublicPaykitRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,13 @@ class PublicPaykitRepo @Inject constructor(
runCatching {
if (!publish) {
removePublishedEndpoints()
settingsStore.update { it.copy(publicPaykitCleanupPending = false) }
return@runCatching
}

val desired = buildWalletEndpoints(refresh = true)
applyPublishedEndpoints(desired)
settingsStore.update { it.copy(publicPaykitCleanupPending = false) }
}
}

Expand All @@ -198,6 +200,7 @@ class PublicPaykitRepo @Inject constructor(
requireEndpoint = requireEndpoint,
)
applyPublishedEndpoints(desired)
settingsStore.update { it.copy(publicPaykitCleanupPending = false) }
}
}

Expand Down
40 changes: 39 additions & 1 deletion app/src/main/java/to/bitkit/repositories/WalletRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import to.bitkit.data.CacheStore
import to.bitkit.data.SettingsStore
import to.bitkit.data.keychain.Keychain
import to.bitkit.di.BgDispatcher
import to.bitkit.env.Env
import to.bitkit.ext.filterOpen
import to.bitkit.ext.nowTimestamp
import to.bitkit.ext.toHex
Expand All @@ -33,12 +34,15 @@ import to.bitkit.models.AddressModel
import to.bitkit.models.BalanceState
import to.bitkit.models.DEFAULT_ADDRESS_TYPE_STRING
import to.bitkit.models.msatFloorOf
import to.bitkit.models.toAccountDerivationPath
import to.bitkit.models.toDerivationPath
import to.bitkit.services.AddressDerivationInfo
import to.bitkit.services.CoreService
import to.bitkit.usecases.DeriveBalanceStateUseCase
import to.bitkit.usecases.WipeWalletUseCase
import to.bitkit.utils.Bip21Utils
import to.bitkit.utils.Logger
import to.bitkit.utils.ServiceError
import to.bitkit.utils.measured
import javax.inject.Inject
import javax.inject.Singleton
Expand Down Expand Up @@ -401,7 +405,14 @@ class WalletRepo @Inject constructor(
isChange = isChange,
startIndex = startIndex,
count = count,
).getOrThrow()
).getOrElse {
deriveAddressInfosFromMnemonic(
addressType = addressType,
isChange = isChange,
startIndex = startIndex,
count = count,
)
}

val addresses = result.map { address ->
AddressModel(
Expand All @@ -417,6 +428,33 @@ class WalletRepo @Inject constructor(
}
}

private suspend fun deriveAddressInfosFromMnemonic(
addressType: AddressType,
isChange: Boolean,
startIndex: Int,
count: Int,
): List<AddressDerivationInfo> {
val mnemonic = keychain.loadString(Keychain.Key.BIP39_MNEMONIC.name)
?: throw ServiceError.MnemonicNotFound()
val passphrase = keychain.loadString(Keychain.Key.BIP39_PASSPHRASE.name)
val baseDerivationPath = addressType.toAccountDerivationPath()

return coreService.onchain.deriveBitcoinAddresses(
mnemonicPhrase = mnemonic,
derivationPathStr = baseDerivationPath,
network = Env.network,
bip39Passphrase = passphrase,
isChange = isChange,
startIndex = startIndex.toUInt(),
count = count.toUInt(),
).addresses.mapIndexed { offset, address ->
AddressDerivationInfo(
address = address.address,
index = startIndex + offset,
)
}
}

fun getBolt11(): String = _walletState.value.bolt11

suspend fun setBolt11(bolt11: String) {
Expand Down
Loading
Loading