From 28cf92ed665c46e722cb1c5cd516ae21558529b0 Mon Sep 17 00:00:00 2001 From: Dominique Padiou <5765435+dpad85@users.noreply.github.com> Date: Wed, 15 Oct 2025 13:56:50 +0200 Subject: [PATCH 1/8] Update to lightning-kmp taproot-channel version This version also updates kotlin and cleans up legacy code. Notably, the tools for the legacy channel data backup have been removed, and the import-channel-data helper can be removed. The spend-from-channel-address helper has been commented out as it is not ready with taproot-channels yet. --- gradle/libs.versions.toml | 5 +- .../settings/channels/ImportChannelsData.kt | 138 ------------------ .../channels/ImportChannelsDataViewModel.kt | 72 --------- phoenix-shared/build.gradle.kts | 4 +- .../csv/WalletPaymentCsvWriter.kt | 4 +- .../fr.acinq.phoenix/data/DefaultOffer.kt | 32 ---- .../fr.acinq.phoenix/data/ExchangeRates.kt | 3 - .../fr.acinq.phoenix/data/LocalChannelInfo.kt | 8 +- .../v11/queries/InboundLiquidityQueries.kt | 2 - .../managers/NodeParamsManager.kt | 14 +- .../managers/PaymentsPageFetcher.kt | 7 +- .../managers/WalletManager.kt | 4 +- .../managers/global/CurrencyManager.kt | 6 +- .../global/fiatapis/BlockchainInfoApi.kt | 4 +- .../managers/global/fiatapis/BluelyticsApi.kt | 4 +- .../managers/global/fiatapis/CoinbaseApi.kt | 6 +- .../managers/global/fiatapis/YadioApi.kt | 4 +- .../utils/channels/ChannelsImportHelper.kt | 79 ---------- .../channels/SpendChannelAddressHelper.kt | 69 +++------ .../utils/extensions/ChannelExtensions.kt | 11 -- .../extensions/PaymentRequestExtensions.kt | 2 - .../utils/migrations/IosMigrationHelper.kt | 2 +- 22 files changed, 61 insertions(+), 419 deletions(-) delete mode 100644 phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/channels/ImportChannelsData.kt delete mode 100644 phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/channels/ImportChannelsDataViewModel.kt delete mode 100644 phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/data/DefaultOffer.kt delete mode 100644 phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/channels/ChannelsImportHelper.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c91acfa40..1dd325208 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,11 +1,12 @@ [versions] -lightningkmp = "1.10.8" -secp256k1 = "0.19.0" # keep in check with lightning-kmp secp version +lightningkmp = "1.10.9-SNAPSHOT" +secp256k1 = "0.20.0" # keep in check with lightning-kmp secp version kotlin = "2.2.10" ktor = "3.1.0" sqldelight = "2.1.0" okio = "3.15.0" +serialization = "1.9.0" # keep in check with lightning-kmp serialization version # iOS skie = "0.10.6" diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/channels/ImportChannelsData.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/channels/ImportChannelsData.kt deleted file mode 100644 index a96f76d57..000000000 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/channels/ImportChannelsData.kt +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2023 ACINQ SAS - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package fr.acinq.phoenix.android.settings.channels - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.DialogProperties -import androidx.lifecycle.viewmodel.compose.viewModel -import fr.acinq.phoenix.PhoenixBusiness -import fr.acinq.phoenix.android.R -import fr.acinq.phoenix.android.components.* -import fr.acinq.phoenix.android.components.buttons.Button -import fr.acinq.phoenix.android.components.TextWithIcon -import fr.acinq.phoenix.android.components.dialogs.Dialog -import fr.acinq.phoenix.android.components.feedback.ErrorMessage -import fr.acinq.phoenix.android.components.inputs.TextInput -import fr.acinq.phoenix.android.components.layouts.Card -import fr.acinq.phoenix.android.components.layouts.DefaultScreenHeader -import fr.acinq.phoenix.android.components.layouts.DefaultScreenLayout -import fr.acinq.phoenix.android.utils.positiveColor -import fr.acinq.phoenix.utils.channels.ChannelsImportResult - -@Composable -fun ImportChannelsData( - business: PhoenixBusiness, - onBackClick: () -> Unit, -) { - val peerManager = business.peerManager - val nodeParamsManager = business.nodeParamsManager - val vm = viewModel(factory = ImportChannelsDataViewModel.Factory(peerManager, nodeParamsManager)) - - var dataInput by remember { mutableStateOf("") } - - DefaultScreenLayout { - DefaultScreenHeader(onBackClick = onBackClick, title = stringResource(id = R.string.channelimport_title)) - Card(internalPadding = PaddingValues(16.dp)) { - Text(text = stringResource(id = R.string.channelimport_instructions)) - Spacer(modifier = Modifier.height(24.dp)) - TextInput( - text = dataInput, - onTextChange = { - if (it != dataInput) { - vm.state.value = ImportChannelsDataState.Init - } - dataInput = it - }, - staticLabel = stringResource(id = R.string.channelimport_input_label), - maxLines = 6, - enabled = vm.state.value !is ImportChannelsDataState.Importing - ) - } - Card( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - val business = business - when (val state = vm.state.value) { - ImportChannelsDataState.Init -> { - Button( - text = stringResource(id = R.string.channelimport_import_button), - icon = R.drawable.ic_restore, - onClick = { vm.importData(dataInput.trim(), business) }, - modifier = Modifier.fillMaxWidth() - ) - } - ImportChannelsDataState.Importing -> { - ProgressView(text = stringResource(id = R.string.channelimport_importing),) - } - is ImportChannelsDataState.Done -> when (val result = state.result) { - is ChannelsImportResult.Success -> { - Dialog( - onDismiss = {}, - properties = DialogProperties(usePlatformDefaultWidth = false, dismissOnBackPress = false, dismissOnClickOutside = false), - buttons = null, - buttonsTopMargin = 0.dp - ) { - Column( - modifier = Modifier.padding(32.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - TextWithIcon( - text = stringResource(id = R.string.channelimport_success), - textStyle = MaterialTheme.typography.body2, - icon = R.drawable.ic_check, - iconTint = positiveColor, - ) - Text(text = stringResource(id = R.string.channelimport_success_restart), textAlign = TextAlign.Center) - } - } - } - is ChannelsImportResult.Failure -> { - ErrorMessage( - header = stringResource(id = R.string.channelimport_error_title), - details = when (result) { - is ChannelsImportResult.Failure.Generic -> result.error.message - is ChannelsImportResult.Failure.MalformedData -> stringResource(id = R.string.channelimport_error_malformed) - is ChannelsImportResult.Failure.DecryptionError -> stringResource(id = R.string.channelimport_error_decryption) - is ChannelsImportResult.Failure.UnknownVersion -> stringResource(id = R.string.channelimport_error_unknown_version, result.version) - }, - alignment = Alignment.CenterHorizontally, - modifier = Modifier.fillMaxWidth() - ) - } - } - } - } - } -} \ No newline at end of file diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/channels/ImportChannelsDataViewModel.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/channels/ImportChannelsDataViewModel.kt deleted file mode 100644 index ee313f5d5..000000000 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/channels/ImportChannelsDataViewModel.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2023 ACINQ SAS - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package fr.acinq.phoenix.android.settings.channels - -import androidx.compose.runtime.mutableStateOf -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewModelScope -import fr.acinq.phoenix.PhoenixBusiness -import fr.acinq.phoenix.managers.NodeParamsManager -import fr.acinq.phoenix.managers.PeerManager -import fr.acinq.phoenix.utils.channels.ChannelsImportHelper -import fr.acinq.phoenix.utils.channels.ChannelsImportResult -import kotlinx.coroutines.CoroutineExceptionHandler -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import org.slf4j.LoggerFactory - -sealed class ImportChannelsDataState { - object Init : ImportChannelsDataState() - object Importing : ImportChannelsDataState() - data class Done(val result: ChannelsImportResult): ImportChannelsDataState() -} - -class ImportChannelsDataViewModel(val peerManager: PeerManager, val nodeParamsManager: NodeParamsManager) : ViewModel() { - - private val log = LoggerFactory.getLogger(this::class.java) - val state = mutableStateOf(ImportChannelsDataState.Init) - - fun importData(data: String, business: PhoenixBusiness) { - if (state.value == ImportChannelsDataState.Importing) return - viewModelScope.launch( - Dispatchers.Default + CoroutineExceptionHandler { _, e -> - log.error("failed to import channels data: ", e) - state.value = ImportChannelsDataState.Done(ChannelsImportResult.Failure.Generic(e)) - } - ) { - state.value = ImportChannelsDataState.Importing - delay(300) - val result = ChannelsImportHelper.doImportChannels( - data = data, - biz = business - ) - state.value = ImportChannelsDataState.Done(result) - } - } - - class Factory( - private val peerManager: PeerManager, - private val nodeParamsManager: NodeParamsManager, - ) : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - @Suppress("UNCHECKED_CAST") - return ImportChannelsDataViewModel(peerManager, nodeParamsManager) as T - } - } -} \ No newline at end of file diff --git a/phoenix-shared/build.gradle.kts b/phoenix-shared/build.gradle.kts index 5fb514529..2b709c1fa 100644 --- a/phoenix-shared/build.gradle.kts +++ b/phoenix-shared/build.gradle.kts @@ -95,10 +95,12 @@ kotlin { implementation("io.ktor:ktor-client-json:${libs.versions.ktor.get()}") implementation("io.ktor:ktor-serialization-kotlinx-json:${libs.versions.ktor.get()}") implementation("io.ktor:ktor-client-content-negotiation:${libs.versions.ktor.get()}") + // serialization + implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:${libs.versions.serialization.get()}") // sqldelight implementation("app.cash.sqldelight:runtime:${libs.versions.sqldelight.get()}") implementation("app.cash.sqldelight:coroutines-extensions:${libs.versions.sqldelight.get()}") - // SKEI + // SKIE implementation("co.touchlab.skie:configuration-annotations:${libs.versions.skie.get()}") } } diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/csv/WalletPaymentCsvWriter.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/csv/WalletPaymentCsvWriter.kt index 6d4f7cf95..08a8e74c9 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/csv/WalletPaymentCsvWriter.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/csv/WalletPaymentCsvWriter.kt @@ -28,7 +28,8 @@ import fr.acinq.lightning.utils.sat import fr.acinq.lightning.utils.sum import fr.acinq.lightning.utils.toMilliSatoshi import fr.acinq.phoenix.data.WalletPaymentMetadata -import kotlinx.datetime.Instant +import kotlin.time.ExperimentalTime +import kotlin.time.Instant class WalletPaymentCsvWriter(val configuration: Configuration) : CsvWriter() { @@ -99,6 +100,7 @@ class WalletPaymentCsvWriter(val configuration: Configuration) : CsvWriter() { val description: String? = null, ) + @OptIn(ExperimentalTime::class) private fun addRow( timestamp: Long, id: UUID, diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/data/DefaultOffer.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/data/DefaultOffer.kt deleted file mode 100644 index 7b09af6e7..000000000 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/data/DefaultOffer.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2024 ACINQ SAS - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package fr.acinq.phoenix.data - -import fr.acinq.bitcoin.PrivateKey -import fr.acinq.lightning.wire.OfferTypes - -/** - * @param defaultOffer The default offer for a node. - * @param payerKey A private key attached to a node. It can be used to sign payments to offers of - * third parties and prove the origin of that payment. The recipient of that payment can then - * decide that this origin is trusted, and show/hide the `payerNote` attached to that payment. - * - */ -data class OfferData( - val defaultOffer: OfferTypes.Offer, - val payerKey: PrivateKey -) \ No newline at end of file diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/data/ExchangeRates.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/data/ExchangeRates.kt index ce6dfc734..04693cbf3 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/data/ExchangeRates.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/data/ExchangeRates.kt @@ -1,8 +1,5 @@ package fr.acinq.phoenix.data -import fr.acinq.phoenix.controllers.MVI -import fr.acinq.phoenix.controllers.main.Home -import kotlinx.datetime.Instant import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/data/LocalChannelInfo.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/data/LocalChannelInfo.kt index 7d70862fd..909ca6fea 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/data/LocalChannelInfo.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/data/LocalChannelInfo.kt @@ -46,8 +46,6 @@ data class LocalChannelInfo( val isTerminated by lazy { state.isTerminated() } /** True if the channel can be used to send/receive payments. */ val isUsable by lazy { state is Normal && !isBooting } - /** True if the channel is `LegacyWaitForFundingConfirmed`, i.e., it may be a zombie channel. */ - val isLegacyWait by lazy { state.isLegacyWait() } /** A string version of the state's class. */ val stateName by lazy { state.stateName } // FIXME: we should also expose the raw channel's balance, which is what should be used in the channel's details screen, rather than the "smart" spendable balance returned by `localBalance()` @@ -70,7 +68,7 @@ data class LocalChannelInfo( val commitmentsInfo: List by lazy { when (state) { is ChannelStateWithCommitments -> { - val params = state.commitments.params + val params = state.commitments.channelParams val changes = state.commitments.changes state.commitments.active.map { CommitmentInfo( @@ -88,7 +86,7 @@ data class LocalChannelInfo( val inactiveCommitmentsInfo: List by lazy { when (state) { is ChannelStateWithCommitments -> { - val params = state.commitments.params + val params = state.commitments.channelParams val changes = state.commitments.changes state.commitments.inactive.map { CommitmentInfo( @@ -110,7 +108,7 @@ data class LocalChannelInfo( buildSet { state.commitments.latest.localCommit.spec.htlcs.forEach { add(it.add.paymentHash) } state.commitments.latest.remoteCommit.spec.htlcs.forEach { add(it.add.paymentHash) } - state.commitments.latest.nextRemoteCommit?.commit?.spec?.htlcs?.forEach { add(it.add.paymentHash) } + state.commitments.latest.nextRemoteCommit?.spec?.htlcs?.forEach { add(it.add.paymentHash) } }.size } else -> 0 diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/migrations/v11/queries/InboundLiquidityQueries.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/migrations/v11/queries/InboundLiquidityQueries.kt index 217b6c412..383ac3a32 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/migrations/v11/queries/InboundLiquidityQueries.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/migrations/v11/queries/InboundLiquidityQueries.kt @@ -66,8 +66,6 @@ object InboundLiquidityQueries { is Bolt12IncomingPayment -> incomingPayment.copy( liquidityPurchaseDetails = liquidityPurchaseDetails ) to incomingPayment.completedAt - - else -> null to null } val liquidityPayment = AutomaticLiquidityPurchasePayment( id = UUID.fromString(id), diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/NodeParamsManager.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/NodeParamsManager.kt index 141f9c313..1d3af423f 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/NodeParamsManager.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/NodeParamsManager.kt @@ -16,26 +16,22 @@ package fr.acinq.phoenix.managers -import fr.acinq.bitcoin.ByteVector32 import fr.acinq.bitcoin.Chain import fr.acinq.bitcoin.PublicKey -import fr.acinq.bitcoin.Satoshi import fr.acinq.lightning.NodeParams import fr.acinq.lightning.NodeUri import fr.acinq.lightning.logging.LoggerFactory import fr.acinq.lightning.payment.LiquidityPolicy import fr.acinq.lightning.utils.msat import fr.acinq.lightning.utils.sat -import fr.acinq.lightning.wire.LiquidityAds import fr.acinq.phoenix.PhoenixBusiness import fr.acinq.phoenix.shared.BuildVersions import fr.acinq.lightning.logging.info -import fr.acinq.phoenix.data.OfferData +import fr.acinq.lightning.wire.OfferTypes import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.MainScope import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch -import kotlin.time.Duration.Companion.hours class NodeParamsManager( @@ -81,11 +77,9 @@ class NodeParamsManager( } } - /** See [NodeParams.defaultOffer]. Returns an [OfferData] object. */ - suspend fun defaultOffer(): OfferData { - return nodeParams.filterNotNull().first().defaultOffer(trampolineNodeId).let { - OfferData(it.first, it.second) - } + /** See [NodeParams.defaultOffer]. Returns an [OfferTypes.OfferAndKey] object. */ + suspend fun defaultOffer(): OfferTypes.OfferAndKey { + return nodeParams.filterNotNull().first().defaultOffer(trampolineNodeId) } companion object { diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/PaymentsPageFetcher.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/PaymentsPageFetcher.kt index 94183a6bb..0531b9364 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/PaymentsPageFetcher.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/PaymentsPageFetcher.kt @@ -6,9 +6,10 @@ import fr.acinq.phoenix.data.WalletPaymentInfo import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.datetime.Clock -import kotlinx.datetime.Instant +import kotlin.time.Clock +import kotlin.time.Instant import kotlin.time.Duration.Companion.seconds +import kotlin.time.ExperimentalTime data class PaymentsPage( /** The offset value you passed to the `subscribeToX()` function. */ @@ -154,6 +155,7 @@ class PaymentsPageFetcher( resetSubscribeToRecentJob(subscriptionIdx) } + @OptIn(ExperimentalTime::class) private fun resetSubscribeToRecentJob(idx: Int) { log.debug { "resetSubscribeToRecentJob(idx=$idx)" } @@ -191,6 +193,7 @@ class PaymentsPageFetcher( } } + @OptIn(ExperimentalTime::class) private fun resetRefreshJob(idx: Int, rows: List) { log.debug { "resetRefreshJob(idx=$idx, rows=${rows.size})" } diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/WalletManager.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/WalletManager.kt index d87bbc14f..5989ec77e 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/WalletManager.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/WalletManager.kt @@ -17,7 +17,7 @@ package fr.acinq.phoenix.managers import fr.acinq.bitcoin.* -import fr.acinq.lightning.crypto.KeyManager +import fr.acinq.lightning.crypto.Bip84OnChainKeys import fr.acinq.lightning.crypto.LocalKeyManager import fr.acinq.lightning.crypto.div import kotlinx.coroutines.CoroutineScope @@ -100,4 +100,4 @@ fun LocalKeyManager.cloudKeyHash(): String { fun LocalKeyManager.isMainnet() = chain == Chain.Mainnet val LocalKeyManager.finalOnChainWalletPath: String - get() = (KeyManager.Bip84OnChainKeys.bip84BasePath(chain) / finalOnChainWallet.account).toString() + get() = (Bip84OnChainKeys.bip84BasePath(chain) / finalOnChainWallet.account).toString() diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/global/CurrencyManager.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/global/CurrencyManager.kt index 256649cb4..e0656b7a2 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/global/CurrencyManager.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/global/CurrencyManager.kt @@ -48,12 +48,13 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.isActive import kotlinx.coroutines.launch -import kotlinx.datetime.Clock -import kotlinx.datetime.Instant +import kotlin.time.Clock +import kotlin.time.Instant import kotlin.collections.plus import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds +import kotlin.time.ExperimentalTime /** * Manages the routines fetching the btc exchange rates. The frontend app must add fiat currencies they @@ -75,6 +76,7 @@ import kotlin.time.Duration.Companion.seconds * However, for the time being, we rely on the more liquid USD-FIAT exchange rates. * Thus, if we fetch both BTC-USD & USD-COP, we can easily convert between any of the 3 currencies. */ +@OptIn(ExperimentalTime::class) class CurrencyManager( loggerFactory: LoggerFactory, val appDb: SqliteAppDb, diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/global/fiatapis/BlockchainInfoApi.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/global/fiatapis/BlockchainInfoApi.kt index 9cdd32daa..589c195e8 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/global/fiatapis/BlockchainInfoApi.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/global/fiatapis/BlockchainInfoApi.kt @@ -18,13 +18,13 @@ package fr.acinq.phoenix.managers.global.fiatapis import fr.acinq.lightning.logging.LoggerFactory import fr.acinq.lightning.logging.error +import fr.acinq.lightning.utils.currentTimestampMillis import fr.acinq.phoenix.data.BlockchainInfoResponse import fr.acinq.phoenix.data.ExchangeRate import fr.acinq.phoenix.data.FiatCurrency import io.ktor.client.request.get import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.bodyAsText -import kotlinx.datetime.Clock import kotlin.time.Duration.Companion.minutes /** @@ -57,7 +57,7 @@ class BlockchainInfoApi(loggerFactory: LoggerFactory) : ExchangeRateApi { } } - val timestampMillis = Clock.System.now().toEpochMilliseconds() + val timestampMillis = currentTimestampMillis() val fetchedRates: List = parsedResponse?.let { targets.mapNotNull { fiatCurrency -> parsedResponse[fiatCurrency.name]?.let { priceObject -> diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/global/fiatapis/BluelyticsApi.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/global/fiatapis/BluelyticsApi.kt index 2b9849897..c6f4bf230 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/global/fiatapis/BluelyticsApi.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/global/fiatapis/BluelyticsApi.kt @@ -18,13 +18,13 @@ package fr.acinq.phoenix.managers.global.fiatapis import fr.acinq.lightning.logging.LoggerFactory import fr.acinq.lightning.logging.error +import fr.acinq.lightning.utils.currentTimestampMillis import fr.acinq.phoenix.data.BluelyticsResponse import fr.acinq.phoenix.data.ExchangeRate import fr.acinq.phoenix.data.FiatCurrency import io.ktor.client.request.get import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.bodyAsText -import kotlinx.datetime.Clock import kotlin.time.Duration.Companion.minutes /** @@ -56,7 +56,7 @@ class BluelyticsAPI(loggerFactory: LoggerFactory) : ExchangeRateApi { } } - val timestampMillis = Clock.System.now().toEpochMilliseconds() + val timestampMillis = currentTimestampMillis() val fetchedRates: List = parsedResponse?.let { targets.filter { it == FiatCurrency.ARS_BM }.map { ExchangeRate.UsdPriceRate( diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/global/fiatapis/CoinbaseApi.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/global/fiatapis/CoinbaseApi.kt index 3e842cb60..fbf8e3445 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/global/fiatapis/CoinbaseApi.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/global/fiatapis/CoinbaseApi.kt @@ -18,13 +18,13 @@ package fr.acinq.phoenix.managers.global.fiatapis import fr.acinq.lightning.logging.LoggerFactory import fr.acinq.lightning.logging.error +import fr.acinq.lightning.utils.currentTimestampMillis import fr.acinq.phoenix.data.CoinbaseResponse import fr.acinq.phoenix.data.ExchangeRate import fr.acinq.phoenix.data.FiatCurrency import io.ktor.client.request.get import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.bodyAsText -import kotlinx.datetime.Clock import kotlin.time.Duration.Companion.minutes /** @@ -38,7 +38,7 @@ class CoinbaseAPI(loggerFactory: LoggerFactory) : ExchangeRateApi { override val name = "coinbase" override val refreshDelay = 60.minutes override val fiatCurrencies = FiatCurrency.Companion.values.filter { - // bascially, everything except USD, EURO, and special markets + // basically, everything except USD, EURO, and special markets !ExchangeRateApi.highLiquidityMarkets.contains(it) && !ExchangeRateApi.specialMarkets.contains(it) && !ExchangeRateApi.missingFromCoinbase.contains(it) }.toSet() @@ -58,7 +58,7 @@ class CoinbaseAPI(loggerFactory: LoggerFactory) : ExchangeRateApi { } } - val timestampMillis = Clock.System.now().toEpochMilliseconds() + val timestampMillis = currentTimestampMillis() val fetchedRates: List = parsedResponse?.let { targets.mapNotNull { fiatCurrency -> parsedResponse.data.rates[fiatCurrency.name]?.let { valueAsString -> diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/global/fiatapis/YadioApi.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/global/fiatapis/YadioApi.kt index 9e743159f..0ad17dc82 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/global/fiatapis/YadioApi.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/managers/global/fiatapis/YadioApi.kt @@ -18,13 +18,13 @@ package fr.acinq.phoenix.managers.global.fiatapis import fr.acinq.lightning.logging.LoggerFactory import fr.acinq.lightning.logging.error +import fr.acinq.lightning.utils.currentTimestampMillis import fr.acinq.phoenix.data.ExchangeRate import fr.acinq.phoenix.data.FiatCurrency import fr.acinq.phoenix.data.YadioResponse import io.ktor.client.request.get import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.bodyAsText -import kotlinx.datetime.Clock import kotlin.time.Duration.Companion.minutes /** @@ -57,7 +57,7 @@ class YadioAPI(loggerFactory: LoggerFactory) : ExchangeRateApi { } } - val timestampMillis = Clock.System.now().toEpochMilliseconds() + val timestampMillis = currentTimestampMillis() val fetchedRates: List = parsedResponse?.let { targets.mapNotNull { fiatCurrency -> val name = fiatCurrency.name.take(3) diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/channels/ChannelsImportHelper.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/channels/ChannelsImportHelper.kt deleted file mode 100644 index 800cc9ce2..000000000 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/channels/ChannelsImportHelper.kt +++ /dev/null @@ -1,79 +0,0 @@ -package fr.acinq.phoenix.utils.channels - -import fr.acinq.bitcoin.ByteVector -import fr.acinq.lightning.channel.states.PersistedChannelState -import fr.acinq.lightning.serialization.channel.Encryption.from -import fr.acinq.lightning.serialization.channel.Serialization -import fr.acinq.lightning.wire.EncryptedChannelData -import fr.acinq.phoenix.PhoenixBusiness -import fr.acinq.lightning.logging.error -import fr.acinq.lightning.logging.info -import fr.acinq.phoenix.db.SqliteChannelsDb -import fr.acinq.secp256k1.Hex -import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.first - - -object ChannelsImportHelper { - - suspend fun doImportChannels( - data: String, - biz: PhoenixBusiness, - ): ChannelsImportResult { - - val loggerFactory = biz.loggerFactory - val log = loggerFactory.newLogger(this::class) - try { - - log.info { "initiating channels-data import" } - - val nodeParams = biz.nodeParamsManager.nodeParams.filterNotNull().first() - val peer = biz.peerManager.getPeer() - - val encryptedChannelData = try { - EncryptedChannelData(ByteVector(Hex.decode(data))) - } catch(e: Exception) { - log.error(e) { "failed to deserialize data blob" } - return ChannelsImportResult.Failure.MalformedData - } - - return PersistedChannelState - .from(nodeParams.nodePrivateKey, encryptedChannelData) - .fold( - onFailure = { - log.error(it) { "failed to decrypt channel state" } - ChannelsImportResult.Failure.DecryptionError - }, - onSuccess = { - when (it) { - is Serialization.DeserializationResult.Success -> { - log.info { "successfully imported channel=${it.state.channelId}" } - peer.db.channels.addOrUpdateChannel(it.state) - val channel = (peer.db.channels as? SqliteChannelsDb)?.getChannel(it.state.channelId) - log.info { "channel added/updated to database, is_closed=${channel?.third}" } - ChannelsImportResult.Success(it.state) - } - is Serialization.DeserializationResult.UnknownVersion -> { - log.error { "cannot use channel state: unknown version=${it.version}" } - ChannelsImportResult.Failure.UnknownVersion(it.version) - } - } - } - ) - - } catch (e: Exception) { - log.error(e) { "error when importing channels" } - return ChannelsImportResult.Failure.Generic(e) - } - } -} - -sealed class ChannelsImportResult { - data class Success(val channel: PersistedChannelState) : ChannelsImportResult() - sealed class Failure : ChannelsImportResult() { - data class Generic(val error: Throwable) : Failure() - data class UnknownVersion(val version: Int) : Failure() - object MalformedData : Failure() - object DecryptionError : Failure() - } -} \ No newline at end of file diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/channels/SpendChannelAddressHelper.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/channels/SpendChannelAddressHelper.kt index 2792ff81f..809d53370 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/channels/SpendChannelAddressHelper.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/channels/SpendChannelAddressHelper.kt @@ -19,6 +19,7 @@ package fr.acinq.phoenix.utils.channels import fr.acinq.bitcoin.ByteVector import fr.acinq.bitcoin.ByteVector64 import fr.acinq.bitcoin.Crypto +import fr.acinq.bitcoin.KeyPath import fr.acinq.bitcoin.PublicKey import fr.acinq.bitcoin.Satoshi import fr.acinq.bitcoin.SigHash @@ -34,7 +35,6 @@ import fr.acinq.lightning.serialization.channel.Encryption.from import fr.acinq.lightning.serialization.channel.Serialization import fr.acinq.lightning.transactions.Scripts import fr.acinq.lightning.transactions.Transactions -import fr.acinq.lightning.wire.EncryptedChannelData import fr.acinq.phoenix.PhoenixBusiness import fr.acinq.secp256k1.Hex import kotlinx.coroutines.flow.filterNotNull @@ -56,21 +56,13 @@ object SpendChannelAddressHelper { business: PhoenixBusiness, amount: Satoshi, fundingTxIndex: Long, - channelData: String, + channelKeyPath: String, remoteFundingPubkey: String, unsignedTx: String ): SpendChannelAddressResult { val loggerFactory = business.loggerFactory val log = loggerFactory.newLogger(this::class) val peer = business.peerManager.getPeer() - val nodeParams = business.nodeParamsManager.nodeParams.filterNotNull().first() - - val deserializedChannelData = try { - EncryptedChannelData(ByteVector(Hex.decode(channelData))) - } catch(e: Exception) { - log.error(e) { "failed to deserialize channels-data blob" } - return SpendChannelAddressResult.Failure.ChannelDataMalformed - } val tx = try { Transaction.read(unsignedTx) @@ -86,45 +78,35 @@ object SpendChannelAddressHelper { return SpendChannelAddressResult.Failure.RemoteFundingPubkeyMalformed(e.message ?: e::class.simpleName.toString()) } - try { - val channelKeyPath = PersistedChannelState.from(nodeParams.nodePrivateKey, deserializedChannelData) - .fold( - onFailure = { - log.error { "failed to decrypt channel" } - return SpendChannelAddressResult.Failure.ChannelDataDecryption - }, - onSuccess = { - when (it) { - is Serialization.DeserializationResult.Success -> { - when (val s = it.state) { - is ChannelStateWithCommitments -> s.commitments.params.localParams.fundingKeyPath - else -> { - log.error { "unhandled channel data state: ${it::class.simpleName}" } - return SpendChannelAddressResult.Failure.ChannelDataUnhandledState(it::class.simpleName) - } - } - } - is Serialization.DeserializationResult.UnknownVersion -> { - log.error { "unhandled channel data version: ${it.version}" } - return SpendChannelAddressResult.Failure.ChannelDataUnhandledVersion(it.version) - } - } - } - ) + val channelKeys = try { + peer.nodeParams.keyManager.channelKeys(KeyPath(channelKeyPath)) + } catch (e: Exception) { + log.error(e) { "failed to read channel keypath=$channelKeyPath" } + return SpendChannelAddressResult.Failure.InvalidChannelKeyPath + } - val channelKeys = peer.nodeParams.keyManager.channelKeys(channelKeyPath) + try { val localFundingKey = channelKeys.fundingKey(fundingTxIndex) val fundingScript = Scripts.multiSig2of2(localFundingKey.publicKey(), pubkey) - val sig = Transactions.sign(tx = tx, inputIndex = 0, Script.write(fundingScript), amount, localFundingKey) + val sig = TODO() //Transactions.sign(tx = tx, inputIndex = 0, Script.write(fundingScript), amount, localFundingKey) val signedData = tx.hashForSigning(0, Script.write(fundingScript), SigHash.SIGHASH_ALL, amount, SigVersion.SIGVERSION_WITNESS_V0) - return if (!Crypto.verifySignature(signedData, sig, localFundingKey.publicKey())) { + + try { + val verifySig = Crypto.verifySignature(signedData, sig, localFundingKey.publicKey()) + if (!verifySig) { + log.error { "signature not verified" } + SpendChannelAddressResult.Failure.InvalidSig(tx.txid, localFundingKey.publicKey(), Script.write(fundingScript).byteVector(), sig) + } else { + SpendChannelAddressResult.Success(tx.txid, localFundingKey.publicKey(), Script.write(fundingScript).byteVector(), sig) + } + } catch (e: Exception) { + log.error(e) { "failed to verify signature" } SpendChannelAddressResult.Failure.InvalidSig(tx.txid, localFundingKey.publicKey(), Script.write(fundingScript).byteVector(), sig) - } else { - SpendChannelAddressResult.Success(tx.txid, localFundingKey.publicKey(), Script.write(fundingScript).byteVector(), sig) } + } catch (e: Exception) { - log.error { "error when spending from channel address: ${e.message}" } + log.error(e) { "failed to sign transaction" } return SpendChannelAddressResult.Failure.Generic(e) } } @@ -134,10 +116,7 @@ sealed class SpendChannelAddressResult { data class Success(val txId: TxId, val publicKey: PublicKey, val fundingScript: ByteVector, val signature: ByteVector64) : SpendChannelAddressResult() sealed class Failure : SpendChannelAddressResult() { data class Generic(val error: Throwable) : Failure() - data object ChannelDataMalformed : Failure() - data object ChannelDataDecryption : Failure() - data class ChannelDataUnhandledState(val stateName: String?) : Failure() - data class ChannelDataUnhandledVersion(val version: Int) : Failure() + data object InvalidChannelKeyPath : Failure() data class TransactionMalformed(val details: String) : Failure() data class RemoteFundingPubkeyMalformed(val details: String) : Failure() data class InvalidSig(val txId: TxId, val publicKey: PublicKey, val fundingScript: ByteVector, val signature: ByteVector64) : Failure() diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/extensions/ChannelExtensions.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/extensions/ChannelExtensions.kt index 357f787d9..2843d0137 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/extensions/ChannelExtensions.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/extensions/ChannelExtensions.kt @@ -31,18 +31,10 @@ fun ChannelState.isTerminated(): Boolean { } } -fun ChannelState.isLegacyWait(): Boolean { - return this is LegacyWaitForFundingConfirmed - || (this is Offline && this.state is LegacyWaitForFundingConfirmed) - || (this is Syncing && this.state is LegacyWaitForFundingConfirmed) -} - fun ChannelState.isBeingCreated(): Boolean { return when (this) { is Syncing -> state.isBeingCreated() is Offline -> state.isBeingCreated() - is LegacyWaitForFundingLocked, - is LegacyWaitForFundingConfirmed, is WaitForAcceptChannel, is WaitForChannelReady, is WaitForFundingConfirmed, @@ -70,8 +62,6 @@ fun ChannelState.localBalance(): MilliSatoshi? { is Aborted -> null // balance is unknown is Negotiating -> null - is LegacyWaitForFundingLocked -> null - is LegacyWaitForFundingConfirmed -> null is WaitForAcceptChannel -> null is WaitForChannelReady -> null is WaitForFundingConfirmed -> null @@ -82,6 +72,5 @@ fun ChannelState.localBalance(): MilliSatoshi? { is WaitForRemotePublishFutureCommitment -> null // regular case is ChannelStateWithCommitments -> commitments.availableBalanceForSend() - else -> null } } diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/extensions/PaymentRequestExtensions.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/extensions/PaymentRequestExtensions.kt index e03cd038b..dfd451833 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/extensions/PaymentRequestExtensions.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/extensions/PaymentRequestExtensions.kt @@ -16,13 +16,11 @@ package fr.acinq.phoenix.utils.extensions -import fr.acinq.lightning.Feature import fr.acinq.lightning.payment.Bolt11Invoice import fr.acinq.lightning.payment.Bolt12Invoice import fr.acinq.lightning.payment.OfferPaymentMetadata import fr.acinq.lightning.payment.PaymentRequest -fun Bolt11Invoice.isAmountlessTrampoline() = this.amount == null && this.features.hasFeature(Feature.TrampolinePayment) /** * In Objective-C, the function name `description()` is already in use (part of NSObject). diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/migrations/IosMigrationHelper.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/migrations/IosMigrationHelper.kt index f2a49484c..34141b068 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/migrations/IosMigrationHelper.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/migrations/IosMigrationHelper.kt @@ -59,7 +59,7 @@ object IosMigrationHelper { private fun ChannelState.isLegacy(): Boolean { return this is ChannelStateWithCommitments && this !is ShuttingDown && this !is Negotiating && this !is Closing && this !is Closed - && !this.commitments.params.channelFeatures.hasFeature(Feature.DualFunding) + && !this.commitments.channelParams.channelFeatures.hasFeature(Feature.DualFunding) } /** From 94db39fe08bfd904d2054d899a0cdb7d3ac15003 Mon Sep 17 00:00:00 2001 From: Dominique Padiou <5765435+dpad85@users.noreply.github.com> Date: Wed, 15 Oct 2025 14:00:47 +0200 Subject: [PATCH 2/8] (android) Update android app with latest lightning-kmp This includes removing the obsolete import-channel-data screen and string resources. --- .../android/components/CalendarView.kt | 2 ++ .../navigation/NavGraphSettingsChannels.kt | 5 ----- .../payments/details/PaymentTechnicalView.kt | 2 +- .../payments/history/PaymentsHistoryView.kt | 10 ++++++--- .../payments/receive/ReceiveViewModel.kt | 2 +- .../payments/send/bolt11/SendToBolt11.kt | 7 ------- .../payments/send/offer/SendOfferViewModel.kt | 2 +- .../channels/SpendFromChannelAddress.kt | 13 ++---------- .../SpendFromChannelAddressViewModel.kt | 20 ++++-------------- .../src/main/res/values-b+es+419/strings.xml | 16 -------------- .../src/main/res/values-cs/strings.xml | 20 ------------------ .../src/main/res/values-de/strings.xml | 16 -------------- .../src/main/res/values-fr/strings.xml | 16 -------------- .../src/main/res/values-pt-rBR/strings.xml | 16 -------------- .../src/main/res/values-sk/strings.xml | 16 -------------- .../src/main/res/values-sw/strings.xml | 16 -------------- .../src/main/res/values-vi/strings.xml | 16 -------------- .../src/main/res/values/strings.xml | 21 +------------------ 18 files changed, 19 insertions(+), 197 deletions(-) diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/components/CalendarView.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/components/CalendarView.kt index 08ad21c0f..08c0c7bb1 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/components/CalendarView.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/components/CalendarView.kt @@ -35,11 +35,13 @@ import fr.acinq.phoenix.android.components.dialogs.Dialog import fr.acinq.phoenix.android.utils.converters.DateFormatter.toAbsoluteDateString import fr.acinq.phoenix.android.utils.mutedBgColor import kotlinx.datetime.* +import kotlin.time.ExperimentalTime /** * Calendar component to pick a day. [onDateSelected] returns the timestamp in millis at the * **start** of day. */ +@OptIn(ExperimentalTime::class) @Composable fun CalendarView( label: String, diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/navigation/NavGraphSettingsChannels.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/navigation/NavGraphSettingsChannels.kt index 3ac878af5..6b6fb22b1 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/navigation/NavGraphSettingsChannels.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/navigation/NavGraphSettingsChannels.kt @@ -24,7 +24,6 @@ import fr.acinq.phoenix.android.AppViewModel import fr.acinq.phoenix.android.settings.MutualCloseView import fr.acinq.phoenix.android.settings.channels.ChannelDetailsView import fr.acinq.phoenix.android.settings.channels.ChannelsView -import fr.acinq.phoenix.android.settings.channels.ImportChannelsData import fr.acinq.phoenix.android.settings.channels.SpendFromChannelAddress fun NavGraphBuilder.channelsNavGraph(navController: NavController, appViewModel: AppViewModel) { @@ -56,10 +55,6 @@ fun NavGraphBuilder.channelsNavGraph(navController: NavController, appViewModel: ChannelDetailsView(business = business, onBackClick = { navController.popBackStack() }, channelId = channelId) } - businessComposable(Screen.BusinessNavGraph.ImportChannelsData.route, appViewModel) { _, _, business -> - ImportChannelsData(business = business, onBackClick = { navController.popBackStack() }) - } - businessComposable(Screen.BusinessNavGraph.SpendChannelAddress.route, appViewModel) { _, _, business -> SpendFromChannelAddress(business = business, onBackClick = { navController.popBackStack() }) } diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/PaymentTechnicalView.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/PaymentTechnicalView.kt index 16c46bde3..e66a7734b 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/PaymentTechnicalView.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/PaymentTechnicalView.kt @@ -184,7 +184,7 @@ fun Bolt12InvoiceSection( Text(text = payerKey.toHex()) val nodeParamsManager = LocalBusiness.current?.nodeParamsManager val offerPayerKey by produceState(initialValue = null, key1 = nodeParamsManager) { - value = nodeParamsManager?.defaultOffer()?.payerKey + value = nodeParamsManager?.defaultOffer()?.privateKey } if (offerPayerKey != null && payerKey == offerPayerKey) { Spacer(modifier = Modifier.heightIn(4.dp)) diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/history/PaymentsHistoryView.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/history/PaymentsHistoryView.kt index bd414c943..9d15df4f3 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/history/PaymentsHistoryView.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/history/PaymentsHistoryView.kt @@ -52,15 +52,18 @@ import fr.acinq.phoenix.android.utils.logger import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map -import kotlinx.datetime.Instant +import kotlin.time.Instant import kotlinx.datetime.Month import kotlinx.datetime.TimeZone import kotlinx.datetime.daysUntil import kotlinx.datetime.toInstant +import kotlinx.datetime.toJavaDayOfWeek +import kotlinx.datetime.toJavaMonth import kotlinx.datetime.toKotlinLocalDateTime import kotlinx.datetime.toLocalDateTime import java.time.format.TextStyle import java.util.Locale +import kotlin.time.ExperimentalTime private sealed class PaymentsGroup { @@ -94,6 +97,7 @@ private sealed class PaymentsGroup { } } +@OptIn(ExperimentalTime::class) @Composable fun PaymentsHistoryView( onBackClick: () -> Unit, @@ -111,7 +115,7 @@ fun PaymentsHistoryView( val groupedPayments = remember(payments) { val timezone = TimeZone.currentSystemDefault() - val (todaysDayOfWeek, today) = java.time.LocalDate.now().atTime(23, 59, 59).toKotlinLocalDateTime().let { it.dayOfWeek to it.toInstant(timezone) } + val (todaysDayOfWeek, today) = java.time.LocalDate.now().atTime(23, 59, 59).toKotlinLocalDateTime().let { it.dayOfWeek.toJavaDayOfWeek() to it.toInstant(timezone) } payments.values.groupBy { val paymentInstant = Instant.fromEpochMilliseconds(it.payment.createdAt) val daysElapsed = paymentInstant.daysUntil(today, timezone) @@ -189,7 +193,7 @@ fun PaymentsHistoryView( PaymentsGroup.Yesterday -> stringResource(id = R.string.payments_history_yesterday) PaymentsGroup.ThisWeek -> stringResource(id = R.string.payments_history_thisweek) PaymentsGroup.LastWeek -> stringResource(id = R.string.payments_history_lastweek) - is PaymentsGroup.Other -> "${header.month.getDisplayName(TextStyle.FULL, Locale.getDefault()).uppercase()} ${header.year}" + is PaymentsGroup.Other -> "${header.month.toJavaMonth().getDisplayName(TextStyle.FULL, Locale.getDefault()).uppercase()} ${header.year}" }, ) Spacer(modifier = Modifier.height(8.dp)) diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/receive/ReceiveViewModel.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/receive/ReceiveViewModel.kt index c3ebbcc18..01142b431 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/receive/ReceiveViewModel.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/receive/ReceiveViewModel.kt @@ -112,7 +112,7 @@ class ReceiveViewModel( if (isReusable) { val nodeParams = nodeParamsManager.nodeParams.filterNotNull().first() - val bolt12Offer = nodeParams.randomOffer(trampolineNodeId = NodeParamsManager.trampolineNodeId, amount = amount, description = description).first + val bolt12Offer = nodeParams.randomOffer(trampolineNodeId = NodeParamsManager.trampolineNodeId, amount = amount, description = description).offer lightningQRBitmap = QRCodeHelper.generateBitmap(bolt12Offer.encode()).asImageBitmap() log.debug("generated new bolt12 offer=${bolt12Offer.encode()}") lightningInvoiceState = LightningInvoiceState.Done.Bolt12(bolt12Offer) diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/send/bolt11/SendToBolt11.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/send/bolt11/SendToBolt11.kt index 37d213126..98ac0e9ca 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/send/bolt11/SendToBolt11.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/send/bolt11/SendToBolt11.kt @@ -47,7 +47,6 @@ import fr.acinq.phoenix.android.components.layouts.SplashLabelRow import fr.acinq.phoenix.android.components.layouts.SplashLayout import fr.acinq.phoenix.android.utils.converters.AmountFormatter.toPrettyString import fr.acinq.phoenix.android.utils.extensions.safeLet -import fr.acinq.phoenix.utils.extensions.isAmountlessTrampoline import kotlinx.coroutines.launch @Composable @@ -149,12 +148,6 @@ fun SendToBolt11View( Text(text = invoice.nodeId.toHex(), maxLines = 2, overflow = TextOverflow.MiddleEllipsis) } } - if (invoice.isAmountlessTrampoline()) { - Spacer(modifier = Modifier.height(16.dp)) - SplashLabelRow(label = "", helpMessage = stringResource(id = R.string.send_trampoline_amountless_warning_details)) { - Text(text = stringResource(id = R.string.send_trampoline_amountless_warning_label)) - } - } Spacer(modifier = Modifier.height(16.dp)) SplashLabelRow(label = stringResource(id = R.string.send_trampoline_fee_label)) { val amt = amount diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/send/offer/SendOfferViewModel.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/send/offer/SendOfferViewModel.kt index e2e2657e2..018dd677a 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/send/offer/SendOfferViewModel.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/send/offer/SendOfferViewModel.kt @@ -76,7 +76,7 @@ class SendOfferViewModel( val useRandomKey = contact == null || !contact.useOfferKey val payerKey = when (useRandomKey) { true -> Lightning.randomKey() - false -> nodeParamsManager.defaultOffer().payerKey + false -> nodeParamsManager.defaultOffer().privateKey } val peer = peerManager.getPeer() val payerNote = message.takeIf { it.isNotBlank() } diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/channels/SpendFromChannelAddress.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/channels/SpendFromChannelAddress.kt index 7b83f2d4b..35c67c064 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/channels/SpendFromChannelAddress.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/channels/SpendFromChannelAddress.kt @@ -164,17 +164,8 @@ fun SpendFromChannelAddress( is SpendFromChannelAddressViewState.Error.TxIndexMalformed -> { stringResource(id = R.string.spendchanneladdress_error_tx_index) } - is SpendFromChannelAddressViewState.Error.ChannelDataMalformed -> { - stringResource(id = R.string.spendchanneladdress_error_channel_data) - } - is SpendFromChannelAddressViewState.Error.ChannelDataDecryption -> { - stringResource(id = R.string.spendchanneladdress_error_channel_data) - } - is SpendFromChannelAddressViewState.Error.ChannelDataUnhandledState -> { - stringResource(id = R.string.spendchanneladdress_error_channel_data_state, state.stateClassName ?: "??") - } - is SpendFromChannelAddressViewState.Error.ChannelDataUnhandledVersion -> { - stringResource(id = R.string.spendchanneladdress_error_channel_data_version, state.version) + is SpendFromChannelAddressViewState.Error.InvalidChannelKeyPath -> { + stringResource(id = R.string.spendchanneladdress_error_channel_keypath) } is SpendFromChannelAddressViewState.Error.PublicKeyMalformed -> { stringResource(id = R.string.spendchanneladdress_error_remote_funding_pubkey, state.details) diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/channels/SpendFromChannelAddressViewModel.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/channels/SpendFromChannelAddressViewModel.kt index 6868ab1ce..9323240be 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/channels/SpendFromChannelAddressViewModel.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/channels/SpendFromChannelAddressViewModel.kt @@ -44,10 +44,7 @@ sealed class SpendFromChannelAddressViewState { data class Generic(val cause: Throwable) : Error() data object AmountMissing : Error() data object TxIndexMalformed : Error() - data object ChannelDataMalformed : Error() - data object ChannelDataDecryption : Error() - data class ChannelDataUnhandledState(val stateClassName: String?) : Error() - data class ChannelDataUnhandledVersion(val version: Int) : Error() + data object InvalidChannelKeyPath: Error() data class PublicKeyMalformed(val details: String) : Error() data class TransactionMalformed(val details: String) : Error() data class InvalidSig(val txId: TxId, val publicKey: PublicKey, val fundingScript: ByteVector, val signature: ByteVector64): Error() @@ -92,7 +89,7 @@ class SpendFromChannelAddressViewModel( business = business, amount = amount, fundingTxIndex = fundingTxIndex, - channelData = channelData, + channelKeyPath = "FIXME", remoteFundingPubkey = remoteFundingPubkey, unsignedTx = unsignedTx, ) @@ -102,17 +99,8 @@ class SpendFromChannelAddressViewModel( delay(300.milliseconds) state.value = SpendFromChannelAddressViewState.SignedTransaction(result.publicKey, result.signature) } - is SpendChannelAddressResult.Failure.ChannelDataDecryption -> { - state.value = SpendFromChannelAddressViewState.Error.ChannelDataDecryption - } - is SpendChannelAddressResult.Failure.ChannelDataMalformed -> { - state.value = SpendFromChannelAddressViewState.Error.ChannelDataMalformed - } - is SpendChannelAddressResult.Failure.ChannelDataUnhandledState -> { - state.value = SpendFromChannelAddressViewState.Error.ChannelDataUnhandledState(result.stateName) - } - is SpendChannelAddressResult.Failure.ChannelDataUnhandledVersion -> { - state.value = SpendFromChannelAddressViewState.Error.ChannelDataUnhandledVersion(result.version) + is SpendChannelAddressResult.Failure.InvalidChannelKeyPath -> { + state.value = SpendFromChannelAddressViewState.Error.InvalidChannelKeyPath } is SpendChannelAddressResult.Failure.Generic -> { state.value = SpendFromChannelAddressViewState.Error.Generic(result.error) diff --git a/phoenix-android/src/main/res/values-b+es+419/strings.xml b/phoenix-android/src/main/res/values-b+es+419/strings.xml index e20fe14c2..2bd5f34e4 100644 --- a/phoenix-android/src/main/res/values-b+es+419/strings.xml +++ b/phoenix-android/src/main/res/values-b+es+419/strings.xml @@ -131,8 +131,6 @@ Comisión N/D Cargando comisión… - Factura sin importe - La factura de este pago no solicita un importe concreto. Los nodos malintencionados pueden aprovecharse de esto durante el pago.\n\nPara mayor seguridad, pídele al destinatario que especifique un importe al generar la factura. Esperando canales… Pagar @@ -237,20 +235,6 @@ Datos de canales Compartir datos de canales - - - Importar datos de canales sin procesar - Esta pantalla es una herramienta de depuración que puede usarse para importar manualmente los datos de canales encriptados.\n\nUtilizar con precaución. - Blob de datos - Importar - Importando datos… - Importación correcta - Debes reiniciar Phoenix ahora. - Error de importación - El formato de los datos es incorrecto. Se espera un blob hexadecimal encriptado. - No se pudieron desencriptar los datos con esta billetera. - La versión %1$d no es compatible - Cargando… diff --git a/phoenix-android/src/main/res/values-cs/strings.xml b/phoenix-android/src/main/res/values-cs/strings.xml index fc0dc1ea0..de7d5098f 100644 --- a/phoenix-android/src/main/res/values-cs/strings.xml +++ b/phoenix-android/src/main/res/values-cs/strings.xml @@ -159,8 +159,6 @@ Poplatek N/A Načítání poplatku… - Faktura bez částky - Faktura pro tuto platbu nevyžaduje konkrétní částku. Toho mohou zneužít škodlivé uzly během platby. \n\nPro jistotu požádejte příjemce, aby při generování faktury uvedl částku. Připojování… Zaplatit Potvrdit & Zaplatit @@ -301,20 +299,6 @@ Data kanálů Sdílet data kanálu - - - Importovat holá data kanálu - Tato obrazovka slouží jako debugovací nástroj, který umožňuje ručně importovat zašifrovaná data kanálů.\n\nPoužívejte opatrně. - Blob dat - Importovat - Importování dat… - Import proběhl úspěšně - Nyní musíte restartovat Phoenix. - Import selhal - Data jsou chybná. Očekává se zašifrovaný hexadecimální blob. - Data se nepodařilo rozšifrovat touto peněženkou. - Verze %1$d není podporovaná - Utratit z adresy kanálu @@ -334,10 +318,6 @@ Nepodařilo se podepsat data Neplatná částka Neplatný index transakce - Chybná data kanálu - Nepodařilo se rozšifrovat data kanálu - Neošetřený stav kanálu [%1$s] - Chybná verze kanálu [%1$s] Chybný vzdálený financující funding veřejný klíč [%1$s] Chybná nepodepsaná transakce [%1$s] Chybný vzdálený financující funding veřejný klíč [%1$s] diff --git a/phoenix-android/src/main/res/values-de/strings.xml b/phoenix-android/src/main/res/values-de/strings.xml index 8b7c589f4..0d0c09ca3 100644 --- a/phoenix-android/src/main/res/values-de/strings.xml +++ b/phoenix-android/src/main/res/values-de/strings.xml @@ -126,8 +126,6 @@ Gebühr N/A Lade Gebühr… - Rechnung ohne Betrag - Die Rechnung für diese Zahlung hat keinen festgelegten Betrag. Dies könnte von bösartigen Nodes ausgenutzt werden.\n\nUm kein Risiko einzugehen, bitten Sie den Empfänger, bei der Erstellung der Rechnung einen Betrag festzulegen. Verbinde… Zahlen @@ -234,20 +232,6 @@ Kanal-Daten Teile Kanal-Daten - - - Rohdaten der Kanäle importieren - Dieser Bildschirm ist ein Debugging-Tool, mit dem du verschlüsselte Kanaldaten manuell importieren kannst. - Datenblob - Importieren - Daten importieren.. - Import erfolgreich - Du musst Phoenix jetzt neu starten. - Der Import ist fehlgeschlagen - Die Daten sind missgebildet. Es wird ein verschlüsselter Hex-Blob erwartet. - Die Daten konnten von dieser Wallet nicht entschlüsselt werden. - Version %1$d wird nicht unterstützt - Lädt… diff --git a/phoenix-android/src/main/res/values-fr/strings.xml b/phoenix-android/src/main/res/values-fr/strings.xml index 758ffc8bf..e3a549aed 100644 --- a/phoenix-android/src/main/res/values-fr/strings.xml +++ b/phoenix-android/src/main/res/values-fr/strings.xml @@ -129,8 +129,6 @@ Frais N/A Calcul des frais… - Requête sans montant - La requête pour ce paiement ne précise pas de montant. Cela peut être exploité par des noeuds malicieux pendant le paiement.\n\nPour plus de sécurité, demandez au destinataire de spécifier un montant lorsqu\'il crée sa requête de paiement. En attente des canaux… Payer @@ -247,20 +245,6 @@ Données du canal Partager les données du canal - - - Importer des données de canal - Cet écran est un outil de debug utilisé pour importer manuellement des données de canaux chiffrées.\n\nUtiliser avec prudence. - Données - Importer - Import des données… - Import réussi - Vous devez maintenant redémarrer Phoenix. - Import échoué - Les données sont malformées. Un blob hexa est attendu. - Les données n\'ont pas pu être déchiffrées. - La version %1$d n\'est pas supportée - Chargement… diff --git a/phoenix-android/src/main/res/values-pt-rBR/strings.xml b/phoenix-android/src/main/res/values-pt-rBR/strings.xml index f7225ced4..c6fdb2891 100644 --- a/phoenix-android/src/main/res/values-pt-rBR/strings.xml +++ b/phoenix-android/src/main/res/values-pt-rBR/strings.xml @@ -129,8 +129,6 @@ Comissão N/A Carregando comissão… - Fatura sem valor - A fatura deste pagamento não solicita um valor específico. Nós maliciosos podem tirar vantagem disso durante a finalização da compra.\n\nPara maior segurança, peça ao destinatário para especificar um valor ao gerar a fatura. Aguardando canais… Pagar @@ -234,20 +232,6 @@ Dados do canal Compartilhar dados do canal - - - Importar dados brutos do canal - Esta tela é uma ferramenta de depuração que pode ser usada para importar manualmente dados criptografados do canal.\n\nUse com cuidado. - Blob de dados - Importar - Importando dados… - Importação bem-sucedida - Você deve reiniciar o Phoenix agora. - Erro de importação - O formato dos dados está incorreto. É esperado um blob hexadecimal criptografado. - Não foi possível descriptografar os dados com esta carteira. - A versão %1$d não é suportada - Carregando… diff --git a/phoenix-android/src/main/res/values-sk/strings.xml b/phoenix-android/src/main/res/values-sk/strings.xml index 232566f30..6bb5fcb81 100644 --- a/phoenix-android/src/main/res/values-sk/strings.xml +++ b/phoenix-android/src/main/res/values-sk/strings.xml @@ -127,8 +127,6 @@ Poplatok N/A Načítavanie poplatku… - Faktúra bez sumy - Faktúra pre túto platbu nevyžaduje konkrétnu sumu. Toto môžu zneužiť škodlivé uzly počas platby. \n\nPre istotu požiadajte príjemcu, aby pri generovaní faktúry uviedol sumu. Pripájanie… Zaplatiť Skúsiť znova @@ -245,20 +243,6 @@ Dáta kanálov Zdieľať dáta kanálov - - - Importovať surové údaje kanála - Táto obrazovka je nástroj na ladenie, ktorý možno použiť na ručný import zašifrovaných údajov kanálov.\n\nPoužívajte s opatrnosťou. - Dátový blok - Importovať - Importujú sa údaje… - Import úspešný - Teraz musíte reštartovať Phoenix. - Import zlyhal - Údaje sú poškodené. Očakáva sa zašifrovaný hex blob. - Údaje nemohla dešifrovať táto peňaženka. - Verzia %1$d nie je podporovaná - Načítavanie… diff --git a/phoenix-android/src/main/res/values-sw/strings.xml b/phoenix-android/src/main/res/values-sw/strings.xml index 1d72cdd51..d47c037d2 100644 --- a/phoenix-android/src/main/res/values-sw/strings.xml +++ b/phoenix-android/src/main/res/values-sw/strings.xml @@ -136,8 +136,6 @@ Gharama N/A Inapakia gharama… - Hati bila kiasi - Hati ya malipo kwa malipo haya haitaji kiasi maalum. Hii inaweza kutumiwa vibaya na nodi hatari wakati wa malipo.\n\nIli kuwa salama, uliza mpokeaji aweke kiasi wakati wa kutengeneza hati. Inasubiri njia za malipo… Lipa Jaribu tena @@ -259,20 +257,6 @@ Data za kanali Shiriki data za kanali - - - Ingiza data za kanali za raw - Skrini hii ni zana ya uundaji inayoweza kutumika kwa kuingiza data za kanali zilizowekwa siri kwa mkono.\n\nTumia kwa tahadhari. - Data blob - Ingiza - Inaingiza data… - Ingizo limefanikiwa - Sasa lazima upige upya Phoenix. - Ingizo limekosa - Data zimeharibika. Blob ya hex yenye usalama inatarajiwa. - Data haiwezi kufunguliwa na pochi hii. - Toleo %1$d halisaidiwi - Inapakia… diff --git a/phoenix-android/src/main/res/values-vi/strings.xml b/phoenix-android/src/main/res/values-vi/strings.xml index 2f4645f03..7b77d6d51 100644 --- a/phoenix-android/src/main/res/values-vi/strings.xml +++ b/phoenix-android/src/main/res/values-vi/strings.xml @@ -128,8 +128,6 @@ Phí Không áp dụng Đang tải phí… - Hoá đơn không có số tiền - Hóa đơn cho khoản thanh toán này không điền số tiền cụ thể. Các nút mạng khác có thể lợi dụng điều này và lừa đảo bạn trong quá trình thanh toán.\n\nĐể tránh nguy cơ lừa đảo, hãy yêu cầu người nhận ghi rõ số tiền khi lập hóa đơn. Đang chờ các kênh… Thanh toán @@ -237,20 +235,6 @@ Dữ liệu các kênh Chia sẻ dữ liệu kênh - - - Nhập dữ liệu thô của kênh - Màn hình này là một công cụ gỡ lỗi mà bạn có thể nhập bằng tay các dữ liệu mã hóa của các kênh.\n\nHãy thận trọng khi sử dụng. - Dữ liệu đối tượng nhị phân lớn - Nhập - Đang nhập dữ liệu… - Nhập thành công - Bạn cần khởi động lại Phoenix ngay bây giờ. - Nhập không thành công - Dữ liệu không đúng định dạng. Phải là định dạng hex đối tượng nhị phân lớn đã được mã hoá. - Dữ liệu không thể giải mã được bằng ví này. - Phiên bản %1$d không đuợc hỗ trợ - Đang tải… diff --git a/phoenix-android/src/main/res/values/strings.xml b/phoenix-android/src/main/res/values/strings.xml index a5083c492..3173419d1 100644 --- a/phoenix-android/src/main/res/values/strings.xml +++ b/phoenix-android/src/main/res/values/strings.xml @@ -165,8 +165,6 @@ Fee N/A Loading fee… - Amountless invoice - The invoice for this payment does not request a specific amount. This may be exploited by malicious nodes during the payment.\n\nTo be safe, ask the recipient to specify an amount when generating the invoice. Waiting for channels… Pay Confirm & Pay @@ -310,20 +308,6 @@ Channels data Share channel data - - - Import raw channel data - This screen is a debugging tool that can be used to manually import encrypted channels data.\n\nUse with caution. - Data blob - Import - Importing data… - Import successful - You must now restart Phoenix. - Import has failed - Data are malformed. A encrypted hex blob is expected. - Data could not be decrypted by this wallet. - Version %1$d is not supported - Spend channel address @@ -343,10 +327,7 @@ Failed to sign data Invalid amount Invalid tx index - Malformed channel data - Cannot decrypt channel data - Unhandled channel state [%1$s] - Malformed channel version [%1$s] + Invalid channel keypath Malformed remote funding pubkey [%1$s] Malformed unsigned tx [%1$s] Malformed remote funding pubkey [%1$s] From 3b8cecf0cf0721a83ce8b0dd86a0afa3d675c5b5 Mon Sep 17 00:00:00 2001 From: Robbie Hanson <304604+robbiehanson@users.noreply.github.com> Date: Wed, 15 Oct 2025 10:19:00 -0500 Subject: [PATCH 3/8] (ios) Fixing compiler issues, and removing dead code --- .../phoenix-ios.xcodeproj/project.pbxproj | 4 - phoenix-ios/phoenix-ios/Localizable.xcstrings | 11 + .../channels/ChannelsConfigurationView.swift | 75 +----- .../channels/ImportChannelsView.swift | 225 ------------------ .../views/receive/LightningDualView.swift | 4 +- .../phoenix-ios/views/send/ValidateView.swift | 4 +- .../views/tools/MergeChannelsView.swift | 2 +- .../acinq/phoenix/utils/LightningExposure.kt | 10 +- 8 files changed, 22 insertions(+), 313 deletions(-) delete mode 100644 phoenix-ios/phoenix-ios/views/configuration/advanced/channels/ImportChannelsView.swift diff --git a/phoenix-ios/phoenix-ios.xcodeproj/project.pbxproj b/phoenix-ios/phoenix-ios.xcodeproj/project.pbxproj index 8c2d3dd48..dc0e28b04 100644 --- a/phoenix-ios/phoenix-ios.xcodeproj/project.pbxproj +++ b/phoenix-ios/phoenix-ios.xcodeproj/project.pbxproj @@ -449,7 +449,6 @@ DCFB8DF72A94066100947698 /* Task+Sleep.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFB8DF62A94066100947698 /* Task+Sleep.swift */; }; DCFB8DF92A94112A00947698 /* Dictionary+MapKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFB8DF82A94112A00947698 /* Dictionary+MapKeys.swift */; }; DCFBC5592AE2CFEF00E3A418 /* BizNotificationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFBC5582AE2CFEF00E3A418 /* BizNotificationCell.swift */; }; - DCFBC55B2AEAC2B000E3A418 /* ImportChannelsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFBC55A2AEAC2B000E3A418 /* ImportChannelsView.swift */; }; DCFC72042862237400D6B293 /* Asserts.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFC72032862237400D6B293 /* Asserts.swift */; }; DCFD079126D84A380020DD8E /* HorizontalActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFD079026D84A380020DD8E /* HorizontalActivity.swift */; }; F4AED298257A50CD009485C1 /* LogsConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4AED296257A50CD009485C1 /* LogsConfigurationView.swift */; }; @@ -897,7 +896,6 @@ DCFB8DF62A94066100947698 /* Task+Sleep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Task+Sleep.swift"; sourceTree = ""; }; DCFB8DF82A94112A00947698 /* Dictionary+MapKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+MapKeys.swift"; sourceTree = ""; }; DCFBC5582AE2CFEF00E3A418 /* BizNotificationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BizNotificationCell.swift; sourceTree = ""; }; - DCFBC55A2AEAC2B000E3A418 /* ImportChannelsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportChannelsView.swift; sourceTree = ""; }; DCFC72032862237400D6B293 /* Asserts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Asserts.swift; sourceTree = ""; }; DCFD079026D84A380020DD8E /* HorizontalActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HorizontalActivity.swift; sourceTree = ""; }; F4AED296257A50CD009485C1 /* LogsConfigurationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogsConfigurationView.swift; sourceTree = ""; }; @@ -1854,7 +1852,6 @@ children = ( 53BEF648B3A03C66B611BC06 /* ChannelsConfigurationView.swift */, DCA5391B29F7202F001BD3D5 /* ChannelInfoPopup.swift */, - DCFBC55A2AEAC2B000E3A418 /* ImportChannelsView.swift */, ); path = channels; sourceTree = ""; @@ -2439,7 +2436,6 @@ DC46CB1628D9F30500C4EAC7 /* LoadingView.swift in Sources */, DC2ABAD92BED142900C11C9C /* ShakeEffect.swift in Sources */, DC72CEFD2C9B25CC00C810A8 /* PaymentDetails.swift in Sources */, - DCFBC55B2AEAC2B000E3A418 /* ImportChannelsView.swift in Sources */, DCFB8DF72A94066100947698 /* Task+Sleep.swift in Sources */, DC4CF3CA2BE91FED003A957F /* SetNewPinView.swift in Sources */, DC39D4EF287497440030F18D /* SmartModal.swift in Sources */, diff --git a/phoenix-ios/phoenix-ios/Localizable.xcstrings b/phoenix-ios/phoenix-ios/Localizable.xcstrings index eae4f2520..98f829fe2 100644 --- a/phoenix-ios/phoenix-ios/Localizable.xcstrings +++ b/phoenix-ios/phoenix-ios/Localizable.xcstrings @@ -5865,6 +5865,7 @@ } }, "An unknown error has occurred." : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -13084,6 +13085,7 @@ } }, "Data could not be decrypted by this wallet." : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -13124,6 +13126,7 @@ } }, "Data is malformed. An encrypted hex blob is expected." : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -21220,6 +21223,7 @@ }, "Import channels" : { "comment" : "Navigation bar title", + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -21260,6 +21264,7 @@ } }, "Import has failed" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -21300,6 +21305,7 @@ } }, "Import successful" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -21380,6 +21386,7 @@ } }, "Importing data…" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -29066,6 +29073,7 @@ } }, "Paste encrypted data blob here" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -41516,6 +41524,7 @@ } }, "This screen is a debugging tool that can be used to manually import encrypted channels data.\n\nUse with caution." : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -44511,6 +44520,7 @@ } }, "Version %d is not supported" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -47713,6 +47723,7 @@ } }, "You must now restart Phoenix." : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { diff --git a/phoenix-ios/phoenix-ios/views/configuration/advanced/channels/ChannelsConfigurationView.swift b/phoenix-ios/phoenix-ios/views/configuration/advanced/channels/ChannelsConfigurationView.swift index 63b7ceffe..801f34b4d 100644 --- a/phoenix-ios/phoenix-ios/views/configuration/advanced/channels/ChannelsConfigurationView.swift +++ b/phoenix-ios/phoenix-ios/views/configuration/advanced/channels/ChannelsConfigurationView.swift @@ -10,10 +10,6 @@ fileprivate var log = LoggerFactory.shared.logger(filename, .warning) struct ChannelsConfigurationView: View { - enum NavLinkTag: String, Codable { - case ImportChannels - } - @State var sharing: String? = nil @State var channels: [LocalChannelInfo] = [] @@ -24,17 +20,12 @@ struct ChannelsConfigurationView: View { ) @State var capacityHeight: CGFloat? = nil - // - @State var navLinkTag: NavLinkTag? = nil - // - @StateObject var toast = Toast() @ObservedObject var currencyPrefs = CurrencyPrefs.current @Environment(\.presentationMode) var presentationMode: Binding - @EnvironmentObject var navCoordinator: NavigationCoordinator @EnvironmentObject var popoverState: PopoverState @EnvironmentObject var deepLinkManager: DeepLinkManager @@ -49,12 +40,6 @@ struct ChannelsConfigurationView: View { .navigationTitle(NSLocalizedString("Payment channels", comment: "Navigation bar title")) .navigationBarTitleDisplayMode(.inline) .navigationBarItems(trailing: menuButton()) - .navigationStackDestination(isPresented: navLinkTagBinding()) { - navLinkView() - } - .navigationStackDestination(for: NavLinkTag.self) { tag in // iOS 17+ - navLinkView(tag) - } } @ViewBuilder @@ -202,9 +187,7 @@ struct ChannelsConfigurationView: View { .foregroundColor(Color.primary) } } // - // .padding([.leading], 10) .padding([.top, .bottom], 8) - // .padding(.trailing) } // } @@ -212,15 +195,6 @@ struct ChannelsConfigurationView: View { func menuButton() -> some View { Menu { - Button { - importChannels() - } label: { - Label { - Text("Import channels") - } icon: { - Image(systemName: "square.and.arrow.down") - } - } if !channels.isEmpty { Button { closeAllChannels() @@ -247,37 +221,10 @@ struct ChannelsConfigurationView: View { } } - @ViewBuilder - func navLinkView() -> some View { - - if let tag = self.navLinkTag { - navLinkView(tag) - } else { - EmptyView() - } - } - - @ViewBuilder - func navLinkView(_ tag: NavLinkTag) -> some View { - - switch tag { - case .ImportChannels: - ImportChannelsView() - } - } - // -------------------------------------------------- // MARK: View Helpers // -------------------------------------------------- - func navLinkTagBinding() -> Binding { - - return Binding( - get: { navLinkTag != nil }, - set: { if !$0 { navLinkTag = nil }} - ) - } - func hasUsableChannels() -> Bool { return channels.contains { $0.isUsable } @@ -335,18 +282,8 @@ struct ChannelsConfigurationView: View { // MARK: Actions // -------------------------------------------------- - func navigateTo(_ tag: NavLinkTag) { - log.trace("navigateTo(\(tag.rawValue))") - - if #available(iOS 17, *) { - navCoordinator.path.append(tag) - } else { - navLinkTag = tag - } - } - func showChannelInfoPopover(_ channel: LocalChannelInfo) { - log.trace("showChannelInfoPopover()") + log.trace(#function) popoverState.display(dismissable: true) { ChannelInfoPopup( @@ -357,14 +294,8 @@ struct ChannelsConfigurationView: View { } } - func importChannels() { - log.trace("importChannels()") - - navigateTo(.ImportChannels) - } - func closeAllChannels() { - log.trace("closeAllChannels()") + log.trace(#function) presentationMode.wrappedValue.dismiss() DispatchQueue.main.asyncAfter(deadline: .now() + 0.55) { @@ -373,7 +304,7 @@ struct ChannelsConfigurationView: View { } func forceCloseAllChannels() { - log.trace("forceCloseAllChannels()") + log.trace(#function) presentationMode.wrappedValue.dismiss() DispatchQueue.main.asyncAfter(deadline: .now() + 0.55) { diff --git a/phoenix-ios/phoenix-ios/views/configuration/advanced/channels/ImportChannelsView.swift b/phoenix-ios/phoenix-ios/views/configuration/advanced/channels/ImportChannelsView.swift deleted file mode 100644 index 1f72d7b43..000000000 --- a/phoenix-ios/phoenix-ios/views/configuration/advanced/channels/ImportChannelsView.swift +++ /dev/null @@ -1,225 +0,0 @@ -import SwiftUI -import PhoenixShared - -fileprivate let filename = "ImportChannelsView" -#if DEBUG && true -fileprivate var log = LoggerFactory.shared.logger(filename, .trace) -#else -fileprivate var log = LoggerFactory.shared.logger(filename, .warning) -#endif - -fileprivate enum ImportResultFailure { - case Generic(error: KotlinThrowable) - case UnknownVersion(version: Int32) - case MalformedData - case DecryptionError - case Unknown -} - -fileprivate enum ImportResult { - case Success - case Failure(reason: ImportResultFailure) -} - - -struct ImportChannelsView: View { - - @State var dataBlobText: String = "" - - @State var importInProgress: Bool = false - @State var importResult: ChannelsImportResult? = nil - - @ViewBuilder - var body: some View { - - content() - .navigationTitle(NSLocalizedString("Import channels", comment: "Navigation bar title")) - .navigationBarTitleDisplayMode(.inline) - } - - @ViewBuilder - func content() -> some View { - - List { - section_info() - section_status() - } - .listStyle(.insetGrouped) - .listBackgroundColor(.primaryBackground) - } - - @ViewBuilder - func section_info() -> some View { - - Section { - VStack(alignment: HorizontalAlignment.leading, spacing: 0) { - Text( - """ - This screen is a debugging tool that can be used to manually import \ - encrypted channels data. - - Use with caution. - """ - ) - .padding(.bottom, 20) - - HStack(alignment: VerticalAlignment.center, spacing: 0) { - TextField("Paste encrypted data blob here", text: $dataBlobText) - - // Clear button (appears when TextField's text is non-empty) - Button { - dataBlobText = "" - } label: { - Image(systemName: "multiply.circle.fill") - .foregroundColor(.secondary) - } - .isHidden(dataBlobText == "") - } // - .padding(.all, 8) - .background( - RoundedRectangle(cornerRadius: 8) - .fill(Color(UIColor.systemBackground)) - ) - .overlay( - RoundedRectangle(cornerRadius: 8) - .stroke(Color.textFieldBorder, lineWidth: 1) - ) - } // - } // - } - - @ViewBuilder - func section_status() -> some View { - - Section { - VStack(alignment: HorizontalAlignment.center, spacing: 15) { - - Button { - importButtonTapped() - } label: { - HStack(alignment: VerticalAlignment.center, spacing: 5) { - Text("Import") - Image(systemName: "square.and.arrow.down") - .imageScale(.small) - } - } - .disabled(importInProgress || dataBlobTextIsEmpty()) - .font(.title3.weight(.medium)) - - if importInProgress { - - HStack(alignment: VerticalAlignment.center, spacing: 5) { - ProgressView() - .progressViewStyle(CircularProgressViewStyle()) - Text("Importing data…") - } // - - } else if let result = importResult { - - switch toEnum(result) { - case .Success: - - Group { - HStack(alignment: VerticalAlignment.center, spacing: 5) { - Image(systemName: "checkmark.circle") - Text("Import successful") - } - Text("You must now restart Phoenix.").bold() - } - .foregroundColor(.appPositive) - - case .Failure(let reason): - - Group { - HStack(alignment: VerticalAlignment.center, spacing: 5) { - Image(systemName: "xmark.circle") - Text("Import has failed") - } - switch reason { - case .Generic(let error): - Text(verbatim: error.message ?? "Unknown error thrown") - case .UnknownVersion(let version): - Text("Version \(version) is not supported") - case .MalformedData: - Text("Data is malformed. An encrypted hex blob is expected.") - case .DecryptionError: - Text("Data could not be decrypted by this wallet.") - case .Unknown: - Text("An unknown error has occurred.") - } // - } - .foregroundColor(.appNegative) - - } // - } - - } // - .frame(maxWidth: .infinity) - - } // - } - - func dataBlobTextIsEmpty() -> Bool { - - return trimmedDataBlobText().isEmpty - } - - func trimmedDataBlobText() -> String { - - return dataBlobText.trimmingCharacters(in: .whitespacesAndNewlines) - } - - func importButtonTapped() { - log.trace("importButtonTapped()") - - importInProgress = true - - let data = dataBlobText - let biz = Biz.business - Task { @MainActor in - - // Give the UI time to update and display "importing..." - // It looks better that way. - try await Task.sleep(seconds: 0.5) - - let result: ChannelsImportResult - do { - result = try await ChannelsImportHelper.shared.doImportChannels(data: data, biz: biz) - } catch { - // Errors SHOULD be caught in Kotlin, and returned as a Failure result. - // So if an error is thrown, it's a bug in Kotlin. - log.error("ChannelsImportHelper.shared.doImportChannels(): threw error: \(error)") - - let message = "Threw error: \(error)" - let kotlinError = KotlinThrowable(message: message) - result = ChannelsImportResult.Failure.FailureGeneric(error: kotlinError) - } - - importResult = result - importInProgress = false - } - } - - private func toEnum(_ result: ChannelsImportResult) -> ImportResult { - - switch result { - case _ as ChannelsImportResult.Success: - return .Success - - case let r as ChannelsImportResult.FailureGeneric: - return .Failure(reason: .Generic(error: r.error)) - - case let r as ChannelsImportResult.FailureUnknownVersion: - return .Failure(reason: .UnknownVersion(version: r.version)) - - case _ as ChannelsImportResult.FailureMalformedData: - return .Failure(reason: .MalformedData) - - case _ as ChannelsImportResult.FailureDecryptionError: - return .Failure(reason: .DecryptionError) - - default: - return .Failure(reason: .Unknown) - } - } -} diff --git a/phoenix-ios/phoenix-ios/views/receive/LightningDualView.swift b/phoenix-ios/phoenix-ios/views/receive/LightningDualView.swift index d820a1b0f..e561d2405 100644 --- a/phoenix-ios/phoenix-ios/views/receive/LightningDualView.swift +++ b/phoenix-ios/phoenix-ios/views/receive/LightningDualView.swift @@ -707,7 +707,7 @@ struct LightningDualView: View { fixedDesc = finalDesc ?? "" } - let offerPair = Lightning_kmpOfferManagerCompanion.shared.deterministicOffer( + let offerAndKey = Lightning_kmpOfferManagerCompanion.shared.deterministicOffer( chainHash: NodeParamsManager.companion.chain.chainHash, nodePrivateKey: nodeParams.nodePrivateKey, trampolineNodeId: NodeParamsManager.companion.trampolineNodeId, @@ -715,7 +715,7 @@ struct LightningDualView: View { description: fixedDesc, pathId: nil ) - offerStr = offerPair.first!.encode() + offerStr = offerAndKey.offer.encode() } } diff --git a/phoenix-ios/phoenix-ios/views/send/ValidateView.swift b/phoenix-ios/phoenix-ios/views/send/ValidateView.swift index 6bad8846c..2c8e344d1 100644 --- a/phoenix-ios/phoenix-ios/views/send/ValidateView.swift +++ b/phoenix-ios/phoenix-ios/views/send/ValidateView.swift @@ -1902,8 +1902,8 @@ struct ValidateView: View { let payerKey: Bitcoin_kmpPrivateKey if contact?.useOfferKey ?? false { - let offerData = try await Biz.business.nodeParamsManager.defaultOffer() - payerKey = offerData.payerKey + let offerAndKey = try await Biz.business.nodeParamsManager.defaultOffer() + payerKey = offerAndKey.privateKey } else { payerKey = Lightning_randomKey() } diff --git a/phoenix-ios/phoenix-ios/views/tools/MergeChannelsView.swift b/phoenix-ios/phoenix-ios/views/tools/MergeChannelsView.swift index 0fdca7c43..d51805835 100644 --- a/phoenix-ios/phoenix-ios/views/tools/MergeChannelsView.swift +++ b/phoenix-ios/phoenix-ios/views/tools/MergeChannelsView.swift @@ -545,7 +545,7 @@ struct MergeChannelsView: View { } if !operationInProgress { - let allChannelsReady = channels.allSatisfy { $0.isTerminated || $0.isUsable || $0.isLegacyWait } + let allChannelsReady = channels.allSatisfy { $0.isTerminated || $0.isUsable } if !allChannelsReady { return NSLocalizedString("restoring connections", comment: "") } diff --git a/phoenix-shared/src/iosMain/kotlin/fr/acinq/phoenix/utils/LightningExposure.kt b/phoenix-shared/src/iosMain/kotlin/fr/acinq/phoenix/utils/LightningExposure.kt index 892f7e59c..65f0ff65c 100644 --- a/phoenix-shared/src/iosMain/kotlin/fr/acinq/phoenix/utils/LightningExposure.kt +++ b/phoenix-shared/src/iosMain/kotlin/fr/acinq/phoenix/utils/LightningExposure.kt @@ -28,11 +28,9 @@ import fr.acinq.lightning.channel.states.ChannelState import fr.acinq.lightning.channel.states.Closed import fr.acinq.lightning.channel.states.Closing import fr.acinq.lightning.channel.states.Offline -import fr.acinq.lightning.crypto.KeyManager -import fr.acinq.lightning.db.IncomingPayment +import fr.acinq.lightning.crypto.SwapInOnChainKeys import fr.acinq.lightning.db.LightningOutgoingPayment import fr.acinq.lightning.io.NativeSocketException -import fr.acinq.lightning.io.OfferNotPaid import fr.acinq.lightning.io.PaymentNotSent import fr.acinq.lightning.io.PaymentProgress import fr.acinq.lightning.io.PaymentSent @@ -50,11 +48,9 @@ import fr.acinq.lightning.utils.toByteArray import fr.acinq.lightning.utils.toNSData import fr.acinq.lightning.wire.LiquidityAds import fr.acinq.lightning.wire.OfferTypes -import fr.acinq.phoenix.managers.SendManager import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import platform.Foundation.NSData -import kotlin.time.Duration.Companion.seconds /** * Class types from lightning-kmp & bitcoin-kmp are not exported to iOS unless we explicitly @@ -340,7 +336,7 @@ fun ByteArray_toNSDataSlice(buffer: ByteArray, offset: Int, length: Int): NSData fun ByteArray_toNSData(buffer: ByteArray): NSData = buffer.toNSData() fun WalletState.WalletWithConfirmations._spendExpiredSwapIn( - swapInKeys: KeyManager.SwapInOnChainKeys, + swapInKeys: SwapInOnChainKeys, scriptPubKey: ByteVector, feerate: FeeratePerKw ): Pair? { @@ -358,7 +354,7 @@ fun OfferManager.Companion._deterministicOffer( amount: MilliSatoshi?, description: String?, pathId: ByteVector32?, -): Pair { +): OfferTypes.OfferAndKey { return deterministicOffer( chainHash = chainHash, nodePrivateKey = nodePrivateKey, From c2127a9d79e7e89169b4f41e4c471bd6cf41bf5b Mon Sep 17 00:00:00 2001 From: Robbie Hanson <304604+robbiehanson@users.noreply.github.com> Date: Wed, 15 Oct 2025 14:21:55 -0500 Subject: [PATCH 4/8] Fixing several issues regarding OfferPaymentMetdata V1 vs V2 --- .../android/payments/details/PaymentLine.kt | 1 + .../details/splash/SplashIncomingBolt12.kt | 2 ++ .../kotlin/KotlinExtensions+Payments.swift | 4 +++- .../fr.acinq.phoenix/db/SqlitePaymentsDb.kt | 1 + .../db/contacts/SqliteContactsDb.kt | 5 ++++- .../utils/extensions/PaymentExtensions.kt | 2 +- .../extensions/PaymentRequestExtensions.kt | 21 +++++++++++++++---- 7 files changed, 29 insertions(+), 7 deletions(-) diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/PaymentLine.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/PaymentLine.kt index 597edd5cb..3c95d73b1 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/PaymentLine.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/PaymentLine.kt @@ -63,6 +63,7 @@ import fr.acinq.phoenix.data.WalletPaymentInfo import fr.acinq.phoenix.utils.extensions.WalletPaymentState import fr.acinq.phoenix.utils.extensions.incomingOfferMetadata import fr.acinq.phoenix.utils.extensions.outgoingInvoiceRequest +import fr.acinq.phoenix.utils.extensions.payerNote import fr.acinq.phoenix.utils.extensions.state @Composable diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/splash/SplashIncomingBolt12.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/splash/SplashIncomingBolt12.kt index cdc2bf2b0..8cd57bf38 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/splash/SplashIncomingBolt12.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/splash/SplashIncomingBolt12.kt @@ -41,6 +41,8 @@ import fr.acinq.phoenix.android.components.contact.OfferContactState import fr.acinq.phoenix.android.utils.extensions.smartDescription import fr.acinq.phoenix.data.WalletPaymentMetadata import fr.acinq.phoenix.utils.extensions.incomingOfferMetadata +import fr.acinq.phoenix.utils.extensions.payerKey +import fr.acinq.phoenix.utils.extensions.payerNote import fr.acinq.phoenix.utils.extensions.state diff --git a/phoenix-ios/phoenix-ios/kotlin/KotlinExtensions+Payments.swift b/phoenix-ios/phoenix-ios/kotlin/KotlinExtensions+Payments.swift index 8998133f7..910381a1b 100644 --- a/phoenix-ios/phoenix-ios/kotlin/KotlinExtensions+Payments.swift +++ b/phoenix-ios/phoenix-ios/kotlin/KotlinExtensions+Payments.swift @@ -70,6 +70,8 @@ extension WalletPaymentInfo { if let bolt11 = incomingPayment as? Lightning_kmpBolt11IncomingPayment { return sanitize(bolt11.paymentRequest.description_) + } else if let bolt12 = incomingPayment as? Lightning_kmpBolt12IncomingPayment { + return sanitize(bolt12.metadata.description__) } } else if let outgoingPayment = payment as? Lightning_kmpOutgoingPayment { @@ -130,7 +132,7 @@ extension WalletPaymentInfo { var msg: String? = nil if let incomingOfferMetadata = payment.incomingOfferMetadata() { - msg = incomingOfferMetadata.payerNote + msg = incomingOfferMetadata.payerNote_ } else if let outgoingInvoiceRequest = payment.outgoingInvoiceRequest() { msg = outgoingInvoiceRequest.payerNote diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/SqlitePaymentsDb.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/SqlitePaymentsDb.kt index ba49d437a..aa1490172 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/SqlitePaymentsDb.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/SqlitePaymentsDb.kt @@ -35,6 +35,7 @@ import fr.acinq.phoenix.db.sqldelight.PaymentsDatabase import fr.acinq.phoenix.managers.PaymentMetadataQueue import fr.acinq.phoenix.utils.extensions.incomingOfferMetadata import fr.acinq.phoenix.utils.extensions.outgoingInvoiceRequest +import fr.acinq.phoenix.utils.extensions.payerKey import kotlin.collections.List import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/contacts/SqliteContactsDb.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/contacts/SqliteContactsDb.kt index fb0cd886c..13ef022f0 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/contacts/SqliteContactsDb.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/contacts/SqliteContactsDb.kt @@ -16,6 +16,7 @@ import fr.acinq.phoenix.db.migrations.appDb.v7.AfterVersion7Result import fr.acinq.phoenix.db.sqldelight.PaymentsDatabase import fr.acinq.phoenix.utils.extensions.incomingOfferMetadata import fr.acinq.phoenix.utils.extensions.outgoingInvoiceRequest +import fr.acinq.phoenix.utils.extensions.payerKey import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -138,7 +139,9 @@ class SqliteContactsDb( private fun contactIdForPayment(payment: WalletPayment, metadata: WalletPaymentMetadata?): UUID? { return if (payment is Bolt12IncomingPayment) { payment.incomingOfferMetadata()?.let { offerMetadata -> - contactIdForPayerPubKey(offerMetadata.payerKey) + offerMetadata.payerKey?.let { payerKey -> + contactIdForPayerPubKey(payerKey) + } } } else { metadata?.lightningAddress?.let { address -> diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/extensions/PaymentExtensions.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/extensions/PaymentExtensions.kt index 3a238dd51..e93249ee3 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/extensions/PaymentExtensions.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/extensions/PaymentExtensions.kt @@ -72,5 +72,5 @@ fun WalletPayment.errorMessage(): String? = when (this) { is IncomingPayment -> null } -fun WalletPayment.incomingOfferMetadata(): OfferPaymentMetadata.V1? = (this as? Bolt12IncomingPayment)?.metadata as? OfferPaymentMetadata.V1 +fun WalletPayment.incomingOfferMetadata(): OfferPaymentMetadata? = (this as? Bolt12IncomingPayment)?.metadata fun WalletPayment.outgoingInvoiceRequest(): OfferTypes.InvoiceRequest? = ((this as? LightningOutgoingPayment)?.details as? LightningOutgoingPayment.Details.Blinded)?.paymentRequest?.invoiceRequest diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/extensions/PaymentRequestExtensions.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/extensions/PaymentRequestExtensions.kt index dfd451833..68dbf1844 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/extensions/PaymentRequestExtensions.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/extensions/PaymentRequestExtensions.kt @@ -16,6 +16,7 @@ package fr.acinq.phoenix.utils.extensions +import fr.acinq.bitcoin.PublicKey import fr.acinq.lightning.payment.Bolt11Invoice import fr.acinq.lightning.payment.Bolt12Invoice import fr.acinq.lightning.payment.OfferPaymentMetadata @@ -34,8 +35,20 @@ val PaymentRequest.desc: String? is Bolt12Invoice -> this.description } +val OfferPaymentMetadata.description: String? + get() = when (this) { + is OfferPaymentMetadata.V1 -> null + is OfferPaymentMetadata.V2 -> this.description + } + +val OfferPaymentMetadata.payerKey: PublicKey? + get() = when (this) { + is OfferPaymentMetadata.V1 -> this.payerKey + is OfferPaymentMetadata.V2 -> this.payerKey + } + val OfferPaymentMetadata.payerNote: String? - get() = when { - this is OfferPaymentMetadata.V1 -> this.payerNote - else -> null - } \ No newline at end of file + get() = when (this) { + is OfferPaymentMetadata.V1 -> this.payerNote + is OfferPaymentMetadata.V2 -> this.payerNote + } From 4438d085721f39d74a223d3f936e41ab88b8e6d3 Mon Sep 17 00:00:00 2001 From: Dominique Padiou <5765435+dpad85@users.noreply.github.com> Date: Fri, 17 Oct 2025 17:07:55 +0200 Subject: [PATCH 5/8] Use abstract payerKey and payerNote in offer metadata See https://github.com/ACINQ/lightning-kmp/pull/821 Also limit the description input to 64 chars for Bolt12 offers as the description + payer note cannot exceed 64 chars. --- .../phoenix/android/payments/details/PaymentLine.kt | 3 --- .../android/payments/details/PaymentTechnicalView.kt | 4 ++-- .../payments/details/splash/SplashIncomingBolt12.kt | 3 --- .../details/technical/TechnicalIncomingBolt12.kt | 8 ++++++-- .../android/payments/receive/ReceiveLightningView.kt | 2 +- .../android/utils/extensions/PaymentExtensions.kt | 3 ++- phoenix-android/src/main/res/values-fr/strings.xml | 1 + phoenix-android/src/main/res/values/strings.xml | 3 ++- .../kotlin/KotlinExtensions+Payments.swift | 2 +- .../kotlin/fr.acinq.phoenix/db/SqlitePaymentsDb.kt | 1 - .../fr.acinq.phoenix/db/contacts/SqliteContactsDb.kt | 1 - .../utils/extensions/PaymentRequestExtensions.kt | 12 ------------ 12 files changed, 15 insertions(+), 28 deletions(-) diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/PaymentLine.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/PaymentLine.kt index 3c95d73b1..a6855c63f 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/PaymentLine.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/PaymentLine.kt @@ -31,8 +31,6 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -63,7 +61,6 @@ import fr.acinq.phoenix.data.WalletPaymentInfo import fr.acinq.phoenix.utils.extensions.WalletPaymentState import fr.acinq.phoenix.utils.extensions.incomingOfferMetadata import fr.acinq.phoenix.utils.extensions.outgoingInvoiceRequest -import fr.acinq.phoenix.utils.extensions.payerNote import fr.acinq.phoenix.utils.extensions.state @Composable diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/PaymentTechnicalView.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/PaymentTechnicalView.kt index e66a7734b..e226e9ca3 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/PaymentTechnicalView.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/PaymentTechnicalView.kt @@ -160,7 +160,7 @@ fun Bolt11InvoiceSection( TechnicalRowAmount(label = stringResource(id = R.string.paymentdetails_invoice_requested_label), amount = it, rateThen = originalFiatRate) } (invoice.description ?: invoice.descriptionHash?.toHex())?.takeIf { it.isNotBlank() }?.let { - TechnicalRowSelectable(label = stringResource(id = R.string.paymentdetails_bolt11_description_label), value = it) + TechnicalRowWithCopy(label = stringResource(id = R.string.paymentdetails_bolt11_description_label), value = it) } TechnicalRowWithCopy(label = stringResource(id = R.string.paymentdetails_payment_hash_label), value = invoice.paymentHash.toHex()) preimage?.let { TechnicalRowWithCopy(label = stringResource(id = R.string.paymentdetails_preimage_label), value = preimage.toHex(), helpMessage = stringResource(id = R.string.paymentdetails_preimage_help)) } @@ -178,7 +178,7 @@ fun Bolt12InvoiceSection( TechnicalRowAmount(label = stringResource(id = R.string.paymentdetails_invoice_requested_label), amount = it, rateThen = originalFiatRate) } invoice.description?.takeIf { it.isNotBlank() }?.let { - TechnicalRowSelectable(label = stringResource(id = R.string.paymentdetails_bolt11_description_label), value = it) + TechnicalRowWithCopy(label = stringResource(id = R.string.paymentdetails_bolt11_description_label), value = it) } TechnicalRow(label = stringResource(id = R.string.paymentdetails_payerkey_label)) { Text(text = payerKey.toHex()) diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/splash/SplashIncomingBolt12.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/splash/SplashIncomingBolt12.kt index 8cd57bf38..4481bc33b 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/splash/SplashIncomingBolt12.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/splash/SplashIncomingBolt12.kt @@ -33,7 +33,6 @@ import fr.acinq.bitcoin.PublicKey import fr.acinq.lightning.db.Bolt12IncomingPayment import fr.acinq.lightning.utils.UUID import fr.acinq.phoenix.PhoenixBusiness -import fr.acinq.phoenix.android.LocalBusiness import fr.acinq.phoenix.android.R import fr.acinq.phoenix.android.components.layouts.SplashLabelRow import fr.acinq.phoenix.android.components.contact.ContactCompactView @@ -41,8 +40,6 @@ import fr.acinq.phoenix.android.components.contact.OfferContactState import fr.acinq.phoenix.android.utils.extensions.smartDescription import fr.acinq.phoenix.data.WalletPaymentMetadata import fr.acinq.phoenix.utils.extensions.incomingOfferMetadata -import fr.acinq.phoenix.utils.extensions.payerKey -import fr.acinq.phoenix.utils.extensions.payerNote import fr.acinq.phoenix.utils.extensions.state diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/technical/TechnicalIncomingBolt12.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/technical/TechnicalIncomingBolt12.kt index 9e7e21e89..702b331f6 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/technical/TechnicalIncomingBolt12.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/technical/TechnicalIncomingBolt12.kt @@ -30,6 +30,7 @@ import fr.acinq.phoenix.android.payments.details.TechnicalRowWithCopy import fr.acinq.phoenix.android.payments.details.TimestampSection import fr.acinq.phoenix.android.utils.converters.MSatDisplayPolicy import fr.acinq.phoenix.data.ExchangeRate +import fr.acinq.phoenix.utils.extensions.description @Composable fun TechnicalIncomingBolt12( @@ -75,10 +76,13 @@ fun IncomingBolt12Details( amount = metadata.amount, rateThen = originalFiatRate ) + metadata.description?.let { description -> + TechnicalRowWithCopy(label = stringResource(id = R.string.paymentdetails_bolt12_description_label), value = description) + } TechnicalRowWithCopy(label = stringResource(id = R.string.paymentdetails_payment_hash_label), value = metadata.paymentHash.toHex()) TechnicalRowWithCopy(label = stringResource(id = R.string.paymentdetails_preimage_label), value = metadata.preimage.toHex()) TechnicalRowWithCopy(label = stringResource(id = R.string.paymentdetails_offer_metadata_label), value = metadata.encode().toHex()) - if (metadata is OfferPaymentMetadata.V1) { - TechnicalRowSelectable(label = stringResource(id = R.string.paymentdetails_payerkey_label), value = metadata.payerKey.toHex()) + metadata.payerKey?.let { payerKey -> + TechnicalRowSelectable(label = stringResource(id = R.string.paymentdetails_payerkey_label), value = payerKey.toHex()) } } \ No newline at end of file diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/receive/ReceiveLightningView.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/receive/ReceiveLightningView.kt index 495caf2d8..7bb6a7558 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/receive/ReceiveLightningView.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/receive/ReceiveLightningView.kt @@ -383,7 +383,7 @@ private fun EditInvoiceView( onTextChange = onDescriptionChange, staticLabel = stringResource(id = R.string.receive_lightning_edit_desc_label), placeholder = { Text(text = stringResource(id = R.string.receive_lightning_edit_desc_placeholder), maxLines = 2, overflow = TextOverflow.Ellipsis) }, - maxChars = 140, + maxChars = if (isReusable) 64 else 140, minLines = 2, maxLines = Int.MAX_VALUE, modifier = Modifier.fillMaxWidth(), diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/utils/extensions/PaymentExtensions.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/utils/extensions/PaymentExtensions.kt index 5745256ad..f6fd5419e 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/utils/extensions/PaymentExtensions.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/utils/extensions/PaymentExtensions.kt @@ -37,6 +37,7 @@ import fr.acinq.phoenix.android.R import fr.acinq.phoenix.android.utils.converters.AmountFormatter.toPrettyString import fr.acinq.phoenix.data.BitcoinUnit import fr.acinq.phoenix.utils.extensions.desc +import fr.acinq.phoenix.utils.extensions.description @Composable fun LightningOutgoingPayment.smartDescription(): String? = when (val details = this.details) { @@ -66,7 +67,7 @@ fun AutomaticLiquidityPurchasePayment.smartDescription(): String = @Composable fun IncomingPayment.smartDescription() : String? = when (this) { is Bolt11IncomingPayment -> paymentRequest.description - is Bolt12IncomingPayment -> null + is Bolt12IncomingPayment -> metadata.description is OnChainIncomingPayment -> stringResource(id = R.string.paymentdetails_desc_swapin) is LegacySwapInIncomingPayment -> stringResource(id = R.string.paymentdetails_desc_swapin) is LegacyPayToOpenIncomingPayment -> when (val origin = origin) { diff --git a/phoenix-android/src/main/res/values-fr/strings.xml b/phoenix-android/src/main/res/values-fr/strings.xml index e3a549aed..f0b38df10 100644 --- a/phoenix-android/src/main/res/values-fr/strings.xml +++ b/phoenix-android/src/main/res/values-fr/strings.xml @@ -378,6 +378,7 @@ Clé publique du destinataire Description de la requête de paiement + Description de l\'offer Hash du paiement Requête de paiement Préimage diff --git a/phoenix-android/src/main/res/values/strings.xml b/phoenix-android/src/main/res/values/strings.xml index 3173419d1..d77d12f4a 100644 --- a/phoenix-android/src/main/res/values/strings.xml +++ b/phoenix-android/src/main/res/values/strings.xml @@ -479,8 +479,9 @@ Preimage Cryptographic proof that the recipient successfully received the payment. Bolt11 invoice - Invoice description Bolt12 invoice + Invoice description + Offer description Offer Metadata Payer key diff --git a/phoenix-ios/phoenix-ios/kotlin/KotlinExtensions+Payments.swift b/phoenix-ios/phoenix-ios/kotlin/KotlinExtensions+Payments.swift index 910381a1b..ab15bda27 100644 --- a/phoenix-ios/phoenix-ios/kotlin/KotlinExtensions+Payments.swift +++ b/phoenix-ios/phoenix-ios/kotlin/KotlinExtensions+Payments.swift @@ -132,7 +132,7 @@ extension WalletPaymentInfo { var msg: String? = nil if let incomingOfferMetadata = payment.incomingOfferMetadata() { - msg = incomingOfferMetadata.payerNote_ + msg = incomingOfferMetadata.payerNote } else if let outgoingInvoiceRequest = payment.outgoingInvoiceRequest() { msg = outgoingInvoiceRequest.payerNote diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/SqlitePaymentsDb.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/SqlitePaymentsDb.kt index aa1490172..ba49d437a 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/SqlitePaymentsDb.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/SqlitePaymentsDb.kt @@ -35,7 +35,6 @@ import fr.acinq.phoenix.db.sqldelight.PaymentsDatabase import fr.acinq.phoenix.managers.PaymentMetadataQueue import fr.acinq.phoenix.utils.extensions.incomingOfferMetadata import fr.acinq.phoenix.utils.extensions.outgoingInvoiceRequest -import fr.acinq.phoenix.utils.extensions.payerKey import kotlin.collections.List import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/contacts/SqliteContactsDb.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/contacts/SqliteContactsDb.kt index 13ef022f0..f32b5512b 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/contacts/SqliteContactsDb.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/db/contacts/SqliteContactsDb.kt @@ -16,7 +16,6 @@ import fr.acinq.phoenix.db.migrations.appDb.v7.AfterVersion7Result import fr.acinq.phoenix.db.sqldelight.PaymentsDatabase import fr.acinq.phoenix.utils.extensions.incomingOfferMetadata import fr.acinq.phoenix.utils.extensions.outgoingInvoiceRequest -import fr.acinq.phoenix.utils.extensions.payerKey import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi diff --git a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/extensions/PaymentRequestExtensions.kt b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/extensions/PaymentRequestExtensions.kt index 68dbf1844..e3c94dea5 100644 --- a/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/extensions/PaymentRequestExtensions.kt +++ b/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/extensions/PaymentRequestExtensions.kt @@ -40,15 +40,3 @@ val OfferPaymentMetadata.description: String? is OfferPaymentMetadata.V1 -> null is OfferPaymentMetadata.V2 -> this.description } - -val OfferPaymentMetadata.payerKey: PublicKey? - get() = when (this) { - is OfferPaymentMetadata.V1 -> this.payerKey - is OfferPaymentMetadata.V2 -> this.payerKey - } - -val OfferPaymentMetadata.payerNote: String? - get() = when (this) { - is OfferPaymentMetadata.V1 -> this.payerNote - is OfferPaymentMetadata.V2 -> this.payerNote - } From 1de8819a96f869fbf94abf095a871b31bbc1fb3d Mon Sep 17 00:00:00 2001 From: Dominique Padiou <5765435+dpad85@users.noreply.github.com> Date: Fri, 17 Oct 2025 19:20:49 +0200 Subject: [PATCH 6/8] (android) Disable spend-from-channel-address screen Also removed unused resources used by the import-channel screen. --- .../navigation/NavGraphSettingsChannels.kt | 2 +- .../android/settings/channels/ChannelsView.kt | 29 +++++++++---------- .../src/main/res/values-b+es+419/strings.xml | 1 - .../src/main/res/values-cs/strings.xml | 1 - .../src/main/res/values-de/strings.xml | 1 - .../src/main/res/values-fr/strings.xml | 1 - .../src/main/res/values-pt-rBR/strings.xml | 1 - .../src/main/res/values-sk/strings.xml | 1 - .../src/main/res/values-sw/strings.xml | 1 - .../src/main/res/values-vi/strings.xml | 1 - .../src/main/res/values/strings.xml | 1 - 11 files changed, 14 insertions(+), 26 deletions(-) diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/navigation/NavGraphSettingsChannels.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/navigation/NavGraphSettingsChannels.kt index 6b6fb22b1..cd16fb113 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/navigation/NavGraphSettingsChannels.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/navigation/NavGraphSettingsChannels.kt @@ -56,7 +56,7 @@ fun NavGraphBuilder.channelsNavGraph(navController: NavController, appViewModel: } businessComposable(Screen.BusinessNavGraph.SpendChannelAddress.route, appViewModel) { _, _, business -> - SpendFromChannelAddress(business = business, onBackClick = { navController.popBackStack() }) + // SpendFromChannelAddress(business = business, onBackClick = { navController.popBackStack() }) } businessComposable(Screen.BusinessNavGraph.MutualClose.route, appViewModel) { _, walletId, business -> diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/channels/ChannelsView.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/channels/ChannelsView.kt index 08d458d1c..85a541328 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/channels/ChannelsView.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/channels/ChannelsView.kt @@ -82,22 +82,19 @@ fun ChannelsView( var showAdvancedMenuPopIn by remember { mutableStateOf(false) } Text(text = stringResource(id = R.string.channelsview_title)) Spacer(modifier = Modifier.weight(1f)) - Box(contentAlignment = Alignment.TopEnd) { - DropdownMenu(expanded = showAdvancedMenuPopIn, onDismissRequest = { showAdvancedMenuPopIn = false }) { - DropdownMenuItem(onClick = onImportChannelsDataClick, contentPadding = PaddingValues(horizontal = 12.dp)) { - Text(text = stringResource(R.string.channelsview_menu_import_channels), style = MaterialTheme.typography.body1) - } - DropdownMenuItem(onClick = onSpendFromChannelBalance, contentPadding = PaddingValues(horizontal = 12.dp)) { - Text(text = stringResource(R.string.channelsview_menu_spend_channel_balance), style = MaterialTheme.typography.body1) - } - } - Button( - icon = R.drawable.ic_menu_dots, - iconTint = MaterialTheme.colors.onSurface, - padding = PaddingValues(12.dp), - onClick = { showAdvancedMenuPopIn = true } - ) - } +// Box(contentAlignment = Alignment.TopEnd) { +// DropdownMenu(expanded = showAdvancedMenuPopIn, onDismissRequest = { showAdvancedMenuPopIn = false }) { +// DropdownMenuItem(onClick = onSpendFromChannelBalance, contentPadding = PaddingValues(horizontal = 12.dp)) { +// Text(text = stringResource(R.string.channelsview_menu_spend_channel_balance), style = MaterialTheme.typography.body1) +// } +// } +// Button( +// icon = R.drawable.ic_menu_dots, +// iconTint = MaterialTheme.colors.onSurface, +// padding = PaddingValues(12.dp), +// onClick = { showAdvancedMenuPopIn = true } +// ) +// } } ) if (!channelsState?.values?.filter { it.isUsable }.isNullOrEmpty()) { diff --git a/phoenix-android/src/main/res/values-b+es+419/strings.xml b/phoenix-android/src/main/res/values-b+es+419/strings.xml index 2bd5f34e4..12a6c676b 100644 --- a/phoenix-android/src/main/res/values-b+es+419/strings.xml +++ b/phoenix-android/src/main/res/values-b+es+419/strings.xml @@ -207,7 +207,6 @@ Canales de pago - Importar canales Saldo El saldo es el monto total de los canales activos. Es lo que puedes gastar en Lightning. Liquidez entrante diff --git a/phoenix-android/src/main/res/values-cs/strings.xml b/phoenix-android/src/main/res/values-cs/strings.xml index de7d5098f..db9b23989 100644 --- a/phoenix-android/src/main/res/values-cs/strings.xml +++ b/phoenix-android/src/main/res/values-cs/strings.xml @@ -268,7 +268,6 @@ Platební kanály - Importovat kanály Utratit z adresy kanálu Přehled Zůstatek diff --git a/phoenix-android/src/main/res/values-de/strings.xml b/phoenix-android/src/main/res/values-de/strings.xml index 0d0c09ca3..120857576 100644 --- a/phoenix-android/src/main/res/values-de/strings.xml +++ b/phoenix-android/src/main/res/values-de/strings.xml @@ -202,7 +202,6 @@ Zahlungs-Kanäle - Kanäle importieren Übersicht Guthaben Guthaben ist das gesamte Guthaben Ihrer aktiven Kanäle. Diesen Betrag können Sie über Lightning ausgeben. diff --git a/phoenix-android/src/main/res/values-fr/strings.xml b/phoenix-android/src/main/res/values-fr/strings.xml index f0b38df10..8abb8516d 100644 --- a/phoenix-android/src/main/res/values-fr/strings.xml +++ b/phoenix-android/src/main/res/values-fr/strings.xml @@ -217,7 +217,6 @@ Canaux de paiements - Importer des canaux Solde Votre solde est le total du solde de vos canaux actifs. C\'est ce que vous pouvez dépenser via Lightning. Liquidité entrante diff --git a/phoenix-android/src/main/res/values-pt-rBR/strings.xml b/phoenix-android/src/main/res/values-pt-rBR/strings.xml index c6fdb2891..d4fd18e4b 100644 --- a/phoenix-android/src/main/res/values-pt-rBR/strings.xml +++ b/phoenix-android/src/main/res/values-pt-rBR/strings.xml @@ -204,7 +204,6 @@ Canais de pagamento - Importar canais Saldo O saldo é a quantidade total de canais ativos. Isso é o que você pode gastar no Lightning. Liquidez de entrada diff --git a/phoenix-android/src/main/res/values-sk/strings.xml b/phoenix-android/src/main/res/values-sk/strings.xml index 6bb5fcb81..6251061f9 100644 --- a/phoenix-android/src/main/res/values-sk/strings.xml +++ b/phoenix-android/src/main/res/values-sk/strings.xml @@ -213,7 +213,6 @@ Platobné kanály - Importovať kanály Prehľad Zostatok Zostatok: Toto je súhrnný zostatok vašich aktívnych kanálov. Toľko môžete poslať cez Lightning. diff --git a/phoenix-android/src/main/res/values-sw/strings.xml b/phoenix-android/src/main/res/values-sw/strings.xml index d47c037d2..ae0b386b7 100644 --- a/phoenix-android/src/main/res/values-sw/strings.xml +++ b/phoenix-android/src/main/res/values-sw/strings.xml @@ -227,7 +227,6 @@ Kanali za malipo - Ingiza kanali Muonekano Salio Salio ni jumla ya salio la kanali zako za kazi. Hii ndiyo unaweza kutumia kwa Lightning. diff --git a/phoenix-android/src/main/res/values-vi/strings.xml b/phoenix-android/src/main/res/values-vi/strings.xml index 7b77d6d51..90c843d4e 100644 --- a/phoenix-android/src/main/res/values-vi/strings.xml +++ b/phoenix-android/src/main/res/values-vi/strings.xml @@ -205,7 +205,6 @@ Kênh thanh toán - Nhập các kênh Tổng quan Số dư Số dư là số dư tổng hợp của tất các kênh đang hoạt động của bạn. Đó là số tiền bạn có thể dùng trên Lightning. diff --git a/phoenix-android/src/main/res/values/strings.xml b/phoenix-android/src/main/res/values/strings.xml index d77d12f4a..1e88ae63a 100644 --- a/phoenix-android/src/main/res/values/strings.xml +++ b/phoenix-android/src/main/res/values/strings.xml @@ -277,7 +277,6 @@ Payment channels - Import channels Spend channel address Overview Balance From ac130515a29ebbaebe564429ee3d1d97f530a477 Mon Sep 17 00:00:00 2001 From: Dominique Padiou <5765435+dpad85@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:57:23 +0200 Subject: [PATCH 7/8] (android) Improve reset wallet logic --- .../phoenix/android/security/SeedManager.kt | 2 +- .../android/settings/reset/ResetWallet.kt | 27 +++++++------------ .../settings/reset/ResetWalletViewModel.kt | 9 ++++--- .../phoenix/android/startup/StartupView.kt | 9 +++---- 4 files changed, 21 insertions(+), 26 deletions(-) diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/security/SeedManager.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/security/SeedManager.kt index 9a2acf469..5ec4b8536 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/security/SeedManager.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/security/SeedManager.kt @@ -55,7 +55,7 @@ object SeedManager { val encryptedSeed = try { loadEncryptedSeedFromDisk(context) } catch (e: Exception) { - log.error("could read seed file: ", e) + log.error("could not read seed file: ", e) return DecryptSeedResult.Failure.SeedFileUnreadable } diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/reset/ResetWallet.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/reset/ResetWallet.kt index 4b10c7016..e99f85ed2 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/reset/ResetWallet.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/reset/ResetWallet.kt @@ -37,7 +37,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -54,7 +53,6 @@ import fr.acinq.phoenix.android.application import fr.acinq.phoenix.android.components.AmountWithFiatBeside import fr.acinq.phoenix.android.components.ProgressView import fr.acinq.phoenix.android.components.TextWithIcon -import fr.acinq.phoenix.android.components.buttons.BorderButton import fr.acinq.phoenix.android.components.buttons.Checkbox import fr.acinq.phoenix.android.components.buttons.Clickable import fr.acinq.phoenix.android.components.buttons.FilledButton @@ -102,7 +100,16 @@ fun ResetWallet( ResetWalletStep.Confirm -> { ReviewWalletBeforeDeletion( business = business, - onConfirmClick = vm::deleteWalletData, + onConfirmClick = { + vm.deleteWalletData(onWalletDeleted = { context -> + BusinessManager.stopBusiness(walletId) + context.startActivity( + Intent(context, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + ) + }) + }, onLightningBalanceClick = onLightningBalanceClick, onSwapInBalanceClick = onSwapInBalanceClick, onFinalBalanceClick = onFinalBalanceClick ) } @@ -288,26 +295,12 @@ private fun DeletingWallet(state: ResetWalletStep.Deleting) { @Composable private fun WalletDeleted(walletId: WalletId) { - val context = LocalContext.current Column( modifier = Modifier.fillMaxWidth().padding(vertical = 24.dp), verticalArrangement = Arrangement.spacedBy(2.dp), horizontalAlignment = Alignment.CenterHorizontally ) { SuccessMessage(header = stringResource(id = R.string.reset_wallet_success)) - Spacer(modifier = Modifier.height(16.dp)) - BorderButton( - text = stringResource(id = R.string.btn_ok), - icon = R.drawable.ic_check, - onClick = { - BusinessManager.stopBusiness(walletId) - context.startActivity( - Intent(context, MainActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - } - ) - } - ) } } diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/reset/ResetWalletViewModel.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/reset/ResetWalletViewModel.kt index de85280f0..67e04a270 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/reset/ResetWalletViewModel.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/settings/reset/ResetWalletViewModel.kt @@ -16,6 +16,7 @@ package fr.acinq.phoenix.android.settings.reset +import android.content.Context import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider @@ -61,7 +62,9 @@ class ResetWalletViewModel(val application: PhoenixApplication, val walletId: Wa val state = mutableStateOf(ResetWalletStep.Init) - fun deleteWalletData() { + fun deleteWalletData( + onWalletDeleted: (Context) -> Unit, + ) { if (state.value != ResetWalletStep.Confirm) return state.value = ResetWalletStep.Deleting.Init @@ -69,7 +72,7 @@ class ResetWalletViewModel(val application: PhoenixApplication, val walletId: Wa log.error("failed to reset wallet data: ", e) state.value = ResetWalletStep.Result.Failure.Error(e) }) { - log.info("resetting wallet with wallet=$walletId") + log.info("resetting wallet=$walletId") delay(350) val context = application.applicationContext @@ -108,7 +111,7 @@ class ResetWalletViewModel(val application: PhoenixApplication, val walletId: Wa delay(300) log.info("successfully deleted wallet=$walletId") - + onWalletDeleted(context) state.value = ResetWalletStep.Result.Success } } diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/startup/StartupView.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/startup/StartupView.kt index 08c717ff4..35f319f6d 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/startup/StartupView.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/startup/StartupView.kt @@ -96,15 +96,14 @@ fun StartupView( onWalletReady: () -> Unit, forceWalletId: WalletId?, ) { - val showIntro = application.globalPrefs.getShowIntro.collectAsState(initial = null) - if (showIntro.value == true) { + val showIntro by application.globalPrefs.getShowIntro.collectAsState(initial = null) + if (showIntro == true) { LaunchedEffect(Unit) { onShowIntro() } + return } Box( - modifier = Modifier - .fillMaxSize() - .imePadding(), + modifier = Modifier.fillMaxSize().imePadding(), contentAlignment = Alignment.Center ) { From a9bbd31209e2164847cb383805914e26ab2b7f15 Mon Sep 17 00:00:00 2001 From: Dominique Padiou <5765435+dpad85@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:00:09 +0200 Subject: [PATCH 8/8] Use lightning-kmp 1.11.0 --- build.gradle.kts | 1 + gradle/libs.versions.toml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 5389a86ff..410e1f383 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,6 +19,7 @@ allprojects { google() mavenCentral() maven("https://oss.sonatype.org/content/repositories/snapshots") + maven("https://central.sonatype.com/repository/maven-snapshots") } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1dd325208..3f5a69f4f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] -lightningkmp = "1.10.9-SNAPSHOT" -secp256k1 = "0.20.0" # keep in check with lightning-kmp secp version +lightningkmp = "1.11.0" +secp256k1 = "0.21.0" # keep in check with lightning-kmp secp version kotlin = "2.2.10" ktor = "3.1.0"