From dff1737b8d5e3775311adcea9b7986db5491d3b7 Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Wed, 30 Jul 2025 07:27:56 -0300 Subject: [PATCH 01/19] feat: setup activity meta data methods --- .../main/java/to/bitkit/data/CacheStore.kt | 19 ++++++++++++ .../to/bitkit/data/dto/ActivityMetaData.kt | 25 ++++++++++++++++ .../serializers/ActivityMetaDataSerializer.kt | 29 +++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 app/src/main/java/to/bitkit/data/dto/ActivityMetaData.kt create mode 100644 app/src/main/java/to/bitkit/data/serializers/ActivityMetaDataSerializer.kt diff --git a/app/src/main/java/to/bitkit/data/CacheStore.kt b/app/src/main/java/to/bitkit/data/CacheStore.kt index 578a17fc3..c90d492c5 100644 --- a/app/src/main/java/to/bitkit/data/CacheStore.kt +++ b/app/src/main/java/to/bitkit/data/CacheStore.kt @@ -9,7 +9,9 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.serialization.Serializable +import to.bitkit.data.dto.ActivityMetaData import to.bitkit.data.dto.PendingBoostActivity +import to.bitkit.data.dto.rawId import to.bitkit.data.serializers.AppCacheSerializer import to.bitkit.models.BackupCategory import to.bitkit.models.BackupItemStatus @@ -113,6 +115,22 @@ class CacheStore @Inject constructor( } } + suspend fun addActivityMetaData(item: ActivityMetaData) { + if (item.rawId() in store.data.first().activitiesMetaData.map { it.rawId() }) return + + store.updateData { + it.copy(activitiesMetaData = it.activitiesMetaData + item) + } + } + + suspend fun removeActivityMetaData(item: ActivityMetaData) { + if (item.rawId() !in store.data.first().activitiesMetaData.map { it.rawId() }) return + + store.updateData { + it.copy(activitiesMetaData = it.activitiesMetaData - item) + } + } + suspend fun reset() { store.updateData { AppCacheData() } Logger.info("Deleted all app cached data.") @@ -135,4 +153,5 @@ data class AppCacheData( val deletedActivities: List = listOf(), val activitiesPendingDelete: List = listOf(), val pendingBoostActivities: List = listOf(), + val activitiesMetaData: List = listOf(), ) diff --git a/app/src/main/java/to/bitkit/data/dto/ActivityMetaData.kt b/app/src/main/java/to/bitkit/data/dto/ActivityMetaData.kt new file mode 100644 index 000000000..b506716a1 --- /dev/null +++ b/app/src/main/java/to/bitkit/data/dto/ActivityMetaData.kt @@ -0,0 +1,25 @@ +package to.bitkit.data.dto + +import kotlinx.serialization.Serializable + +@Serializable +sealed interface ActivityMetaData { + data class OnChainActivity( + val txId: String, + val feeRate: UInt, + val address: String, + val isTransfer: Boolean, + val channelId: String?, + val transferTxId: String?, + ) : ActivityMetaData + + data class Bolt11( + val paymentId: String, + val invoice: String, + ) : ActivityMetaData +} + +fun ActivityMetaData.rawId() = when(this) { + is ActivityMetaData.Bolt11 -> paymentId + is ActivityMetaData.OnChainActivity -> txId +} diff --git a/app/src/main/java/to/bitkit/data/serializers/ActivityMetaDataSerializer.kt b/app/src/main/java/to/bitkit/data/serializers/ActivityMetaDataSerializer.kt new file mode 100644 index 000000000..805e95d19 --- /dev/null +++ b/app/src/main/java/to/bitkit/data/serializers/ActivityMetaDataSerializer.kt @@ -0,0 +1,29 @@ +package to.bitkit.data.serializers + +import androidx.datastore.core.Serializer +import kotlinx.serialization.SerializationException +import to.bitkit.data.dto.ActivityMetaData +import to.bitkit.di.json +import to.bitkit.utils.Logger +import java.io.InputStream +import java.io.OutputStream + +object ActivityMetaDataSerializer : Serializer { + override val defaultValue: ActivityMetaData = ActivityMetaData.Bolt11( + paymentId = "", + invoice = "" + ) + + override suspend fun readFrom(input: InputStream): ActivityMetaData { + return try { + json.decodeFromString(input.readBytes().decodeToString()) + } catch (e: SerializationException) { + Logger.error("Failed to deserialize ActivityMetaData: $e") + defaultValue + } + } + + override suspend fun writeTo(t: ActivityMetaData, output: OutputStream) { + output.write(json.encodeToString(t).encodeToByteArray()) + } +} From 0a1bf907a02149a3d661a7b5a417c43023740c5e Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Wed, 30 Jul 2025 07:28:19 -0300 Subject: [PATCH 02/19] feat: implement activity meta data methods --- .../data/serializers/AppCacheSerializer.kt | 1 - .../to/bitkit/repositories/ActivityRepo.kt | 57 ++++++++++++++++++- .../to/bitkit/repositories/LightningRepo.kt | 13 +++++ 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/to/bitkit/data/serializers/AppCacheSerializer.kt b/app/src/main/java/to/bitkit/data/serializers/AppCacheSerializer.kt index 1c48c78f5..c68e356b7 100644 --- a/app/src/main/java/to/bitkit/data/serializers/AppCacheSerializer.kt +++ b/app/src/main/java/to/bitkit/data/serializers/AppCacheSerializer.kt @@ -3,7 +3,6 @@ package to.bitkit.data.serializers import androidx.datastore.core.Serializer import kotlinx.serialization.SerializationException import to.bitkit.data.AppCacheData -import to.bitkit.data.SettingsData import to.bitkit.di.json import to.bitkit.utils.Logger import java.io.InputStream diff --git a/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt b/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt index 1016729b2..7a63402db 100644 --- a/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt @@ -10,7 +10,9 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext import org.lightningdevkit.ldknode.PaymentDetails import to.bitkit.data.CacheStore +import to.bitkit.data.dto.ActivityMetaData import to.bitkit.data.dto.PendingBoostActivity +import to.bitkit.data.dto.rawId import to.bitkit.di.BgDispatcher import to.bitkit.ext.matchesPaymentId import to.bitkit.ext.rawId @@ -46,6 +48,7 @@ class ActivityRepo @Inject constructor( .onSuccess { payments -> Logger.debug("Got payments with success, syncing activities", context = TAG) syncLdkNodePayments(payments = payments) + updateActivitiesMetaData() boostPendingActivities() isSyncingLdkNodePayments = false return@withContext Result.success(Unit) @@ -183,7 +186,7 @@ class ActivityRepo @Inject constructor( suspend fun updateActivity( id: String, activity: Activity, - forceUpdate: Boolean = false + forceUpdate: Boolean = false, ): Result = withContext(bgDispatcher) { return@withContext runCatching { if (id in cacheStore.data.first().deletedActivities && !forceUpdate) { @@ -241,6 +244,56 @@ class ActivityRepo @Inject constructor( } } + private suspend fun updateActivitiesMetaData() = withContext(bgDispatcher) { + cacheStore.data.first().activitiesMetaData.forEach { activityMetaData -> + findActivityByPaymentId( + paymentHashOrTxId = activityMetaData.rawId(), + type = ActivityFilter.ALL, + txType = PaymentType.SENT + ).onSuccess { activityToUpdate -> + Logger.debug("updateActivitiesMetaData = Activity found: ${activityToUpdate.rawId()}", context = TAG) + + when (activityToUpdate) { + is Activity.Lightning -> { + val metaData = activityMetaData as? ActivityMetaData.Bolt11 + val updatedActivity = Activity.Lightning( + v1 = activityToUpdate.v1.copy( + invoice = metaData?.invoice.orEmpty(), + ) + ) + + updateActivity( + id = updatedActivity.v1.id, + activity = updatedActivity + ).onSuccess { + cacheStore.removeActivityMetaData(activityMetaData) + } + } + + is Activity.Onchain -> { + val metaData = activityMetaData as? ActivityMetaData.OnChainActivity + val updatedActivity = Activity.Onchain( + v1 = activityToUpdate.v1.copy( + feeRate = metaData?.feeRate?.toULong() ?: 1u, + address = metaData?.address.orEmpty(), + isTransfer = metaData?.isTransfer ?: false, + channelId = metaData?.channelId, + transferTxId = metaData?.transferTxId + ) + ) + + updateActivity( + id = updatedActivity.v1.id, + activity = updatedActivity + ).onSuccess { + cacheStore.removeActivityMetaData(activityMetaData) + } + } + } + } + } + } + private suspend fun boostPendingActivities() = withContext(bgDispatcher) { cacheStore.data.first().pendingBoostActivities.forEach { pendingBoostActivity -> findActivityByPaymentId( @@ -353,7 +406,7 @@ class ActivityRepo @Inject constructor( paymentHashOrTxId: String, type: ActivityFilter, txType: PaymentType, - tags: List + tags: List, ): Result = withContext(bgDispatcher) { if (tags.isEmpty()) return@withContext Result.failure(IllegalArgumentException("No tags selected")) diff --git a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt index 614fb501b..57636bc51 100644 --- a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt @@ -27,6 +27,7 @@ import org.lightningdevkit.ldknode.Txid import org.lightningdevkit.ldknode.UserChannelId import to.bitkit.data.CacheStore import to.bitkit.data.SettingsStore +import to.bitkit.data.dto.ActivityMetaData import to.bitkit.data.keychain.Keychain import to.bitkit.di.BgDispatcher import to.bitkit.env.Env @@ -475,6 +476,10 @@ class LightningRepo @Inject constructor( suspend fun payInvoice(bolt11: String, sats: ULong? = null): Result = executeWhenNodeRunning("Pay invoice") { val paymentId = lightningService.send(bolt11 = bolt11, sats = sats) + cacheStore.addActivityMetaData(ActivityMetaData.Bolt11( + paymentId = paymentId, + invoice = bolt11 + )) syncState() Result.success(paymentId) } @@ -514,6 +519,14 @@ class LightningRepo @Inject constructor( satsPerVByte = satsPerVByte, utxosToSpend = finalUtxosToSpend, ) + cacheStore.addActivityMetaData(ActivityMetaData.OnChainActivity( + txId = txId, + feeRate = satsPerVByte, + address = address, + isTransfer = false, + channelId = null, + transferTxId = null + )) syncState() Result.success(txId) } From ee2deec597d1cf47da322ea9937f394f8bebca37 Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Wed, 30 Jul 2025 09:02:45 -0300 Subject: [PATCH 03/19] fix: serialization --- .../to/bitkit/data/dto/ActivityMetaData.kt | 6 +++- .../serializers/ActivityMetaDataSerializer.kt | 29 ------------------- app/src/main/java/to/bitkit/di/JsonModule.kt | 10 +++++++ 3 files changed, 15 insertions(+), 30 deletions(-) delete mode 100644 app/src/main/java/to/bitkit/data/serializers/ActivityMetaDataSerializer.kt diff --git a/app/src/main/java/to/bitkit/data/dto/ActivityMetaData.kt b/app/src/main/java/to/bitkit/data/dto/ActivityMetaData.kt index b506716a1..9853faf6d 100644 --- a/app/src/main/java/to/bitkit/data/dto/ActivityMetaData.kt +++ b/app/src/main/java/to/bitkit/data/dto/ActivityMetaData.kt @@ -1,9 +1,12 @@ package to.bitkit.data.dto +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable sealed interface ActivityMetaData { + @Serializable + @SerialName("onchain") data class OnChainActivity( val txId: String, val feeRate: UInt, @@ -12,7 +15,8 @@ sealed interface ActivityMetaData { val channelId: String?, val transferTxId: String?, ) : ActivityMetaData - + @Serializable + @SerialName("bolt11") data class Bolt11( val paymentId: String, val invoice: String, diff --git a/app/src/main/java/to/bitkit/data/serializers/ActivityMetaDataSerializer.kt b/app/src/main/java/to/bitkit/data/serializers/ActivityMetaDataSerializer.kt deleted file mode 100644 index 805e95d19..000000000 --- a/app/src/main/java/to/bitkit/data/serializers/ActivityMetaDataSerializer.kt +++ /dev/null @@ -1,29 +0,0 @@ -package to.bitkit.data.serializers - -import androidx.datastore.core.Serializer -import kotlinx.serialization.SerializationException -import to.bitkit.data.dto.ActivityMetaData -import to.bitkit.di.json -import to.bitkit.utils.Logger -import java.io.InputStream -import java.io.OutputStream - -object ActivityMetaDataSerializer : Serializer { - override val defaultValue: ActivityMetaData = ActivityMetaData.Bolt11( - paymentId = "", - invoice = "" - ) - - override suspend fun readFrom(input: InputStream): ActivityMetaData { - return try { - json.decodeFromString(input.readBytes().decodeToString()) - } catch (e: SerializationException) { - Logger.error("Failed to deserialize ActivityMetaData: $e") - defaultValue - } - } - - override suspend fun writeTo(t: ActivityMetaData, output: OutputStream) { - output.write(json.encodeToString(t).encodeToByteArray()) - } -} diff --git a/app/src/main/java/to/bitkit/di/JsonModule.kt b/app/src/main/java/to/bitkit/di/JsonModule.kt index 684c7dc7e..3cdce78d6 100644 --- a/app/src/main/java/to/bitkit/di/JsonModule.kt +++ b/app/src/main/java/to/bitkit/di/JsonModule.kt @@ -5,6 +5,10 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import kotlinx.serialization.json.Json +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.polymorphic +import kotlinx.serialization.modules.subclass +import to.bitkit.data.dto.ActivityMetaData import javax.inject.Singleton val json = Json { @@ -12,6 +16,12 @@ val json = Json { isLenient = true ignoreUnknownKeys = true encodeDefaults = true + serializersModule = SerializersModule { + polymorphic(ActivityMetaData::class) { + subclass(ActivityMetaData.OnChainActivity::class) + subclass(ActivityMetaData.Bolt11::class) + } + } } @Module From 1fe17164b90aea21c468c48ce6b81d082561c631 Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Wed, 30 Jul 2025 11:02:04 -0300 Subject: [PATCH 04/19] fix: lightning activity update --- .../to/bitkit/repositories/ActivityRepo.kt | 11 +++++ .../java/to/bitkit/services/CoreService.kt | 46 +++++++++++++------ 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt b/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt index 7a63402db..02706daf0 100644 --- a/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt @@ -15,6 +15,7 @@ import to.bitkit.data.dto.PendingBoostActivity import to.bitkit.data.dto.rawId import to.bitkit.di.BgDispatcher import to.bitkit.ext.matchesPaymentId +import to.bitkit.ext.nowTimestamp import to.bitkit.ext.rawId import to.bitkit.services.CoreService import to.bitkit.utils.Logger @@ -259,6 +260,7 @@ class ActivityRepo @Inject constructor( val updatedActivity = Activity.Lightning( v1 = activityToUpdate.v1.copy( invoice = metaData?.invoice.orEmpty(), + timestamp = nowTimestamp().toEpochMilli().toULong() ) ) @@ -266,7 +268,16 @@ class ActivityRepo @Inject constructor( id = updatedActivity.v1.id, activity = updatedActivity ).onSuccess { + Logger.debug( + "updateActivitiesMetaData - Activity updated with success. " + + "new data: $updatedActivity", context = TAG + ) cacheStore.removeActivityMetaData(activityMetaData) + }.onFailure { + Logger.warn( + "updateActivitiesMetaData - Failed updating activity ${updatedActivity.rawId()}", + context = TAG + ) } } diff --git a/app/src/main/java/to/bitkit/services/CoreService.kt b/app/src/main/java/to/bitkit/services/CoreService.kt index 76f273e10..02b806b77 100644 --- a/app/src/main/java/to/bitkit/services/CoreService.kt +++ b/app/src/main/java/to/bitkit/services/CoreService.kt @@ -282,8 +282,9 @@ class ActivityService( } val existingActivity = getActivityById(payment.id) - if (existingActivity != null && existingActivity is Activity.Onchain && (existingActivity.v1.updatedAt - ?: 0u) > payment.latestUpdateTimestamp + if ( + existingActivity as? Activity.Onchain != null + && (existingActivity.v1.updatedAt ?: 0u) > payment.latestUpdateTimestamp ) { continue } @@ -334,19 +335,34 @@ class ActivityService( continue } - val ln = LightningActivity( - id = payment.id, - txType = payment.direction.toPaymentType(), - status = state, - value = payment.amountSats ?: 0u, - fee = (payment.feePaidMsat ?: 0u) / 1000u, - invoice = "lnbc123_todo", // TODO - message = kind.description.orEmpty(), - timestamp = payment.latestUpdateTimestamp, - preimage = kind.preimage, - createdAt = payment.latestUpdateTimestamp, - updatedAt = payment.latestUpdateTimestamp, - ) + val existingActivity = getActivityById(payment.id) + if ( + existingActivity as? Activity.Lightning != null + && (existingActivity.v1.updatedAt ?: 0u) > payment.latestUpdateTimestamp + ) { + continue + } + + val ln = if (existingActivity is Activity.Lightning) { + existingActivity.v1.copy( + updatedAt = payment.latestUpdateTimestamp, + status = state + ) + } else { + LightningActivity( + id = payment.id, + txType = payment.direction.toPaymentType(), + status = state, + value = payment.amountSats ?: 0u, + fee = (payment.feePaidMsat ?: 0u) / 1000u, + invoice = "loading...", + message = kind.description.orEmpty(), + timestamp = payment.latestUpdateTimestamp, + preimage = kind.preimage, + createdAt = payment.latestUpdateTimestamp, + updatedAt = payment.latestUpdateTimestamp, + ) + } if (getActivityById(payment.id) != null) { updateActivity(payment.id, Activity.Lightning(ln)) From e361050371d3994b3eb9523c7735a703c2f65c03 Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Wed, 30 Jul 2025 11:19:00 -0300 Subject: [PATCH 05/19] fix: existingActivity validation --- app/src/main/java/to/bitkit/services/CoreService.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/to/bitkit/services/CoreService.kt b/app/src/main/java/to/bitkit/services/CoreService.kt index 02b806b77..5b34bfd46 100644 --- a/app/src/main/java/to/bitkit/services/CoreService.kt +++ b/app/src/main/java/to/bitkit/services/CoreService.kt @@ -282,9 +282,8 @@ class ActivityService( } val existingActivity = getActivityById(payment.id) - if ( - existingActivity as? Activity.Onchain != null - && (existingActivity.v1.updatedAt ?: 0u) > payment.latestUpdateTimestamp + if (existingActivity != null && existingActivity is Activity.Onchain && (existingActivity.v1.updatedAt + ?: 0u) > payment.latestUpdateTimestamp ) { continue } From 9eef9baec0af5f0fed51a2cef6e2aa512ba77dd5 Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Wed, 30 Jul 2025 13:14:22 -0300 Subject: [PATCH 06/19] feat: add metadata to transfer --- .../java/to/bitkit/viewmodels/TransferViewModel.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt index bdfc910b6..6983c63b3 100644 --- a/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt @@ -27,6 +27,8 @@ import org.lightningdevkit.ldknode.ChannelDetails import to.bitkit.R import to.bitkit.data.CacheStore import to.bitkit.data.SettingsStore +import to.bitkit.data.dto.ActivityMetaData +import to.bitkit.ext.getSatsPerVByteFor import to.bitkit.models.TransactionSpeed import to.bitkit.repositories.BlocktankRepo import to.bitkit.repositories.CurrencyRepo @@ -173,6 +175,16 @@ class TransferViewModel @Inject constructor( ) .onSuccess { txId -> cacheStore.addPaidOrder(orderId = order.id, txId = txId) + cacheStore.addActivityMetaData( + ActivityMetaData.OnChainActivity( + txId = txId, + feeRate = 1u, //TODO UPDATE sendOnChain result + address = order.payment.onchain.address, + isTransfer = true, + channelId = order.channel?.shortChannelId, + transferTxId = txId + ) + ) settingsStore.update { it.copy(lightningSetupStep = 0) } watchOrder(order.id) } From df151ea477f248d92df6286608f8ad9c2ccec9ac Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Wed, 30 Jul 2025 13:22:08 -0300 Subject: [PATCH 07/19] feat: add metadata to transfer --- .../java/to/bitkit/repositories/LightningRepo.kt | 8 +++++--- .../java/to/bitkit/viewmodels/TransferViewModel.kt | 12 ++---------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt index 57636bc51..ad4ecdec8 100644 --- a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt @@ -500,6 +500,8 @@ class LightningRepo @Inject constructor( sats: ULong, speed: TransactionSpeed? = null, utxosToSpend: List? = null, + isTransfer: Boolean = false, + channelId: String? = null, ): Result = executeWhenNodeRunning("Send on-chain") { val transactionSpeed = speed ?: settingsStore.data.first().defaultTransactionSpeed @@ -523,9 +525,9 @@ class LightningRepo @Inject constructor( txId = txId, feeRate = satsPerVByte, address = address, - isTransfer = false, - channelId = null, - transferTxId = null + isTransfer = isTransfer, + channelId = channelId, + transferTxId = txId.takeIf { isTransfer } )) syncState() Result.success(txId) diff --git a/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt index 6983c63b3..c6a139029 100644 --- a/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt @@ -172,19 +172,11 @@ class TransferViewModel @Inject constructor( address = order.payment.onchain.address, sats = order.feeSat, speed = speed, + isTransfer = true, + channelId = order.channel?.shortChannelId, ) .onSuccess { txId -> cacheStore.addPaidOrder(orderId = order.id, txId = txId) - cacheStore.addActivityMetaData( - ActivityMetaData.OnChainActivity( - txId = txId, - feeRate = 1u, //TODO UPDATE sendOnChain result - address = order.payment.onchain.address, - isTransfer = true, - channelId = order.channel?.shortChannelId, - transferTxId = txId - ) - ) settingsStore.update { it.copy(lightningSetupStep = 0) } watchOrder(order.id) } From 7866d334f1df5141b92780bf2a5b65089f1c0051 Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Thu, 31 Jul 2025 08:10:33 -0300 Subject: [PATCH 08/19] chore: update core version --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index de1fbaf28..492770aba 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -86,7 +86,7 @@ ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "k ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } #ldk-node-android = { module = "org.lightningdevkit:ldk-node-android", version = "0.6.1" } # upstream -ldk-node-android = { module = "com.github.synonymdev:ldk-node", version = "v0.6.1-rc.3" } # fork +ldk-node-android = { module = "com.github.synonymdev:ldk-node", version = "v0.6.1-rc.4" } # fork lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "lifecycle" } lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycle" } lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" } From 498f45f2d0228744a862cbd64c1d6f8dd0f1bc35 Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Thu, 31 Jul 2025 08:10:50 -0300 Subject: [PATCH 09/19] refactor: remove bolt11 metadata --- .../to/bitkit/data/dto/ActivityMetaData.kt | 7 ------ .../to/bitkit/repositories/LightningRepo.kt | 7 ++---- .../java/to/bitkit/services/CoreService.kt | 24 ++++++++++--------- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/to/bitkit/data/dto/ActivityMetaData.kt b/app/src/main/java/to/bitkit/data/dto/ActivityMetaData.kt index 9853faf6d..bc332e611 100644 --- a/app/src/main/java/to/bitkit/data/dto/ActivityMetaData.kt +++ b/app/src/main/java/to/bitkit/data/dto/ActivityMetaData.kt @@ -15,15 +15,8 @@ sealed interface ActivityMetaData { val channelId: String?, val transferTxId: String?, ) : ActivityMetaData - @Serializable - @SerialName("bolt11") - data class Bolt11( - val paymentId: String, - val invoice: String, - ) : ActivityMetaData } fun ActivityMetaData.rawId() = when(this) { - is ActivityMetaData.Bolt11 -> paymentId is ActivityMetaData.OnChainActivity -> txId } diff --git a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt index ad4ecdec8..20bfd937c 100644 --- a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt @@ -476,10 +476,6 @@ class LightningRepo @Inject constructor( suspend fun payInvoice(bolt11: String, sats: ULong? = null): Result = executeWhenNodeRunning("Pay invoice") { val paymentId = lightningService.send(bolt11 = bolt11, sats = sats) - cacheStore.addActivityMetaData(ActivityMetaData.Bolt11( - paymentId = paymentId, - invoice = bolt11 - )) syncState() Result.success(paymentId) } @@ -521,7 +517,8 @@ class LightningRepo @Inject constructor( satsPerVByte = satsPerVByte, utxosToSpend = finalUtxosToSpend, ) - cacheStore.addActivityMetaData(ActivityMetaData.OnChainActivity( + cacheStore.addActivityMetaData( + ActivityMetaData.OnChainActivity( txId = txId, feeRate = satsPerVByte, address = address, diff --git a/app/src/main/java/to/bitkit/services/CoreService.kt b/app/src/main/java/to/bitkit/services/CoreService.kt index 5b34bfd46..8535373e2 100644 --- a/app/src/main/java/to/bitkit/services/CoreService.kt +++ b/app/src/main/java/to/bitkit/services/CoreService.kt @@ -52,7 +52,6 @@ import org.lightningdevkit.ldknode.PaymentKind import org.lightningdevkit.ldknode.PaymentStatus import to.bitkit.async.ServiceQueue import to.bitkit.data.CacheStore -import to.bitkit.data.SettingsStore import to.bitkit.env.Env import to.bitkit.ext.amountSats import to.bitkit.models.LnPeer @@ -70,7 +69,6 @@ import kotlin.random.Random class CoreService @Inject constructor( private val lightningService: LightningService, private val httpClient: HttpClient, - private val settingsStore: SettingsStore, private val cacheStore: CacheStore, ) { private var walletIndex: Int = 0 @@ -282,8 +280,9 @@ class ActivityService( } val existingActivity = getActivityById(payment.id) - if (existingActivity != null && existingActivity is Activity.Onchain && (existingActivity.v1.updatedAt - ?: 0u) > payment.latestUpdateTimestamp + if (existingActivity != null && + existingActivity is Activity.Onchain && + (existingActivity.v1.updatedAt ?: 0u) > payment.latestUpdateTimestamp ) { continue } @@ -301,16 +300,16 @@ class ActivityService( txId = kind.txid, value = payment.amountSats ?: 0u, fee = (payment.feePaidMsat ?: 0u) / 1000u, - feeRate = 1u, // TODO: get from somewhere - address = "todo_find_address", // TODO: find address + feeRate = 1u, + address = "Loading...", confirmed = isConfirmed, timestamp = timestamp, isBoosted = false, - isTransfer = false, // TODO: handle when paying for order + isTransfer = false, doesExist = true, confirmTimestamp = confirmedTimestamp, - channelId = null, // TODO: get from linked order - transferTxId = null, // TODO: get from linked order + channelId = null, + transferTxId = null, createdAt = timestamp, updatedAt = timestamp, ) @@ -330,7 +329,10 @@ class ActivityService( is PaymentKind.Bolt11 -> { // Skip pending inbound payments, just means they created an invoice - if (payment.status == PaymentStatus.PENDING && payment.direction == PaymentDirection.INBOUND) { + if ( + payment.status == PaymentStatus.PENDING && + payment.direction == PaymentDirection.INBOUND + ) { continue } @@ -354,7 +356,7 @@ class ActivityService( status = state, value = payment.amountSats ?: 0u, fee = (payment.feePaidMsat ?: 0u) / 1000u, - invoice = "loading...", + invoice = kind.bolt11 ?: "Loading...", message = kind.description.orEmpty(), timestamp = payment.latestUpdateTimestamp, preimage = kind.preimage, From 92b04a5bd900c7c05a7c48833d1c58604bf9440a Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Thu, 31 Jul 2025 08:16:00 -0300 Subject: [PATCH 10/19] refactor: remove bolt11 metadata --- app/src/main/java/to/bitkit/di/JsonModule.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/di/JsonModule.kt b/app/src/main/java/to/bitkit/di/JsonModule.kt index 3cdce78d6..e411beded 100644 --- a/app/src/main/java/to/bitkit/di/JsonModule.kt +++ b/app/src/main/java/to/bitkit/di/JsonModule.kt @@ -19,7 +19,6 @@ val json = Json { serializersModule = SerializersModule { polymorphic(ActivityMetaData::class) { subclass(ActivityMetaData.OnChainActivity::class) - subclass(ActivityMetaData.Bolt11::class) } } } From 051b90a6d71cd4e103d46ee8f48f5cd20c03ef4f Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Thu, 31 Jul 2025 08:25:16 -0300 Subject: [PATCH 11/19] refactor: remove bolt11 metadata --- .../to/bitkit/repositories/ActivityRepo.kt | 32 +++---------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt b/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt index 02706daf0..9a962830c 100644 --- a/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt @@ -1,6 +1,7 @@ package to.bitkit.repositories import com.synonym.bitkitcore.Activity +import com.synonym.bitkitcore.Activity.Onchain import com.synonym.bitkitcore.ActivityFilter import com.synonym.bitkitcore.PaymentType import com.synonym.bitkitcore.SortDirection @@ -15,7 +16,6 @@ import to.bitkit.data.dto.PendingBoostActivity import to.bitkit.data.dto.rawId import to.bitkit.di.BgDispatcher import to.bitkit.ext.matchesPaymentId -import to.bitkit.ext.nowTimestamp import to.bitkit.ext.rawId import to.bitkit.services.CoreService import to.bitkit.utils.Logger @@ -255,35 +255,9 @@ class ActivityRepo @Inject constructor( Logger.debug("updateActivitiesMetaData = Activity found: ${activityToUpdate.rawId()}", context = TAG) when (activityToUpdate) { - is Activity.Lightning -> { - val metaData = activityMetaData as? ActivityMetaData.Bolt11 - val updatedActivity = Activity.Lightning( - v1 = activityToUpdate.v1.copy( - invoice = metaData?.invoice.orEmpty(), - timestamp = nowTimestamp().toEpochMilli().toULong() - ) - ) - - updateActivity( - id = updatedActivity.v1.id, - activity = updatedActivity - ).onSuccess { - Logger.debug( - "updateActivitiesMetaData - Activity updated with success. " + - "new data: $updatedActivity", context = TAG - ) - cacheStore.removeActivityMetaData(activityMetaData) - }.onFailure { - Logger.warn( - "updateActivitiesMetaData - Failed updating activity ${updatedActivity.rawId()}", - context = TAG - ) - } - } - is Activity.Onchain -> { val metaData = activityMetaData as? ActivityMetaData.OnChainActivity - val updatedActivity = Activity.Onchain( + val updatedActivity = Onchain( v1 = activityToUpdate.v1.copy( feeRate = metaData?.feeRate?.toULong() ?: 1u, address = metaData?.address.orEmpty(), @@ -300,6 +274,8 @@ class ActivityRepo @Inject constructor( cacheStore.removeActivityMetaData(activityMetaData) } } + + is Activity.Lightning -> Unit } } } From f5be88ac3b53135725ad793ce9e052f2571304f7 Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Thu, 31 Jul 2025 08:32:16 -0300 Subject: [PATCH 12/19] chore: lint --- .../java/to/bitkit/data/dto/ActivityMetaData.kt | 2 +- .../java/to/bitkit/repositories/LightningRepo.kt | 15 ++++++++------- .../main/java/to/bitkit/services/CoreService.kt | 4 ++-- .../to/bitkit/viewmodels/TransferViewModel.kt | 2 -- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/to/bitkit/data/dto/ActivityMetaData.kt b/app/src/main/java/to/bitkit/data/dto/ActivityMetaData.kt index bc332e611..ef98f7fb4 100644 --- a/app/src/main/java/to/bitkit/data/dto/ActivityMetaData.kt +++ b/app/src/main/java/to/bitkit/data/dto/ActivityMetaData.kt @@ -17,6 +17,6 @@ sealed interface ActivityMetaData { ) : ActivityMetaData } -fun ActivityMetaData.rawId() = when(this) { +fun ActivityMetaData.rawId() = when (this) { is ActivityMetaData.OnChainActivity -> txId } diff --git a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt index 20bfd937c..b7d533086 100644 --- a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt @@ -519,13 +519,14 @@ class LightningRepo @Inject constructor( ) cacheStore.addActivityMetaData( ActivityMetaData.OnChainActivity( - txId = txId, - feeRate = satsPerVByte, - address = address, - isTransfer = isTransfer, - channelId = channelId, - transferTxId = txId.takeIf { isTransfer } - )) + txId = txId, + feeRate = satsPerVByte, + address = address, + isTransfer = isTransfer, + channelId = channelId, + transferTxId = txId.takeIf { isTransfer } + ) + ) syncState() Result.success(txId) } diff --git a/app/src/main/java/to/bitkit/services/CoreService.kt b/app/src/main/java/to/bitkit/services/CoreService.kt index 8535373e2..4aef63a19 100644 --- a/app/src/main/java/to/bitkit/services/CoreService.kt +++ b/app/src/main/java/to/bitkit/services/CoreService.kt @@ -338,8 +338,8 @@ class ActivityService( val existingActivity = getActivityById(payment.id) if ( - existingActivity as? Activity.Lightning != null - && (existingActivity.v1.updatedAt ?: 0u) > payment.latestUpdateTimestamp + existingActivity as? Activity.Lightning != null && + (existingActivity.v1.updatedAt ?: 0u) > payment.latestUpdateTimestamp ) { continue } diff --git a/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt index c6a139029..d7d06f4a0 100644 --- a/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt @@ -27,8 +27,6 @@ import org.lightningdevkit.ldknode.ChannelDetails import to.bitkit.R import to.bitkit.data.CacheStore import to.bitkit.data.SettingsStore -import to.bitkit.data.dto.ActivityMetaData -import to.bitkit.ext.getSatsPerVByteFor import to.bitkit.models.TransactionSpeed import to.bitkit.repositories.BlocktankRepo import to.bitkit.repositories.CurrencyRepo From d820d1323580fe82f1ed81171d41b63cf17004c0 Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Thu, 31 Jul 2025 09:43:41 -0300 Subject: [PATCH 13/19] test: sendOnChain should cache activity meta data --- .../bitkit/repositories/LightningRepoTest.kt | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt b/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt index a88bf9851..c59c730ac 100644 --- a/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt +++ b/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt @@ -7,23 +7,31 @@ import org.junit.Before import org.junit.Test import org.lightningdevkit.ldknode.ChannelDetails import org.lightningdevkit.ldknode.NodeStatus +import org.lightningdevkit.ldknode.OutPoint import org.lightningdevkit.ldknode.PaymentDetails +import org.lightningdevkit.ldknode.SpendableUtxo import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.inOrder import org.mockito.kotlin.mock +import org.mockito.kotlin.spy import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyBlocking import org.mockito.kotlin.whenever import org.mockito.kotlin.wheneverBlocking import to.bitkit.data.CacheStore import to.bitkit.data.SettingsData import to.bitkit.data.SettingsStore +import to.bitkit.data.dto.ActivityMetaData import to.bitkit.data.keychain.Keychain import to.bitkit.ext.createChannelDetails import to.bitkit.models.ElectrumServer import to.bitkit.models.LnPeer import to.bitkit.models.NodeLifecycleState +import to.bitkit.models.TransactionSpeed import to.bitkit.services.BlocktankNotificationsService import to.bitkit.services.CoreService import to.bitkit.services.LdkNodeEventBus @@ -340,6 +348,59 @@ class LightningRepoTest : BaseUnitTest() { assertTrue(result.isFailure) } + @Test + fun `sendOnChain should cache activity meta data`() = test { + val mockSettingsData = SettingsData( + defaultTransactionSpeed = TransactionSpeed.Fast, + coinSelectAuto = false // Disable auto coin selection to simplify the test + ) + whenever(settingsStore.data).thenReturn(flowOf(mockSettingsData)) + + wheneverBlocking { cacheStore.addActivityMetaData(any()) }.thenReturn(Unit) + + whenever( + lightningService.send( + address = any(), + sats = any(), + satsPerVByte = any(), + utxosToSpend = anyOrNull() + ) + ).thenReturn("testPaymentId") + + startNodeForTesting() + + // Create a spy to mock the getFeeRateForSpeed method + val spySut = spy(sut) + doReturn(Result.success(10uL)).whenever(spySut).getFeeRateForSpeed(any()) + + val result = spySut.sendOnChain( + address = "test_address", + sats = 1000uL, + speed = TransactionSpeed.Fast, + utxosToSpend = null, // This was the missing parameter! + isTransfer = true, + channelId = "test_channel_id" + ) + + // Verify the result is successful + assertTrue(result.isSuccess) + assertEquals("testPaymentId", result.getOrNull()) + + // Verify the cache call + val captor = argumentCaptor() + verifyBlocking(cacheStore) { + addActivityMetaData(captor.capture()) + } + + val capturedActivity = captor.firstValue + assertEquals("testPaymentId", capturedActivity.txId) + assertEquals("test_address", capturedActivity.address) + assertEquals(true, capturedActivity.isTransfer) + assertEquals("test_channel_id", capturedActivity.channelId) + assertEquals("testPaymentId", capturedActivity.transferTxId) + assertEquals(10u, capturedActivity.feeRate) + } + @Test fun `registerForNotifications should fail when node is not running`() = test { val result = sut.registerForNotifications() From 16dcf2687d9ba524e41fb7d31a6fd5f5d1686b76 Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Thu, 31 Jul 2025 09:49:52 -0300 Subject: [PATCH 14/19] chore: lint --- app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt b/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt index c59c730ac..f5fbfab41 100644 --- a/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt +++ b/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt @@ -7,9 +7,7 @@ import org.junit.Before import org.junit.Test import org.lightningdevkit.ldknode.ChannelDetails import org.lightningdevkit.ldknode.NodeStatus -import org.lightningdevkit.ldknode.OutPoint import org.lightningdevkit.ldknode.PaymentDetails -import org.lightningdevkit.ldknode.SpendableUtxo import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.argumentCaptor From 928d337dd94709cb0b73e95413f7bca7c227f9f6 Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Thu, 31 Jul 2025 13:24:11 -0300 Subject: [PATCH 15/19] fix: metadata typo --- app/src/main/java/to/bitkit/data/CacheStore.kt | 16 ++++++++-------- ...ctivityMetaData.kt => TransactionMetadata.kt} | 8 ++++---- app/src/main/java/to/bitkit/di/JsonModule.kt | 6 +++--- .../java/to/bitkit/repositories/ActivityRepo.kt | 12 ++++++------ .../java/to/bitkit/repositories/LightningRepo.kt | 6 +++--- .../to/bitkit/repositories/LightningRepoTest.kt | 8 ++++---- 6 files changed, 28 insertions(+), 28 deletions(-) rename app/src/main/java/to/bitkit/data/dto/{ActivityMetaData.kt => TransactionMetadata.kt} (70%) diff --git a/app/src/main/java/to/bitkit/data/CacheStore.kt b/app/src/main/java/to/bitkit/data/CacheStore.kt index c90d492c5..a7668aee1 100644 --- a/app/src/main/java/to/bitkit/data/CacheStore.kt +++ b/app/src/main/java/to/bitkit/data/CacheStore.kt @@ -9,8 +9,8 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.serialization.Serializable -import to.bitkit.data.dto.ActivityMetaData import to.bitkit.data.dto.PendingBoostActivity +import to.bitkit.data.dto.TransactionMetadata import to.bitkit.data.dto.rawId import to.bitkit.data.serializers.AppCacheSerializer import to.bitkit.models.BackupCategory @@ -115,19 +115,19 @@ class CacheStore @Inject constructor( } } - suspend fun addActivityMetaData(item: ActivityMetaData) { - if (item.rawId() in store.data.first().activitiesMetaData.map { it.rawId() }) return + suspend fun addTransactionMetadata(item: TransactionMetadata) { + if (item.rawId() in store.data.first().transactionsMetadata.map { it.rawId() }) return store.updateData { - it.copy(activitiesMetaData = it.activitiesMetaData + item) + it.copy(transactionsMetadata = it.transactionsMetadata + item) } } - suspend fun removeActivityMetaData(item: ActivityMetaData) { - if (item.rawId() !in store.data.first().activitiesMetaData.map { it.rawId() }) return + suspend fun removeTransactionMetadata(item: TransactionMetadata) { + if (item.rawId() !in store.data.first().transactionsMetadata.map { it.rawId() }) return store.updateData { - it.copy(activitiesMetaData = it.activitiesMetaData - item) + it.copy(transactionsMetadata = it.transactionsMetadata - item) } } @@ -153,5 +153,5 @@ data class AppCacheData( val deletedActivities: List = listOf(), val activitiesPendingDelete: List = listOf(), val pendingBoostActivities: List = listOf(), - val activitiesMetaData: List = listOf(), + val transactionsMetadata: List = listOf(), ) diff --git a/app/src/main/java/to/bitkit/data/dto/ActivityMetaData.kt b/app/src/main/java/to/bitkit/data/dto/TransactionMetadata.kt similarity index 70% rename from app/src/main/java/to/bitkit/data/dto/ActivityMetaData.kt rename to app/src/main/java/to/bitkit/data/dto/TransactionMetadata.kt index ef98f7fb4..4b374f9a9 100644 --- a/app/src/main/java/to/bitkit/data/dto/ActivityMetaData.kt +++ b/app/src/main/java/to/bitkit/data/dto/TransactionMetadata.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -sealed interface ActivityMetaData { +sealed interface TransactionMetadata { @Serializable @SerialName("onchain") data class OnChainActivity( @@ -14,9 +14,9 @@ sealed interface ActivityMetaData { val isTransfer: Boolean, val channelId: String?, val transferTxId: String?, - ) : ActivityMetaData + ) : TransactionMetadata } -fun ActivityMetaData.rawId() = when (this) { - is ActivityMetaData.OnChainActivity -> txId +fun TransactionMetadata.rawId() = when (this) { + is TransactionMetadata.OnChainActivity -> txId } diff --git a/app/src/main/java/to/bitkit/di/JsonModule.kt b/app/src/main/java/to/bitkit/di/JsonModule.kt index e411beded..7e5c01c7f 100644 --- a/app/src/main/java/to/bitkit/di/JsonModule.kt +++ b/app/src/main/java/to/bitkit/di/JsonModule.kt @@ -8,7 +8,7 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.polymorphic import kotlinx.serialization.modules.subclass -import to.bitkit.data.dto.ActivityMetaData +import to.bitkit.data.dto.TransactionMetadata import javax.inject.Singleton val json = Json { @@ -17,8 +17,8 @@ val json = Json { ignoreUnknownKeys = true encodeDefaults = true serializersModule = SerializersModule { - polymorphic(ActivityMetaData::class) { - subclass(ActivityMetaData.OnChainActivity::class) + polymorphic(TransactionMetadata::class) { + subclass(TransactionMetadata.OnChainActivity::class) } } } diff --git a/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt b/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt index 9a962830c..60b5fa17e 100644 --- a/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt @@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext import org.lightningdevkit.ldknode.PaymentDetails import to.bitkit.data.CacheStore -import to.bitkit.data.dto.ActivityMetaData +import to.bitkit.data.dto.TransactionMetadata import to.bitkit.data.dto.PendingBoostActivity import to.bitkit.data.dto.rawId import to.bitkit.di.BgDispatcher @@ -49,7 +49,7 @@ class ActivityRepo @Inject constructor( .onSuccess { payments -> Logger.debug("Got payments with success, syncing activities", context = TAG) syncLdkNodePayments(payments = payments) - updateActivitiesMetaData() + updateActivitiesMetadata() boostPendingActivities() isSyncingLdkNodePayments = false return@withContext Result.success(Unit) @@ -245,8 +245,8 @@ class ActivityRepo @Inject constructor( } } - private suspend fun updateActivitiesMetaData() = withContext(bgDispatcher) { - cacheStore.data.first().activitiesMetaData.forEach { activityMetaData -> + private suspend fun updateActivitiesMetadata() = withContext(bgDispatcher) { + cacheStore.data.first().transactionsMetadata.forEach { activityMetaData -> findActivityByPaymentId( paymentHashOrTxId = activityMetaData.rawId(), type = ActivityFilter.ALL, @@ -256,7 +256,7 @@ class ActivityRepo @Inject constructor( when (activityToUpdate) { is Activity.Onchain -> { - val metaData = activityMetaData as? ActivityMetaData.OnChainActivity + val metaData = activityMetaData as? TransactionMetadata.OnChainActivity val updatedActivity = Onchain( v1 = activityToUpdate.v1.copy( feeRate = metaData?.feeRate?.toULong() ?: 1u, @@ -271,7 +271,7 @@ class ActivityRepo @Inject constructor( id = updatedActivity.v1.id, activity = updatedActivity ).onSuccess { - cacheStore.removeActivityMetaData(activityMetaData) + cacheStore.removeTransactionMetadata(activityMetaData) } } diff --git a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt index b7d533086..272194656 100644 --- a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt @@ -27,7 +27,7 @@ import org.lightningdevkit.ldknode.Txid import org.lightningdevkit.ldknode.UserChannelId import to.bitkit.data.CacheStore import to.bitkit.data.SettingsStore -import to.bitkit.data.dto.ActivityMetaData +import to.bitkit.data.dto.TransactionMetadata import to.bitkit.data.keychain.Keychain import to.bitkit.di.BgDispatcher import to.bitkit.env.Env @@ -517,8 +517,8 @@ class LightningRepo @Inject constructor( satsPerVByte = satsPerVByte, utxosToSpend = finalUtxosToSpend, ) - cacheStore.addActivityMetaData( - ActivityMetaData.OnChainActivity( + cacheStore.addTransactionMetadata( + TransactionMetadata.OnChainActivity( txId = txId, feeRate = satsPerVByte, address = address, diff --git a/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt b/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt index f5fbfab41..ba2dc9ce2 100644 --- a/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt +++ b/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt @@ -23,7 +23,7 @@ import org.mockito.kotlin.wheneverBlocking import to.bitkit.data.CacheStore import to.bitkit.data.SettingsData import to.bitkit.data.SettingsStore -import to.bitkit.data.dto.ActivityMetaData +import to.bitkit.data.dto.TransactionMetadata import to.bitkit.data.keychain.Keychain import to.bitkit.ext.createChannelDetails import to.bitkit.models.ElectrumServer @@ -354,7 +354,7 @@ class LightningRepoTest : BaseUnitTest() { ) whenever(settingsStore.data).thenReturn(flowOf(mockSettingsData)) - wheneverBlocking { cacheStore.addActivityMetaData(any()) }.thenReturn(Unit) + wheneverBlocking { cacheStore.addTransactionMetadata(any()) }.thenReturn(Unit) whenever( lightningService.send( @@ -385,9 +385,9 @@ class LightningRepoTest : BaseUnitTest() { assertEquals("testPaymentId", result.getOrNull()) // Verify the cache call - val captor = argumentCaptor() + val captor = argumentCaptor() verifyBlocking(cacheStore) { - addActivityMetaData(captor.capture()) + addTransactionMetadata(captor.capture()) } val capturedActivity = captor.firstValue From b0576c6c05bbb5d9e63ed679fc706393d66faac6 Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Thu, 31 Jul 2025 13:34:52 -0300 Subject: [PATCH 16/19] chore: lint --- .../to/bitkit/repositories/ActivityRepo.kt | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt b/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt index 60b5fa17e..1733fe574 100644 --- a/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt @@ -103,7 +103,9 @@ class ActivityRepo @Inject constructor( type: ActivityFilter, txType: PaymentType, ): Result = withContext(bgDispatcher) { - if (paymentHashOrTxId.isEmpty()) return@withContext Result.failure(IllegalArgumentException("paymentHashOrTxId is empty")) + if (paymentHashOrTxId.isEmpty()) return@withContext Result.failure( + IllegalArgumentException("paymentHashOrTxId is empty") + ) return@withContext try { suspend fun findActivity(): Activity? = getActivities( @@ -135,10 +137,13 @@ class ActivityRepo @Inject constructor( } } - if (activity != null) Result.success(activity) else Result.failure(IllegalStateException("Activity not found")) + if (activity != null) Result.success(activity) else Result.failure( + IllegalStateException("Activity not found") + ) } catch (e: Exception) { Logger.error( - "findActivityByPaymentId error. Parameters:\n paymentHashOrTxId:$paymentHashOrTxId type:$type txType:$txType", + "findActivityByPaymentId error. Parameters:" + + "\n paymentHashOrTxId:$paymentHashOrTxId type:$type txType:$txType", context = TAG ) Result.failure(e) @@ -162,7 +167,15 @@ class ActivityRepo @Inject constructor( coreService.activity.get(filter, txType, tags, search, minDate, maxDate, limit, sortDirection) }.onFailure { e -> Logger.error( - "getActivities error. Parameters:\nfilter:$filter txType:$txType tags:$tags search:$search minDate:$minDate maxDate:$maxDate limit:$limit sortDirection:$sortDirection", + "getActivities error. Parameters:" + + "\nfilter:$filter " + + "txType:$txType " + + "tags:$tags " + + "search:$search " + + "minDate:$minDate " + + "maxDate:$maxDate " + + "limit:$limit " + + "sortDirection:$sortDirection", e = e, context = TAG ) @@ -192,7 +205,11 @@ class ActivityRepo @Inject constructor( return@withContext runCatching { if (id in cacheStore.data.first().deletedActivities && !forceUpdate) { Logger.debug("Activity $id was deleted", context = TAG) - return@withContext Result.failure(Exception("Activity $id was deleted. If you want update it, set forceUpdate as true")) + return@withContext Result.failure( + Exception( + "Activity $id was deleted. If you want update it, set forceUpdate as true" + ) + ) } coreService.activity.update(id, activity) }.onFailure { e -> @@ -201,7 +218,8 @@ class ActivityRepo @Inject constructor( } /** - * Updates an activity and delete other one. In case of failure in the update or deletion, the data will be cached to try again on the next sync + * Updates an activity and delete other one. In case of failure in the update or deletion, the data will be cached + * to try again on the next sync */ suspend fun replaceActivity( id: String, @@ -228,7 +246,8 @@ class ActivityRepo @Inject constructor( }, onFailure = { e -> Logger.error( - "Update activity fail. Parameters: id:$id, activityIdToDelete:$activityIdToDelete activity:$activity", + "Update activity fail. Parameters: id:$id, " + + "activityIdToDelete:$activityIdToDelete activity:$activity", e = e, context = TAG ) From c437452e4371cb6a710a654fa4481cff842fdda8 Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Thu, 31 Jul 2025 13:36:54 -0300 Subject: [PATCH 17/19] chore: add comments --- app/src/main/java/to/bitkit/services/CoreService.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/to/bitkit/services/CoreService.kt b/app/src/main/java/to/bitkit/services/CoreService.kt index 4aef63a19..b7f99faca 100644 --- a/app/src/main/java/to/bitkit/services/CoreService.kt +++ b/app/src/main/java/to/bitkit/services/CoreService.kt @@ -300,16 +300,16 @@ class ActivityService( txId = kind.txid, value = payment.amountSats ?: 0u, fee = (payment.feePaidMsat ?: 0u) / 1000u, - feeRate = 1u, - address = "Loading...", + feeRate = 1u, // TODO: get from somewhere + address = "Loading...", // TODO: find address confirmed = isConfirmed, timestamp = timestamp, isBoosted = false, - isTransfer = false, + isTransfer = false, // TODO: handle when paying for order doesExist = true, confirmTimestamp = confirmedTimestamp, - channelId = null, - transferTxId = null, + channelId = null, // TODO: get from linked order + transferTxId = null, // TODO: get from linked order createdAt = timestamp, updatedAt = timestamp, ) From b1f974ffc9ba176a18ad7e96b169188398374408 Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Thu, 31 Jul 2025 13:45:14 -0300 Subject: [PATCH 18/19] refactor: convert TransactionMetadata into a data class --- .../main/java/to/bitkit/data/CacheStore.kt | 5 ++-- .../to/bitkit/data/dto/TransactionMetadata.kt | 25 ++++++------------- app/src/main/java/to/bitkit/di/JsonModule.kt | 9 ------- .../to/bitkit/repositories/ActivityRepo.kt | 15 +++++------ .../to/bitkit/repositories/LightningRepo.kt | 2 +- .../bitkit/repositories/LightningRepoTest.kt | 2 +- 6 files changed, 18 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/to/bitkit/data/CacheStore.kt b/app/src/main/java/to/bitkit/data/CacheStore.kt index a7668aee1..308a289d2 100644 --- a/app/src/main/java/to/bitkit/data/CacheStore.kt +++ b/app/src/main/java/to/bitkit/data/CacheStore.kt @@ -11,7 +11,6 @@ import kotlinx.coroutines.flow.map import kotlinx.serialization.Serializable import to.bitkit.data.dto.PendingBoostActivity import to.bitkit.data.dto.TransactionMetadata -import to.bitkit.data.dto.rawId import to.bitkit.data.serializers.AppCacheSerializer import to.bitkit.models.BackupCategory import to.bitkit.models.BackupItemStatus @@ -116,7 +115,7 @@ class CacheStore @Inject constructor( } suspend fun addTransactionMetadata(item: TransactionMetadata) { - if (item.rawId() in store.data.first().transactionsMetadata.map { it.rawId() }) return + if (item.txId in store.data.first().transactionsMetadata.map { it.txId }) return store.updateData { it.copy(transactionsMetadata = it.transactionsMetadata + item) @@ -124,7 +123,7 @@ class CacheStore @Inject constructor( } suspend fun removeTransactionMetadata(item: TransactionMetadata) { - if (item.rawId() !in store.data.first().transactionsMetadata.map { it.rawId() }) return + if (item.txId !in store.data.first().transactionsMetadata.map { it.txId }) return store.updateData { it.copy(transactionsMetadata = it.transactionsMetadata - item) diff --git a/app/src/main/java/to/bitkit/data/dto/TransactionMetadata.kt b/app/src/main/java/to/bitkit/data/dto/TransactionMetadata.kt index 4b374f9a9..6bbb13a59 100644 --- a/app/src/main/java/to/bitkit/data/dto/TransactionMetadata.kt +++ b/app/src/main/java/to/bitkit/data/dto/TransactionMetadata.kt @@ -1,22 +1,13 @@ package to.bitkit.data.dto -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -sealed interface TransactionMetadata { - @Serializable - @SerialName("onchain") - data class OnChainActivity( - val txId: String, - val feeRate: UInt, - val address: String, - val isTransfer: Boolean, - val channelId: String?, - val transferTxId: String?, - ) : TransactionMetadata -} - -fun TransactionMetadata.rawId() = when (this) { - is TransactionMetadata.OnChainActivity -> txId -} +data class TransactionMetadata( + val txId: String, + val feeRate: UInt, + val address: String, + val isTransfer: Boolean, + val channelId: String?, + val transferTxId: String?, +) diff --git a/app/src/main/java/to/bitkit/di/JsonModule.kt b/app/src/main/java/to/bitkit/di/JsonModule.kt index 7e5c01c7f..684c7dc7e 100644 --- a/app/src/main/java/to/bitkit/di/JsonModule.kt +++ b/app/src/main/java/to/bitkit/di/JsonModule.kt @@ -5,10 +5,6 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import kotlinx.serialization.json.Json -import kotlinx.serialization.modules.SerializersModule -import kotlinx.serialization.modules.polymorphic -import kotlinx.serialization.modules.subclass -import to.bitkit.data.dto.TransactionMetadata import javax.inject.Singleton val json = Json { @@ -16,11 +12,6 @@ val json = Json { isLenient = true ignoreUnknownKeys = true encodeDefaults = true - serializersModule = SerializersModule { - polymorphic(TransactionMetadata::class) { - subclass(TransactionMetadata.OnChainActivity::class) - } - } } @Module diff --git a/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt b/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt index 1733fe574..2c5a48de5 100644 --- a/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt @@ -11,9 +11,7 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext import org.lightningdevkit.ldknode.PaymentDetails import to.bitkit.data.CacheStore -import to.bitkit.data.dto.TransactionMetadata import to.bitkit.data.dto.PendingBoostActivity -import to.bitkit.data.dto.rawId import to.bitkit.di.BgDispatcher import to.bitkit.ext.matchesPaymentId import to.bitkit.ext.rawId @@ -267,7 +265,7 @@ class ActivityRepo @Inject constructor( private suspend fun updateActivitiesMetadata() = withContext(bgDispatcher) { cacheStore.data.first().transactionsMetadata.forEach { activityMetaData -> findActivityByPaymentId( - paymentHashOrTxId = activityMetaData.rawId(), + paymentHashOrTxId = activityMetaData.txId, type = ActivityFilter.ALL, txType = PaymentType.SENT ).onSuccess { activityToUpdate -> @@ -275,14 +273,13 @@ class ActivityRepo @Inject constructor( when (activityToUpdate) { is Activity.Onchain -> { - val metaData = activityMetaData as? TransactionMetadata.OnChainActivity val updatedActivity = Onchain( v1 = activityToUpdate.v1.copy( - feeRate = metaData?.feeRate?.toULong() ?: 1u, - address = metaData?.address.orEmpty(), - isTransfer = metaData?.isTransfer ?: false, - channelId = metaData?.channelId, - transferTxId = metaData?.transferTxId + feeRate = activityMetaData.feeRate.toULong(), + address = activityMetaData.address, + isTransfer = activityMetaData.isTransfer, + channelId = activityMetaData.channelId, + transferTxId = activityMetaData.transferTxId ) ) diff --git a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt index 272194656..997150079 100644 --- a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt @@ -518,7 +518,7 @@ class LightningRepo @Inject constructor( utxosToSpend = finalUtxosToSpend, ) cacheStore.addTransactionMetadata( - TransactionMetadata.OnChainActivity( + TransactionMetadata( txId = txId, feeRate = satsPerVByte, address = address, diff --git a/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt b/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt index ba2dc9ce2..13427a20b 100644 --- a/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt +++ b/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt @@ -385,7 +385,7 @@ class LightningRepoTest : BaseUnitTest() { assertEquals("testPaymentId", result.getOrNull()) // Verify the cache call - val captor = argumentCaptor() + val captor = argumentCaptor() verifyBlocking(cacheStore) { addTransactionMetadata(captor.capture()) } From 09dcc4988629675598f1dfe3cda8758a8c68c192 Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Thu, 31 Jul 2025 13:55:47 -0300 Subject: [PATCH 19/19] chore: lint --- .../java/to/bitkit/repositories/ActivityRepo.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt b/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt index 2c5a48de5..0ebb40a6b 100644 --- a/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt @@ -101,9 +101,11 @@ class ActivityRepo @Inject constructor( type: ActivityFilter, txType: PaymentType, ): Result = withContext(bgDispatcher) { - if (paymentHashOrTxId.isEmpty()) return@withContext Result.failure( - IllegalArgumentException("paymentHashOrTxId is empty") - ) + if (paymentHashOrTxId.isEmpty()) { + return@withContext Result.failure( + IllegalArgumentException("paymentHashOrTxId is empty") + ) + } return@withContext try { suspend fun findActivity(): Activity? = getActivities( @@ -135,9 +137,11 @@ class ActivityRepo @Inject constructor( } } - if (activity != null) Result.success(activity) else Result.failure( - IllegalStateException("Activity not found") - ) + if (activity != null) { + Result.success(activity) + } else { + Result.failure(IllegalStateException("Activity not found")) + } } catch (e: Exception) { Logger.error( "findActivityByPaymentId error. Parameters:" +