Skip to content

Commit

Permalink
Add screen to spend expired swap-ins (#564)
Browse files Browse the repository at this point in the history
Funds that have not been swapped after 6 months can be spent
unilaterally by the wallet to any address.

---------

Co-authored-by: Robbie Hanson <304604+robbiehanson@users.noreply.github.com>
  • Loading branch information
dpad85 and robbiehanson committed Jun 6, 2024
1 parent 5ff7b06 commit f629cb2
Show file tree
Hide file tree
Showing 48 changed files with 1,714 additions and 358 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ import fr.acinq.phoenix.android.settings.fees.AdvancedIncomingFeePolicy
import fr.acinq.phoenix.android.settings.fees.LiquidityPolicyView
import fr.acinq.phoenix.android.payments.liquidity.RequestLiquidityView
import fr.acinq.phoenix.android.settings.walletinfo.FinalWalletInfo
import fr.acinq.phoenix.android.settings.walletinfo.SendSwapInRefundView
import fr.acinq.phoenix.android.settings.walletinfo.SwapInAddresses
import fr.acinq.phoenix.android.settings.walletinfo.SwapInSignerView
import fr.acinq.phoenix.android.settings.walletinfo.SwapInWallet
Expand Down Expand Up @@ -411,6 +412,7 @@ fun AppView(
onBackClick = { navController.popBackStack() },
onViewChannelPolicyClick = { navController.navigate(Screen.LiquidityPolicy.route) },
onAdvancedClick = { navController.navigate(Screen.WalletInfo.SwapInSigner.route) },
onSpendRefundable = { navController.navigate(Screen.WalletInfo.SwapInRefund.route) },
)
}
composable(Screen.WalletInfo.SwapInAddresses.route) {
Expand All @@ -419,6 +421,9 @@ fun AppView(
composable(Screen.WalletInfo.SwapInSigner.route) {
SwapInSignerView(onBackClick = { navController.popBackStack() })
}
composable(Screen.WalletInfo.SwapInRefund.route) {
SendSwapInRefundView(onBackClick = { navController.popBackStack() })
}
composable(Screen.WalletInfo.FinalWallet.route) {
FinalWalletInfo(onBackClick = { navController.popBackStack() })
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,49 +22,50 @@ import org.slf4j.LoggerFactory


sealed class Screen(val route: String) {
object SwitchToLegacy : Screen("switchtolegacy")
object Intro : Screen("intro")
object InitWallet : Screen("initwallet")
object CreateWallet : Screen("createwallet")
object RestoreWallet : Screen("restorewallet")
object Startup : Screen("startup")
object Home : Screen("home")
object Receive : Screen("receive")
data object SwitchToLegacy : Screen("switchtolegacy")
data object Intro : Screen("intro")
data object InitWallet : Screen("initwallet")
data object CreateWallet : Screen("createwallet")
data object RestoreWallet : Screen("restorewallet")
data object Startup : Screen("startup")
data object Home : Screen("home")
data object Receive : Screen("receive")
/**
* This route also manages the payment flow.
* TODO: Separate scanning the data from processing the data (aka send payment, process lnurl...). Split to be done at the controller level.
*/
object ScanData : Screen("readdata")
object PaymentDetails : Screen("payments")
object PaymentsHistory : Screen("payments/all")
object PaymentsCsvExport : Screen("payments/export")
data object ScanData : Screen("readdata")
data object PaymentDetails : Screen("payments")
data object PaymentsHistory : Screen("payments/all")
data object PaymentsCsvExport : Screen("payments/export")

// -- settings
object Settings : Screen("settings")
object DisplaySeed : Screen("settings/seed")
object ElectrumServer : Screen("settings/electrum")
object TorConfig : Screen("settings/tor")
object Channels : Screen("settings/channels")
object ChannelDetails : Screen("settings/channeldetails")
object ImportChannelsData : Screen("settings/importchannels")
object MutualClose : Screen("settings/mutualclose")
object ForceClose : Screen("settings/forceclose")
object Preferences : Screen("settings/preferences")
object About : Screen("settings/about")
object AppLock : Screen("settings/applock")
object PaymentSettings : Screen("settings/paymentsettings")
object Logs : Screen("settings/logs")
object WalletInfo : Screen("settings/walletinfo") {
object SwapInWallet: Screen("settings/walletinfo/swapin")
object SwapInAddresses: Screen("settings/walletinfo/swapinaddresses")
object SwapInSigner: Screen("settings/walletinfo/swapinsigner")
object FinalWallet: Screen("settings/walletinfo/final")
data object Settings : Screen("settings")
data object DisplaySeed : Screen("settings/seed")
data object ElectrumServer : Screen("settings/electrum")
data object TorConfig : Screen("settings/tor")
data object Channels : Screen("settings/channels")
data object ChannelDetails : Screen("settings/channeldetails")
data object ImportChannelsData : Screen("settings/importchannels")
data object MutualClose : Screen("settings/mutualclose")
data object ForceClose : Screen("settings/forceclose")
data object Preferences : Screen("settings/preferences")
data object About : Screen("settings/about")
data object AppLock : Screen("settings/applock")
data object PaymentSettings : Screen("settings/paymentsettings")
data object Logs : Screen("settings/logs")
data object WalletInfo : Screen("settings/walletinfo") {
data object SwapInWallet: Screen("settings/walletinfo/swapin")
data object SwapInAddresses: Screen("settings/walletinfo/swapinaddresses")
data object SwapInSigner: Screen("settings/walletinfo/swapinsigner")
data object FinalWallet: Screen("settings/walletinfo/final")
data object SwapInRefund: Screen("settings/walletinfo/swapinrefund")
}
object LiquidityPolicy: Screen("settings/liquiditypolicy")
object LiquidityRequest: Screen("settings/requestliquidity")
object AdvancedLiquidityPolicy: Screen("settings/advancedliquiditypolicy")
object Notifications: Screen("notifications")
object ResetWallet: Screen("settings/resetwallet")
data object LiquidityPolicy: Screen("settings/liquiditypolicy")
data object LiquidityRequest: Screen("settings/requestliquidity")
data object AdvancedLiquidityPolicy: Screen("settings/advancedliquiditypolicy")
data object Notifications: Screen("notifications")
data object ResetWallet: Screen("settings/resetwallet")
}

fun NavController.navigate(screen: Screen, arg: List<Any> = emptyList(), builder: NavOptionsBuilder.() -> Unit = {}) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ fun AmountInput(
colors = outlinedTextFieldColors(),
interactionSource = interactionSource,
shape = RoundedCornerShape(8.dp),
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp, top = if (staticLabel != null) 14.dp else 0.dp)
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp, top = if (staticLabel != null) 12.dp else 0.dp)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,12 @@ import fr.acinq.phoenix.data.MempoolFeerate
fun FeerateSlider(
feerate: Satoshi,
onFeerateChange: (Satoshi) -> Unit,
mempoolFeerate: MempoolFeerate?
mempoolFeerate: MempoolFeerate?,
enabled: Boolean,
modifier: Modifier = Modifier,
) {
var showUnknownMempoolStateDialog by remember { mutableStateOf(false) }
Column {
Column(modifier = modifier) {
// the actual value of the feerate, in sat/vbyte
Text(text = stringResource(id = R.string.cpfp_feerate_value, feerate.sat), style = MaterialTheme.typography.body2)

Expand Down Expand Up @@ -72,13 +74,13 @@ fun FeerateSlider(
CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.body1.copy(fontSize = 14.sp)) {
when {
feerate >= mempoolFeerate.fastest.feerate -> {
Text(text = stringResource(id = R.string.mempool_fastest), maxLines = 1)
Text(text = stringResource(id = R.string.mempool_fastest), maxLines = 1, modifier = Modifier.padding(vertical = 1.dp))
}
feerate >= mempoolFeerate.halfHour.feerate -> {
Text(text = stringResource(id = R.string.mempool_halfhour), maxLines = 1)
Text(text = stringResource(id = R.string.mempool_halfhour), maxLines = 1, modifier = Modifier.padding(vertical = 1.dp))
}
feerate >= mempoolFeerate.hour.feerate -> {
Text(text = stringResource(id = R.string.mempool_hour), maxLines = 1)
Text(text = stringResource(id = R.string.mempool_hour), maxLines = 1, modifier = Modifier.padding(vertical = 1.dp))
}
else -> {
TextWithIcon(text = stringResource(id = R.string.mempool_slow), icon = R.drawable.ic_alert_triangle, iconTint = MaterialTheme.colors.onSurface, space = 4.dp, maxLines = 1)
Expand All @@ -94,7 +96,8 @@ fun FeerateSlider(
amount = feerate,
onAmountChange = onFeerateChange,
minAmount = mempoolFeerate?.minimum?.feerate ?: 1.sat,
maxAmount = mempoolFeerate?.fastest?.feerate?.times(2) ?: 500.sat
maxAmount = mempoolFeerate?.fastest?.feerate?.times(2) ?: 500.sat,
enabled = enabled
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ fun TextInput(
colors = if (errorMessage.isNullOrBlank()) outlinedTextFieldColors() else errorOutlinedTextFieldColors(),
shape = RoundedCornerShape(8.dp),
interactionSource = interactionSource,
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp, top = if (staticLabel != null) 14.dp else 0.dp)
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp, top = if (staticLabel != null) 12.dp else 0.dp)
)

staticLabel?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ fun CpfpView(
if (feerate != it) vm.state = CpfpState.Init
feerate = it
},
mempoolFeerate = mempoolFeerate
mempoolFeerate = mempoolFeerate,
enabled = true
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ import fr.acinq.bitcoin.ByteVector32
import fr.acinq.bitcoin.TxId
import fr.acinq.bitcoin.utils.Either
import fr.acinq.lightning.blockchain.electrum.ElectrumConnectionStatus
import fr.acinq.lightning.blockchain.electrum.getConfirmations
import fr.acinq.lightning.db.*
import fr.acinq.lightning.payment.FinalFailure
import fr.acinq.lightning.payment.OutgoingPaymentFailure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import fr.acinq.phoenix.android.utils.datastore.SwapAddressFormat
import fr.acinq.phoenix.android.utils.datastore.UserPrefsRepository
import fr.acinq.phoenix.managers.PeerManager
import fr.acinq.phoenix.managers.WalletManager
import fr.acinq.phoenix.managers.phoenixSwapInWallet
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
Expand Down Expand Up @@ -111,7 +112,7 @@ class ReceiveViewModel(
viewModelScope.launch {
val swapAddressFormat = userPrefs.getSwapAddressFormat.first()
if (swapAddressFormat == SwapAddressFormat.LEGACY) {
val legacySwapInAddress = peerManager.getPeer().swapInWallet.legacySwapInAddress
val legacySwapInAddress = peerManager.getPeer().phoenixSwapInWallet.legacySwapInAddress
val image = BitmapHelper.generateBitmap(legacySwapInAddress).asImageBitmap()
currentSwapAddress = BitcoinAddressState.Show(0, legacySwapInAddress, image)
} else {
Expand All @@ -124,7 +125,7 @@ class ReceiveViewModel(
log.info("starting with swap-in address $startAddress:$startIndex")

// monitor the actual address from the swap-in wallet -- might take some time since the wallet must check all previous addresses
peerManager.getPeer().swapInWallet.swapInAddressFlow.filterNotNull().collect { (newAddress, newIndex) ->
peerManager.getPeer().phoenixSwapInWallet.swapInAddressFlow.filterNotNull().collect { (newAddress, newIndex) ->
log.info("swap-in wallet current address update: $newAddress:$newIndex")
val newImage = BitmapHelper.generateBitmap(newAddress).asImageBitmap()
internalDataRepository.saveLastUsedSwapIndex(newIndex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ fun SendSpliceOutView(
}
feerate = newFeerate
},
mempoolFeerate = mempoolFeerate
mempoolFeerate = mempoolFeerate,
enabled = vm.state !is SpliceOutState.Executing || vm.state !is SpliceOutState.Preparing
)
} ?: ProgressView(text = stringResource(id = R.string.send_spliceout_feerate_waiting_for_value), padding = PaddingValues(0.dp))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import org.slf4j.LoggerFactory


sealed class SpliceOutState {
object Init: SpliceOutState()
data object Init: SpliceOutState()
data class Preparing(val userAmount: Satoshi, val feeratePerByte: Satoshi): SpliceOutState()
data class ReadyToSend(val userAmount: Satoshi, val userFeerate: FeeratePerKw, val actualFeerate: FeeratePerKw, val estimatedFee: Satoshi): SpliceOutState()
data class Executing(val userAmount: Satoshi, val feerate: FeeratePerKw): SpliceOutState()
Expand All @@ -50,7 +50,7 @@ sealed class SpliceOutState {
}
sealed class Error: SpliceOutState() {
data class Thrown(val e: Throwable): Error()
object NoChannels: Error()
data object NoChannels: Error()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class ChannelsWatcher(context: Context, workerParams: WorkerParameters) : Corout

// connect electrum (and only electrum), and wait for the watcher to catch-up
withTimeout(ELECTRUM_TIMEOUT_MILLIS) {
peer.watcher.openUpToDateFlow().first()
business.electrumWatcher.openUpToDateFlow().first()
}
log.info("electrum watcher is up-to-date")
business.appConnectionsDaemon?.decrementDisconnectCount(AppConnectionsDaemon.ControlTarget.Electrum)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,7 @@ fun MutualCloseView(
MVIView(CF::closeChannelsConfiguration) { model, postIntent ->
if (showScannerView) {
var scanView by remember { mutableStateOf<DecoratedBarcodeView?>(null) }
Box(
Modifier
.fillMaxWidth()
.fillMaxHeight()
) {
Box(Modifier.fillMaxSize()) {
ScannerView(
onScanViewBinding = { scanView = it },
onScannedText = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import androidx.lifecycle.viewmodel.CreationExtras
import fr.acinq.lightning.blockchain.electrum.WalletState
import fr.acinq.phoenix.android.PhoenixApplication
import fr.acinq.phoenix.managers.PeerManager
import fr.acinq.phoenix.managers.phoenixSwapInWallet
import kotlinx.coroutines.launch
import org.slf4j.LoggerFactory

Expand All @@ -44,7 +45,7 @@ class SwapInAddressesViewModel(private val peerManager: PeerManager) : ViewModel
@UiThread
private fun monitorSwapAddresses() {
viewModelScope.launch {
peerManager.getPeer().swapInWallet.wallet.walletStateFlow.collect { walletState ->
peerManager.getPeer().phoenixSwapInWallet.wallet.walletStateFlow.collect { walletState ->
val newAddresses = walletState.addresses.toList().sortedByDescending {
val meta = it.second.meta
if (meta is WalletState.AddressMeta.Derived) {
Expand Down
Loading

0 comments on commit f629cb2

Please sign in to comment.