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
73 changes: 73 additions & 0 deletions app/src/main/java/to/bitkit/repositories/WalletRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package to.bitkit.repositories

import androidx.compose.runtime.Immutable
import com.synonym.bitkitcore.AddressType
import com.synonym.bitkitcore.LegacyRnCloseRecoveryScanResult
import com.synonym.bitkitcore.LegacyRnCloseRecoverySweepPreview
import com.synonym.bitkitcore.PreActivityMetadata
import com.synonym.bitkitcore.Scanner
import kotlinx.collections.immutable.ImmutableList
Expand Down Expand Up @@ -114,6 +116,77 @@ class WalletRepo @Inject constructor(
}
}

suspend fun scanLegacyRnNativeSegwitRecoveryFunds(
indexLimit: UInt,
): Result<LegacyRnCloseRecoveryScanResult> = withContext(bgDispatcher) {
runCatching {
val (mnemonic, passphrase) = recoveryWalletCredentials()
val electrumUrl = settingsStore.data.first().electrumServer

coreService.onchain.scanLegacyRnNativeSegwitRecoveryFunds(
mnemonicPhrase = mnemonic,
network = Env.network,
electrumUrl = electrumUrl,
indexLimit = indexLimit,
bip39Passphrase = passphrase,
)
}.onFailure {
Logger.error("Legacy RN recovery scan failed", it, context = TAG)
}
}

suspend fun prepareLegacyRnNativeSegwitRecoverySweep(
indexLimit: UInt,
feeRateSatsPerVbyte: UInt?,
): Result<LegacyRnCloseRecoverySweepPreview> = withContext(bgDispatcher) {
runCatching {
val (mnemonic, passphrase) = recoveryWalletCredentials()
val electrumUrl = settingsStore.data.first().electrumServer
val destinationAddress = recoverySweepDestinationAddress()

coreService.onchain.prepareLegacyRnNativeSegwitRecoverySweep(
mnemonicPhrase = mnemonic,
network = Env.network,
electrumUrl = electrumUrl,
destinationAddress = destinationAddress,
feeRateSatsPerVbyte = feeRateSatsPerVbyte,
indexLimit = indexLimit,
bip39Passphrase = passphrase,
)
}.onFailure {
Logger.error("Legacy RN recovery sweep prepare failed", it, context = TAG)
}
}

suspend fun broadcastLegacyRnNativeSegwitRecoverySweep(txHex: String): Result<String> = withContext(bgDispatcher) {
runCatching {
val electrumUrl = settingsStore.data.first().electrumServer
val txid = coreService.onchain.broadcastRawTx(serializedTx = txHex, electrumUrl = electrumUrl)
syncNodeAndWallet(SyncSource.MANUAL).onFailure {
Logger.warn("Legacy RN recovery post-broadcast sync failed", it, context = TAG)
}
txid
}.onFailure {
Logger.error("Legacy RN recovery sweep broadcast failed", it, context = TAG)
}
}

private fun recoveryWalletCredentials(): Pair<String, String?> {
val mnemonic = keychain.loadString(Keychain.Key.BIP39_MNEMONIC.name)
?: throw ServiceError.MnemonicNotFound()
val passphrase = keychain.loadString(Keychain.Key.BIP39_PASSPHRASE.name)
return mnemonic to passphrase
}

private suspend fun recoverySweepDestinationAddress(): String {
val currentAddress = getOnchainAddress()
if (currentAddress.isNotBlank()) return currentAddress

return newAddress().getOrThrow().also {
require(it.isNotBlank()) { "Destination address unavailable" }
}
}

suspend fun refreshBip21(): Result<Unit> = withContext(bgDispatcher) {
Logger.debug("Refreshing bip21", context = TAG)

Expand Down
55 changes: 55 additions & 0 deletions app/src/main/java/to/bitkit/services/CoreService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import com.synonym.bitkitcore.IBtEstimateFeeResponse2
import com.synonym.bitkitcore.IBtInfo
import com.synonym.bitkitcore.IBtOrder
import com.synonym.bitkitcore.IcJitEntry
import com.synonym.bitkitcore.LegacyRnCloseRecoveryScanResult
import com.synonym.bitkitcore.LegacyRnCloseRecoverySweepPreview
import com.synonym.bitkitcore.LightningActivity
import com.synonym.bitkitcore.OnchainActivity
import com.synonym.bitkitcore.PaymentState
Expand All @@ -40,10 +42,13 @@ import com.synonym.bitkitcore.getOrders
import com.synonym.bitkitcore.getTags
import com.synonym.bitkitcore.initDb
import com.synonym.bitkitcore.insertActivity
import com.synonym.bitkitcore.onchainBroadcastRawTx
import com.synonym.bitkitcore.openChannel
import com.synonym.bitkitcore.prepareLegacyRnNativeSegwitRecoverySweep
import com.synonym.bitkitcore.refreshActiveCjitEntries
import com.synonym.bitkitcore.refreshActiveOrders
import com.synonym.bitkitcore.removeTags
import com.synonym.bitkitcore.scanLegacyRnNativeSegwitRecoveryFunds
import com.synonym.bitkitcore.updateActivity
import com.synonym.bitkitcore.updateBlocktankUrl
import com.synonym.bitkitcore.upsertActivities
Expand Down Expand Up @@ -1810,6 +1815,56 @@ class OnchainService {
}
}

suspend fun scanLegacyRnNativeSegwitRecoveryFunds(
mnemonicPhrase: String,
network: Network?,
electrumUrl: String,
indexLimit: UInt,
bip39Passphrase: String?,
): LegacyRnCloseRecoveryScanResult {
return ServiceQueue.CORE.background {
scanLegacyRnNativeSegwitRecoveryFunds(
mnemonicPhrase = mnemonicPhrase,
network = network?.toCoreNetwork(),
electrumUrl = electrumUrl,
indexLimit = indexLimit,
bip39Passphrase = bip39Passphrase,
)
}
}

@Suppress("LongParameterList")
suspend fun prepareLegacyRnNativeSegwitRecoverySweep(
mnemonicPhrase: String,
network: Network?,
electrumUrl: String,
destinationAddress: String,
feeRateSatsPerVbyte: UInt?,
indexLimit: UInt,
bip39Passphrase: String?,
): LegacyRnCloseRecoverySweepPreview {
return ServiceQueue.CORE.background {
prepareLegacyRnNativeSegwitRecoverySweep(
mnemonicPhrase = mnemonicPhrase,
network = network?.toCoreNetwork(),
electrumUrl = electrumUrl,
destinationAddress = destinationAddress,
feeRateSatsPerVbyte = feeRateSatsPerVbyte,
indexLimit = indexLimit,
bip39Passphrase = bip39Passphrase,
)
}
}

suspend fun broadcastRawTx(
serializedTx: String,
electrumUrl: String,
): String {
return ServiceQueue.CORE.background {
onchainBroadcastRawTx(serializedTx = serializedTx, electrumUrl = electrumUrl)
}
}

suspend fun derivePrivateKey(
mnemonicPhrase: String,
derivationPathStr: String?,
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/java/to/bitkit/ui/ContentView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ import to.bitkit.ui.screens.recovery.RecoveryModeScreen
import to.bitkit.ui.screens.settings.DevSettingsScreen
import to.bitkit.ui.screens.settings.FeeSettingsScreen
import to.bitkit.ui.screens.settings.LdkDebugScreen
import to.bitkit.ui.screens.settings.LegacyRnRecoveryScreen
import to.bitkit.ui.screens.settings.ProbingToolScreen
import to.bitkit.ui.screens.settings.VssDebugScreen
import to.bitkit.ui.screens.shop.ShopIntroScreen
Expand Down Expand Up @@ -955,6 +956,9 @@ private fun NavGraphBuilder.settings(
composableWithDefaultTransitions<Routes.DevSettings> {
DevSettingsScreen(navController)
}
composableWithDefaultTransitions<Routes.LegacyRnRecovery> {
LegacyRnRecoveryScreen(navController)
}
composableWithDefaultTransitions<Routes.Trezor> {
TrezorScreen(navController)
}
Expand Down Expand Up @@ -1946,6 +1950,9 @@ sealed interface Routes {
@Serializable
data object DevSettings : Routes

@Serializable
data object LegacyRnRecovery : Routes

@Serializable
data object LdkDebug : Routes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ fun DevSettingsScreen(
SettingsButtonRow("VSS") { navController.navigateTo(Routes.VssDebug) }
SettingsButtonRow("Probing Tool") { navController.navigateTo(Routes.ProbingTool) }

SectionHeader("RECOVERY")
SettingsButtonRow("Legacy Close Recovery") { navController.navigateTo(Routes.LegacyRnRecovery) }

if (PaykitFeatureFlags.isUiAvailable) {
SectionHeader("PAYKIT")
SettingsSwitchRow(
Expand Down
Loading
Loading