Skip to content

Commit c49025b

Browse files
authored
Merge branch 'master' into fix/toast-polish
2 parents d412f10 + 4b062fe commit c49025b

File tree

24 files changed

+807
-602
lines changed

24 files changed

+807
-602
lines changed

app/src/main/java/to/bitkit/ext/DateTime.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ fun Instant.formatted(pattern: String = DatePattern.DATE_TIME): String {
3939
return dateTime.format(formatter)
4040
}
4141

42+
fun ULong?.formatToString(pattern: String = DatePattern.DATE_TIME): String? {
43+
return this?.let { Instant.ofEpochSecond(toLong()).formatted(pattern) }
44+
}
45+
4246
fun Long.toTimeUTC(): String {
4347
val instant = Instant.ofEpochMilli(this)
4448
val dateTime = LocalDateTime.ofInstant(instant, ZoneId.of("UTC"))
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package to.bitkit.models
2+
3+
import androidx.annotation.DrawableRes
4+
import androidx.annotation.StringRes
5+
import androidx.compose.ui.graphics.Color
6+
import to.bitkit.R
7+
import to.bitkit.ui.theme.Colors
8+
9+
enum class ActivityBannerType(
10+
@DrawableRes val icon: Int,
11+
@StringRes val title: Int,
12+
val color: Color,
13+
) {
14+
SPENDING(
15+
color = Colors.Purple,
16+
icon = R.drawable.ic_transfer,
17+
title = R.string.activity_banner__transfer_in_progress
18+
),
19+
SAVINGS(
20+
color = Colors.Brand,
21+
icon = R.drawable.ic_transfer,
22+
title = R.string.activity_banner__transfer_in_progress
23+
)
24+
}

app/src/main/java/to/bitkit/models/Suggestion.kt

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -67,40 +67,6 @@ enum class Suggestion(
6767
color = Colors.Green24,
6868
icon = R.drawable.fast_forward
6969
),
70-
71-
/**Replaces LIGHTNING when a LN channel is being force closed*/
72-
TRANSFER_PENDING(
73-
title = R.string.cards__lightningSettingUp__title,
74-
description = R.string.cards__transferPending__description,
75-
color = Colors.Purple24,
76-
icon = R.drawable.transfer,
77-
dismissible = false
78-
),
79-
80-
/**When the LN channel could not be cooped closed immediately*/
81-
TRANSFER_CLOSING_CHANNEL(
82-
title = R.string.cards__transferClosingChannel__title,
83-
description = R.string.cards__transferClosingChannel__description,
84-
color = Colors.Red24,
85-
icon = R.drawable.transfer,
86-
dismissible = false
87-
),
88-
89-
/**Replaces LIGHTNING when the transfer to spending balance is in progress*/
90-
LIGHTNING_SETTING_UP(
91-
title = R.string.cards__lightningSettingUp__title,
92-
description = R.string.cards__lightningSettingUp__description,
93-
color = Colors.Purple24,
94-
icon = R.drawable.transfer,
95-
dismissible = false
96-
),
97-
LIGHTNING_READY(
98-
title = R.string.cards__lightningReady__title,
99-
description = R.string.cards__lightningReady__description,
100-
color = Colors.Purple24,
101-
icon = R.drawable.transfer,
102-
dismissible = false,
103-
),
10470
NOTIFICATIONS(
10571
title = R.string.cards__notifications__title,
10672
description = R.string.cards__notifications__description,

app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package to.bitkit.repositories
22

33
import com.synonym.bitkitcore.BtOrderState2
4+
import com.synonym.bitkitcore.ChannelLiquidityOptions
5+
import com.synonym.bitkitcore.ChannelLiquidityParams
46
import com.synonym.bitkitcore.CreateCjitOptions
57
import com.synonym.bitkitcore.CreateOrderOptions
8+
import com.synonym.bitkitcore.DefaultLspBalanceParams
69
import com.synonym.bitkitcore.IBtEstimateFeeResponse2
710
import com.synonym.bitkitcore.IBtInfo
811
import com.synonym.bitkitcore.IBtOrder
912
import com.synonym.bitkitcore.IcJitEntry
13+
import com.synonym.bitkitcore.calculateChannelLiquidityOptions
14+
import com.synonym.bitkitcore.getDefaultLspBalance
1015
import com.synonym.bitkitcore.giftOrder
1116
import com.synonym.bitkitcore.giftPay
1217
import kotlinx.coroutines.CoroutineDispatcher
@@ -46,7 +51,6 @@ import javax.inject.Inject
4651
import javax.inject.Named
4752
import javax.inject.Singleton
4853
import kotlin.math.ceil
49-
import kotlin.math.min
5054
import kotlin.time.Duration
5155
import kotlin.time.Duration.Companion.seconds
5256

@@ -345,38 +349,49 @@ class BlocktankRepo @Inject constructor(
345349
refreshInfo()
346350
}
347351

348-
val maxLspBalance = _blocktankState.value.info?.options?.maxChannelSizeSat ?: 0uL
352+
val satsPerEur = getSatsPerEur()
353+
?: throw ServiceError.CurrencyRateUnavailable
349354

350-
// Calculate thresholds in sats
351-
val threshold1 = currencyRepo.convertFiatToSats(BigDecimal(225), EUR_CURRENCY).getOrNull()
352-
val threshold2 = currencyRepo.convertFiatToSats(BigDecimal(495), EUR_CURRENCY).getOrNull()
353-
val defaultLspBalanceSats = currencyRepo.convertFiatToSats(BigDecimal(450), EUR_CURRENCY).getOrNull()
355+
val params = DefaultLspBalanceParams(
356+
clientBalanceSat = clientBalance,
357+
maxChannelSizeSat = _blocktankState.value.info?.options?.maxChannelSizeSat ?: 0uL,
358+
satsPerEur = satsPerEur
359+
)
360+
361+
return@withContext getDefaultLspBalance(params)
362+
}
363+
364+
fun calculateLiquidityOptions(clientBalanceSat: ULong): Result<ChannelLiquidityOptions> {
365+
val blocktankInfo = blocktankState.value.info
366+
?: return Result.failure(ServiceError.BlocktankInfoUnavailable)
354367

355-
Logger.debug("getDefaultLspBalance - clientBalance: $clientBalance", context = TAG)
356-
Logger.debug("getDefaultLspBalance - maxLspBalance: $maxLspBalance", context = TAG)
357-
Logger.debug(
358-
"getDefaultLspBalance - defaultLspBalance: $defaultLspBalanceSats",
359-
context = TAG
368+
val satsPerEur = getSatsPerEur()
369+
?: return Result.failure(ServiceError.CurrencyRateUnavailable)
370+
371+
val existingChannelsTotalSat = totalBtChannelsValueSats(blocktankInfo)
372+
373+
val params = ChannelLiquidityParams(
374+
clientBalanceSat = clientBalanceSat,
375+
existingChannelsTotalSat = existingChannelsTotalSat,
376+
minChannelSizeSat = blocktankInfo.options.minChannelSizeSat,
377+
maxChannelSizeSat = blocktankInfo.options.maxChannelSizeSat,
378+
satsPerEur = satsPerEur
360379
)
361380

362-
if (threshold1 == null || threshold2 == null || defaultLspBalanceSats == null) {
363-
Logger.error("Failed to get rates for lspBalance calculation", context = TAG)
364-
throw ServiceError.CurrencyRateUnavailable
365-
}
381+
return Result.success(calculateChannelLiquidityOptions(params))
382+
}
366383

367-
// Safely calculate lspBalance to avoid arithmetic overflow
368-
var lspBalance: ULong = 0u
369-
if (defaultLspBalanceSats > clientBalance) {
370-
lspBalance = defaultLspBalanceSats - clientBalance
371-
}
372-
if (clientBalance > threshold1) {
373-
lspBalance = clientBalance
374-
}
375-
if (clientBalance > threshold2) {
376-
lspBalance = maxLspBalance
377-
}
384+
private fun getSatsPerEur(): ULong? {
385+
return currencyRepo.convertFiatToSats(BigDecimal(1), EUR_CURRENCY).getOrNull()
386+
}
387+
388+
private fun totalBtChannelsValueSats(info: IBtInfo?): ULong {
389+
val channels = lightningRepo.getChannels() ?: return 0u
390+
val btNodeIds = info?.nodes?.map { it.pubkey } ?: return 0u
391+
392+
val btChannels = channels.filter { btNodeIds.contains(it.counterpartyNodeId) }
378393

379-
return@withContext min(lspBalance, maxLspBalance)
394+
return btChannels.sumOf { it.channelValueSats }
380395
}
381396

382397
suspend fun resetState() = withContext(bgDispatcher) {

app/src/main/java/to/bitkit/repositories/WalletRepo.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.update
1515
import kotlinx.coroutines.launch
1616
import kotlinx.coroutines.withContext
1717
import org.lightningdevkit.ldknode.Event
18+
import org.lightningdevkit.ldknode.WordCount
1819
import to.bitkit.data.CacheStore
1920
import to.bitkit.data.SettingsStore
2021
import to.bitkit.data.keychain.Keychain
@@ -564,7 +565,7 @@ class WalletRepo @Inject constructor(
564565
}
565566

566567
private fun generateEntropyMnemonic(): String {
567-
return org.lightningdevkit.ldknode.generateEntropyMnemonic()
568+
return org.lightningdevkit.ldknode.generateEntropyMnemonic(wordCount = WordCount.WORDS12)
568569
}
569570

570571
private companion object {

app/src/main/java/to/bitkit/services/LightningService.kt

Lines changed: 58 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import org.lightningdevkit.ldknode.ElectrumSyncConfig
2424
import org.lightningdevkit.ldknode.Event
2525
import org.lightningdevkit.ldknode.FeeRate
2626
import org.lightningdevkit.ldknode.Node
27+
import org.lightningdevkit.ldknode.NodeEntropy
2728
import org.lightningdevkit.ldknode.NodeException
2829
import org.lightningdevkit.ldknode.NodeStatus
2930
import org.lightningdevkit.ldknode.PaymentDetails
@@ -78,15 +79,29 @@ class LightningService @Inject constructor(
7879
customServerUrl: String? = null,
7980
customRgsServerUrl: String? = null,
8081
) {
81-
val mnemonic = keychain.loadString(Keychain.Key.BIP39_MNEMONIC.name) ?: throw ServiceError.MnemonicNotFound
82-
val passphrase = keychain.loadString(Keychain.Key.BIP39_PASSPHRASE.name)
82+
Logger.debug("Building node…")
8383

84-
// TODO get trustedLnPeers from blocktank info
85-
this.trustedPeers = Env.trustedLnPeers
84+
val config = config(walletIndex)
85+
node = build(
86+
walletIndex,
87+
customServerUrl,
88+
customRgsServerUrl,
89+
config,
90+
)
91+
92+
Logger.info("LDK node setup")
93+
}
94+
95+
private fun config(
96+
walletIndex: Int,
97+
): Config {
8698
val dirPath = Env.ldkStoragePath(walletIndex)
8799

100+
// TODO get trustedLnPeers from blocktank info
101+
this.trustedPeers = Env.trustedLnPeers
88102
val trustedPeerNodeIds = trustedPeers.map { it.nodeId }
89-
val config = defaultConfig().copy(
103+
104+
return defaultConfig().copy(
90105
storageDirPath = dirPath,
91106
network = Env.network,
92107
trustedPeers0conf = trustedPeerNodeIds,
@@ -95,46 +110,52 @@ class LightningService @Inject constructor(
95110
perChannelReserveSats = 1u,
96111
),
97112
)
113+
}
114+
115+
private suspend fun build(
116+
walletIndex: Int,
117+
customServerUrl: String?,
118+
customRgsServerUrl: String?,
119+
config: Config,
120+
): Node = ServiceQueue.LDK.background {
121+
val nodeEntropy = NodeEntropy.fromBip39Mnemonic(
122+
mnemonic = keychain.loadString(Keychain.Key.BIP39_MNEMONIC.name) ?: throw ServiceError.MnemonicNotFound,
123+
passphrase = keychain.loadString(Keychain.Key.BIP39_PASSPHRASE.name),
124+
)
98125

99126
val builder = Builder.fromConfig(config).apply {
100127
setCustomLogger(LdkLogWriter())
101-
102128
configureChainSource(customServerUrl)
103129
configureGossipSource(customRgsServerUrl)
104-
105-
setEntropyBip39Mnemonic(mnemonic, passphrase)
106130
}
107-
108-
Logger.debug("Building node…")
109-
110-
val vssStoreId = vssStoreIdProvider.getVssStoreId(walletIndex)
111-
112-
ServiceQueue.LDK.background {
113-
node = try {
114-
val lnurlAuthServerUrl = Env.lnurlAuthServerUrl
115-
val vssUrl = Env.vssServerUrl
116-
Logger.verbose("Building ldk-node with vssUrl: '$vssUrl'")
117-
Logger.verbose("Building ldk-node with lnurlAuthServerUrl: '$lnurlAuthServerUrl'")
118-
if (lnurlAuthServerUrl.isNotEmpty()) {
119-
builder.buildWithVssStore(
120-
vssUrl = vssUrl,
121-
storeId = vssStoreId,
122-
lnurlAuthServerUrl = lnurlAuthServerUrl,
123-
fixedHeaders = emptyMap(),
124-
)
125-
} else {
126-
builder.buildWithVssStoreAndFixedHeaders(
127-
vssUrl = vssUrl,
128-
storeId = vssStoreId,
129-
fixedHeaders = emptyMap(),
130-
)
131-
}
132-
} catch (e: BuildException) {
133-
throw LdkError(e)
131+
try {
132+
val vssStoreId = vssStoreIdProvider.getVssStoreId(walletIndex)
133+
val lnurlAuthServerUrl = Env.lnurlAuthServerUrl
134+
val vssUrl = Env.vssServerUrl
135+
Logger.verbose("Building ldk-node with vssUrl: '$vssUrl'")
136+
Logger.verbose("Building ldk-node with lnurlAuthServerUrl: '$lnurlAuthServerUrl'")
137+
if (lnurlAuthServerUrl.isNotEmpty()) {
138+
builder.buildWithVssStore(
139+
vssUrl = vssUrl,
140+
storeId = vssStoreId,
141+
lnurlAuthServerUrl = lnurlAuthServerUrl,
142+
fixedHeaders = emptyMap(),
143+
nodeEntropy = nodeEntropy,
144+
)
145+
} else {
146+
builder.buildWithVssStoreAndFixedHeaders(
147+
vssUrl = vssUrl,
148+
storeId = vssStoreId,
149+
fixedHeaders = emptyMap(),
150+
nodeEntropy = nodeEntropy,
151+
)
134152
}
153+
} catch (e: BuildException) {
154+
throw LdkError(e)
155+
} finally {
156+
// cleanup sensitive data
157+
nodeEntropy.destroy()
135158
}
136-
137-
Logger.info("LDK node setup")
138159
}
139160

140161
private suspend fun Builder.configureGossipSource(customRgsServerUrl: String?) {

app/src/main/java/to/bitkit/ui/ContentView.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,6 @@ fun ContentView(
348348

349349
val hasSeenWidgetsIntro by settingsViewModel.hasSeenWidgetsIntro.collectAsStateWithLifecycle()
350350
val hasSeenShopIntro by settingsViewModel.hasSeenShopIntro.collectAsStateWithLifecycle()
351-
352351
val currentSheet by appViewModel.currentSheet.collectAsStateWithLifecycle()
353352

354353
Box(

0 commit comments

Comments
 (0)