Incorporate new SDK initializer improvements.
The first step in working with viewingKeys directly.
This commit is contained in:
parent
808c124d3b
commit
d38626c205
|
@ -1,7 +1,5 @@
|
|||
package cash.z.ecc.android.di.component
|
||||
|
||||
import cash.z.ecc.android.ZcashWalletApp
|
||||
import cash.z.ecc.android.di.annotation.ActivityScope
|
||||
import cash.z.ecc.android.di.annotation.SynchronizerScope
|
||||
import cash.z.ecc.android.di.module.InitializerModule
|
||||
import cash.z.ecc.android.sdk.Initializer
|
||||
|
@ -13,10 +11,10 @@ import dagger.Subcomponent
|
|||
interface InitializerSubcomponent {
|
||||
|
||||
fun initializer(): Initializer
|
||||
fun birthdayStore(): Initializer.WalletBirthdayStore
|
||||
fun config(): Initializer.Builder
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
fun create(@BindsInstance birthdayStore: Initializer.WalletBirthdayStore = Initializer.DefaultBirthdayStore(ZcashWalletApp.instance)): InitializerSubcomponent
|
||||
fun create(@BindsInstance config: Initializer.Builder): InitializerSubcomponent
|
||||
}
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
package cash.z.ecc.android.di.module
|
||||
|
||||
import android.content.Context
|
||||
import cash.z.ecc.android.ext.Const
|
||||
import cash.z.ecc.android.lockbox.LockBox
|
||||
import cash.z.ecc.android.sdk.Initializer
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
@ -11,16 +9,7 @@ import dagger.Reusable
|
|||
@Module
|
||||
class InitializerModule {
|
||||
|
||||
companion object {
|
||||
const val defaultHost = "lightwalletd.electriccoin.co"
|
||||
const val defaultPort = 9067
|
||||
}
|
||||
|
||||
// TODO: Read SecurePrefs
|
||||
// private val host: String = sharedPreferences.getString(Const.Pref.SERVER_NAME, defaultHost) ?: defaultHost
|
||||
// private val port: Int = sharedPreferences.getInt(Const.Pref.SERVER_PORT, defaultPort)
|
||||
|
||||
@Provides
|
||||
@Reusable
|
||||
fun provideInitializer(appContext: Context) = Initializer(appContext, defaultHost, defaultPort)
|
||||
fun provideInitializer(appContext: Context, config: Initializer.Builder) = Initializer(appContext, config)
|
||||
}
|
||||
|
|
|
@ -13,19 +13,19 @@ import cash.z.ecc.android.di.viewmodel.viewModel
|
|||
import cash.z.ecc.android.ext.*
|
||||
import cash.z.ecc.android.feedback.Report
|
||||
import cash.z.ecc.android.feedback.Report.Tap.*
|
||||
import cash.z.ecc.android.sdk.Synchronizer.Status.*
|
||||
import cash.z.ecc.android.sdk.ext.*
|
||||
import cash.z.ecc.android.ui.base.BaseFragment
|
||||
import cash.z.ecc.android.ui.home.HomeFragment.BannerAction.*
|
||||
import cash.z.ecc.android.ui.send.SendViewModel
|
||||
import cash.z.ecc.android.ui.setup.WalletSetupViewModel
|
||||
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.NO_SEED
|
||||
import cash.z.ecc.android.sdk.Synchronizer.Status.*
|
||||
import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
|
||||
import cash.z.ecc.android.sdk.ext.convertZecToZatoshi
|
||||
import cash.z.ecc.android.sdk.ext.safelyConvertToBigDecimal
|
||||
import cash.z.ecc.android.sdk.ext.twig
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.scanReduce
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
@ -57,19 +57,17 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||
twig("ZZZ ===================== HOME FRAGMENT CREATED ==================================")
|
||||
super.onAttach(context)
|
||||
|
||||
// this will call startSync either now or later (after initializing with newly created seed)
|
||||
walletSetup.checkSeed().onEach {
|
||||
twig("Checking seed")
|
||||
walletSetup.checkSeed().onFirstWith(lifecycleScope) {
|
||||
if (it == NO_SEED) {
|
||||
// interact with user to create, backup and verify seed
|
||||
// leads to a call to startSync(), later (after accounts are created from seed)
|
||||
twig("Seed not found, therefore, launching seed creation flow")
|
||||
twig("Previous wallet not found, therefore, launching seed creation flow")
|
||||
mainActivity?.safeNavigate(R.id.action_nav_home_to_create_wallet)
|
||||
} else {
|
||||
twig("Found seed. Re-opening existing wallet")
|
||||
mainActivity?.startSync(walletSetup.openWallet())
|
||||
twig("Previous wallet found. Re-opening it.")
|
||||
mainActivity?.startSync(walletSetup.buildInitializer())
|
||||
}
|
||||
}
|
||||
}.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
|
|
@ -12,18 +12,17 @@ import cash.z.ecc.android.feedback.Report.Issue
|
|||
import cash.z.ecc.android.feedback.Report.MetricType
|
||||
import cash.z.ecc.android.feedback.Report.MetricType.*
|
||||
import cash.z.ecc.android.lockbox.LockBox
|
||||
import cash.z.ecc.android.ui.setup.WalletSetupViewModel
|
||||
import cash.z.ecc.android.ui.util.INCLUDE_MEMO_PREFIX_STANDARD
|
||||
import cash.z.ecc.android.sdk.Initializer
|
||||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
import cash.z.ecc.android.sdk.db.entity.*
|
||||
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
||||
import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
|
||||
import cash.z.ecc.android.sdk.ext.twig
|
||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||
import cash.z.ecc.android.sdk.validate.AddressType
|
||||
import cash.z.ecc.android.ui.setup.WalletSetupViewModel
|
||||
import cash.z.ecc.android.ui.util.INCLUDE_MEMO_PREFIX_STANDARD
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -40,9 +39,6 @@ class SendViewModel @Inject constructor() : ViewModel() {
|
|||
@Inject
|
||||
lateinit var synchronizer: Synchronizer
|
||||
|
||||
@Inject
|
||||
lateinit var initializer: Initializer
|
||||
|
||||
@Inject
|
||||
lateinit var feedback: Feedback
|
||||
|
||||
|
@ -63,7 +59,7 @@ class SendViewModel @Inject constructor() : ViewModel() {
|
|||
fun send(): Flow<PendingTransaction> {
|
||||
funnel(SendSelected)
|
||||
val memoToSend = createMemoToSend()
|
||||
val keys = initializer.deriveSpendingKeys(
|
||||
val keys = DerivationTool.deriveSpendingKeys(
|
||||
lockBox.getBytes(WalletSetupViewModel.LockBoxKey.SEED)!!
|
||||
)
|
||||
funnel(SpendingKeyFound)
|
||||
|
|
|
@ -1,23 +1,27 @@
|
|||
package cash.z.ecc.android.ui.setup
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import cash.z.ecc.android.ZcashWalletApp
|
||||
import cash.z.ecc.android.ext.Const
|
||||
import cash.z.ecc.android.feedback.Feedback
|
||||
import cash.z.ecc.android.feedback.Report.MetricType.*
|
||||
import cash.z.ecc.android.feedback.measure
|
||||
import cash.z.ecc.android.lockbox.LockBox
|
||||
import cash.z.ecc.android.sdk.Initializer
|
||||
import cash.z.ecc.android.sdk.exception.InitializerException
|
||||
import cash.z.ecc.android.sdk.ext.toHex
|
||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||
import cash.z.ecc.android.sdk.tool.WalletBirthdayTool
|
||||
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.*
|
||||
import cash.z.ecc.kotlin.mnemonic.Mnemonics
|
||||
import cash.z.ecc.android.sdk.Initializer
|
||||
import cash.z.ecc.android.sdk.Initializer.DefaultBirthdayStore
|
||||
import cash.z.ecc.android.sdk.Initializer.DefaultBirthdayStore.Companion.ImportedWalletBirthdayStore
|
||||
import cash.z.ecc.android.sdk.Initializer.DefaultBirthdayStore.Companion.NewWalletBirthdayStore
|
||||
import cash.z.ecc.android.sdk.ext.twig
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.single
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
class WalletSetupViewModel @Inject constructor() : ViewModel() {
|
||||
|
||||
|
@ -27,6 +31,10 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
|
|||
@Inject
|
||||
lateinit var lockBox: LockBox
|
||||
|
||||
@Inject
|
||||
@Named(Const.Name.APP_PREFS)
|
||||
lateinit var prefs: LockBox
|
||||
|
||||
@Inject
|
||||
lateinit var feedback: Feedback
|
||||
|
||||
|
@ -42,37 +50,61 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-open an existing wallet. This is the most common use case, where a user has previously
|
||||
* created or imported their seed and is returning to the wallet. In other words, this is the
|
||||
* non-FTUE case.
|
||||
*/
|
||||
fun openWallet(): Initializer {
|
||||
twig("Opening existing wallet")
|
||||
return ZcashWalletApp.component.initializerSubcomponent()
|
||||
.create(DefaultBirthdayStore(ZcashWalletApp.instance)).run {
|
||||
initializer().open(birthdayStore().getBirthday())
|
||||
suspend fun buildInitializer() = ZcashWalletApp.component.initializerSubcomponent().create(loadConfig().single()).initializer()
|
||||
|
||||
private fun loadConfig() = flow<Initializer.Builder> {
|
||||
emit(
|
||||
Initializer.Builder { builder ->
|
||||
val vk = lockBox.getCharsUtf8(LockBoxKey.VIEWING_KEY)?.let { String(it) }
|
||||
?: throw InitializerException.MissingViewingKeyException
|
||||
val birthdayHeight = loadBirthdayHeight()
|
||||
?: throw InitializerException.MissingBirthdayException
|
||||
val host = prefs[Const.Pref.SERVER_HOST] ?: Const.Default.Server.HOST
|
||||
val port = prefs[Const.Pref.SERVER_PORT] ?: Const.Default.Server.PORT
|
||||
|
||||
builder.import(vk, birthdayHeight, host, port)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun loadBirthdayHeight(): Int? {
|
||||
val h: Int? = lockBox[LockBoxKey.BIRTHDAY_HEIGHT]
|
||||
twigFix("Loaded birthday with key ${LockBoxKey.BIRTHDAY_HEIGHT} and found $h")
|
||||
return h
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Re-open an existing wallet. This is the most common use case, where a user has previously
|
||||
// * created or imported their seed and is returning to the wallet. In other words, this is the
|
||||
// * non-FTUE case.
|
||||
// */
|
||||
// fun openWallet(): Initializer {
|
||||
// twigFix("Opening existing wallet")
|
||||
// return ZcashWalletApp.component.initializerSubcomponent()
|
||||
// .create(DefaultBirthdayStore(ZcashWalletApp.instance)).run {
|
||||
// initializer().open(birthdayStore().getBirthday())
|
||||
// }
|
||||
// }
|
||||
|
||||
suspend fun newWallet(): Initializer {
|
||||
twig("Initializing new wallet")
|
||||
return ZcashWalletApp.component.initializerSubcomponent()
|
||||
.create(NewWalletBirthdayStore(ZcashWalletApp.instance)).run {
|
||||
initializer().apply {
|
||||
new(createWallet(), birthdayStore().getBirthday())
|
||||
}
|
||||
}
|
||||
twigFix("Initializing new wallet")
|
||||
createWallet()
|
||||
return buildInitializer()
|
||||
}
|
||||
|
||||
suspend fun importWallet(seedPhrase: String, birthdayHeight: Int): Initializer {
|
||||
twig("Importing wallet. Requested birthday: $birthdayHeight")
|
||||
return ZcashWalletApp.component.initializerSubcomponent()
|
||||
.create(ImportedWalletBirthdayStore(ZcashWalletApp.instance, birthdayHeight)).run {
|
||||
initializer().apply {
|
||||
import(importWallet(seedPhrase.toCharArray()), birthdayStore().getBirthday())
|
||||
twigFix("Importing wallet. Requested birthday: $birthdayHeight")
|
||||
val seedPhraseChars = seedPhrase.toCharArray()
|
||||
storeSeedPhrase(seedPhraseChars)
|
||||
storeSeed(mnemonics.toSeed(seedPhraseChars))
|
||||
WalletBirthdayTool.loadNearest(ZcashWalletApp.instance, birthdayHeight).let { birthday ->
|
||||
storeBirthday(birthday)
|
||||
}
|
||||
return buildInitializer()
|
||||
}
|
||||
|
||||
private fun twigFix(s: String) {
|
||||
Log.e("@TWIG", s)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,13 +124,12 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
|
|||
feedback.measure(SEED_PHRASE_CREATED) { nextMnemonic(entropy) }
|
||||
.let { seedPhrase ->
|
||||
feedback.measure(SEED_CREATED) { toSeed(seedPhrase) }.let { bip39Seed ->
|
||||
storeSeedPhrase(seedPhrase)
|
||||
storeSeed(bip39Seed)
|
||||
|
||||
lockBox.setCharsUtf8(LockBoxKey.SEED_PHRASE, seedPhrase)
|
||||
lockBox.setBoolean(LockBoxKey.HAS_SEED_PHRASE, true)
|
||||
|
||||
lockBox.setBytes(LockBoxKey.SEED, bip39Seed)
|
||||
lockBox.setBoolean(LockBoxKey.HAS_SEED, true)
|
||||
|
||||
WalletBirthdayTool.loadNearest(ZcashWalletApp.instance).let { birthday ->
|
||||
storeBirthday(birthday)
|
||||
}
|
||||
bip39Seed
|
||||
}
|
||||
}
|
||||
|
@ -107,11 +138,23 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun loadBirthdayHeight(): Int = withContext(Dispatchers.IO) {
|
||||
DefaultBirthdayStore(ZcashWalletApp.instance).getBirthday().height
|
||||
private fun storeBirthday(birthday: WalletBirthdayTool.WalletBirthday) {
|
||||
twigFix("Storing birthday ${birthday.height} with and key ${LockBoxKey.BIRTHDAY_HEIGHT}")
|
||||
lockBox[LockBoxKey.BIRTHDAY_HEIGHT] = birthday.height
|
||||
}
|
||||
|
||||
private fun storeSeed(bip39Seed: ByteArray) {
|
||||
twigFix("Storing seed: ${bip39Seed.toHex().length}")
|
||||
lockBox.setBytes(LockBoxKey.SEED, bip39Seed)
|
||||
lockBox[LockBoxKey.VIEWING_KEY] = DerivationTool.deriveViewingKeys(bip39Seed)[0]
|
||||
lockBox[LockBoxKey.HAS_SEED] = true
|
||||
}
|
||||
|
||||
private fun storeSeedPhrase(seedPhrase: CharArray) {
|
||||
twigFix("Storing seedphrase: ${seedPhrase.size}")
|
||||
lockBox[LockBoxKey.SEED_PHRASE] = seedPhrase
|
||||
lockBox[LockBoxKey.HAS_SEED_PHRASE] = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Take all the steps necessary to import a wallet and measure how long it takes.
|
||||
|
@ -130,11 +173,10 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
|
|||
mnemonics.run {
|
||||
feedback.measure(SEED_IMPORTED) { toSeed(seedPhrase) }.let { bip39Seed ->
|
||||
|
||||
lockBox.setCharsUtf8(LockBoxKey.SEED_PHRASE, seedPhrase)
|
||||
lockBox.setBoolean(LockBoxKey.HAS_SEED_PHRASE, true)
|
||||
storeSeedPhrase(seedPhrase)
|
||||
|
||||
lockBox.setBytes(LockBoxKey.SEED, bip39Seed)
|
||||
lockBox.setBoolean(LockBoxKey.HAS_SEED, true)
|
||||
lockBox[LockBoxKey.HAS_SEED] = true
|
||||
|
||||
bip39Seed
|
||||
}
|
||||
|
@ -142,6 +184,7 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Throw an exception if the seed phrase is bad.
|
||||
*/
|
||||
|
@ -155,5 +198,9 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
|
|||
const val HAS_SEED = "cash.z.ecc.android.HAS_SEED"
|
||||
const val HAS_SEED_PHRASE = "cash.z.ecc.android.HAS_SEED_PHRASE"
|
||||
const val HAS_BACKUP = "cash.z.ecc.android.HAS_BACKUP"
|
||||
|
||||
// Config
|
||||
const val VIEWING_KEY = "cash.z.ecc.android.VIEWING_KEY"
|
||||
const val BIRTHDAY_HEIGHT = "cash.z.ecc.android.BIRTHDAY_HEIGHT"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue