diff --git a/app/src/main/java/com/example/umlandowallet/DispatchActivity.kt b/app/src/main/java/com/example/umlandowallet/DispatchActivity.kt index 9ecafc3..af79a0d 100644 --- a/app/src/main/java/com/example/umlandowallet/DispatchActivity.kt +++ b/app/src/main/java/com/example/umlandowallet/DispatchActivity.kt @@ -45,6 +45,8 @@ class DispatchActivity : AppCompatActivity() { serializedChannelMonitors = monitors.joinToString(separator = ",") + Log.i(TAG, "Successfully created/restored wallet with mnemonic ${OnchainWallet.recoveryPhrase()}") + start( OnchainWallet.getLdkEntropy(), latestBlockHeight.toInt(), diff --git a/app/src/main/java/com/example/umlandowallet/Global.kt b/app/src/main/java/com/example/umlandowallet/Global.kt index f742a97..269b98c 100644 --- a/app/src/main/java/com/example/umlandowallet/Global.kt +++ b/app/src/main/java/com/example/umlandowallet/Global.kt @@ -13,9 +13,9 @@ object Global { val prefixScorer = "scorer" - val feerateFast = 5000 // estimate fee rate in BTC/kB - val feerateMedium = 5000 // estimate fee rate in BTC/kB - val feerateSlow = 5000 // estimate fee rate in BTC/kB + val feerateFast = 2000 // estimate fee rate in BTC/kB + val feerateMedium = 2000 // estimate fee rate in BTC/kB + val feerateSlow = 2000 // estimate fee rate in BTC/kB var eventsTxBroadcast: Array = arrayOf() var eventsPaymentSent: Array = arrayOf() diff --git a/app/src/main/java/com/example/umlandowallet/Start.kt b/app/src/main/java/com/example/umlandowallet/Start.kt index a8f6ae7..247bc93 100644 --- a/app/src/main/java/com/example/umlandowallet/Start.kt +++ b/app/src/main/java/com/example/umlandowallet/Start.kt @@ -71,12 +71,6 @@ fun start( channelMonitorList.add(channelMonitorBytes) } channelMonitors = channelMonitorList.toTypedArray() - -// val list = Global.chainMonitor!!.list_monitors() -// list.iterator().forEach { outPoint -> -// val monitor = // Retrieve channel monitor saved in step 4 -// Global.chainMonitor!!.as_Watch().watch_channel(outPoint, monitor) -// } } // This is going to be the fee policy for __incoming__ channels. they are set upfront globally: @@ -117,10 +111,6 @@ fun start( ) Global.channelManager = Global.channelManagerConstructor!!.channel_manager - Global.channelManagerConstructor!!.chain_sync_completed( - ChannelManagerEventHandler, - scorer - ) Global.peerManager = Global.channelManagerConstructor!!.peer_manager Global.nioPeerHandler = Global.channelManagerConstructor!!.nio_peer_handler Global.router = Global.channelManagerConstructor!!.net_graph @@ -139,13 +129,13 @@ fun start( logger ) Global.channelManager = Global.channelManagerConstructor!!.channel_manager + Global.peerManager = Global.channelManagerConstructor!!.peer_manager + Global.nioPeerHandler = Global.channelManagerConstructor!!.nio_peer_handler + Global.router = Global.channelManagerConstructor!!.net_graph Global.channelManagerConstructor!!.chain_sync_completed( ChannelManagerEventHandler, scorer ) - Global.peerManager = Global.channelManagerConstructor!!.peer_manager - Global.nioPeerHandler = Global.channelManagerConstructor!!.nio_peer_handler - Global.router = Global.channelManagerConstructor!!.net_graph } // If you want to communicate from your computer to your emulator, @@ -181,7 +171,7 @@ object LDKBroadcaster : BroadcasterInterface.BroadcasterInterfaceInterface { val service = Service.create() tx?.let { - GlobalScope.launch { + CoroutineScope(Dispatchers.IO).launch { val txid = service.broadcastTx(tx) Log.i(LDKTAG, "We've broadcast a transaction with txid $txid") } diff --git a/app/src/main/java/com/example/umlandowallet/data/OnchainWallet.kt b/app/src/main/java/com/example/umlandowallet/data/OnchainWallet.kt index f73f148..96fca15 100644 --- a/app/src/main/java/com/example/umlandowallet/data/OnchainWallet.kt +++ b/app/src/main/java/com/example/umlandowallet/data/OnchainWallet.kt @@ -76,8 +76,8 @@ object OnchainWallet { fun buildFundingTx(value: Long, script: ByteArray): ByteArray { sync() val scriptListUByte: List = script.toUByteArray().asList() - val outputScript: Script = Script(scriptListUByte) - val (psbt, txDetails) = TxBuilder() + val outputScript = Script(scriptListUByte) + val (psbt, _) = TxBuilder() .addRecipient(outputScript, value.toULong()) .feeRate(4.0F) .finish(onchainWallet) diff --git a/app/src/main/java/com/example/umlandowallet/data/remote/Access.kt b/app/src/main/java/com/example/umlandowallet/data/remote/Access.kt index e084776..c1611c1 100644 --- a/app/src/main/java/com/example/umlandowallet/data/remote/Access.kt +++ b/app/src/main/java/com/example/umlandowallet/data/remote/Access.kt @@ -29,14 +29,7 @@ interface Access { companion object { fun create(): Access { - // Setup BDK Esplora client - val esploraURL: String = "http://10.0.2.2:3002" - val blockchainConfig = - BlockchainConfig.Esplora(EsploraConfig(esploraURL, null, 5u, 20u, null)) - - return AccessImpl( - blockchain = Blockchain(blockchainConfig), - ) + return AccessImpl() } } } \ No newline at end of file diff --git a/app/src/main/java/com/example/umlandowallet/data/remote/AccessImpl.kt b/app/src/main/java/com/example/umlandowallet/data/remote/AccessImpl.kt index 0337a2c..1eed51e 100644 --- a/app/src/main/java/com/example/umlandowallet/data/remote/AccessImpl.kt +++ b/app/src/main/java/com/example/umlandowallet/data/remote/AccessImpl.kt @@ -1,5 +1,7 @@ package com.example.umlandowallet.data.remote +import com.example.umlandowallet.ChannelManagerEventHandler +import com.example.umlandowallet.Global import com.example.umlandowallet.data.* import com.example.umlandowallet.toByteArray import com.example.umlandowallet.toHex @@ -9,10 +11,29 @@ import org.ldk.structs.ChannelManager import org.ldk.structs.TwoTuple_usizeTransactionZ class AccessImpl( - private val blockchain: Blockchain, ) : Access { override suspend fun sync() { - val currentHeight = blockchain.getHeight() + this.syncWallet(OnchainWallet) + + val relevantTxIdsFromChannelManager: Array = + Global.channelManager!!.as_Confirm()._relevant_txids + val relevantTxIdsFromChainMonitor: Array = + Global.chainMonitor!!.as_Confirm()._relevant_txids + + val relevantTxIds: Array = + relevantTxIdsFromChannelManager + relevantTxIdsFromChainMonitor + + this.syncTransactionConfirmed(relevantTxIds, Global.channelManager!!, Global.chainMonitor!!) + this.syncTransactionsUnconfirmed( + relevantTxIds, + Global.channelManager!!, + Global.chainMonitor!! + ) + this.syncBestBlockConnected(Global.channelManager!!, Global.chainMonitor!!) + + Global.channelManagerConstructor!!.chain_sync_completed( + ChannelManagerEventHandler, Global.scorer!! + ) } override suspend fun syncWallet(onchainWallet: OnchainWallet) { @@ -48,18 +69,17 @@ class AccessImpl( if (txStatus.confirmed) { val txHex = service.getTxHex(txId) val tx = service.getTx(txId) - if (tx.status.block_height != null) { - val blockHeader = service.getHeader(tx.status.block_hash) - val merkleProof = service.getMerkleProof(txId) - if (tx.status.block_height == merkleProof.block_height) { - confirmedTxs.add(ConfirmedTx( - tx = txHex.toByteArray(), - block_height = tx.status.block_height, - block_header = blockHeader, - merkle_proof_pos = merkleProof.pos - ) + val blockHeader = service.getHeader(tx.status.block_hash) + val merkleProof = service.getMerkleProof(txId) + if (tx.status.block_height == merkleProof.block_height) { + confirmedTxs.add( + ConfirmedTx( + tx = txHex.toByteArray(), + block_height = tx.status.block_height, + block_header = blockHeader, + merkle_proof_pos = merkleProof.pos ) - } + ) } } } @@ -68,13 +88,23 @@ class AccessImpl( for (cTx in confirmedTxs) { channelManager.as_Confirm().transactions_confirmed( cTx.block_header.toByteArray(), - arrayOf(TwoTuple_usizeTransactionZ.of(cTx.block_height.toLong(), cTx.tx)), + arrayOf( + TwoTuple_usizeTransactionZ.of( + cTx.block_height.toLong(), + cTx.tx + ) + ), cTx.block_height ) chainMonitor.as_Confirm().transactions_confirmed( cTx.block_header.toByteArray(), - arrayOf(TwoTuple_usizeTransactionZ.of(cTx.block_height.toLong(), cTx.tx)), + arrayOf( + TwoTuple_usizeTransactionZ.of( + cTx.block_height.toLong(), + cTx.tx + ) + ), cTx.block_height ) } diff --git a/app/src/main/java/com/example/umlandowallet/data/remote/Service.kt b/app/src/main/java/com/example/umlandowallet/data/remote/Service.kt index bbe1e17..ff92520 100644 --- a/app/src/main/java/com/example/umlandowallet/data/remote/Service.kt +++ b/app/src/main/java/com/example/umlandowallet/data/remote/Service.kt @@ -29,7 +29,7 @@ interface Service { suspend fun getMerkleProof(txid: String) : MerkleProof - suspend fun connectPeer(pubkeyHex: String, hostname: String, port: Int) : Boolean + suspend fun connectPeer(pubkeyHex: String, hostname: String, port: Int) companion object { fun create() : Service { diff --git a/app/src/main/java/com/example/umlandowallet/ui/Navigation.kt b/app/src/main/java/com/example/umlandowallet/ui/Navigation.kt index 4cf9116..475aa93 100644 --- a/app/src/main/java/com/example/umlandowallet/ui/Navigation.kt +++ b/app/src/main/java/com/example/umlandowallet/ui/Navigation.kt @@ -137,6 +137,23 @@ fun Navigation( } ) { OpenChannelScreen() } + // List a channel + composable( + route = Screen.ListChannelsScreen.route, + enterTransition = { + slideIntoContainer(AnimatedContentScope.SlideDirection.Up, animationSpec = tween(animationDuration)) + }, + popEnterTransition = { + slideIntoContainer(AnimatedContentScope.SlideDirection.Up, animationSpec = tween(animationDuration)) + }, + exitTransition = { + slideOutOfContainer(AnimatedContentScope.SlideDirection.Down, animationSpec = tween(animationDuration)) + }, + popExitTransition = { + slideOutOfContainer(AnimatedContentScope.SlideDirection.Down, animationSpec = tween(animationDuration)) + } + ) { ListChannelsScreen() } + // Recovery phrase composable( route = Screen.RecoveryPhraseScreen.route, diff --git a/app/src/main/java/com/example/umlandowallet/ui/Screen.kt b/app/src/main/java/com/example/umlandowallet/ui/Screen.kt index a0e90ad..7abba98 100644 --- a/app/src/main/java/com/example/umlandowallet/ui/Screen.kt +++ b/app/src/main/java/com/example/umlandowallet/ui/Screen.kt @@ -7,5 +7,6 @@ sealed class Screen(val route: String) { object ListPeersScreen : Screen("list_peers") object ConnectPeerScreen : Screen("connect_peer") object OpenChannelScreen : Screen("open_channel") + object ListChannelsScreen : Screen("list_channels") object RecoveryPhraseScreen : Screen("recovery_phrase") } diff --git a/app/src/main/java/com/example/umlandowallet/ui/SettingsScreen.kt b/app/src/main/java/com/example/umlandowallet/ui/SettingsScreen.kt index b258b71..19cf3fa 100644 --- a/app/src/main/java/com/example/umlandowallet/ui/SettingsScreen.kt +++ b/app/src/main/java/com/example/umlandowallet/ui/SettingsScreen.kt @@ -101,6 +101,19 @@ fun SettingsScreen(navController: NavController) { } } ) + + // List channels + SettingButton( + label = "List channels", + onClick = { + navController.navigate(Screen.ListChannelsScreen.route) { + navController.graph.startDestinationRoute?.let { route -> + popUpTo(route) + } + launchSingleTop = true + } + } + ) Text( text = "Onchain Wallet", fontSize = 18.sp, diff --git a/app/src/main/java/com/example/umlandowallet/ui/WalletScreen.kt b/app/src/main/java/com/example/umlandowallet/ui/WalletScreen.kt index 2bdc6aa..d008a3a 100644 --- a/app/src/main/java/com/example/umlandowallet/ui/WalletScreen.kt +++ b/app/src/main/java/com/example/umlandowallet/ui/WalletScreen.kt @@ -21,6 +21,8 @@ import kotlinx.coroutines.launch @Composable fun WalletScreen() { + val accessImpl = AccessImpl() + Column( modifier = Modifier .padding(top = 48.dp) @@ -47,24 +49,8 @@ fun WalletScreen() { ) Button( onClick = { - val relevantTxIdsFromChannelManager: Array = Global.channelManager!!.as_Confirm()._relevant_txids - val relevantTxIdsFromChainMonitor: Array = Global.chainMonitor!!.as_Confirm()._relevant_txids - - val relevantTxIds: Array = relevantTxIdsFromChannelManager + relevantTxIdsFromChainMonitor - CoroutineScope(Dispatchers.IO).launch { - val access = Access.create() - - // Sync BDK wallet - access.syncWallet(OnchainWallet) - - // Sync LDK/Lightning - access.syncTransactionsUnconfirmed(relevantTxIds, Global.channelManager!!, Global.chainMonitor!!) - access.syncTransactionConfirmed(relevantTxIds, Global.channelManager!!, Global.chainMonitor!!) - access.syncBestBlockConnected(Global.channelManager!!, Global.chainMonitor!!) - - Global.channelManagerConstructor!!.chain_sync_completed( - ChannelManagerEventHandler, Global.scorer!!) + accessImpl.sync() } Log.i(LDKTAG, "Wallet synced") diff --git a/app/src/main/java/com/example/umlandowallet/ui/settings/ConnectPeerScreen.kt b/app/src/main/java/com/example/umlandowallet/ui/settings/ConnectPeerScreen.kt index 361cc14..54c4fa8 100644 --- a/app/src/main/java/com/example/umlandowallet/ui/settings/ConnectPeerScreen.kt +++ b/app/src/main/java/com/example/umlandowallet/ui/settings/ConnectPeerScreen.kt @@ -8,7 +8,10 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.example.umlandowallet.data.remote.Access import com.example.umlandowallet.data.remote.Service +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -62,17 +65,10 @@ fun ConnectPeerScreen() { ) Button( onClick = { - // val pubKey = pubKey val host = "10.0.2.2" - // val port = port - GlobalScope.launch { - val hasConnected = service.connectPeer(pubKey, host, port.toInt()) - if(hasConnected) { - setConnectPeerStatus("Successfully connected to peer") - } else { - setConnectPeerStatus("Failed to connect") - } + CoroutineScope(Dispatchers.IO).launch { + service.connectPeer(pubKey, host, port.toInt()) } }, modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp), diff --git a/app/src/main/java/com/example/umlandowallet/ui/settings/ListChannelsScreen.kt b/app/src/main/java/com/example/umlandowallet/ui/settings/ListChannelsScreen.kt new file mode 100644 index 0000000..12f6439 --- /dev/null +++ b/app/src/main/java/com/example/umlandowallet/ui/settings/ListChannelsScreen.kt @@ -0,0 +1,126 @@ +package com.example.umlandowallet.ui.settings + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.umlandowallet.Global +import com.example.umlandowallet.toByteArray +import com.example.umlandowallet.toHex +import com.example.umlandowallet.ui.Screen +import org.ldk.structs.* +import java.time.temporal.TemporalAmount + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ListChannelsScreen() { + Column( + modifier = Modifier + .fillMaxWidth() + .padding( + top = 48.dp, + start = 32.dp, + end = 32.dp + ) + ) + { + Text( + text = "Local balance sats: ${getLocalBalance()} ", + fontSize = 24.sp, + fontWeight = FontWeight.SemiBold, + color = Color(0xff1f0208), + ) + Spacer(modifier = Modifier.size(32.dp)) + Text( + text = "Channels", + fontSize = 24.sp, + fontWeight = FontWeight.SemiBold, + color = Color(0xff1f0208), + ) + Spacer(modifier = Modifier.size(24.dp)) + val usableChannels = getUsuableChannels() + + if (usableChannels != null) { + if (usableChannels.isNotEmpty()) { + usableChannels.forEach { channel -> + val status = channel._is_channel_ready + + var balance = 0L + balance = if (channel._outbound_capacity_msat == 0L) { + 0L + } else { + channel._outbound_capacity_msat / channel._balance_msat + } + val nodeId = channel._counterparty._node_id.toHex() + val sendAmount = channel._outbound_capacity_msat / 1000 + val receiveAmount = channel._inbound_capacity_msat / 1000 + + ListItem( + status, + nodeId, + balance.toFloat(), + sendAmount.toString(), + receiveAmount.toString() + ) + } + } + } + + + } +} + +@Composable +fun ListItem( + status: Boolean, + nodeId: String, + progress: Float, + sendAmount: String, + receiveAmount: String +) { + var progress by remember { mutableStateOf(progress) } + + if (status) { + Text(text = "Active", style = TextStyle(color = Color.Green)) + } + Text(text = nodeId) + LinearProgressIndicator( + modifier = Modifier + .fillMaxWidth() + .height(25.dp) + .padding(top = 16.dp), + progress = progress + ) + Row( + modifier = Modifier + .fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween + ) { + Text(text = "$sendAmount sats") + Text(text = "$receiveAmount sats") + } + Spacer(modifier = Modifier.size(16.dp)) + + +} + +fun getUsuableChannels(): Array? { + return Global.channelManager!!.list_usable_channels() +} + +fun getLocalBalance(): String { + val usableChannels = Global.channelManager!!.list_usable_channels() + + val localBalance = usableChannels.sumOf { it._balance_msat } + + return (localBalance / 1000).toString() +} \ No newline at end of file