Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
dff1737
feat: setup activity meta data methods
jvsena42 Jul 30, 2025
0a1bf90
feat: implement activity meta data methods
jvsena42 Jul 30, 2025
c134c58
Merge branch 'refactor/state-handling-improvement' into feat/activiti…
jvsena42 Jul 30, 2025
ee2deec
fix: serialization
jvsena42 Jul 30, 2025
8a76e20
Merge branch 'refactor/state-handling-improvement' into feat/activiti…
jvsena42 Jul 30, 2025
efd7fa9
Merge branch 'master' into feat/activities-meta-data
jvsena42 Jul 30, 2025
1fe1716
fix: lightning activity update
jvsena42 Jul 30, 2025
e361050
fix: existingActivity validation
jvsena42 Jul 30, 2025
9eef9ba
feat: add metadata to transfer
jvsena42 Jul 30, 2025
df151ea
feat: add metadata to transfer
jvsena42 Jul 30, 2025
7866d33
chore: update core version
jvsena42 Jul 31, 2025
498f45f
refactor: remove bolt11 metadata
jvsena42 Jul 31, 2025
92b04a5
refactor: remove bolt11 metadata
jvsena42 Jul 31, 2025
051b90a
refactor: remove bolt11 metadata
jvsena42 Jul 31, 2025
f5be88a
chore: lint
jvsena42 Jul 31, 2025
d820d13
test: sendOnChain should cache activity meta data
jvsena42 Jul 31, 2025
16dcf26
chore: lint
jvsena42 Jul 31, 2025
f4e21bb
Merge branch 'master' into feat/activities-meta-data
jvsena42 Jul 31, 2025
928d337
fix: metadata typo
jvsena42 Jul 31, 2025
e415df0
Merge branch 'master' into feat/activities-meta-data
jvsena42 Jul 31, 2025
b0576c6
chore: lint
jvsena42 Jul 31, 2025
c437452
chore: add comments
jvsena42 Jul 31, 2025
b1f974f
refactor: convert TransactionMetadata into a data class
jvsena42 Jul 31, 2025
09dcc49
chore: lint
jvsena42 Jul 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions app/src/main/java/to/bitkit/data/CacheStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.first
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.serializers.AppCacheSerializer
import to.bitkit.models.BackupCategory
import to.bitkit.models.BackupItemStatus
Expand Down Expand Up @@ -113,6 +114,22 @@ class CacheStore @Inject constructor(
}
}

suspend fun addTransactionMetadata(item: TransactionMetadata) {
if (item.txId in store.data.first().transactionsMetadata.map { it.txId }) return

store.updateData {
it.copy(transactionsMetadata = it.transactionsMetadata + item)
}
}

suspend fun removeTransactionMetadata(item: TransactionMetadata) {
if (item.txId !in store.data.first().transactionsMetadata.map { it.txId }) return

store.updateData {
it.copy(transactionsMetadata = it.transactionsMetadata - item)
}
}

suspend fun reset() {
store.updateData { AppCacheData() }
Logger.info("Deleted all app cached data.")
Expand All @@ -135,4 +152,5 @@ data class AppCacheData(
val deletedActivities: List<String> = listOf(),
val activitiesPendingDelete: List<String> = listOf(),
val pendingBoostActivities: List<PendingBoostActivity> = listOf(),
val transactionsMetadata: List<TransactionMetadata> = listOf(),
)
13 changes: 13 additions & 0 deletions app/src/main/java/to/bitkit/data/dto/TransactionMetadata.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package to.bitkit.data.dto

import kotlinx.serialization.Serializable

@Serializable
data class TransactionMetadata(
val txId: String,
val feeRate: UInt,
val address: String,
val isTransfer: Boolean,
val channelId: String?,
val transferTxId: String?,
)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
74 changes: 65 additions & 9 deletions app/src/main/java/to/bitkit/repositories/ActivityRepo.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -46,6 +47,7 @@
.onSuccess { payments ->
Logger.debug("Got payments with success, syncing activities", context = TAG)
syncLdkNodePayments(payments = payments)
updateActivitiesMetadata()
boostPendingActivities()
isSyncingLdkNodePayments = false
return@withContext Result.success(Unit)
Expand Down Expand Up @@ -99,7 +101,9 @@
type: ActivityFilter,
txType: PaymentType,
): Result<Activity> = 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(
Expand Down Expand Up @@ -131,10 +135,13 @@
}
}

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)
Expand All @@ -158,7 +165,15 @@
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
)
Expand All @@ -183,12 +198,16 @@
suspend fun updateActivity(
id: String,
activity: Activity,
forceUpdate: Boolean = false
forceUpdate: Boolean = false,
): Result<Unit> = withContext(bgDispatcher) {
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 ->
Expand All @@ -197,7 +216,8 @@
}

