Skip to content

Commit 9e99b07

Browse files
committed
fix: show short channel id in connection details
1 parent 1fe7f46 commit 9e99b07

4 files changed

Lines changed: 107 additions & 12 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package to.bitkit.ext
2+
3+
/**
4+
* Decodes a BOLT short channel id into Core Lightning `block x tx x output` form
5+
* (e.g. `777477x916x0`): block height in the high 24 bits, transaction index in the next 24,
6+
* funding output index in the low 16.
7+
*/
8+
fun ULong.formattedAsShortChannelId(): String {
9+
val blockHeight = this shr 40
10+
val txIndex = (this shr 16) and 0xFFFFFFu
11+
val outputIndex = this and 0xFFFFu
12+
return "${blockHeight}x${txIndex}x$outputIndex"
13+
}
14+
15+
/**
16+
* Short channel id for display. Uses the channel's own scid (open channels) and, for closed
17+
* channels which carry none, the scid from the confidently-linked order. Null when unavailable.
18+
*/
19+
internal fun resolveDisplayShortChannelId(channelScid: ULong?, linkedOrderScid: String?): String? =
20+
channelScid?.formattedAsShortChannelId()
21+
?: linkedOrderScid?.toULongOrNull()?.formattedAsShortChannelId()

app/src/main/java/to/bitkit/ui/settings/lightning/ChannelDetailScreen.kt

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import to.bitkit.env.Env
5959
import to.bitkit.ext.DatePattern
6060
import to.bitkit.ext.amountOnClose
6161
import to.bitkit.ext.createChannelDetails
62+
import to.bitkit.ext.resolveDisplayShortChannelId
6263
import to.bitkit.ext.setClipboardText
6364
import to.bitkit.models.Toast
6465
import to.bitkit.models.msatFloorOf
@@ -220,6 +221,15 @@ private fun ChannelDetailContent(
220221

221222
val order = blocktankOrder ?: cjitEntry
222223

224+
val linkedOrderScid = when (order) {
225+
is IBtOrder -> order.channel?.shortChannelId
226+
is IcJitEntry -> order.channel?.shortChannelId
227+
else -> null
228+
}
229+
val displayShortChannelId = remember(channel, linkedOrderScid) {
230+
resolveDisplayShortChannelId(channel.details.shortChannelId, linkedOrderScid)
231+
}
232+
223233
val capacity = channel.details.channelValueSats.toLong()
224234
val localBalance = channel.details.amountOnClose.toLong()
225235
val remoteBalance = msatFloorOf(channel.details.inboundCapacityMsat).toLong()
@@ -429,18 +439,20 @@ private fun ChannelDetailContent(
429439
)
430440
}
431441

432-
SectionRow(
433-
name = stringResource(R.string.lightning__channel_id),
434-
valueContent = {
435-
CaptionB(
436-
text = channel.details.channelId,
437-
maxLines = 1,
438-
overflow = TextOverflow.MiddleEllipsis,
439-
textAlign = TextAlign.End,
440-
)
441-
},
442-
onClick = { onCopyText(channel.details.channelId) }
443-
)
442+
displayShortChannelId?.let { scid ->
443+
SectionRow(
444+
name = stringResource(R.string.lightning__channel_id),
445+
valueContent = {
446+
CaptionB(
447+
text = scid,
448+
maxLines = 1,
449+
overflow = TextOverflow.MiddleEllipsis,
450+
textAlign = TextAlign.End,
451+
)
452+
},
453+
onClick = { onCopyText(scid) }
454+
)
455+
}
444456

445457
channel.details.fundingTxo?.let { fundingTxo ->
446458
val channelPoint = "${fundingTxo.txid}:${fundingTxo.vout}"
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package to.bitkit.ext
2+
3+
import org.junit.Test
4+
import to.bitkit.test.BaseUnitTest
5+
import kotlin.test.assertEquals
6+
import kotlin.test.assertNull
7+
8+
class ShortChannelIdTest : BaseUnitTest() {
9+
10+
@Test
11+
fun `formattedAsShortChannelId formats lnd scid into cln form`() {
12+
// The two formats the issue calls out (LND uint64 and CLN block x tx x output)
13+
// are two encodings of the same channel.
14+
assertEquals("777477x916x0", 854_845_001_888_432_128uL.formattedAsShortChannelId())
15+
}
16+
17+
@Test
18+
fun `formattedAsShortChannelId formats from components`() {
19+
val scid = (700_000uL shl 40) or (1uL shl 16) or 2uL
20+
assertEquals("700000x1x2", scid.formattedAsShortChannelId())
21+
}
22+
23+
@Test
24+
fun `formattedAsShortChannelId formats zero as all zeroes`() {
25+
assertEquals("0x0x0", 0uL.formattedAsShortChannelId())
26+
}
27+
28+
@Test
29+
fun `formattedAsShortChannelId keeps max components in their fields`() {
30+
val scid = (0xFFFFFFuL shl 40) or (0xFFFFFFuL shl 16) or 0xFFFFuL
31+
assertEquals("16777215x16777215x65535", scid.formattedAsShortChannelId())
32+
}
33+
34+
@Test
35+
fun `resolveDisplayShortChannelId prefers channel scid for open channel`() {
36+
val result = resolveDisplayShortChannelId(
37+
channelScid = 854_845_001_888_432_128uL,
38+
linkedOrderScid = "0",
39+
)
40+
assertEquals("777477x916x0", result)
41+
}
42+
43+
@Test
44+
fun `resolveDisplayShortChannelId falls back to linked order scid for closed channel`() {
45+
val result = resolveDisplayShortChannelId(
46+
channelScid = null,
47+
linkedOrderScid = "854845001888432128",
48+
)
49+
assertEquals("777477x916x0", result)
50+
}
51+
52+
@Test
53+
fun `resolveDisplayShortChannelId returns null when both unavailable`() {
54+
assertNull(resolveDisplayShortChannelId(channelScid = null, linkedOrderScid = null))
55+
}
56+
57+
@Test
58+
fun `resolveDisplayShortChannelId returns null for non-numeric order scid`() {
59+
assertNull(resolveDisplayShortChannelId(channelScid = null, linkedOrderScid = "not-a-number"))
60+
}
61+
}

changelog.d/next/702.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Connection Details now shows the short channel ID for Lightning connections instead of the long internal channel ID.

0 commit comments

Comments
 (0)