Skip to content
109 changes: 51 additions & 58 deletions app/src/main/java/to/bitkit/ext/ChannelDetails.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import org.lightningdevkit.ldknode.MaxDustHtlcExposure
* - outbound_capacity: What we can spend now over Lightning
* - our_reserve: Our reserve that we get back on close
*/
@Suppress("ForbiddenComment")
Comment thread
ovitrif marked this conversation as resolved.
Outdated
val ChannelDetails.amountOnClose: ULong
// TODO: use channelDetails.claimableOnCloseSats
get() {
val outboundCapacitySat = this.outboundCapacityMsat / 1000u
val ourReserve = this.unspendablePunishmentReserve ?: 0u
Expand All @@ -23,68 +25,59 @@ val ChannelDetails.amountOnClose: ULong
}

/** Returns only `open` channels, filtering out pending ones. */
fun List<ChannelDetails>.filterOpen(): List<ChannelDetails> {
return this.filter { it.isChannelReady }
}
fun List<ChannelDetails>.filterOpen(): List<ChannelDetails> = this.filter { it.isChannelReady }

/** Returns only `pending` channels. */
fun List<ChannelDetails>.filterPending(): List<ChannelDetails> {
return this.filterNot { it.isChannelReady }
}
fun List<ChannelDetails>.filterPending(): List<ChannelDetails> = this.filterNot { it.isChannelReady }

/** Returns a limit in sats as close as possible to the HTLC limit we can currently send. */
fun List<ChannelDetails>?.totalNextOutboundHtlcLimitSats(): ULong {
return this?.filter { it.isUsable }
?.sumOf { it.nextOutboundHtlcLimitMsat / 1000u }
?: 0u
}
fun List<ChannelDetails>?.totalNextOutboundHtlcLimitSats(): ULong = this?.filter { it.isUsable }
?.sumOf { it.nextOutboundHtlcLimitMsat / 1000u }
?: 0u

/** Calculates the total remote balance (inbound capacity) from open channels. */
fun List<ChannelDetails>.calculateRemoteBalance(): ULong {
return this
.filterOpen()
.sumOf { it.inboundCapacityMsat / 1000u }
}
fun List<ChannelDetails>.calculateRemoteBalance(): ULong = this
.filterOpen()
.sumOf { it.inboundCapacityMsat / 1000u }