/**
* 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,
Expand All @@ -224,7 +244,8 @@
},
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
)
Expand All @@ -241,6 +262,41 @@
}
}

private suspend fun updateActivitiesMetadata() = withContext(bgDispatcher) {
cacheStore.data.first().transactionsMetadata.forEach { activityMetaData ->
findActivityByPaymentId(
paymentHashOrTxId = activityMetaData.txId,
type = ActivityFilter.ALL,
txType = PaymentType.SENT
).onSuccess { activityToUpdate ->
Logger.debug("updateActivitiesMetaData = Activity found: ${activityToUpdate.rawId()}", context = TAG)

when (activityToUpdate) {
is Activity.Onchain -> {
val updatedActivity = Onchain(
v1 = activityToUpdate.v1.copy(
feeRate = activityMetaData.feeRate.toULong(),
address = activityMetaData.address,
isTransfer = activityMetaData.isTransfer,
channelId = activityMetaData.channelId,
transferTxId = activityMetaData.transferTxId
)
)

updateActivity(
id = updatedActivity.v1.id,
activity = updatedActivity
).onSuccess {
cacheStore.removeTransactionMetadata(activityMetaData)
}
}

is Activity.Lightning -> Unit
}
}
}
}

private suspend fun boostPendingActivities() = withContext(bgDispatcher) {
cacheStore.data.first().pendingBoostActivities.forEach { pendingBoostActivity ->
findActivityByPaymentId(
Expand Down Expand Up @@ -353,7 +409,7 @@
paymentHashOrTxId: String,
type: ActivityFilter,
txType: PaymentType,
tags: List<String>
tags: List<String>,
): Result<Unit> = withContext(bgDispatcher) {

if (tags.isEmpty()) return@withContext Result.failure(IllegalArgumentException("No tags selected"))
Expand Down
13 changes: 13 additions & 0 deletions app/src/main/java/to/bitkit/repositories/LightningRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.TransactionMetadata
import to.bitkit.data.keychain.Keychain
import to.bitkit.di.BgDispatcher
import to.bitkit.env.Env
Expand Down Expand Up @@ -495,6 +496,8 @@ class LightningRepo @Inject constructor(
sats: ULong,
speed: TransactionSpeed? = null,
utxosToSpend: List<SpendableUtxo>? = null,
isTransfer: Boolean = false,
channelId: String? = null,
): Result<Txid> =
executeWhenNodeRunning("Send on-chain") {
val transactionSpeed = speed ?: settingsStore.data.first().defaultTransactionSpeed
Expand All @@ -514,6 +517,16 @@ class LightningRepo @Inject constructor(
satsPerVByte = satsPerVByte,
utxosToSpend = finalUtxosToSpend,
)
cacheStore.addTransactionMetadata(
TransactionMetadata(
txId = txId,
feeRate = satsPerVByte,
address = address,
isTransfer = isTransfer,
channelId = channelId,
transferTxId = txId.takeIf { isTransfer }
)
)
syncState()
Result.success(txId)
}
Expand Down
55 changes: 36 additions & 19 deletions app/src/main/java/to/bitkit/services/CoreService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -302,7 +301,7 @@ class ActivityService(
value = payment.amountSats ?: 0u,
fee = (payment.feePaidMsat ?: 0u) / 1000u,
feeRate = 1u, // TODO: get from somewhere
address = "todo_find_address", // TODO: find address
address = "Loading...", // TODO: find address
confirmed = isConfirmed,
timestamp = timestamp,
isBoosted = false,
Expand Down Expand Up @@ -330,23 +329,41 @@ 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
}

val existingActivity = getActivityById(payment.id)
if (
existingActivity as? Activity.Lightning != null &&
(existingActivity.v1.updatedAt ?: 0u) > payment.latestUpdateTimestamp
) {
continue
}

val ln = LightningActivity(
id = payment.id,
txType = payment.direction.toPaymentType(),
status = state,
value = payment.amountSats ?: 0u,
fee = (payment.feePaidMsat ?: 0u) / 1000u,
invoice = kind.bolt11 ?: "Loading…",
message = kind.description.orEmpty(),
timestamp = payment.latestUpdateTimestamp,
preimage = kind.preimage,
createdAt = payment.latestUpdateTimestamp,
updatedAt = payment.latestUpdateTimestamp,
)
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 = kind.bolt11 ?: "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))
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ 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)
Expand Down
Loading