Skip to content

Commit 6efdfaa

Browse files
authored
Merge pull request #487 from synonymdev/ldk-events-integration
Integrate LDK events
2 parents 6bbab66 + d6b6799 commit 6efdfaa

27 files changed

+955
-772
lines changed

app/src/main/java/to/bitkit/data/CacheStore.kt

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -83,22 +83,6 @@ class CacheStore @Inject constructor(
8383
}
8484
}
8585

86-
suspend fun addActivityToPendingDelete(activityId: String) {
87-
if (activityId.isBlank()) return
88-
if (activityId in store.data.first().activitiesPendingDelete) return
89-
store.updateData {
90-
it.copy(activitiesPendingDelete = it.activitiesPendingDelete + activityId)
91-
}
92-
}
93-
94-
suspend fun removeActivityFromPendingDelete(activityId: String) {
95-
if (activityId.isBlank()) return
96-
if (activityId !in store.data.first().activitiesPendingDelete) return
97-
store.updateData {
98-
it.copy(activitiesPendingDelete = it.activitiesPendingDelete - activityId)
99-
}
100-
}
101-
10286
suspend fun addActivityToPendingBoost(pendingBoostActivity: PendingBoostActivity) {
10387
if (pendingBoostActivity in store.data.first().pendingBoostActivities) return
10488
store.updateData {
@@ -137,7 +121,6 @@ data class AppCacheData(
137121
val balance: BalanceState? = null,
138122
val backupStatuses: Map<BackupCategory, BackupItemStatus> = mapOf(),
139123
val deletedActivities: List<String> = listOf(),
140-
val activitiesPendingDelete: List<String> = listOf(),
141124
val lastLightningPaymentId: String? = null,
142125
val pendingBoostActivities: List<PendingBoostActivity> = listOf(),
143126
)

app/src/main/java/to/bitkit/domain/commands/NotifyPaymentReceivedHandler.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class NotifyPaymentReceivedHandler @Inject constructor(
3939
is NotifyPaymentReceived.Command.Lightning -> true
4040
is NotifyPaymentReceived.Command.Onchain -> {
4141
delay(DELAY_FOR_ACTIVITY_SYNC_MS)
42-
activityRepo.shouldShowPaymentReceived(command.paymentHashOrTxId, command.sats)
42+
activityRepo.shouldShowReceivedSheet(command.paymentHashOrTxId, command.sats)
4343
}
4444
}
4545

app/src/main/java/to/bitkit/ext/Activities.kt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,6 @@ fun Activity.totalValue() = when (this) {
3333
}
3434
}
3535

36-
fun Activity.canBeBoosted() = when (this) {
37-
is Activity.Onchain -> !v1.confirmed && v1.doesExist && !v1.isBoosted && !v1.isTransfer && v1.value > 0uL
38-
else -> false
39-
}
40-
4136
fun Activity.isBoosted() = when (this) {
4237
is Activity.Onchain -> v1.isBoosted
4338
else -> false

app/src/main/java/to/bitkit/repositories/ActivityRepo.kt

Lines changed: 56 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import org.lightningdevkit.ldknode.ChannelDetails
2626
import org.lightningdevkit.ldknode.PaymentDetails
2727
import org.lightningdevkit.ldknode.PaymentDirection
2828
import org.lightningdevkit.ldknode.PaymentKind
29+
import org.lightningdevkit.ldknode.TransactionDetails
2930
import to.bitkit.data.CacheStore
3031
import to.bitkit.data.dto.PendingBoostActivity
3132
import to.bitkit.di.BgDispatcher
@@ -36,7 +37,6 @@ import to.bitkit.ext.nowTimestamp
3637
import to.bitkit.ext.rawId
3738
import to.bitkit.models.ActivityBackupV1
3839
import to.bitkit.services.CoreService
39-
import to.bitkit.utils.AddressChecker
4040
import to.bitkit.utils.Logger
4141
import javax.inject.Inject
4242
import javax.inject.Singleton
@@ -50,7 +50,6 @@ class ActivityRepo @Inject constructor(
5050
private val coreService: CoreService,
5151
private val lightningRepo: LightningRepo,
5252
private val blocktankRepo: BlocktankRepo,
53-
private val addressChecker: AddressChecker,
5453
private val cacheStore: CacheStore,
5554
private val transferRepo: TransferRepo,
5655
private val clock: Clock,
@@ -83,8 +82,6 @@ class ActivityRepo @Inject constructor(
8382

8483
isSyncingLdkNodePayments.update { true }
8584

86-
deletePendingActivities()
87-
8885
lightningRepo.getPayments().mapCatching { payments ->
8986
Logger.debug("Got payments with success, syncing activities", context = TAG)
9087
syncLdkNodePayments(payments).getOrThrow()
@@ -167,67 +164,13 @@ class ActivityRepo @Inject constructor(
167164
}
168165

169166
private suspend fun findClosedChannelForTransaction(txid: String): String? {
170-
return try {
171-
val closedChannelsResult = getClosedChannels(SortDirection.DESC)
172-
val closedChannels = closedChannelsResult.getOrNull() ?: return null
173-
if (closedChannels.isEmpty()) return null
174-
175-
val txDetails = addressChecker.getTransaction(txid)
176-
177-
txDetails.vin.firstNotNullOfOrNull { input ->
178-
val inputTxid = input.txid ?: return@firstNotNullOfOrNull null
179-
val inputVout = input.vout ?: return@firstNotNullOfOrNull null
180-
181-
closedChannels.firstOrNull { channel ->
182-
channel.fundingTxoTxid == inputTxid && channel.fundingTxoIndex == inputVout.toUInt()
183-
}?.channelId
184-
}
185-
} catch (e: Exception) {
186-
Logger.warn(
187-
"Failed to check if transaction $txid spends closed channel funding UTXO",
188-
e,
189-
context = TAG
190-
)
191-
null
192-
}
167+
return coreService.activity.findClosedChannelForTransaction(txid, null)
193168
}
194169

195-
private suspend fun getOnchainActivityByTxId(txid: String): OnchainActivity? {
170+
suspend fun getOnchainActivityByTxId(txid: String): OnchainActivity? {
196171
return coreService.activity.getOnchainActivityByTxId(txid)
197172
}
198173

199-
/**
200-
* Determines whether to show the payment received UI for an onchain transaction.
201-
* Returns false for:
202-
* - Zero value transactions
203-
* - Channel closure transactions (transfers to savings)
204-
* - RBF replacement transactions with the same value as the original
205-
*/
206-
suspend fun shouldShowPaymentReceived(txid: String, value: ULong): Boolean = withContext(bgDispatcher) {
207-
if (value == 0uL) return@withContext false
208-
209-
if (findClosedChannelForTransaction(txid) != null) {
210-
Logger.debug("Skipping payment received UI for channel closure tx: $txid", context = TAG)
211-
return@withContext false
212-
}
213-
214-
val onchainActivity = getOnchainActivityByTxId(txid)
215-
if (onchainActivity != null && onchainActivity.boostTxIds.isNotEmpty()) {
216-
for (replacedTxid in onchainActivity.boostTxIds) {
217-
val replacedActivity = getOnchainActivityByTxId(replacedTxid)
218-
if (replacedActivity != null && replacedActivity.value == value) {
219-
Logger.info(
220-
"Skipping payment received UI for RBF replacement $txid with same value as $replacedTxid",
221-
context = TAG
222-
)
223-
return@withContext false
224-
}
225-
}
226-
}
227-
228-
return@withContext true
229-
}
230-
231174
/**
232175
* Checks if a transaction is inbound (received) by looking up the payment direction.
233176
*/
@@ -247,6 +190,58 @@ class ActivityRepo @Inject constructor(
247190
return@withContext !onchainActivity.doesExist
248191
}
249192

193+
suspend fun handleOnchainTransactionReceived(
194+
txid: String,
195+
details: TransactionDetails,
196+
) {
197+
coreService.activity.handleOnchainTransactionReceived(txid, details)
198+
notifyActivitiesChanged()
199+
}
200+
201+
suspend fun handleOnchainTransactionConfirmed(
202+
txid: String,
203+
details: TransactionDetails,
204+
) {
205+
coreService.activity.handleOnchainTransactionConfirmed(txid, details)
206+
notifyActivitiesChanged()
207+
}
208+
209+
suspend fun handleOnchainTransactionReplaced(txid: String, conflicts: List<String>) {
210+
coreService.activity.handleOnchainTransactionReplaced(txid, conflicts)
211+
notifyActivitiesChanged()
212+
}
213+
214+
suspend fun handleOnchainTransactionReorged(txid: String) {
215+
coreService.activity.handleOnchainTransactionReorged(txid)
216+
notifyActivitiesChanged()
217+
}
218+
219+
suspend fun handleOnchainTransactionEvicted(txid: String) {
220+
coreService.activity.handleOnchainTransactionEvicted(txid)
221+
notifyActivitiesChanged()
222+
}
223+
224+
suspend fun handlePaymentEvent(paymentHash: String) {
225+
coreService.activity.handlePaymentEvent(paymentHash)
226+
notifyActivitiesChanged()
227+
}
228+
229+
suspend fun shouldShowReceivedSheet(txid: String, value: ULong): Boolean {
230+
return coreService.activity.shouldShowReceivedSheet(txid, value)
231+
}
232+
233+
suspend fun getBoostTxDoesExist(boostTxIds: List<String>): Map<String, Boolean> {
234+
return coreService.activity.getBoostTxDoesExist(boostTxIds)
235+
}
236+
237+
suspend fun isCpfpChildTransaction(txId: String): Boolean {
238+
return coreService.activity.isCpfpChildTransaction(txId)
239+
}
240+
241+
suspend fun getTxIdsInBoostTxIds(): Set<String> {
242+
return coreService.activity.getTxIdsInBoostTxIds()
243+
}
244+
250245
/**
251246
* Gets a specific activity by payment hash or txID with retry logic
252247
*/
@@ -392,22 +387,13 @@ class ActivityRepo @Inject constructor(
392387
).fold(
393388
onSuccess = {
394389
Logger.debug(
395-
"Activity $id updated with success. new data: $activity. " +
396-
"Marking activity $activityIdToDelete as removed from mempool",
390+
"Activity $id updated with success. new data: $activity",
397391
context = TAG
398392
)
399393

400394
val tags = coreService.activity.tags(activityIdToDelete)
401395
addTagsToActivity(activityId = id, tags = tags)
402396

403-
markActivityAsRemovedFromMempool(activityIdToDelete).onFailure { e ->
404-
Logger.warn(
405-
"Failed to mark $activityIdToDelete as removed from mempool, caching to retry on next sync",
406-
e = e,
407-
context = TAG
408-
)
409-
cacheStore.addActivityToPendingDelete(activityId = activityIdToDelete)
410-
}
411397
Result.success(Unit)
412398
},
413399
onFailure = { e ->
@@ -422,16 +408,6 @@ class ActivityRepo @Inject constructor(
422408
)
423409
}
424410

425-
private suspend fun deletePendingActivities() = withContext(bgDispatcher) {
426-
cacheStore.data.first().activitiesPendingDelete.map { activityId ->
427-
async {
428-
markActivityAsRemovedFromMempool(activityId).onSuccess {
429-
cacheStore.removeActivityFromPendingDelete(activityId)
430-
}
431-
}
432-
}.awaitAll()
433-
}
434-
435411
private suspend fun boostPendingActivities() = withContext(bgDispatcher) {
436412
cacheStore.data.first().pendingBoostActivities.map { pendingBoostActivity ->
437413
async {
@@ -484,32 +460,6 @@ class ActivityRepo @Inject constructor(
484460
}.awaitAll()
485461
}
486462

487-
/**
488-
* Marks an activity as removed from mempool (sets doesExist = false).
489-
* Used for RBFed transactions that are replaced.
490-
*/
491-
private suspend fun markActivityAsRemovedFromMempool(activityId: String): Result<Unit> = withContext(bgDispatcher) {
492-
return@withContext runCatching {
493-
val existingActivity = getActivity(activityId).getOrNull()
494-
?: return@withContext Result.failure(Exception("Activity $activityId not found"))
495-
496-
if (existingActivity is Activity.Onchain) {
497-
val updatedActivity = Activity.Onchain(
498-
v1 = existingActivity.v1.copy(
499-
doesExist = false,
500-
updatedAt = nowTimestamp().toEpochMilli().toULong()
501-
)
502-
)
503-
updateActivity(id = activityId, activity = updatedActivity, forceUpdate = true).getOrThrow()
504-
notifyActivitiesChanged()
505-
} else {
506-
return@withContext Result.failure(Exception("Activity $activityId is not an onchain activity"))
507-
}
508-
}.onFailure { e ->
509-
Logger.error("markActivityAsRemovedFromMempool error for ID: $activityId", e, context = TAG)
510-
}
511-
}
512-
513463
/**
514464
* Deletes an activity
515465
*/

app/src/main/java/to/bitkit/repositories/LightningRepo.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import org.lightningdevkit.ldknode.PaymentDetails
3535
import org.lightningdevkit.ldknode.PaymentId
3636
import org.lightningdevkit.ldknode.PeerDetails
3737
import org.lightningdevkit.ldknode.SpendableUtxo
38+
import org.lightningdevkit.ldknode.TransactionDetails
3839
import org.lightningdevkit.ldknode.Txid
3940
import to.bitkit.data.CacheStore
4041
import to.bitkit.data.SettingsStore
@@ -717,6 +718,18 @@ class LightningRepo @Inject constructor(
717718
Result.success(payments)
718719
}
719720

721+
suspend fun getTransactionDetails(txid: Txid): Result<TransactionDetails?> = executeWhenNodeRunning(
722+
"Get transaction details by txid"
723+
) {
724+
Result.success(lightningService.getTransactionDetails(txid))
725+
}
726+
727+
suspend fun getAddressBalance(address: String): Result<ULong> = executeWhenNodeRunning("Get address balance") {
728+
runCatching {
729+
lightningService.getAddressBalance(address)
730+
}
731+
}
732+
720733
suspend fun listSpendableOutputs(): Result<List<SpendableUtxo>> = executeWhenNodeRunning("List spendable outputs") {
721734
lightningService.listSpendableOutputs()
722735
}

app/src/main/java/to/bitkit/repositories/WalletRepo.kt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import to.bitkit.models.toDerivationPath
2929
import to.bitkit.services.CoreService
3030
import to.bitkit.usecases.DeriveBalanceStateUseCase
3131
import to.bitkit.usecases.WipeWalletUseCase
32-
import to.bitkit.utils.AddressChecker
3332
import to.bitkit.utils.Bip21Utils
3433
import to.bitkit.utils.Logger
3534
import to.bitkit.utils.ServiceError
@@ -43,7 +42,6 @@ class WalletRepo @Inject constructor(
4342
private val keychain: Keychain,
4443
private val coreService: CoreService,
4544
private val settingsStore: SettingsStore,
46-
private val addressChecker: AddressChecker,
4745
private val lightningRepo: LightningRepo,
4846
private val cacheStore: CacheStore,
4947
private val preActivityMetadataRepo: PreActivityMetadataRepo,
@@ -83,9 +81,8 @@ class WalletRepo @Inject constructor(
8381

8482
suspend fun checkAddressUsage(address: String): Result<Boolean> = withContext(bgDispatcher) {
8583
return@withContext try {
86-
val addressInfo = addressChecker.getAddressInfo(address)
87-
val hasTransactions = addressInfo.chain_stats.tx_count > 0 || addressInfo.mempool_stats.tx_count > 0
88-
Result.success(hasTransactions)
84+
val result = coreService.isAddressUsed(address)
85+
Result.success(result)
8986
} catch (e: Exception) {
9087
Logger.error("checkAddressUsage error", e, context = TAG)
9188
Result.failure(e)

0 commit comments

Comments
 (0)