Skip to content

Commit 6c7ffcf

Browse files
feat: wire the new Kyoto type into the wallet
1 parent a823e1b commit 6c7ffcf

6 files changed

Lines changed: 79 additions & 132 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,6 @@ app.run.xml
2020
release/
2121
.claude/
2222
design/
23+
website/.venv/
24+
website/site/
25+
website/.cache/

app/src/main/java/org/bitcoindevkit/devkitwallet/domain/Wallet.kt

Lines changed: 17 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,16 @@ import kotlinx.coroutines.runBlocking
1010
import org.bitcoindevkit.Address
1111
import org.bitcoindevkit.AddressInfo
1212
import org.bitcoindevkit.Amount
13-
import org.bitcoindevkit.BlockId
1413
import org.bitcoindevkit.CanonicalTx
15-
import org.bitcoindevkit.CbfBuilder
16-
import org.bitcoindevkit.CbfClient
1714
import org.bitcoindevkit.ChainPosition
1815
import org.bitcoindevkit.Descriptor
1916
import org.bitcoindevkit.DescriptorSecretKey
2017
import org.bitcoindevkit.FeeRate
21-
import org.bitcoindevkit.IpAddress
2218
import org.bitcoindevkit.KeychainKind
2319
import org.bitcoindevkit.Mnemonic
2420
import org.bitcoindevkit.Network
25-
import org.bitcoindevkit.Peer
2621
import org.bitcoindevkit.Persister
2722
import org.bitcoindevkit.Psbt
28-
import org.bitcoindevkit.ScanType
2923
import org.bitcoindevkit.Script
3024
import org.bitcoindevkit.TxBuilder
3125
import org.bitcoindevkit.Update
@@ -46,22 +40,26 @@ import org.bitcoindevkit.Wallet as BdkWallet
4640
private const val TAG = "Wallet"
4741

