Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
21 changes: 21 additions & 0 deletions app/src/main/java/to/bitkit/ext/ShortChannelId.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package to.bitkit.ext

/**
* Decodes a BOLT short channel id into Core Lightning `block x tx x output` form
* (e.g. `777477x916x0`): block height in the high 24 bits, transaction index in the next 24,
* funding output index in the low 16.
*/
fun ULong.formattedAsShortChannelId(): String {
val blockHeight = this shr 40
val txIndex = (this shr 16) and 0xFFFFFFu
val outputIndex = this and 0xFFFFu
return "${blockHeight}x${txIndex}x$outputIndex"
}

/**
* Short channel id for display. Uses the channel's own scid (open channels) and, for closed
* channels which carry none, the scid from the confidently-linked order. Null when unavailable.
*/
internal fun resolveDisplayShortChannelId(channelScid: ULong?, linkedOrderScid: String?): String? =
channelScid?.formattedAsShortChannelId()
?: linkedOrderScid?.toULongOrNull()?.formattedAsShortChannelId()
Comment thread
jvsena42 marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import to.bitkit.env.Env
import to.bitkit.ext.DatePattern
import to.bitkit.ext.amountOnClose
import to.bitkit.ext.createChannelDetails
import to.bitkit.ext.resolveDisplayShortChannelId
import to.bitkit.ext.setClipboardText
import to.bitkit.models.Toast
import to.bitkit.models.msatFloorOf
Expand Down Expand Up @@ -220,6 +221,15 @@ private fun ChannelDetailContent(

val order = blocktankOrder ?: cjitEntry

val linkedOrderScid = when (order) {
is IBtOrder -> order.channel?.shortChannelId
is IcJitEntry -> order.channel?.shortChannelId
else -> null
}
val displayShortChannelId = remember(channel, linkedOrderScid) {
resolveDisplayShortChannelId(channel.details.shortChannelId, linkedOrderScid)
}

val capacity = channel.details.channelValueSats.toLong()
val localBalance = channel.details.amountOnClose.toLong()
val remoteBalance = msatFloorOf(channel.details.inboundCapacityMsat).toLong()
Expand Down Expand Up @@ -429,18 +439,20 @@ private fun ChannelDetailContent(
)
}

SectionRow(
name = stringResource(R.string.lightning__channel_id),
valueContent = {
CaptionB(
text = channel.details.channelId,
maxLines = 1,
overflow = TextOverflow.MiddleEllipsis,
textAlign = TextAlign.End,
)
},
onClick = { onCopyText(channel.details.channelId) }
)
displayShortChannelId?.let { scid ->
SectionRow(
name = stringResource(R.string.lightning__channel_id),
valueContent = {
CaptionB(
text = scid,
maxLines = 1,
overflow = TextOverflow.MiddleEllipsis,
textAlign = TextAlign.End,
)
},
onClick = { onCopyText(scid) }
)
}

channel.details.fundingTxo?.let { fundingTxo ->
val channelPoint = "${fundingTxo.txid}:${fundingTxo.vout}"
Expand Down
61 changes: 61 additions & 0 deletions app/src/test/java/to/bitkit/ext/ShortChannelIdTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package to.bitkit.ext

import org.junit.Test
import to.bitkit.test.BaseUnitTest
import kotlin.test.assertEquals
import kotlin.test.assertNull

class ShortChannelIdTest : BaseUnitTest() {

@Test
fun `formattedAsShortChannelId formats lnd scid into cln form`() {
// The two formats the issue calls out (LND uint64 and CLN block x tx x output)
// are two encodings of the same channel.
assertEquals("777477x916x0", 854_845_001_888_432_128uL.formattedAsShortChannelId())
}

@Test
fun `formattedAsShortChannelId formats from components`() {
val scid = (700_000uL shl 40) or (1uL shl 16) or 2uL
assertEquals("700000x1x2", scid.formattedAsShortChannelId())
}

@Test
fun `formattedAsShortChannelId formats zero as all zeroes`() {
assertEquals("0x0x0", 0uL.formattedAsShortChannelId())
}

@Test
fun `formattedAsShortChannelId keeps max components in their fields`() {
val scid = (0xFFFFFFuL shl 40) or (0xFFFFFFuL shl 16) or 0xFFFFuL
assertEquals("16777215x16777215x65535", scid.formattedAsShortChannelId())
}

@Test
fun `resolveDisplayShortChannelId prefers channel scid for open channel`() {
val result = resolveDisplayShortChannelId(
channelScid = 854_845_001_888_432_128uL,
linkedOrderScid = "0",
)
assertEquals("777477x916x0", result)
}

@Test
fun `resolveDisplayShortChannelId falls back to linked order scid for closed channel`() {
val result = resolveDisplayShortChannelId(
channelScid = null,
linkedOrderScid = "854845001888432128",
)
assertEquals("777477x916x0", result)
}

@Test
fun `resolveDisplayShortChannelId returns null when both unavailable`() {
assertNull(resolveDisplayShortChannelId(channelScid = null, linkedOrderScid = null))
}

@Test
fun `resolveDisplayShortChannelId returns null for non-numeric order scid`() {
assertNull(resolveDisplayShortChannelId(channelScid = null, linkedOrderScid = "not-a-number"))
}
}
1 change: 1 addition & 0 deletions changelog.d/next/1002.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Connection Details now shows the short channel ID for Lightning connections instead of the long internal channel ID.
Loading