mirror of
https://github.com/mollyim/monero-wallet-sdk.git
synced 2025-05-12 21:20:42 +01:00
demo: add restore wallet screen
This commit is contained in:
parent
e61021d8bd
commit
70231790ee
19 changed files with 365 additions and 132 deletions
|
@ -2,7 +2,7 @@
|
||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"identityHash": "419c430462df613cab5f23d35923c2b7",
|
"identityHash": "a717d86bf72794f75768dfa37ee61831",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "wallets",
|
"tableName": "wallets",
|
||||||
|
@ -39,17 +39,7 @@
|
||||||
"id"
|
"id"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"indices": [
|
"indices": [],
|
||||||
{
|
|
||||||
"name": "index_wallets_public_address",
|
|
||||||
"unique": true,
|
|
||||||
"columnNames": [
|
|
||||||
"public_address"
|
|
||||||
],
|
|
||||||
"orders": [],
|
|
||||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_wallets_public_address` ON `${TABLE_NAME}` (`public_address`)"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"foreignKeys": []
|
"foreignKeys": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -179,7 +169,7 @@
|
||||||
"views": [],
|
"views": [],
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '419c430462df613cab5f23d35923c2b7')"
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a717d86bf72794f75768dfa37ee61831')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,12 +5,22 @@ import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onStart
|
import kotlinx.coroutines.flow.onStart
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic class that holds a value with its loading status.
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
sealed interface Result<out T> {
|
sealed interface Result<out T> {
|
||||||
data class Success<T>(val data: T) : Result<T>
|
data class Success<T>(val data: T) : Result<T>
|
||||||
data class Error(val exception: Throwable? = null) : Result<Nothing>
|
data class Error(val exception: Throwable? = null) : Result<Nothing>
|
||||||
object Loading : Result<Nothing>
|
object Loading : Result<Nothing>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `true` if [Result] is of type [Result.Success] & holds non-null [Result.Success.data].
|
||||||
|
*/
|
||||||
|
val Result<*>.succeeded
|
||||||
|
get() = this is Result.Success && data != null
|
||||||
|
|
||||||
fun <T> Flow<T>.asResult(): Flow<Result<T>> {
|
fun <T> Flow<T>.asResult(): Flow<Result<T>> {
|
||||||
return this
|
return this
|
||||||
.map<T, Result<T>> {
|
.map<T, Result<T>> {
|
||||||
|
|
|
@ -28,6 +28,23 @@ class MoneroSdkClient(private val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun restoreWallet(
|
||||||
|
network: MoneroNetwork,
|
||||||
|
filename: String,
|
||||||
|
secretSpendKey: SecretKey,
|
||||||
|
restorePoint: RestorePoint,
|
||||||
|
): MoneroWallet {
|
||||||
|
val provider = providerDeferred.await()
|
||||||
|
return provider.restoreWallet(
|
||||||
|
network = network,
|
||||||
|
dataStore = WalletDataStoreFile(filename, newFile = true),
|
||||||
|
secretSpendKey = secretSpendKey,
|
||||||
|
restorePoint = restorePoint,
|
||||||
|
).also { wallet ->
|
||||||
|
wallet.commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun openWallet(
|
suspend fun openWallet(
|
||||||
network: MoneroNetwork,
|
network: MoneroNetwork,
|
||||||
filename: String,
|
filename: String,
|
||||||
|
|
|
@ -74,6 +74,29 @@ class WalletRepository(
|
||||||
return walletId to wallet
|
return walletId to wallet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun restoreWallet(
|
||||||
|
moneroNetwork: MoneroNetwork,
|
||||||
|
name: String,
|
||||||
|
remoteNodeIds: List<Long>,
|
||||||
|
secretSpendKey: SecretKey,
|
||||||
|
restorePoint: RestorePoint,
|
||||||
|
): Pair<Long, MoneroWallet> {
|
||||||
|
val uniqueFilename = UUID.randomUUID().toString()
|
||||||
|
val wallet = moneroSdkClient.restoreWallet(
|
||||||
|
moneroNetwork,
|
||||||
|
uniqueFilename,
|
||||||
|
secretSpendKey,
|
||||||
|
restorePoint,
|
||||||
|
)
|
||||||
|
val walletId = walletDataSource.createWalletConfig(
|
||||||
|
publicAddress = wallet.primaryAddress,
|
||||||
|
filename = uniqueFilename,
|
||||||
|
name = name,
|
||||||
|
remoteNodeIds = remoteNodeIds,
|
||||||
|
)
|
||||||
|
return walletId to wallet
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun updateWalletConfig(walletConfig: WalletConfig) =
|
suspend fun updateWalletConfig(walletConfig: WalletConfig) =
|
||||||
walletDataSource.updateWalletConfig(walletConfig)
|
walletDataSource.updateWalletConfig(walletConfig)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,6 @@ import im.molly.monero.demo.data.model.WalletConfig
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
tableName = "wallets",
|
tableName = "wallets",
|
||||||
indices = [
|
|
||||||
Index(value = ["public_address"], unique = true)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
data class WalletEntity(
|
data class WalletEntity(
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
|
|
@ -47,7 +47,7 @@ class SyncService(
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
val result = wallet.awaitRefresh()
|
val result = wallet.awaitRefresh()
|
||||||
if (result.isError()) {
|
if (result.isError()) {
|
||||||
break
|
// TODO: Handle non-recoverable errors
|
||||||
}
|
}
|
||||||
wallet.commit()
|
wallet.commit()
|
||||||
delay(10.seconds)
|
delay(10.seconds)
|
||||||
|
|
|
@ -4,15 +4,19 @@ import androidx.compose.runtime.*
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import im.molly.monero.MoneroNetwork
|
import im.molly.monero.MoneroNetwork
|
||||||
|
import im.molly.monero.RestorePoint
|
||||||
|
import im.molly.monero.SecretKey
|
||||||
import im.molly.monero.demo.AppModule
|
import im.molly.monero.demo.AppModule
|
||||||
import im.molly.monero.demo.data.RemoteNodeRepository
|
import im.molly.monero.demo.data.RemoteNodeRepository
|
||||||
import im.molly.monero.demo.data.WalletRepository
|
import im.molly.monero.demo.data.WalletRepository
|
||||||
import im.molly.monero.demo.data.model.DefaultMoneroNetwork
|
import im.molly.monero.demo.data.model.DefaultMoneroNetwork
|
||||||
import im.molly.monero.demo.data.model.RemoteNode
|
import im.molly.monero.demo.data.model.RemoteNode
|
||||||
|
import im.molly.monero.util.parseHex
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
|
import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class AddWalletViewModel(
|
class AddWalletViewModel(
|
||||||
|
@ -26,6 +30,15 @@ class AddWalletViewModel(
|
||||||
var walletName by mutableStateOf("")
|
var walletName by mutableStateOf("")
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
var secretSpendKeyHex by mutableStateOf("")
|
||||||
|
private set
|
||||||
|
|
||||||
|
var creationDate by mutableStateOf("")
|
||||||
|
private set
|
||||||
|
|
||||||
|
var restoreHeight by mutableStateOf("")
|
||||||
|
private set
|
||||||
|
|
||||||
val currentRemoteNodes: StateFlow<List<RemoteNode>> =
|
val currentRemoteNodes: StateFlow<List<RemoteNode>> =
|
||||||
snapshotFlow { network }
|
snapshotFlow { network }
|
||||||
.flatMapLatest {
|
.flatMapLatest {
|
||||||
|
@ -44,6 +57,18 @@ class AddWalletViewModel(
|
||||||
private fun getSelectedRemoteNodeIds() =
|
private fun getSelectedRemoteNodeIds() =
|
||||||
selectedRemoteNodes.filterValues { checked -> checked }.keys.filterNotNull()
|
selectedRemoteNodes.filterValues { checked -> checked }.keys.filterNotNull()
|
||||||
|
|
||||||
|
init {
|
||||||
|
val previousNodes = mutableSetOf<RemoteNode>()
|
||||||
|
|
||||||
|
currentRemoteNodes.onEach { remoteNodes ->
|
||||||
|
val unseenNodes = remoteNodes.filter { it !in previousNodes }
|
||||||
|
unseenNodes.forEach { node ->
|
||||||
|
selectedRemoteNodes[node.id] = true
|
||||||
|
previousNodes.add(node)
|
||||||
|
}
|
||||||
|
}.launchIn(viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
fun toggleSelectedNetwork(network: MoneroNetwork) {
|
fun toggleSelectedNetwork(network: MoneroNetwork) {
|
||||||
this.network = network
|
this.network = network
|
||||||
}
|
}
|
||||||
|
@ -52,7 +77,47 @@ class AddWalletViewModel(
|
||||||
this.walletName = name
|
this.walletName = name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateSecretSpendKeyHex(value: String) {
|
||||||
|
this.secretSpendKeyHex = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateCreationDate(value: String) {
|
||||||
|
this.creationDate = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateRestoreHeight(value: String) {
|
||||||
|
this.restoreHeight = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun validateSecretSpendKeyHex(): Boolean =
|
||||||
|
secretSpendKeyHex.length == 64 && runCatching { secretSpendKeyHex.parseHex() }.isSuccess
|
||||||
|
|
||||||
|
fun validateCreationDate(): Boolean =
|
||||||
|
creationDate.isEmpty() || runCatching { LocalDate.parse(creationDate) }.isSuccess
|
||||||
|
|
||||||
|
fun validateRestoreHeight(): Boolean =
|
||||||
|
restoreHeight.isEmpty() || runCatching { RestorePoint(restoreHeight.toLong()) }.isSuccess
|
||||||
|
|
||||||
fun createWallet() = viewModelScope.launch {
|
fun createWallet() = viewModelScope.launch {
|
||||||
walletRepository.addWallet(network, walletName, getSelectedRemoteNodeIds())
|
walletRepository.addWallet(network, walletName, getSelectedRemoteNodeIds())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun restoreWallet() = viewModelScope.launch {
|
||||||
|
val restorePoint = if (creationDate.isNotEmpty()) {
|
||||||
|
RestorePoint(creationDate = LocalDate.parse(creationDate))
|
||||||
|
} else if (restoreHeight.isNotEmpty()) {
|
||||||
|
RestorePoint(blockHeight = restoreHeight.toLong())
|
||||||
|
} else {
|
||||||
|
RestorePoint(blockHeight = 0)
|
||||||
|
}
|
||||||
|
SecretKey(secretSpendKeyHex.parseHex()).use { secretSpendKey ->
|
||||||
|
walletRepository.restoreWallet(
|
||||||
|
network,
|
||||||
|
walletName,
|
||||||
|
getSelectedRemoteNodeIds(),
|
||||||
|
secretSpendKey,
|
||||||
|
restorePoint
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,13 @@ package im.molly.monero.demo.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
@ -71,7 +72,7 @@ private fun FirstStepScreen(
|
||||||
Text("Create a new wallet")
|
Text("Create a new wallet")
|
||||||
}
|
}
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = {}, // TODO: onRestoreClick,
|
onClick = onRestoreClick,
|
||||||
modifier = Modifier.padding(top = 8.dp),
|
modifier = Modifier.padding(top = 8.dp),
|
||||||
) {
|
) {
|
||||||
Text("I already have a wallet")
|
Text("I already have a wallet")
|
||||||
|
@ -95,13 +96,26 @@ fun AddWalletSecondStepRoute(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onBackClick = onBackClick,
|
onBackClick = onBackClick,
|
||||||
onCreateClick = {
|
onCreateClick = {
|
||||||
viewModel.createWallet()
|
if (showRestoreOptions) {
|
||||||
|
viewModel.restoreWallet()
|
||||||
|
} else {
|
||||||
|
viewModel.createWallet()
|
||||||
|
}
|
||||||
onNavigateToHome()
|
onNavigateToHome()
|
||||||
},
|
},
|
||||||
walletName = viewModel.walletName,
|
walletName = viewModel.walletName,
|
||||||
network = viewModel.network,
|
network = viewModel.network,
|
||||||
|
secretSpendKeyHex = viewModel.secretSpendKeyHex,
|
||||||
|
secretSpendKeyHexError = !viewModel.validateSecretSpendKeyHex(),
|
||||||
|
creationDate = viewModel.creationDate,
|
||||||
|
creationDateError = !viewModel.validateCreationDate(),
|
||||||
|
restoreHeight = viewModel.restoreHeight,
|
||||||
|
restoreHeightError = !viewModel.validateRestoreHeight(),
|
||||||
onWalletNameChanged = { name -> viewModel.updateWalletName(name) },
|
onWalletNameChanged = { name -> viewModel.updateWalletName(name) },
|
||||||
onNetworkChanged = { network -> viewModel.toggleSelectedNetwork(network) },
|
onNetworkChanged = { network -> viewModel.toggleSelectedNetwork(network) },
|
||||||
|
onSecretSpendKeyHexChanged = { value -> viewModel.updateSecretSpendKeyHex(value) },
|
||||||
|
onCreationDateChanged = { value -> viewModel.updateCreationDate(value) },
|
||||||
|
onRestoreHeightChanged = { value -> viewModel.updateRestoreHeight(value) },
|
||||||
remoteNodes = remoteNodes,
|
remoteNodes = remoteNodes,
|
||||||
selectedRemoteNodeIds = viewModel.selectedRemoteNodes,
|
selectedRemoteNodeIds = viewModel.selectedRemoteNodes,
|
||||||
)
|
)
|
||||||
|
@ -112,12 +126,21 @@ fun AddWalletSecondStepRoute(
|
||||||
private fun SecondStepScreen(
|
private fun SecondStepScreen(
|
||||||
showRestoreOptions: Boolean,
|
showRestoreOptions: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit = {},
|
||||||
onCreateClick: () -> Unit,
|
onCreateClick: () -> Unit = {},
|
||||||
walletName: String,
|
walletName: String,
|
||||||
|
secretSpendKeyHex: String,
|
||||||
|
secretSpendKeyHexError: Boolean,
|
||||||
|
creationDate: String,
|
||||||
|
creationDateError: Boolean,
|
||||||
|
restoreHeight: String,
|
||||||
|
restoreHeightError: Boolean,
|
||||||
network: MoneroNetwork,
|
network: MoneroNetwork,
|
||||||
onWalletNameChanged: (String) -> Unit,
|
onWalletNameChanged: (String) -> Unit = {},
|
||||||
onNetworkChanged: (MoneroNetwork) -> Unit,
|
onNetworkChanged: (MoneroNetwork) -> Unit = {},
|
||||||
|
onSecretSpendKeyHexChanged: (String) -> Unit = {},
|
||||||
|
onCreationDateChanged: (String) -> Unit = {},
|
||||||
|
onRestoreHeightChanged: (String) -> Unit = {},
|
||||||
remoteNodes: List<RemoteNode>,
|
remoteNodes: List<RemoteNode>,
|
||||||
selectedRemoteNodeIds: MutableMap<Long?, Boolean> = mutableMapOf(),
|
selectedRemoteNodeIds: MutableMap<Long?, Boolean> = mutableMapOf(),
|
||||||
) {
|
) {
|
||||||
|
@ -139,6 +162,7 @@ private fun SecondStepScreen(
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
|
.imePadding()
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
|
@ -173,13 +197,63 @@ private fun SecondStepScreen(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 16.dp),
|
.padding(start = 16.dp),
|
||||||
)
|
)
|
||||||
|
if (showRestoreOptions) {
|
||||||
|
Text(
|
||||||
|
text = "Deterministic wallet recovery",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp),
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
value = secretSpendKeyHex,
|
||||||
|
label = { Text("Secret spend key") },
|
||||||
|
onValueChange = onSecretSpendKeyHexChanged,
|
||||||
|
singleLine = true,
|
||||||
|
isError = secretSpendKeyHexError,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 16.dp, end = 16.dp),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Synchronization",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp),
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
value = creationDate,
|
||||||
|
label = { Text("Wallet creation date") },
|
||||||
|
onValueChange = onCreationDateChanged,
|
||||||
|
singleLine = true,
|
||||||
|
isError = creationDateError,
|
||||||
|
enabled = restoreHeight.isEmpty(),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 16.dp, end = 16.dp),
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
value = restoreHeight,
|
||||||
|
label = { Text("Restore height") },
|
||||||
|
onValueChange = onRestoreHeightChanged,
|
||||||
|
singleLine = true,
|
||||||
|
isError = restoreHeightError,
|
||||||
|
enabled = creationDate.isEmpty(),
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 16.dp, end = 16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
|
val validInput = !showRestoreOptions || !(secretSpendKeyHexError || creationDateError || restoreHeightError)
|
||||||
Button(
|
Button(
|
||||||
onClick = onCreateClick,
|
onClick = onCreateClick,
|
||||||
|
enabled = validInput,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(16.dp),
|
.padding(16.dp),
|
||||||
) {
|
) {
|
||||||
|
@ -196,12 +270,14 @@ private fun CreateWalletScreenPreview() {
|
||||||
AppTheme {
|
AppTheme {
|
||||||
SecondStepScreen(
|
SecondStepScreen(
|
||||||
showRestoreOptions = false,
|
showRestoreOptions = false,
|
||||||
onBackClick = {},
|
|
||||||
onCreateClick = {},
|
|
||||||
walletName = "Personal",
|
walletName = "Personal",
|
||||||
network = DefaultMoneroNetwork,
|
network = DefaultMoneroNetwork,
|
||||||
onWalletNameChanged = {},
|
secretSpendKeyHex = "d2ca26e22489bd9871c910c58dee3ab08e66b9d566825a064c8c0af061cd8706",
|
||||||
onNetworkChanged = {},
|
secretSpendKeyHexError = false,
|
||||||
|
creationDate = "",
|
||||||
|
creationDateError = false,
|
||||||
|
restoreHeight = "",
|
||||||
|
restoreHeightError = false,
|
||||||
remoteNodes = listOf(RemoteNode.EMPTY),
|
remoteNodes = listOf(RemoteNode.EMPTY),
|
||||||
selectedRemoteNodeIds = mutableMapOf(),
|
selectedRemoteNodeIds = mutableMapOf(),
|
||||||
)
|
)
|
||||||
|
@ -214,12 +290,14 @@ private fun RestoreWalletScreenPreview() {
|
||||||
AppTheme {
|
AppTheme {
|
||||||
SecondStepScreen(
|
SecondStepScreen(
|
||||||
showRestoreOptions = true,
|
showRestoreOptions = true,
|
||||||
onBackClick = {},
|
|
||||||
onCreateClick = {},
|
|
||||||
walletName = "Personal",
|
walletName = "Personal",
|
||||||
network = DefaultMoneroNetwork,
|
network = DefaultMoneroNetwork,
|
||||||
onWalletNameChanged = {},
|
secretSpendKeyHex = "d2ca26e22489bd9871c910c58dee3ab08e66b9d566825a064c8c0af061cd8706",
|
||||||
onNetworkChanged = {},
|
secretSpendKeyHexError = false,
|
||||||
|
creationDate = "",
|
||||||
|
creationDateError = false,
|
||||||
|
restoreHeight = "",
|
||||||
|
restoreHeightError = false,
|
||||||
remoteNodes = listOf(RemoteNode.EMPTY),
|
remoteNodes = listOf(RemoteNode.EMPTY),
|
||||||
selectedRemoteNodeIds = mutableMapOf(),
|
selectedRemoteNodeIds = mutableMapOf(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,14 +18,21 @@ fun MultiSelectRemoteNodeList(
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
remoteNodes.forEach { remoteNode ->
|
if (remoteNodes.isNotEmpty()) {
|
||||||
RemoteNodeItem(
|
remoteNodes.forEach { remoteNode ->
|
||||||
remoteNode,
|
RemoteNodeItem(
|
||||||
checked = selectedIds[remoteNode.id] ?: false,
|
remoteNode,
|
||||||
showCheckbox = true,
|
checked = selectedIds[remoteNode.id] ?: false,
|
||||||
onCheckedChange = { checked ->
|
showCheckbox = true,
|
||||||
selectedIds[remoteNode.id] = checked
|
onCheckedChange = { checked ->
|
||||||
},
|
selectedIds[remoteNode.id] = checked
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = "No matching remote nodes",
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,3 +131,13 @@ private fun MultiSelectRemoteNodeListPreview() {
|
||||||
selectedIds = mutableMapOf(),
|
selectedIds = mutableMapOf(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun EmptyMultiSelectRemoteNodeListPreview() {
|
||||||
|
val aNode = RemoteNode.EMPTY.copy(uri = Uri.parse("http://node.monero"))
|
||||||
|
MultiSelectRemoteNodeList(
|
||||||
|
remoteNodes = listOf(),
|
||||||
|
selectedIds = mutableMapOf(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.withStyle
|
import androidx.compose.ui.text.withStyle
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import im.molly.monero.Balance
|
import im.molly.monero.Balance
|
||||||
|
@ -80,69 +81,67 @@ private fun WalletScreenPopulated(
|
||||||
) {
|
) {
|
||||||
var showRenameDialog by remember { mutableStateOf(false) }
|
var showRenameDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val amountValueString =
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
Scaffold(
|
Toolbar(
|
||||||
topBar = {
|
navigationIcon = {
|
||||||
Toolbar(
|
IconButton(onClick = onBackClick) {
|
||||||
navigationIcon = {
|
Icon(
|
||||||
IconButton(onClick = onBackClick) {
|
imageVector = AppIcons.ArrowBack,
|
||||||
Icon(
|
contentDescription = "Back",
|
||||||
imageVector = AppIcons.ArrowBack,
|
|
||||||
contentDescription = "Back",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
WalletKebabMenu(
|
|
||||||
onRenameClick = { showRenameDialog = true },
|
|
||||||
onDeleteClick = { },
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
},
|
||||||
}
|
actions = {
|
||||||
) { padding ->
|
WalletKebabMenu(
|
||||||
Column(
|
onRenameClick = { showRenameDialog = true },
|
||||||
modifier = modifier
|
onDeleteClick = { },
|
||||||
.fillMaxSize()
|
)
|
||||||
.padding(padding),
|
}
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
)
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
style = MaterialTheme.typography.headlineLarge,
|
|
||||||
text = buildAnnotatedString {
|
|
||||||
append(MoneroCurrency.symbol + " ")
|
|
||||||
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
|
||||||
append(MoneroCurrency.format(ledger.balance.totalAmount))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Text(text = walletConfig.name, style = MaterialTheme.typography.headlineSmall)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showRenameDialog) {
|
|
||||||
var name by remember { mutableStateOf(walletConfig.name) }
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = { showRenameDialog = false },
|
|
||||||
title = { Text("Enter wallet name") },
|
|
||||||
text = {
|
|
||||||
OutlinedTextField(
|
|
||||||
value = name,
|
|
||||||
onValueChange = { name = it },
|
|
||||||
singleLine = true,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = {
|
|
||||||
onWalletConfigChange(walletConfig.copy(name = name))
|
|
||||||
showRenameDialog = false
|
|
||||||
}) {
|
|
||||||
Text("Rename")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
) { padding ->
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(padding),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
style = MaterialTheme.typography.headlineLarge,
|
||||||
|
text = buildAnnotatedString {
|
||||||
|
append(MoneroCurrency.symbol + " ")
|
||||||
|
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||||
|
append(MoneroCurrency.format(ledger.balance.totalAmount))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Text(text = walletConfig.name, style = MaterialTheme.typography.headlineSmall)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showRenameDialog) {
|
||||||
|
var name by remember { mutableStateOf(walletConfig.name) }
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showRenameDialog = false },
|
||||||
|
title = { Text("Enter wallet name") },
|
||||||
|
text = {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = name,
|
||||||
|
onValueChange = { name = it },
|
||||||
|
singleLine = true,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
onWalletConfigChange(walletConfig.copy(name = name))
|
||||||
|
showRenameDialog = false
|
||||||
|
}) {
|
||||||
|
Text("Rename")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
@ -7,7 +7,7 @@ import im.molly.monero.WalletConfig;
|
||||||
|
|
||||||
interface IWalletService {
|
interface IWalletService {
|
||||||
oneway void createWallet(in WalletConfig config, in IWalletServiceCallbacks callback);
|
oneway void createWallet(in WalletConfig config, in IWalletServiceCallbacks callback);
|
||||||
oneway void restoreWallet(in WalletConfig config, in IWalletServiceCallbacks callback, in SecretKey spendSecretKey, long accountCreationTimestamp);
|
oneway void restoreWallet(in WalletConfig config, in IWalletServiceCallbacks callback, in SecretKey spendSecretKey, long restorePoint);
|
||||||
oneway void openWallet(in WalletConfig config, in IWalletServiceCallbacks callback);
|
oneway void openWallet(in WalletConfig config, in IWalletServiceCallbacks callback);
|
||||||
void setListener(in IWalletServiceListener listener);
|
void setListener(in IWalletServiceListener listener);
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,6 @@ set(WALLET_API_SOURCES
|
||||||
src/net/http.cpp
|
src/net/http.cpp
|
||||||
src/net/i2p_address.cpp
|
src/net/i2p_address.cpp
|
||||||
src/net/parse.cpp
|
src/net/parse.cpp
|
||||||
src/net/parse.cpp
|
|
||||||
src/net/socks.cpp
|
src/net/socks.cpp
|
||||||
src/net/socks_connect.cpp
|
src/net/socks_connect.cpp
|
||||||
src/net/tor_address.cpp
|
src/net/tor_address.cpp
|
||||||
|
|
|
@ -54,16 +54,20 @@ void generateAccountKeys(cryptonote::account_base& account,
|
||||||
LOG_FATAL_IF(gen != secret_key);
|
LOG_FATAL_IF(gen != secret_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Wallet::restoreAccount(const std::vector<char>& secret_scalar, uint64_t account_timestamp) {
|
void Wallet::restoreAccount(const std::vector<char>& secret_scalar, uint64_t restore_point) {
|
||||||
LOG_FATAL_IF(m_account_ready, "Account should not be reinitialized");
|
LOG_FATAL_IF(m_account_ready, "Account should not be reinitialized");
|
||||||
std::lock_guard<std::mutex> lock(m_wallet_mutex);
|
std::lock_guard<std::mutex> lock(m_wallet_mutex);
|
||||||
auto& account = m_wallet.get_account();
|
auto& account = m_wallet.get_account();
|
||||||
generateAccountKeys(account, secret_scalar);
|
generateAccountKeys(account, secret_scalar);
|
||||||
if (account_timestamp > account.get_createtime()) {
|
if (restore_point < CRYPTONOTE_MAX_BLOCK_NUMBER) {
|
||||||
account.set_createtime(account_timestamp);
|
m_restore_height = restore_point;
|
||||||
|
} else {
|
||||||
|
if (restore_point > account.get_createtime()) {
|
||||||
|
account.set_createtime(restore_point);
|
||||||
|
}
|
||||||
|
m_restore_height = estimateRestoreHeight(account.get_createtime());
|
||||||
}
|
}
|
||||||
m_wallet.rescan_blockchain(true, false, false);
|
m_wallet.rescan_blockchain(true, false, false);
|
||||||
m_restore_height = estimateRestoreHeight(account.get_createtime());
|
|
||||||
m_account_ready = true;
|
m_account_ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,12 +260,12 @@ Java_im_molly_monero_WalletNative_nativeRestoreAccount(
|
||||||
jobject thiz,
|
jobject thiz,
|
||||||
jlong handle,
|
jlong handle,
|
||||||
jbyteArray p_secret_scalar,
|
jbyteArray p_secret_scalar,
|
||||||
jlong account_timestamp) {
|
jlong restore_point) {
|
||||||
auto* wallet = reinterpret_cast<Wallet*>(handle);
|
auto* wallet = reinterpret_cast<Wallet*>(handle);
|
||||||
std::vector<char> secret_scalar = jvmToNativeByteArray(
|
std::vector<char> secret_scalar = jvmToNativeByteArray(
|
||||||
env, JvmParamRef<jbyteArray>(p_secret_scalar));
|
env, JvmParamRef<jbyteArray>(p_secret_scalar));
|
||||||
Eraser secret_eraser(secret_scalar);
|
Eraser secret_eraser(secret_scalar);
|
||||||
wallet->restoreAccount(secret_scalar, account_timestamp);
|
wallet->restoreAccount(secret_scalar, restore_point);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
|
|
|
@ -26,7 +26,7 @@ class Wallet : tools::i_wallet2_callback {
|
||||||
int network_id,
|
int network_id,
|
||||||
const JvmRef<jobject>& wallet_native);
|
const JvmRef<jobject>& wallet_native);
|
||||||
|
|
||||||
void restoreAccount(const std::vector<char>& secret_scalar, uint64_t account_timestamp);
|
void restoreAccount(const std::vector<char>& secret_scalar, uint64_t restore_point);
|
||||||
uint64_t estimateRestoreHeight(uint64_t timestamp);
|
uint64_t estimateRestoreHeight(uint64_t timestamp);
|
||||||
|
|
||||||
bool parseFrom(std::istream& input);
|
bool parseFrom(std::istream& input);
|
||||||
|
|
26
lib/android/src/main/kotlin/im/molly/monero/RestorePoint.kt
Normal file
26
lib/android/src/main/kotlin/im/molly/monero/RestorePoint.kt
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package im.molly.monero
|
||||||
|
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
class RestorePoint {
|
||||||
|
val heightOrTimestamp: Long
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
heightOrTimestamp = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(blockHeight: Long) {
|
||||||
|
require(blockHeight >= 0) { "Block height cannot be negative" }
|
||||||
|
require(blockHeight < 500_000_000) { "Block height too large" }
|
||||||
|
heightOrTimestamp = blockHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(creationDate: LocalDate) {
|
||||||
|
heightOrTimestamp = creationDate.toEpochDay().coerceAtLeast(500_000_000)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(creationDate: Instant) {
|
||||||
|
heightOrTimestamp = creationDate.epochSecond.coerceAtLeast(500_000_000)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,8 +2,6 @@ package im.molly.monero
|
||||||
|
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.parcelize.Parceler
|
|
||||||
import kotlinx.parcelize.Parcelize
|
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
|
@ -15,7 +13,6 @@ import javax.security.auth.Destroyable
|
||||||
* SecretKey wraps a secret scalar value, helping to prevent accidental exposure and securely
|
* SecretKey wraps a secret scalar value, helping to prevent accidental exposure and securely
|
||||||
* erasing the value from memory.
|
* erasing the value from memory.
|
||||||
*/
|
*/
|
||||||
@Parcelize
|
|
||||||
class SecretKey : Destroyable, Closeable, Parcelable {
|
class SecretKey : Destroyable, Closeable, Parcelable {
|
||||||
|
|
||||||
private val secret = ByteArray(32)
|
private val secret = ByteArray(32)
|
||||||
|
@ -33,14 +30,6 @@ class SecretKey : Destroyable, Closeable, Parcelable {
|
||||||
parcel.readByteArray(secret)
|
parcel.readByteArray(secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object : Parceler<SecretKey> {
|
|
||||||
override fun create(parcel: Parcel) = SecretKey(parcel)
|
|
||||||
|
|
||||||
override fun SecretKey.write(parcel: Parcel, flags: Int) {
|
|
||||||
parcel.writeByteArray(secret)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val bytes: ByteArray
|
val bytes: ByteArray
|
||||||
get() {
|
get() {
|
||||||
check(!destroyed) { "Secret key has been already destroyed" }
|
check(!destroyed) { "Secret key has been already destroyed" }
|
||||||
|
@ -61,6 +50,24 @@ class SecretKey : Destroyable, Closeable, Parcelable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeByteArray(secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeContents(): Int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object CREATOR : Parcelable.Creator<SecretKey> {
|
||||||
|
override fun createFromParcel(parcel: Parcel): SecretKey {
|
||||||
|
return SecretKey(parcel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<SecretKey?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other !is SecretKey) return false
|
if (other !is SecretKey) return false
|
||||||
|
|
|
@ -24,7 +24,7 @@ class WalletNative private constructor(
|
||||||
storageAdapter: IStorageAdapter? = null,
|
storageAdapter: IStorageAdapter? = null,
|
||||||
remoteNodeClient: IRemoteNodeClient? = null,
|
remoteNodeClient: IRemoteNodeClient? = null,
|
||||||
secretSpendKey: SecretKey? = null,
|
secretSpendKey: SecretKey? = null,
|
||||||
accountTimestamp: Long? = null,
|
restorePoint: Long? = null,
|
||||||
coroutineContext: CoroutineContext = Dispatchers.Default + SupervisorJob(),
|
coroutineContext: CoroutineContext = Dispatchers.Default + SupervisorJob(),
|
||||||
ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
|
ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||||
) = WalletNative(
|
) = WalletNative(
|
||||||
|
@ -36,13 +36,13 @@ class WalletNative private constructor(
|
||||||
).apply {
|
).apply {
|
||||||
when {
|
when {
|
||||||
secretSpendKey != null -> {
|
secretSpendKey != null -> {
|
||||||
require(accountTimestamp == null || accountTimestamp >= 0)
|
require(restorePoint == null || restorePoint >= 0)
|
||||||
val timestampOrNow = accountTimestamp ?: (System.currentTimeMillis() / 1000)
|
val restorePointOrNow = restorePoint ?: (System.currentTimeMillis() / 1000)
|
||||||
nativeRestoreAccount(handle, secretSpendKey.bytes, timestampOrNow)
|
nativeRestoreAccount(handle, secretSpendKey.bytes, restorePointOrNow)
|
||||||
tryWriteState()
|
tryWriteState()
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
require(accountTimestamp == null)
|
require(restorePoint == null)
|
||||||
readState()
|
readState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -251,7 +251,7 @@ class WalletNative private constructor(
|
||||||
private external fun nativeLoad(handle: Long, fd: Int): Boolean
|
private external fun nativeLoad(handle: Long, fd: Int): Boolean
|
||||||
private external fun nativeNonReentrantRefresh(handle: Long, skipCoinbase: Boolean): Int
|
private external fun nativeNonReentrantRefresh(handle: Long, skipCoinbase: Boolean): Int
|
||||||
private external fun nativeRestoreAccount(
|
private external fun nativeRestoreAccount(
|
||||||
handle: Long, secretScalar: ByteArray, accountTimestamp: Long
|
handle: Long, secretScalar: ByteArray, restorePoint: Long
|
||||||
)
|
)
|
||||||
|
|
||||||
private external fun nativeSave(handle: Long, fd: Int): Boolean
|
private external fun nativeSave(handle: Long, fd: Int): Boolean
|
||||||
|
|
|
@ -69,7 +69,7 @@ class WalletProvider private constructor(
|
||||||
dataStore: WalletDataStore? = null,
|
dataStore: WalletDataStore? = null,
|
||||||
client: RemoteNodeClient? = null,
|
client: RemoteNodeClient? = null,
|
||||||
secretSpendKey: SecretKey,
|
secretSpendKey: SecretKey,
|
||||||
accountCreationTime: Instant,
|
restorePoint: RestorePoint,
|
||||||
): MoneroWallet {
|
): MoneroWallet {
|
||||||
val storageAdapter = StorageAdapter(dataStore)
|
val storageAdapter = StorageAdapter(dataStore)
|
||||||
val wallet = suspendCancellableCoroutine { continuation ->
|
val wallet = suspendCancellableCoroutine { continuation ->
|
||||||
|
@ -77,7 +77,7 @@ class WalletProvider private constructor(
|
||||||
buildConfig(network, StorageAdapter(dataStore), client),
|
buildConfig(network, StorageAdapter(dataStore), client),
|
||||||
WalletResultCallback(continuation),
|
WalletResultCallback(continuation),
|
||||||
secretSpendKey,
|
secretSpendKey,
|
||||||
accountCreationTime.epochSecond,
|
restorePoint.heightOrTimestamp,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return MoneroWallet(wallet, storageAdapter, client)
|
return MoneroWallet(wallet, storageAdapter, client)
|
||||||
|
@ -122,6 +122,7 @@ class WalletProvider private constructor(
|
||||||
wallet.close()
|
wallet.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> TODO()
|
else -> TODO()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,11 +57,11 @@ internal class WalletServiceImpl(
|
||||||
config: WalletConfig?,
|
config: WalletConfig?,
|
||||||
callback: IWalletServiceCallbacks?,
|
callback: IWalletServiceCallbacks?,
|
||||||
secretSpendKey: SecretKey?,
|
secretSpendKey: SecretKey?,
|
||||||
accountCreationTimestamp: Long,
|
restorePoint: Long,
|
||||||
) {
|
) {
|
||||||
serviceScope.launch {
|
serviceScope.launch {
|
||||||
val wallet = secretSpendKey.use { secret ->
|
val wallet = secretSpendKey.use { secret ->
|
||||||
createOrRestoreWallet(config, secret, accountCreationTimestamp)
|
createOrRestoreWallet(config, secret, restorePoint)
|
||||||
}
|
}
|
||||||
callback?.onWalletResult(wallet)
|
callback?.onWalletResult(wallet)
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ internal class WalletServiceImpl(
|
||||||
private fun createOrRestoreWallet(
|
private fun createOrRestoreWallet(
|
||||||
config: WalletConfig?,
|
config: WalletConfig?,
|
||||||
secretSpendKey: SecretKey?,
|
secretSpendKey: SecretKey?,
|
||||||
accountCreationTimestamp: Long? = null,
|
restorePoint: Long? = null,
|
||||||
): IWallet {
|
): IWallet {
|
||||||
requireNotNull(config)
|
requireNotNull(config)
|
||||||
requireNotNull(secretSpendKey)
|
requireNotNull(secretSpendKey)
|
||||||
|
@ -95,7 +95,7 @@ internal class WalletServiceImpl(
|
||||||
storageAdapter = config.storageAdapter,
|
storageAdapter = config.storageAdapter,
|
||||||
remoteNodeClient = config.remoteNodeClient,
|
remoteNodeClient = config.remoteNodeClient,
|
||||||
secretSpendKey = secretSpendKey,
|
secretSpendKey = secretSpendKey,
|
||||||
accountTimestamp = accountCreationTimestamp,
|
restorePoint = restorePoint,
|
||||||
coroutineContext = serviceScope.coroutineContext,
|
coroutineContext = serviceScope.coroutineContext,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue