@@ -50,6 +50,7 @@ import com.synonym.bitkitcore.upsertCjitEntries
5050import com.synonym.bitkitcore.upsertClosedChannels
5151import com.synonym.bitkitcore.upsertInfo
5252import com.synonym.bitkitcore.upsertOrders
53+ import com.synonym.bitkitcore.upsertTransactionDetails
5354import com.synonym.bitkitcore.wipeAllDatabases
5455import io.ktor.client.HttpClient
5556import io.ktor.client.request.get
@@ -77,6 +78,10 @@ import to.bitkit.utils.ServiceError
7778import javax.inject.Inject
7879import javax.inject.Singleton
7980import kotlin.random.Random
81+ import com.synonym.bitkitcore.TransactionDetails as BitkitCoreTransactionDetails
82+ import com.synonym.bitkitcore.TxInput as BitkitCoreTxInput
83+ import com.synonym.bitkitcore.TxOutput as BitkitCoreTxOutput
84+ import com.synonym.bitkitcore.getTransactionDetails as getBitkitCoreTransactionDetails
8085
8186// region Core
8287
@@ -248,6 +253,48 @@ class ActivityService(
248253 upsertActivities(activities)
249254 }
250255
256+ private fun mapToCoreTransactionDetails (
257+ txid : String ,
258+ details : TransactionDetails ,
259+ ): BitkitCoreTransactionDetails {
260+ val inputs = details.inputs.map { input ->
261+ BitkitCoreTxInput (
262+ txid = input.txid,
263+ vout = input.vout,
264+ scriptsig = input.scriptsig,
265+ witness = input.witness,
266+ sequence = input.sequence,
267+ )
268+ }
269+ val outputs = details.outputs.map { output ->
270+ BitkitCoreTxOutput (
271+ scriptpubkey = output.scriptpubkey,
272+ scriptpubkeyType = output.scriptpubkeyType,
273+ scriptpubkeyAddress = output.scriptpubkeyAddress,
274+ value = output.value,
275+ n = output.n,
276+ )
277+ }
278+ return BitkitCoreTransactionDetails (
279+ txId = txid,
280+ amountSats = details.amountSats,
281+ inputs = inputs,
282+ outputs = outputs,
283+ )
284+ }
285+
286+ suspend fun saveTransactionDetails (txid : String , details : TransactionDetails ) {
287+ ServiceQueue .CORE .background {
288+ val coreDetails = mapToCoreTransactionDetails(txid, details)
289+ upsertTransactionDetails(listOf (coreDetails))
290+ }
291+ }
292+
293+ suspend fun getTransactionDetails (txid : String ): BitkitCoreTransactionDetails ? =
294+ ServiceQueue .CORE .background {
295+ getBitkitCoreTransactionDetails(txid)
296+ }
297+
251298 suspend fun getActivity (id : String ): Activity ? {
252299 return ServiceQueue .CORE .background {
253300 getActivityById(id)
@@ -484,7 +531,7 @@ class ActivityService(
484531 fee = (payment.feePaidMsat ? : 0u ) / 1000u ,
485532 message = kind.description.orEmpty(),
486533 preimage = kind.preimage,
487- seenAt = null , // TODO implement synonymdev/bitkit-ios#270 changes
534+ seenAt = null ,
488535 )
489536 }
490537
@@ -499,8 +546,15 @@ class ActivityService(
499546 * Check pre-activity metadata for addresses in the transaction
500547 * Returns the first address found in pre-activity metadata that matches a transaction output
501548 */
549+ private suspend fun fetchTransactionDetails (txid : String ): BitkitCoreTransactionDetails ? =
550+ runCatching { getTransactionDetails(txid) }
551+ .onFailure { e ->
552+ Logger .warn(" Failed to fetch stored transaction details for $txid : $e " , context = TAG )
553+ }
554+ .getOrNull()
555+
502556 private suspend fun findAddressInPreActivityMetadata (
503- details : TransactionDetails
557+ details : BitkitCoreTransactionDetails ,
504558 ): String? {
505559 for (output in details.outputs) {
506560 val address = output.scriptpubkeyAddress ? : continue
@@ -519,14 +573,14 @@ class ActivityService(
519573 kind : PaymentKind .Onchain ,
520574 existingActivity : Activity ? ,
521575 payment : PaymentDetails ,
522- transactionDetails : TransactionDetails ? = null,
576+ transactionDetails : BitkitCoreTransactionDetails ? = null,
523577 ): String? {
524578 if (existingActivity != null || payment.direction != PaymentDirection .INBOUND ) {
525579 return null
526580 }
527581
528582 // Get transaction details if not provided
529- val details = transactionDetails ? : lightningService.getTransactionDetails (kind.txid)
583+ val details = transactionDetails ? : fetchTransactionDetails (kind.txid)
530584 if (details == null ) {
531585 Logger .verbose(" Transaction details not available for txid: ${kind.txid} " , context = TAG )
532586 return null
@@ -609,7 +663,7 @@ class ActivityService(
609663 isTransfer = isTransfer,
610664 confirmTimestamp = confirmationData.confirmedTimestamp,
611665 channelId = channelId,
612- seenAt = null , // TODO implement synonymdev/bitkit-ios#270 changes
666+ seenAt = null ,
613667 )
614668 }
615669
@@ -618,7 +672,7 @@ class ActivityService(
618672 payment : PaymentDetails ,
619673 forceUpdate : Boolean ,
620674 channelId : String? = null,
621- transactionDetails : TransactionDetails ? = null,
675+ transactionDetails : BitkitCoreTransactionDetails ? = null,
622676 ) {
623677 val timestamp = payment.latestUpdateTimestamp
624678 val confirmationData = getConfirmationStatus(kind, timestamp)
@@ -769,6 +823,9 @@ class ActivityService(
769823 suspend fun handleOnchainTransactionReceived (txid : String , details : TransactionDetails ) {
770824 ServiceQueue .CORE .background {
771825 runCatching {
826+ val coreDetails = mapToCoreTransactionDetails(txid, details)
827+ upsertTransactionDetails(listOf (coreDetails))
828+
772829 val payments = lightningService.payments ? : run {
773830 Logger .warn(" No payments available for transaction $txid " , context = TAG )
774831 return @background
@@ -786,7 +843,7 @@ class ActivityService(
786843 payment = payment,
787844 forceUpdate = false ,
788845 channelId = null ,
789- transactionDetails = details ,
846+ transactionDetails = coreDetails ,
790847 )
791848 }.onFailure { e ->
792849 Logger .error(" Error handling onchain transaction received for $txid " , e, context = TAG )
@@ -797,6 +854,9 @@ class ActivityService(
797854 suspend fun handleOnchainTransactionConfirmed (txid : String , details : TransactionDetails ) {
798855 ServiceQueue .CORE .background {
799856 runCatching {
857+ val coreDetails = mapToCoreTransactionDetails(txid, details)
858+ upsertTransactionDetails(listOf (coreDetails))
859+
800860 val payments = lightningService.payments ? : run {
801861 Logger .warn(" No payments available for transaction $txid " , context = TAG )
802862 return @background
@@ -814,7 +874,7 @@ class ActivityService(
814874 payment = payment,
815875 forceUpdate = false ,
816876 channelId = null ,
817- transactionDetails = details ,
877+ transactionDetails = coreDetails ,
818878 )
819879 }.onFailure { e ->
820880 Logger .error(" Error handling onchain transaction confirmed for $txid " , e, context = TAG )
@@ -1000,6 +1060,15 @@ class ActivityService(
10001060 runCatching {
10011061 val onchain = getOnchainActivityByTxId(txid) ? : return @background true
10021062
1063+ // Check if activity has already been seen
1064+ if (onchain.seenAt != null ) {
1065+ Logger .info(
1066+ " Skipping received sheet for transaction $txid - already seen at ${onchain.seenAt} " ,
1067+ context = TAG
1068+ )
1069+ return @background false
1070+ }
1071+
10031072 if (onchain.boostTxIds.isEmpty()) {
10041073 return @background true
10051074 }
@@ -1023,6 +1092,40 @@ class ActivityService(
10231092 }
10241093 }
10251094
1095+ suspend fun isActivitySeen (activityId : String ): Boolean = ServiceQueue .CORE .background {
1096+ val activity = getActivityById(activityId) ? : return @background false
1097+ return @background when (activity) {
1098+ is Activity .Lightning -> activity.v1.seenAt != null
1099+ is Activity .Onchain -> activity.v1.seenAt != null
1100+ }
1101+ }
1102+
1103+ suspend fun markActivityAsSeen (activityId : String , seenAt : ULong? = null) = ServiceQueue .CORE .background {
1104+ val activity = getActivityById(activityId) ? : run {
1105+ Logger .warn(" Cannot mark activity as seen - activity not found: $activityId " , context = TAG )
1106+ return @background
1107+ }
1108+
1109+ val timestamp = seenAt ? : (System .currentTimeMillis().toULong() / 1000u )
1110+ val updatedActivity = when (activity) {
1111+ is Activity .Lightning -> Activity .Lightning (activity.v1.copy(seenAt = timestamp))
1112+ is Activity .Onchain -> Activity .Onchain (activity.v1.copy(seenAt = timestamp))
1113+ }
1114+
1115+ updateActivity(activityId, updatedActivity)
1116+ Logger .info(" Marked activity $activityId as seen at $timestamp " , context = TAG )
1117+ }
1118+
1119+ suspend fun markOnchainActivityAsSeen (txid : String , seenAt : ULong? = null) {
1120+ val activity = ServiceQueue .CORE .background {
1121+ getOnchainActivityByTxId(txid)
1122+ } ? : run {
1123+ Logger .warn(" Cannot mark onchain activity as seen - activity not found for txid: $txid " , context = TAG )
1124+ return
1125+ }
1126+ markActivityAsSeen(activity.id, seenAt)
1127+ }
1128+
10261129 suspend fun getBoostTxDoesExist (boostTxIds : List <String >): Map <String , Boolean > {
10271130 return ServiceQueue .CORE .background {
10281131 val doesExistMap = mutableMapOf<String , Boolean >()
@@ -1071,7 +1174,7 @@ class ActivityService(
10711174 private suspend fun findChannelForTransaction (
10721175 txid : String ,
10731176 direction : PaymentDirection ,
1074- transactionDetails : TransactionDetails ? = null,
1177+ transactionDetails : BitkitCoreTransactionDetails ? = null,
10751178 ): String? {
10761179 return when (direction) {
10771180 PaymentDirection .INBOUND -> {
@@ -1123,15 +1226,18 @@ class ActivityService(
11231226 }.getOrNull()
11241227 }
11251228
1126- suspend fun findClosedChannelForTransaction (txid : String , transactionDetails : TransactionDetails ? = null): String? {
1229+ suspend fun findClosedChannelForTransaction (
1230+ txid : String ,
1231+ transactionDetails : BitkitCoreTransactionDetails ? = null,
1232+ ): String? {
11271233 return runCatching {
11281234 val closedChannelsList = closedChannels(SortDirection .DESC )
11291235 if (closedChannelsList.isEmpty()) {
11301236 return null
11311237 }
11321238
1133- // Use provided transaction details if available, otherwise try node
1134- val details = transactionDetails ? : lightningService.getTransactionDetails (txid) ? : run {
1239+ // Use provided transaction details if available, otherwise fetch from bitkitcore
1240+ val details = transactionDetails ? : fetchTransactionDetails (txid) ? : run {
11351241 Logger .warn(" Transaction details not available for $txid " , context = TAG )
11361242 return null
11371243 }
0 commit comments