Skip to content

Commit 74aba74

Browse files
committed
fix: keep bip 21 onchain info to onchain only payments
1 parent 805dd54 commit 74aba74

File tree

2 files changed

+190
-1
lines changed

2 files changed

+190
-1
lines changed

app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveInvoiceUtils.kt

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ fun getInvoiceForTab(
2222
): String {
2323
return when (tab) {
2424
ReceiveTab.SAVINGS -> {
25-
onchainAddress
25+
// Return BIP21 without lightning parameter to preserve amount and other parameters
26+
removeLightningFromBip21(bip21, onchainAddress)
2627
}
2728

2829
ReceiveTab.AUTO -> {
@@ -37,6 +38,25 @@ fun getInvoiceForTab(
3738
}
3839
}
3940

41+
/**
42+
* Removes the lightning parameter from a BIP21 URI while preserving all other parameters.
43+
*
44+
* @param bip21 Full BIP21 URI (e.g., bitcoin:address?amount=0.001&lightning=lnbc...)
45+
* @param fallbackAddress Fallback address if BIP21 is empty or invalid
46+
* @return BIP21 URI without the lightning parameter (e.g., bitcoin:address?amount=0.001)
47+
*/
48+
private fun removeLightningFromBip21(bip21: String, fallbackAddress: String): String {
49+
if (bip21.isBlank()) return fallbackAddress
50+
51+
// Remove lightning parameter using regex
52+
// Handles both "?lightning=..." and "&lightning=..." cases
53+
val withoutLightning = bip21
54+
.replace(Regex("[?&]lightning=[^&]*"), "")
55+
.replace(Regex("\\?$"), "") // Remove trailing ? if it's the last char
56+
57+
return withoutLightning.ifBlank { fallbackAddress }
58+
}
59+
4060
/**
4161
* Returns the appropriate QR code logo resource for the selected tab.
4262
*
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package to.bitkit.ui.screens.wallets.receive
2+
3+
import org.junit.Assert.assertEquals
4+
import org.junit.Test
5+
6+
class ReceiveInvoiceUtilsTest {
7+
8+
private val testAddress = "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"
9+
private val testBolt11 = "lnbc1500n1pn2s39xpp5wyxw0e9fvvf..."
10+
private val testCjitInvoice = "lnbc2000n1pn2s39xpp5zyxw0e9fvvf..."
11+
12+
@Test
13+
fun `getInvoiceForTab SAVINGS returns BIP21 without lightning parameter`() {
14+
val bip21WithAmount = "bitcoin:$testAddress?amount=0.001&message=Test&lightning=$testBolt11"
15+
16+
val result = getInvoiceForTab(
17+
tab = ReceiveTab.SAVINGS,
18+
bip21 = bip21WithAmount,
19+
bolt11 = testBolt11,
20+
cjitInvoice = null,
21+
isNodeRunning = true,
22+
onchainAddress = testAddress
23+
)
24+
25+
assertEquals("bitcoin:$testAddress?amount=0.001&message=Test", result)
26+
}
27+
28+
@Test
29+
fun `getInvoiceForTab SAVINGS preserves amount when lightning is last parameter`() {
30+
val bip21 = "bitcoin:$testAddress?amount=0.00050000&lightning=$testBolt11"
31+
32+
val result = getInvoiceForTab(
33+
tab = ReceiveTab.SAVINGS,
34+
bip21 = bip21,
35+
bolt11 = testBolt11,
36+
cjitInvoice = null,
37+
isNodeRunning = true,
38+
onchainAddress = testAddress
39+
)
40+
41+
assertEquals("bitcoin:$testAddress?amount=0.00050000", result)
42+
}
43+
44+
@Test
45+
fun `getInvoiceForTab SAVINGS handles BIP21 without lightning parameter`() {
46+
val bip21WithoutLightning = "bitcoin:$testAddress?amount=0.002&message=Test"
47+
48+
val result = getInvoiceForTab(
49+
tab = ReceiveTab.SAVINGS,
50+
bip21 = bip21WithoutLightning,
51+
bolt11 = testBolt11,
52+
cjitInvoice = null,
53+
isNodeRunning = true,
54+
onchainAddress = testAddress
55+
)
56+
57+
assertEquals("bitcoin:$testAddress?amount=0.002&message=Test", result)
58+
}
59+
60+
@Test
61+
fun `getInvoiceForTab SAVINGS returns fallback address when BIP21 is empty`() {
62+
val result = getInvoiceForTab(
63+
tab = ReceiveTab.SAVINGS,
64+
bip21 = "",
65+
bolt11 = testBolt11,
66+
cjitInvoice = null,
67+
isNodeRunning = true,
68+
onchainAddress = testAddress
69+
)
70+
71+
assertEquals(testAddress, result)
72+
}
73+
74+
@Test
75+
fun `getInvoiceForTab SAVINGS returns fallback when BIP21 only has lightning`() {
76+
val bip21OnlyLightning = "bitcoin:$testAddress?lightning=$testBolt11"
77+
78+
val result = getInvoiceForTab(
79+
tab = ReceiveTab.SAVINGS,
80+
bip21 = bip21OnlyLightning,
81+
bolt11 = testBolt11,
82+
cjitInvoice = null,
83+
isNodeRunning = true,
84+
onchainAddress = testAddress
85+
)
86+
87+
assertEquals("bitcoin:$testAddress", result)
88+
}
89+
90+
@Test
91+
fun `getInvoiceForTab AUTO returns full BIP21 when node is running`() {
92+
val bip21 = "bitcoin:$testAddress?amount=0.001&lightning=$testBolt11"
93+
94+
val result = getInvoiceForTab(
95+
tab = ReceiveTab.AUTO,
96+
bip21 = bip21,
97+
bolt11 = testBolt11,
98+
cjitInvoice = null,
99+
isNodeRunning = true,
100+
onchainAddress = testAddress
101+
)
102+
103+
assertEquals(bip21, result)
104+
}
105+
106+
@Test
107+
fun `getInvoiceForTab AUTO returns empty when node is not running`() {
108+
val bip21 = "bitcoin:$testAddress?amount=0.001&lightning=$testBolt11"
109+
110+
val result = getInvoiceForTab(
111+
tab = ReceiveTab.AUTO,
112+
bip21 = bip21,
113+
bolt11 = testBolt11,
114+
cjitInvoice = null,
115+
isNodeRunning = false,
116+
onchainAddress = testAddress
117+
)
118+
119+
assertEquals("", result)
120+
}
121+
122+
@Test
123+
fun `getInvoiceForTab SPENDING returns CJIT invoice when available and node running`() {
124+
val bip21 = "bitcoin:$testAddress?lightning=$testBolt11"
125+
126+
val result = getInvoiceForTab(
127+
tab = ReceiveTab.SPENDING,
128+
bip21 = bip21,
129+
bolt11 = testBolt11,
130+
cjitInvoice = testCjitInvoice,
131+
isNodeRunning = true,
132+
onchainAddress = testAddress
133+
)
134+
135+
assertEquals(testCjitInvoice, result)
136+
}
137+
138+
@Test
139+
fun `getInvoiceForTab SPENDING returns bolt11 when CJIT unavailable`() {
140+
val bip21 = "bitcoin:$testAddress?lightning=$testBolt11"
141+
142+
val result = getInvoiceForTab(
143+
tab = ReceiveTab.SPENDING,
144+
bip21 = bip21,
145+
bolt11 = testBolt11,
146+
cjitInvoice = null,
147+
isNodeRunning = true,
148+
onchainAddress = testAddress
149+
)
150+
151+
assertEquals(testBolt11, result)
152+
}
153+
154+
@Test
155+
fun `getInvoiceForTab SPENDING returns bolt11 when node not running even with CJIT`() {
156+
val bip21 = "bitcoin:$testAddress?lightning=$testBolt11"
157+
158+
val result = getInvoiceForTab(
159+
tab = ReceiveTab.SPENDING,
160+
bip21 = bip21,
161+
bolt11 = testBolt11,
162+
cjitInvoice = testCjitInvoice,
163+
isNodeRunning = false,
164+
onchainAddress = testAddress
165+
)
166+
167+
assertEquals(testBolt11, result)
168+
}
169+
}

0 commit comments

Comments
 (0)