fun createChannelDetails(): ChannelDetails {
return ChannelDetails(
channelId = "channelId",
counterpartyNodeId = "counterpartyNodeId",
fundingTxo = null,
shortChannelId = null,
outboundScidAlias = null,
inboundScidAlias = null,
channelValueSats = 0u,
unspendablePunishmentReserve = null,
userChannelId = "0",
feerateSatPer1000Weight = 0u,
outboundCapacityMsat = 0u,
inboundCapacityMsat = 0u,
confirmationsRequired = null,
confirmations = null,
isOutbound = false,
isChannelReady = false,
isUsable = false,
isAnnounced = false,
cltvExpiryDelta = null,
counterpartyUnspendablePunishmentReserve = 0u,
counterpartyOutboundHtlcMinimumMsat = null,
counterpartyOutboundHtlcMaximumMsat = null,
counterpartyForwardingInfoFeeBaseMsat = null,
counterpartyForwardingInfoFeeProportionalMillionths = null,
counterpartyForwardingInfoCltvExpiryDelta = null,
nextOutboundHtlcLimitMsat = 0u,
nextOutboundHtlcMinimumMsat = 0u,
forceCloseSpendDelay = null,
inboundHtlcMinimumMsat = 0u,
inboundHtlcMaximumMsat = null,
config = ChannelConfig(
forwardingFeeProportionalMillionths = 0u,
forwardingFeeBaseMsat = 0u,
cltvExpiryDelta = 0u,
maxDustHtlcExposure = MaxDustHtlcExposure.FixedLimit(limitMsat = 0u),
forceCloseAvoidanceMaxFeeSatoshis = 0u,
acceptUnderpayingHtlcs = false,
),
)
}
fun createChannelDetails(): ChannelDetails = ChannelDetails(
channelId = "channelId",
counterpartyNodeId = "counterpartyNodeId",
fundingTxo = null,
shortChannelId = null,
outboundScidAlias = null,
inboundScidAlias = null,
channelValueSats = 0u,
unspendablePunishmentReserve = null,
userChannelId = "0",
feerateSatPer1000Weight = 0u,
outboundCapacityMsat = 0u,
inboundCapacityMsat = 0u,
confirmationsRequired = null,
confirmations = null,
isOutbound = false,
isChannelReady = false,
isUsable = false,
isAnnounced = false,
cltvExpiryDelta = null,
counterpartyUnspendablePunishmentReserve = 0u,
counterpartyOutboundHtlcMinimumMsat = null,
counterpartyOutboundHtlcMaximumMsat = null,
counterpartyForwardingInfoFeeBaseMsat = null,
counterpartyForwardingInfoFeeProportionalMillionths = null,
counterpartyForwardingInfoCltvExpiryDelta = null,
nextOutboundHtlcLimitMsat = 0u,
nextOutboundHtlcMinimumMsat = 0u,
forceCloseSpendDelay = null,
inboundHtlcMinimumMsat = 0u,
inboundHtlcMaximumMsat = null,
claimableOnCloseSats = 0u,
config = ChannelConfig(
forwardingFeeProportionalMillionths = 0u,
forwardingFeeBaseMsat = 0u,
cltvExpiryDelta = 0u,
maxDustHtlcExposure = MaxDustHtlcExposure.FixedLimit(limitMsat = 0u),
forceCloseAvoidanceMaxFeeSatoshis = 0u,
acceptUnderpayingHtlcs = false,
),
)
4 changes: 1 addition & 3 deletions app/src/main/java/to/bitkit/services/CoreService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,8 @@ class CoreService @Inject constructor(
com.synonym.bitkitcore.isAddressUsed(address = address)
}

