-
Notifications
You must be signed in to change notification settings - Fork 1
Improve Wallet and LDK node business logic handling #113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 23 commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
77088d7
refactor: create the LightningRepository
jvsena42 ba2d19b
refactor: create the WalletRepository
jvsena42 e68b813
refactor: create the WalletRepository
jvsena42 fd6bb1b
refactor: replace business logic in viewmodel WIP
jvsena42 5a04366
refactor: replace business logic in viewmodel WIP
jvsena42 f8a57bd
refactor: move default invoice message to env file
jvsena42 fa17c3c
refactor: move move business logic from viewmodel to service
jvsena42 46fd709
refactor: move move business logic from viewmodel to service
jvsena42 eb337da
refactor: hold node state in a mutable state flow
jvsena42 d0f514d
refactor: only sync after the node starts with success
jvsena42 b9ff988
refactor: replace LightningService direct access with repository in W…
jvsena42 447c05a
refactor: rename WalletRepository
jvsena42 8e6de6a
refactor: rename LightningRepository
jvsena42 0035c44
refactor: replace LightningService direct access in AppViewModel.kt
jvsena42 7fe233f
refactor: replace LightningService direct access in ActivityViewModel.kt
jvsena42 99b5cd4
refactor: replace LightningService direct access in TransferViewModel.kt
jvsena42 8e6b7a6
test: update tests
jvsena42 3a1c23f
Merge branch 'master' into refactor/lightning-repository
jvsena42 77e460b
fix: solve conflicts
jvsena42 9363f4f
refactor: move logic to repository
jvsena42 36f0a7c
test: update tests
jvsena42 4b4d9af
test: update tests
jvsena42 6b6e222
fix: add wipe wallet method
jvsena42 ff7b5c5
reactor: remove unnecessary module
jvsena42 3e61c0a
reactor: remove unnecessary save string method
jvsena42 e7ebcba
fix: change sendOnChain typeAlias return
jvsena42 8f663c7
refactor: apply env variable
jvsena42 71b62c0
refactor: remove comment
jvsena42 03d07ff
refactor: implement bg dispatcher
jvsena42 84d81c8
refactor: add todo
jvsena42 4a21145
refactor: remove unused method
jvsena42 395718b
refactor: remove unused method
jvsena42 7e819d8
refactor: remove unused code
jvsena42 98625f2
refactor: fix typo
jvsena42 fec5a31
refactor: implement bg dispatcher
jvsena42 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| @file:Suppress("unused") | ||
|
|
||
| package to.bitkit.di | ||
|
|
||
| import android.content.Context | ||
| import com.google.firebase.messaging.FirebaseMessaging | ||
| import dagger.Module | ||
| import dagger.Provides | ||
| import dagger.hilt.InstallIn | ||
| import dagger.hilt.android.qualifiers.ApplicationContext | ||
| import dagger.hilt.components.SingletonComponent | ||
| import kotlinx.coroutines.CoroutineDispatcher | ||
| import to.bitkit.data.AppDb | ||
| import to.bitkit.data.AppStorage | ||
| import to.bitkit.data.SettingsStore | ||
| import to.bitkit.data.keychain.Keychain | ||
| import to.bitkit.repositories.LightningRepo | ||
| import to.bitkit.repositories.WalletRepo | ||
| import to.bitkit.services.BlocktankNotificationsService | ||
| import to.bitkit.services.CoreService | ||
| import to.bitkit.services.LdkNodeEventBus | ||
| import to.bitkit.services.LightningService | ||
| import to.bitkit.utils.AddressChecker | ||
|
|
||
| @Module | ||
| @InstallIn(SingletonComponent::class) | ||
| object RepositoryModule { | ||
|
|
||
| @Provides | ||
| fun provideLightningRepository( | ||
| @BgDispatcher bgDispatcher: CoroutineDispatcher, | ||
| lightningService: LightningService, | ||
| ldkNodeEventBus: LdkNodeEventBus, | ||
| addressChecker: AddressChecker | ||
| ): LightningRepo { | ||
| return LightningRepo( | ||
| bgDispatcher = bgDispatcher, | ||
| lightningService = lightningService, | ||
| ldkNodeEventBus = ldkNodeEventBus, | ||
| addressChecker = addressChecker | ||
| ) | ||
| } | ||
|
|
||
| @Provides | ||
| fun provideWalletRepository( | ||
| @BgDispatcher bgDispatcher: CoroutineDispatcher, | ||
| @ApplicationContext appContext: Context, | ||
| appStorage: AppStorage, | ||
| db: AppDb, | ||
| keychain: Keychain, | ||
| coreService: CoreService, | ||
| blocktankNotificationsService: BlocktankNotificationsService, | ||
| firebaseMessaging: FirebaseMessaging, | ||
| settingsStore: SettingsStore, | ||
| ): WalletRepo { | ||
| return WalletRepo( | ||
| bgDispatcher = bgDispatcher, | ||
| appContext = appContext, | ||
| appStorage = appStorage, | ||
| db = db, | ||
| keychain = keychain, | ||
| coreService = coreService, | ||
| blocktankNotificationsService = blocktankNotificationsService, | ||
| firebaseMessaging = firebaseMessaging, | ||
| settingsStore = settingsStore | ||
| ) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
267 changes: 267 additions & 0 deletions
267
app/src/main/java/to/bitkit/repositories/LightningRepo.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,267 @@ | ||
| package to.bitkit.repositories | ||
|
|
||
| import kotlinx.coroutines.CoroutineDispatcher | ||
| import kotlinx.coroutines.delay | ||
| import kotlinx.coroutines.flow.Flow | ||
| import kotlinx.coroutines.flow.MutableStateFlow | ||
| import kotlinx.coroutines.flow.asStateFlow | ||
| import kotlinx.coroutines.withContext | ||
| import org.lightningdevkit.ldknode.Address | ||
| import org.lightningdevkit.ldknode.BalanceDetails | ||
| import org.lightningdevkit.ldknode.Bolt11Invoice | ||
| import org.lightningdevkit.ldknode.ChannelDetails | ||
| import org.lightningdevkit.ldknode.NodeStatus | ||
| import org.lightningdevkit.ldknode.PaymentDetails | ||
| import org.lightningdevkit.ldknode.PaymentId | ||
| import org.lightningdevkit.ldknode.UserChannelId | ||
| import org.lightningdevkit.ldknode.generateEntropyMnemonic | ||
| import to.bitkit.di.BgDispatcher | ||
| import to.bitkit.models.LnPeer | ||
| import to.bitkit.models.NodeLifecycleState | ||
| import to.bitkit.services.LdkNodeEventBus | ||
| import to.bitkit.services.LightningService | ||
| import to.bitkit.services.NodeEventHandler | ||
| import to.bitkit.utils.AddressChecker | ||
| import to.bitkit.utils.Logger | ||
| import to.bitkit.utils.ServiceError | ||
| import javax.inject.Inject | ||
| import javax.inject.Singleton | ||
| import kotlin.time.Duration | ||
|
|
||
| @Singleton | ||
| class LightningRepo @Inject constructor( | ||
| @BgDispatcher private val bgDispatcher: CoroutineDispatcher, | ||
| private val lightningService: LightningService, | ||
| private val ldkNodeEventBus: LdkNodeEventBus, | ||
| private val addressChecker: AddressChecker | ||
| ) { | ||
| private val _nodeLifecycleState: MutableStateFlow<NodeLifecycleState> = MutableStateFlow(NodeLifecycleState.Stopped) | ||
| val nodeLifecycleState = _nodeLifecycleState.asStateFlow() | ||
|
|
||
| suspend fun setup(walletIndex: Int): Result<Unit> = withContext(bgDispatcher) { | ||
| return@withContext try { | ||
| lightningService.setup(walletIndex) | ||
| Result.success(Unit) | ||
| } catch (e: Throwable) { | ||
| Logger.error("Node setup error", e) | ||
| Result.failure(e) | ||
| } | ||
| } | ||
|
|
||
| suspend fun start( | ||
| walletIndex: Int, | ||
| timeout: Duration? = null, | ||
| eventHandler: NodeEventHandler? = null | ||
| ): Result<Unit> = | ||
| withContext(bgDispatcher) { | ||
| if (nodeLifecycleState.value.isRunningOrStarting()) { | ||
| return@withContext Result.success(Unit) | ||
| } | ||
|
|
||
| try { | ||
| _nodeLifecycleState.value = NodeLifecycleState.Starting | ||
|
|
||
| // Setup if not already setup | ||
| if (lightningService.node == null) { | ||
| val setupResult = setup(walletIndex) | ||
| if (setupResult.isFailure) { | ||
| _nodeLifecycleState.value = NodeLifecycleState.ErrorStarting( | ||
| setupResult.exceptionOrNull() ?: Exception("Unknown setup error") | ||
| ) | ||
| return@withContext setupResult | ||
| } | ||
| } | ||
|
|
||
| // Start the node service | ||
| lightningService.start(timeout) { event -> | ||
| eventHandler?.invoke(event) | ||
| ldkNodeEventBus.emit(event) | ||
| } | ||
|
|
||
| _nodeLifecycleState.value = NodeLifecycleState.Running | ||
| Result.success(Unit) | ||
| } catch (e: Throwable) { | ||
| Logger.error("Node start error", e) | ||
| _nodeLifecycleState.value = NodeLifecycleState.ErrorStarting(e) | ||
| Result.failure(e) | ||
| } | ||
| } | ||
|
|
||
| suspend fun waitForNodeStart(timeout: Long = 30000): Result<Unit> = withContext(bgDispatcher) { | ||
| val startTime = System.currentTimeMillis() | ||
|
|
||
| while (System.currentTimeMillis() - startTime < timeout) { | ||
| when (nodeLifecycleState.value) { | ||
| NodeLifecycleState.Running -> return@withContext Result.success(Unit) | ||
| is NodeLifecycleState.ErrorStarting -> { | ||
| val error = (nodeLifecycleState.value as NodeLifecycleState.ErrorStarting).cause | ||
| return@withContext Result.failure(error) | ||
| } | ||
|
|
||
| else -> delay(100) // Wait a bit before checking again | ||
| } | ||
| } | ||
|
|
||
| Result.failure(ServiceError.NodeStartTimeout) | ||
| } | ||
|
|
||
| suspend fun stop(): Result<Unit> = withContext(bgDispatcher) { | ||
| if (nodeLifecycleState.value.isStoppedOrStopping()) { | ||
| return@withContext Result.success(Unit) | ||
| } | ||
|
|
||
| try { | ||
| _nodeLifecycleState.value = NodeLifecycleState.Stopping | ||
| lightningService.stop() | ||
| _nodeLifecycleState.value = NodeLifecycleState.Stopped | ||
| Result.success(Unit) | ||
| } catch (e: Throwable) { | ||
| Logger.error("Node stop error", e) | ||
| Result.failure(e) | ||
| } | ||
| } | ||
|
|
||
| suspend fun sync(): Result<Unit> = withContext(bgDispatcher) { | ||
| try { | ||
| lightningService.sync() | ||
| Result.success(Unit) | ||
| } catch (e: Throwable) { | ||
| Logger.error("Sync error", e) | ||
| Result.failure(e) | ||
| } | ||
| } | ||
|
|
||
| suspend fun wipeStorage(walletIndex: Int): Result<Unit> = withContext(bgDispatcher) { | ||
| try { | ||
| lightningService.wipeStorage(walletIndex) | ||
| Result.success(Unit) | ||
| } catch (e: Throwable) { | ||
| Logger.error("Wipe storage error", e) | ||
| Result.failure(e) | ||
| } | ||
| } | ||
|
|
||
| suspend fun connectToTrustedPeers(): Result<Unit> = withContext(bgDispatcher) { | ||
| try { | ||
| lightningService.connectToTrustedPeers() | ||
| Result.success(Unit) | ||
| } catch (e: Throwable) { | ||
| Logger.error("Connect to trusted peers error", e) | ||
| Result.failure(e) | ||
| } | ||
| } | ||
|
|
||
| suspend fun disconnectPeer(peer: LnPeer): Result<Unit> = withContext(bgDispatcher) { | ||
| try { | ||
| lightningService.disconnectPeer(peer) | ||
| Result.success(Unit) | ||
| } catch (e: Throwable) { | ||
| Logger.error("Disconnect peer error", e) | ||
| Result.failure(e) | ||
| } | ||
| } | ||
|
|
||
| suspend fun newAddress(): Result<String> = withContext(bgDispatcher) { | ||
| try { | ||
| val address = lightningService.newAddress() | ||
| Result.success(address) | ||
| } catch (e: Throwable) { | ||
| Logger.error("New address error", e) | ||
| Result.failure(e) | ||
| } | ||
| } | ||
|
|
||
| suspend fun checkAddressUsage(address: String): Result<Boolean> = withContext(bgDispatcher) { | ||
| try { | ||
| val addressInfo = addressChecker.getAddressInfo(address) | ||
| val hasTransactions = addressInfo.chain_stats.tx_count > 0 || addressInfo.mempool_stats.tx_count > 0 | ||
| Result.success(hasTransactions) | ||
| } catch (e: Throwable) { | ||
| Logger.error("Check address usage error", e) | ||
| Result.failure(e) | ||
| } | ||
| } | ||
|
|
||
| suspend fun createInvoice( | ||
| amountSats: ULong? = null, | ||
| description: String, | ||
| expirySeconds: UInt = 86_400u | ||
| ): Result<Bolt11Invoice> = withContext(bgDispatcher) { | ||
| try { | ||
| val invoice = lightningService.receive(amountSats, description, expirySeconds) | ||
| Result.success(invoice) | ||
| } catch (e: Throwable) { | ||
| Logger.error("Create invoice error", e) | ||
| Result.failure(e) | ||
| } | ||
| } | ||
|
|
||
| suspend fun payInvoice(bolt11: String, sats: ULong? = null): Result<PaymentId> = withContext(bgDispatcher) { | ||
| try { | ||
| val paymentId = lightningService.send(bolt11 = bolt11, sats = sats) | ||
| Result.success(paymentId) | ||
| } catch (e: Throwable) { | ||
| Logger.error("Pay invoice error", e) | ||
| Result.failure(e) | ||
| } | ||
| } | ||
|
|
||
| suspend fun sendOnChain(address: Address, sats: ULong): Result<PaymentId> = withContext(bgDispatcher) { | ||
| try { | ||
| val paymentId = lightningService.send(address = address, sats = sats) | ||
| Result.success(paymentId) | ||
| } catch (e: Throwable) { | ||
| Logger.error("sendOnChain error", e) | ||
| Result.failure(e) | ||
| } | ||
| } | ||
jvsena42 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| suspend fun getPayments(): Result< List<PaymentDetails>> = withContext(bgDispatcher) { | ||
| try { | ||
| val payments = lightningService.payments ?: return@withContext Result.failure(Exception("It wan't possible get the payments")) | ||
| Result.success(payments) | ||
| } catch (e: Throwable) { | ||
| Logger.error("getPayments error", e) | ||
| Result.failure(e) | ||
| } | ||
| } | ||
|
|
||
| suspend fun openChannel( | ||
| peer: LnPeer, | ||
| channelAmountSats: ULong, | ||
| pushToCounterpartySats: ULong? = null | ||
| ): Result<UserChannelId> = withContext(bgDispatcher) { | ||
| try { | ||
| val result = lightningService.openChannel(peer, channelAmountSats, pushToCounterpartySats) | ||
| result | ||
| } catch (e: Throwable) { | ||
| Logger.error("Open channel error", e) | ||
| Result.failure(e) | ||
| } | ||
| } | ||
|
|
||
| suspend fun closeChannel(userChannelId: String, counterpartyNodeId: String): Result<Unit> = | ||
| withContext(bgDispatcher) { | ||
| try { | ||
| lightningService.closeChannel(userChannelId, counterpartyNodeId) | ||
| Result.success(Unit) | ||
| } catch (e: Throwable) { | ||
| Logger.error("Close channel error", e) | ||
| Result.failure(e) | ||
| } | ||
| } | ||
|
|
||
| fun canSend(amountSats: ULong): Boolean = lightningService.canSend(amountSats) | ||
|
|
||
| fun getSyncFlow(): Flow<Unit> = lightningService.syncFlow() | ||
|
|
||
| fun getNodeId(): String? = lightningService.nodeId | ||
| fun getBalances(): BalanceDetails? = lightningService.balances | ||
| fun getStatus(): NodeStatus? = lightningService.status | ||
| fun getPeers(): List<LnPeer>? = lightningService.peers | ||
| fun getChannels(): List<ChannelDetails>? = lightningService.channels | ||
|
|
||
| fun hasChannels(): Boolean = lightningService.channels?.isNotEmpty() == true | ||
|
|
||
| fun generateMnemonic(): String = generateEntropyMnemonic() | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.