4842
class Wallet private constructor(
49-
private val wallet: BdkWallet,
43+
val wallet: BdkWallet,
5044
private val walletSecrets: WalletSecrets,
5145
private val connection: Persister,
5246
private var fullScanCompleted: Boolean,
5347
private val walletId: String,
5448
private val userPreferencesRepository: UserPreferencesRepository,
55-
private val internalAppFilesPath: String,
49+
val internalAppFilesPath: String,
5650
blockchainClientsConfig: BlockchainClientsConfig,
51+
val network: Network,
5752
) {
5853
private var currentBlockchainClient: BlockchainClient? = blockchainClientsConfig.getClient()
59-
public var kyotoClient: CbfClient? = null
6054

6155
fun getWalletSecrets(): WalletSecrets {
6256
return walletSecrets
6357
}
6458

59+
fun bestBlock(): UInt {
60+
return wallet.latestCheckpoint().height
61+
}
62+
6563
fun createTransaction(recipientList: List<Recipient>, feeRate: FeeRate, opReturnMsg: String?): Psbt {
6664
// technique 1 for adding a list of recipients to the TxBuilder
6765
// var txBuilder = TxBuilder()
@@ -74,7 +72,7 @@ class Wallet private constructor(
7472
var txBuilder =
7573
recipientList.fold(TxBuilder()) { builder, recipient ->
7674
// val address = Address(recipient.address)
77-
val scriptPubKey: Script = Address(recipient.address, Network.TESTNET).scriptPubkey()
75+
val scriptPubKey: Script = Address(recipient.address, this.network).scriptPubkey()
7876
builder.addRecipient(scriptPubKey, Amount.fromSat(recipient.amount))
7977
}
8078
// if (!opReturnMsg.isNullOrEmpty()) {
@@ -115,11 +113,11 @@ class Wallet private constructor(
115113
return wallet.sign(psbt)
116114
}
117115

118-
fun broadcast(signedPsbt: Psbt): String {
119-
currentBlockchainClient?.broadcast(signedPsbt.extractTx())
120-
?: throw IllegalStateException("Blockchain client not initialized")
121-
return signedPsbt.extractTx().computeTxid().toString()
122-
}
116+
// fun broadcast(signedPsbt: Psbt): String {
117+
// currentBlockchainClient?.broadcast(signedPsbt.extractTx())
118+
// ?: throw IllegalStateException("Blockchain client not initialized")
119+
// return signedPsbt.extractTx().computeTxid().toString()
120+
// }
123121

124122
private fun getAllTransactions(): List<CanonicalTx> = wallet.transactions()
125123

@@ -180,39 +178,10 @@ class Wallet private constructor(
180178

181179
fun getNewAddress(): AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
182180

183-
fun getLastCheckpoint(): BlockId = wallet.latestCheckpoint()
184-
185-
fun startKyotoNode() {
186-
Log.i(TAG, "Starting Kyoto node")
187-
// Regtest
188-
val ip: IpAddress = IpAddress.fromIpv4(10u, 0u, 2u, 2u)
189-
val peer1: Peer = Peer(ip, 18444u, false)
190-
191-
// Signet
192-
// val ip: IpAddress = IpAddress.fromIpv4(68u, 47u, 229u, 218u)
193-
// val peer1: Peer = Peer(ip, null, false)
194-
val peers: List<Peer> = listOf(peer1)
195-
196-
val (client, node) =
197-
CbfBuilder()
198-
.dataDir(this.internalAppFilesPath)
199-
.peers(peers)
200-
.connections(1u)
201-
.scanType(ScanType.Sync)
202-
.build(this.wallet)
203-
204-
node.run()
205-
kyotoClient = client
206-
Log.i(TAG, "Kyoto node started")
207-
}
208-
209-
suspend fun stopKyotoNode() {
210-
kyotoClient?.shutdown()
211-
}
212-
213181
fun applyUpdate(update: Update) {
214182
wallet.applyUpdate(update)
215183
wallet.persist(connection)
184+
Log.i("KYOTOTEST", "Wallet applied a Kyoto update")
216185
}
217186

218187
companion object {
@@ -276,6 +245,7 @@ class Wallet private constructor(
276245
userPreferencesRepository = userPreferencesRepository,
277246
internalAppFilesPath = internalAppFilesPath,
278247
blockchainClientsConfig = BlockchainClientsConfig.createDefaultConfig(newWalletConfig.network),
248+
network = newWalletConfig.network
279249
)
280250
}
281251

@@ -306,6 +276,7 @@ class Wallet private constructor(
306276
blockchainClientsConfig = BlockchainClientsConfig.createDefaultConfig(
307277
activeWallet.network.intoDomain()
308278
),
279+
network = activeWallet.network.intoDomain()
309280
)
310281
}
311282

@@ -380,6 +351,7 @@ class Wallet private constructor(
380351
userPreferencesRepository = userPreferencesRepository,
381352
internalAppFilesPath = internalAppFilesPath,
382353
blockchainClientsConfig = BlockchainClientsConfig.createDefaultConfig(recoverWalletConfig.network),
354+
network = recoverWalletConfig.network
383355
)
384356
}
385357
}

app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/ui/screens/wallet/SendScreen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ private fun ConfirmDialog(
447447
if (checkRecipientList(recipientList = recipientList, feeRate = feeRate, context = context)) {
448448
val txDataBundle =
449449
TxDataBundle(
450-
recipients = recipientList,
450+
recipients = recipientList.toList(),
451451
feeRate = feeRate.value.toULong(),
452452
transactionType = transactionType,
453453
opReturnMsg = opReturnMsg,

app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/viewmodels/SendViewModel.kt

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ package org.bitcoindevkit.devkitwallet.presentation.viewmodels
77

88
import android.util.Log
99
import androidx.lifecycle.ViewModel
10+
import androidx.lifecycle.viewModelScope
11+
import kotlinx.coroutines.launch
1012
import org.bitcoindevkit.FeeRate
1113
import org.bitcoindevkit.Psbt
14+
import org.bitcoindevkit.devkitwallet.data.Kyoto
15+
import org.bitcoindevkit.devkitwallet.data.KyotoNotInitialized
1216
import org.bitcoindevkit.devkitwallet.domain.Wallet
1317
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.SendScreenAction
1418
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.TransactionType
@@ -24,28 +28,36 @@ internal class SendViewModel(private val wallet: Wallet) : ViewModel() {
2428
}
2529

2630
private fun broadcast(txInfo: TxDataBundle) {
27-
try {
28-
// Create, sign, and broadcast
29-
val psbt: Psbt =
30-
when (txInfo.transactionType) {
31-
TransactionType.STANDARD ->
32-
wallet.createTransaction(
33-
recipientList = txInfo.recipients,
34-
feeRate = FeeRate.fromSatPerVb(txInfo.feeRate),
35-
opReturnMsg = txInfo.opReturnMsg,
36-
)
37-
// TransactionType.SEND_ALL -> Wallet.createSendAllTransaction(recipientList[0].address, FeeRate.fromSatPerVb(feeRate), rbfEnabled, opReturnMsg)
38-
TransactionType.SEND_ALL -> throw NotImplementedError("Send all not implemented")
31+
Log.i(TAG, "The tx data bundle is $txInfo")
32+
33+
// TODO: Add error snackbar if Kyoto node is not running, or maybe simply disable the button
34+
viewModelScope.launch {
35+
try {
36+
// Create, sign, and broadcast
37+
val psbt: Psbt =
38+
when (txInfo.transactionType) {
39+
TransactionType.STANDARD ->
40+
wallet.createTransaction(
41+
recipientList = txInfo.recipients,
42+
feeRate = FeeRate.fromSatPerVb(txInfo.feeRate),
43+
opReturnMsg = txInfo.opReturnMsg,
44+
)
45+
// TransactionType.SEND_ALL -> Wallet.createSendAllTransaction(recipientList[0].address, FeeRate.fromSatPerVb(feeRate), rbfEnabled, opReturnMsg)
46+
TransactionType.SEND_ALL -> throw NotImplementedError("Send all not implemented")
47+
}
48+
val isSigned = wallet.sign(psbt)
49+
if (isSigned) {
50+
val transaction = psbt.extractTx()
51+
val wtxid: String = Kyoto.getInstance().broadcast(transaction).toString()
52+
Log.i(TAG, "Transaction was broadcast! txid: $wtxid")
53+
} else {
54+
Log.i(TAG, "Transaction not signed.")
3955
}
40-
val isSigned = wallet.sign(psbt)
41-
if (isSigned) {
42-
val txid: String = wallet.broadcast(psbt)
43-
Log.i(TAG, "Transaction was broadcast! txid: $txid")
44-
} else {
45-
Log.i(TAG, "Transaction not signed.")
56+
} catch (e: KyotoNotInitialized) {
57+
Log.i(TAG, "Kyoto was not initialized! Transaction cannot be broadcast.")
58+
} catch (e: Throwable) {
59+
Log.i(TAG, "Broadcast error message: ${e.message}")
4660
}
47-
} catch (e: Throwable) {
48-
Log.i(TAG, "Broadcast error: ${e.message}")
4961
}
5062
}
5163
}

app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/viewmodels/WalletViewModel.kt

Lines changed: 22 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,12 @@ import androidx.lifecycle.ViewModel
1313
import androidx.lifecycle.viewModelScope
1414
import kotlinx.coroutines.CoroutineScope
1515
import kotlinx.coroutines.Dispatchers
16-
import kotlinx.coroutines.cancelChildren
1716
import kotlinx.coroutines.launch
18-
import org.bitcoindevkit.Warning
17+
import org.bitcoindevkit.devkitwallet.data.Kyoto
1918
import org.bitcoindevkit.devkitwallet.domain.CurrencyUnit
2019
import org.bitcoindevkit.devkitwallet.domain.DwLogger
2120
import org.bitcoindevkit.devkitwallet.domain.DwLogger.LogLevel.INFO
2221
import org.bitcoindevkit.devkitwallet.domain.Wallet
23-
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.KyotoNodeStatus
2422
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.WalletScreenAction
2523
import org.bitcoindevkit.devkitwallet.presentation.viewmodels.mvi.WalletScreenState
2624

@@ -33,15 +31,15 @@ internal class WalletViewModel(
3331
private set
3432

3533
private val kyotoCoroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
36-
private var latestBlock: Int = 0
34+
private var kyoto: Kyoto? = null
3735

3836
fun onAction(action: WalletScreenAction) {
3937
when (action) {
40-
WalletScreenAction.SwitchUnit -> switchUnit()
41-
WalletScreenAction.UpdateBalance -> updateBalance()
42-
WalletScreenAction.StartKyotoNode -> startKyotoNode()
43-
WalletScreenAction.StopKyotoNode -> stopKyotoNode()
44-
WalletScreenAction.ClearSnackbar -> clearSnackbar()
38+
WalletScreenAction.SwitchUnit -> switchUnit()
39+
WalletScreenAction.UpdateBalance -> updateBalance()
40+
WalletScreenAction.ActivateCbfNode -> activateKyoto()
41+
WalletScreenAction.StopKyotoNode -> stopKyotoNode()
42+
WalletScreenAction.ClearSnackbar -> clearSnackbar()
4543
}
4644
}
4745

@@ -62,7 +60,7 @@ internal class WalletViewModel(
6260
}
6361

6462
private fun updateLatestBlock(blockHeight: UInt) {
65-
state = state.copy(latestBlock = blockHeight)
63+
state = state.copy(bestBlockHeight = blockHeight)
6664
}
6765

6866
private fun updateBalance() {
@@ -77,65 +75,27 @@ internal class WalletViewModel(
7775
}
7876
}
7977

80-
private fun startKyotoNode() {
81-
Log.i("Kyoto", "Starting Kyoto node")
82-
DwLogger.log(INFO, "Starting Kyoto node")
83-
wallet.startKyotoNode()
84-
state = state.copy(kyotoNodeStatus = KyotoNodeStatus.Running)
85-
86-
Log.i("Kyoto", "Starting Kyoto sync")
87-
DwLogger.log(INFO, "Starting Kyoto sync")
78+
private fun activateKyoto() {
79+
val dataDir = wallet.internalAppFilesPath
80+
this.kyoto = Kyoto.create(wallet.wallet, dataDir, wallet.network)
81+
val updatesFlow = kyoto!!.start()
8882
kyotoCoroutineScope.launch {
89-
while (wallet.kyotoClient != null) {
90-
val update = wallet.kyotoClient?.update()
91-
if (update == null) {
92-
Log.i("Kyoto", "UPDATE: Update is null")
93-
} else {
94-
Log.i("Kyoto", "UPDATE: Applying an update to the wallet")
95-
wallet.applyUpdate(update)
96-
}
83+
updatesFlow.collect {
84+
Log.i(TAG, "Collecting a flow update")
85+
wallet.applyUpdate(it)
9786
updateBalance()
87+
updateBestBlock()
9888
}
9989
}
100-
101-
kyotoCoroutineScope.launch {
102-
while (wallet.kyotoClient != null) {
103-
val nextInfo = wallet.kyotoClient!!.nextInfo()
104-
Log.i("Kyoto", "LOG: $nextInfo")
105-
val lastNumber = wallet.getLastCheckpoint().height.toInt()
106-
107-
if (lastNumber > latestBlock) {
108-
latestBlock = lastNumber
109-
updateLatestBlock(latestBlock.toUInt())
110-
showSnackbar("New block mined! $latestBlock \uD83C\uDF89\uD83C\uDF89")
111-
}
112-
}
113-
}
114-
115-
kyotoCoroutineScope.launch {
116-
while (wallet.kyotoClient != null) {
117-
val nextWarning: Warning = wallet.kyotoClient!!.nextWarning()
118-
Log.i("Kyoto", "WARNING: $nextWarning")
119-
}
120-
}
90+
kyoto!!.logToLogcat()
12191
}
12292

12393
private fun stopKyotoNode() {
124-
Log.i("Kyoto", "Stopping Kyoto node")
125-
DwLogger.log(INFO, "Stopping Kyoto node")
126-
viewModelScope.launch {
127-
try {
128-
Log.i("Kyoto", "Calling wallet.stopKyotoNode() on thread: ${Thread.currentThread().name}")
129-
wallet.stopKyotoNode()
130-
131-
// Cancel all coroutines started by startKyotoSync
132-
kyotoCoroutineScope.coroutineContext.cancelChildren()
94+
kyoto!!.shutdown()
95+
}
13396

134-
Log.i("Kyoto", "Kyoto node stopped successfully.")
135-
state = state.copy(kyotoNodeStatus = KyotoNodeStatus.Stopped)
136-
} catch (e: Exception) {
137-
Log.e("Kyoto", "Error stopping Kyoto node: ${e.message}", e)
138-
}
139-
}
97+
private fun updateBestBlock() {
98+
val bestBlockHeight = wallet.bestBlock()
99+
state = state.copy(bestBlockHeight = bestBlockHeight)
140100
}
141101
}

app/src/main/java/org/bitcoindevkit/devkitwallet/presentation/viewmodels/mvi/MviWalletScreen.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,24 @@ import org.bitcoindevkit.devkitwallet.domain.CurrencyUnit
1010
data class WalletScreenState(
1111
val balance: ULong = 0u,
1212
val unit: CurrencyUnit = CurrencyUnit.Bitcoin,
13-
val latestBlock: UInt = 0u,
13+
val bestBlockHeight: UInt = 0u,
1414
val snackbarMessage: String? = null,
15-
val kyotoNodeStatus: KyotoNodeStatus = KyotoNodeStatus.Stopped,
15+
val kyotoNodeStatus: CbfNodeStatus = CbfNodeStatus.Stopped,
1616
)
1717

1818
sealed interface WalletScreenAction {
1919
data object UpdateBalance : WalletScreenAction
2020

2121
data object SwitchUnit : WalletScreenAction
2222

23-
data object StartKyotoNode : WalletScreenAction
23+
data object ActivateCbfNode : WalletScreenAction
2424

2525
data object StopKyotoNode : WalletScreenAction
2626

2727
data object ClearSnackbar : WalletScreenAction
2828
}
2929

30-
enum class KyotoNodeStatus {
30+
enum class CbfNodeStatus {
3131
Running,
3232
Stopped,
3333
}

0 commit comments

Comments
 (0)