@Suppress("ForbiddenComment")
suspend fun decode(input: String): Scanner = ServiceQueue.CORE.background {
// TODO: Remove lowercase workaround when https://github.com/synonymdev/bitkit-core/issues/66 is fixed
com.synonym.bitkitcore.decode(input.lowercase())
com.synonym.bitkitcore.decode(input)
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,7 @@ private fun PreviewAutoMode() {
forceCloseSpendDelay = null,
inboundHtlcMinimumMsat = 0uL,
inboundHtlcMaximumMsat = null,
claimableOnCloseSats = 0uL,
config = org.lightningdevkit.ldknode.ChannelConfig(
forwardingFeeProportionalMillionths = 0u,
forwardingFeeBaseMsat = 0u,
Expand Down Expand Up @@ -764,6 +765,7 @@ private fun PreviewSpendingMode() {
forceCloseSpendDelay = null,
inboundHtlcMinimumMsat = 0uL,
inboundHtlcMaximumMsat = null,
claimableOnCloseSats = 0uL,
config = org.lightningdevkit.ldknode.ChannelConfig(
forwardingFeeProportionalMillionths = 0u,
forwardingFeeBaseMsat = 0u,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import android.app.Notification
import android.app.NotificationManager
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import com.google.firebase.messaging.FirebaseMessaging
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.runBlocking
import org.junit.After
Expand All @@ -23,7 +25,6 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.lightningdevkit.ldknode.Event
import org.mockito.kotlin.KArgumentCaptor
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argumentCaptor
Expand All @@ -42,6 +43,7 @@ import to.bitkit.data.AppCacheData
import to.bitkit.data.CacheStore
import to.bitkit.di.DbModule
import to.bitkit.di.DispatchersModule
import to.bitkit.di.ViewModelModule
import to.bitkit.domain.commands.NotifyPaymentReceived
import to.bitkit.domain.commands.NotifyPaymentReceivedHandler
import to.bitkit.models.NewTransactionSheetDetails
Expand All @@ -52,16 +54,23 @@ import to.bitkit.repositories.LightningRepo
import to.bitkit.repositories.WalletRepo
import to.bitkit.services.NodeEventHandler
import to.bitkit.test.BaseUnitTest
import to.bitkit.ui.shared.toast.ToastQueueManager

@HiltAndroidTest
@UninstallModules(DispatchersModule::class, DbModule::class)
@UninstallModules(DispatchersModule::class, DbModule::class, ViewModelModule::class)
@Config(application = HiltTestApplication::class, sdk = [34]) // Pin Robolectric to an SDK that supports Java 17
@RunWith(RobolectricTestRunner::class)
class LightningNodeServiceTest : BaseUnitTest() {

@get:Rule(order = 1)
var hiltRule = HiltAndroidRule(this)

@BindValue
val firebaseMessaging = mock<FirebaseMessaging>()

@BindValue
val toastManagerProvider = mock<(CoroutineScope) -> ToastQueueManager>()

@BindValue
val lightningRepo = mock<LightningRepo>()

Expand All @@ -74,8 +83,8 @@ class LightningNodeServiceTest : BaseUnitTest() {
@BindValue
val cacheStore = mock<CacheStore>()

private val captor: KArgumentCaptor<NodeEventHandler?> = argumentCaptor()
private val cacheDataFlow = MutableSharedFlow<AppCacheData>(replay = 1)
private val captor = argumentCaptor<NodeEventHandler>()
private val cacheData = MutableSharedFlow<AppCacheData>(replay = 1)
private val context = ApplicationProvider.getApplicationContext<Context>()

@Before
Expand All @@ -95,7 +104,7 @@ class LightningNodeServiceTest : BaseUnitTest() {
whenever(lightningRepo.stop()).thenReturn(Result.success(Unit))

// Set up CacheStore mock
whenever(cacheStore.data).thenReturn(cacheDataFlow)
whenever(cacheStore.data).thenReturn(cacheData)

// Mock NotifyPaymentReceivedHandler to return ShowNotification result
val sheet = NewTransactionSheetDetails(
Expand Down
10 changes: 5 additions & 5 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ activity-compose = { module = "androidx.activity:activity-compose", version = "1
appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.1" }
barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version = "17.3.0" }
biometric = { module = "androidx.biometric:biometric", version = "1.4.0-alpha05" }
bitkit-core = { module = "com.synonym:bitkit-core-android", version = "0.1.35" }
bitkit-core = { module = "com.synonym:bitkit-core-android", version = "0.1.38" }
bouncycastle-provider-jdk = { module = "org.bouncycastle:bcprov-jdk18on", version = "1.83" }
camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camera" }
camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camera" }
Expand All @@ -41,7 +41,7 @@ core-splashscreen = { group = "androidx.core", name = "core-splashscreen", versi
datastore-preferences = { module = "androidx.datastore:datastore-preferences", version = "1.2.0" }
detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
detekt-compose-rules = { module = "io.nlopez.compose.rules:detekt", version = "0.5.3" }
firebase-bom = { module = "com.google.firebase:firebase-bom", version = "34.7.0" }
firebase-bom = { module = "com.google.firebase:firebase-bom", version = "34.8.0" }
firebase-messaging = { module = "com.google.firebase:firebase-messaging" }
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" }
Expand All @@ -58,7 +58,7 @@ ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
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 = "com.github.synonymdev:ldk-node", version = "v0.7.0-rc.8" } # fork | local: remove `v`
ldk-node-android = { module = "com.github.synonymdev.ldk-node:ldk-node-android", version = "v0.7.0-rc.17" }
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" }
Expand All @@ -79,8 +79,8 @@ test-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-
test-espresso-core = { module = "androidx.test.espresso:espresso-core", version = "3.7.0" }
test-junit = { group = "junit", name = "junit", version = "4.13.2" }
test-junit-ext = { module = "androidx.test.ext:junit", version = "1.3.0" }
test-mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version = "6.1.0" }
test-robolectric = { module = "org.robolectric:robolectric", version = "4.16" }
test-mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version = "6.2.2" }
test-robolectric = { module = "org.robolectric:robolectric", version = "4.16.1" }
test-turbine = { group = "app.cash.turbine", name = "turbine", version = "1.2.1" }
vss-client = { module = "com.synonym:vss-client-android", version = "0.4.0" }
work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version = "2.11.0" }
Expand Down
Loading