[#642] Adopt SDK 1.10.0 API changes
* Adopt SDK 1.10.0 API changes * Rename ShieldedSapling -> LegacySapling * Viewing key re-derived for fix fixture value * Changes used API for retrieving Transparent address - Switched API DerivationTool.deriveTransparentAddress() -> Synchronizer.getLegacyTransparentAddress() - Renamed Transparent -> LegacyTransparent * Adds new common transaction model class - Synchronizer.toTransactions() now works only with clearedTransactions and pendingTransactions -Added CommonTransaction wrapper for all attributes of PendingTransaction and TransactionOverview to cover as much as possible. It's prepared for discussion about which attributes we really need and which we can omit. * Regeneration of addresses in fixture - Applies for both Transparent and Sapling addresses - Fixed related test - Added a new test for checking Transparent address abbreviation, as it also serves as a little check of the fixture address and can prevent an incorrect address change in the future * Remove Viewing key usage from entire app - As we agreed that the app should not access to it now - Removed from all related places * Change CommonTransaction to sealed class * Comments update * Switch SDK to 1.10.0-beta01-SNAPSHOT version * Revert back legacy naming on Transparent and Sapling addresses * Increased timeout limit to satisfy SDK initialization * Remove unused import
This commit is contained in:
parent
381af575ef
commit
e71c7854a9
|
@ -145,7 +145,7 @@ ZCASH_ANDROID_WALLET_PLUGINS_VERSION=1.0.0
|
|||
ZCASH_BIP39_VERSION=1.0.4
|
||||
# TODO [#279]: Revert to stable SDK before app release
|
||||
# TODO [#279]: https://github.com/zcash/secant-android-wallet/issues/279
|
||||
ZCASH_SDK_VERSION=1.9.0-beta02
|
||||
ZCASH_SDK_VERSION=1.10.0-beta01-SNAPSHOT
|
||||
ZXING_VERSION=3.5.0
|
||||
|
||||
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
package cash.z.ecc.sdk.model
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import cash.z.ecc.sdk.fixture.PersistableWalletFixture
|
||||
import cash.z.ecc.sdk.fixture.WalletAddressesFixture
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Test
|
||||
|
||||
|
@ -16,17 +14,8 @@ class WalletAddressesTest {
|
|||
fun security() = runTest {
|
||||
val walletAddresses = WalletAddressesFixture.new()
|
||||
val actual = WalletAddressesFixture.new().toString()
|
||||
assertFalse(actual.contains(walletAddresses.shieldedSapling.address))
|
||||
assertFalse(actual.contains(walletAddresses.sapling.address))
|
||||
assertFalse(actual.contains(walletAddresses.transparent.address))
|
||||
assertFalse(actual.contains(walletAddresses.unified.address))
|
||||
assertFalse(actual.contains(walletAddresses.viewingKey))
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
fun new() = runTest {
|
||||
val expected = WalletAddressesFixture.new()
|
||||
val actual = WalletAddresses.new(PersistableWalletFixture.new())
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
package cash.z.ecc.sdk
|
||||
|
||||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
||||
import cash.z.ecc.sdk.model.ZecSend
|
||||
|
||||
fun Synchronizer.send(spendingKey: String, send: ZecSend) = sendToAddress(
|
||||
suspend fun Synchronizer.send(spendingKey: UnifiedSpendingKey, send: ZecSend) = sendToAddress(
|
||||
spendingKey,
|
||||
send.amount,
|
||||
send.destination.address,
|
||||
|
|
|
@ -6,13 +6,14 @@ object WalletAddressFixture {
|
|||
// These fixture values are derived from the secret defined in PersistableWalletFixture
|
||||
|
||||
// TODO [#161]: Pending SDK support
|
||||
// TODO [#161]: https://github.com/zcash/secant-android-wallet/issues/161
|
||||
const val UNIFIED_ADDRESS_STRING = "Unified GitHub Issue #161"
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
const val SHIELDED_SAPLING_ADDRESS_STRING = "ztestsapling1475xtm56czrzmleqzzlu4cxvjjfsy2p6rv78q07232cpsx5ee52k0mn5jyndq09mampkgvrxnwg"
|
||||
const val TRANSPARENT_ADDRESS_STRING = "tmXuTnE11JojToagTqxXUn6KvdxDE3iLKbp"
|
||||
const val SAPLING_ADDRESS_STRING = "zs1hf72k87gev2qnvg9228vn2xt97adfelju2hm2ap4xwrxkau5dz56mvkeseer3u8283wmy7skt4u"
|
||||
const val TRANSPARENT_ADDRESS_STRING = "t1QZMTZaU1EwXppCLL5dR6U9y2M4ph3CSPK"
|
||||
|
||||
suspend fun unified() = WalletAddress.Unified.new(UNIFIED_ADDRESS_STRING)
|
||||
suspend fun shieldedSapling() = WalletAddress.ShieldedSapling.new(SHIELDED_SAPLING_ADDRESS_STRING)
|
||||
suspend fun sapling() = WalletAddress.Sapling.new(SAPLING_ADDRESS_STRING)
|
||||
suspend fun transparent() = WalletAddress.Transparent.new(TRANSPARENT_ADDRESS_STRING)
|
||||
}
|
||||
|
|
|
@ -4,18 +4,14 @@ import cash.z.ecc.sdk.model.WalletAddress
|
|||
import cash.z.ecc.sdk.model.WalletAddresses
|
||||
|
||||
object WalletAddressesFixture {
|
||||
// These fixture values are derived from the secret defined in PersistableWalletFixture
|
||||
const val VIEWING_KEY = "03feaa290589a20f795f302ba03847b0a6c9c2b571d75d80bc4ebb02382d0549da"
|
||||
|
||||
suspend fun new(
|
||||
unified: String = WalletAddressFixture.UNIFIED_ADDRESS_STRING,
|
||||
shieldedSapling: String = WalletAddressFixture.SHIELDED_SAPLING_ADDRESS_STRING,
|
||||
transparent: String = WalletAddressFixture.TRANSPARENT_ADDRESS_STRING,
|
||||
viewingKey: String = VIEWING_KEY
|
||||
sapling: String = WalletAddressFixture.SAPLING_ADDRESS_STRING,
|
||||
transparent: String = WalletAddressFixture.TRANSPARENT_ADDRESS_STRING
|
||||
) = WalletAddresses(
|
||||
WalletAddress.Unified.new(unified),
|
||||
WalletAddress.ShieldedSapling.new(shieldedSapling),
|
||||
WalletAddress.Transparent.new(transparent),
|
||||
viewingKey
|
||||
WalletAddress.Sapling.new(sapling),
|
||||
WalletAddress.Transparent.new(transparent)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ data class SeedPhrase(val split: List<String>) {
|
|||
|
||||
fun joinToString() = split.joinToString(DEFAULT_DELIMITER)
|
||||
|
||||
fun toByteArray() = joinToString().encodeToByteArray()
|
||||
|
||||
companion object {
|
||||
const val SEED_PHRASE_SIZE = 24
|
||||
|
||||
|
|
|
@ -11,22 +11,22 @@ sealed class WalletAddress(val address: String) {
|
|||
}
|
||||
}
|
||||
|
||||
class ShieldedSapling private constructor(address: String) : WalletAddress(address) {
|
||||
class Sapling private constructor(address: String) : WalletAddress(address) {
|
||||
companion object {
|
||||
suspend fun new(address: String): WalletAddress.ShieldedSapling {
|
||||
// https://github.com/zcash/zcash-android-wallet-sdk/issues/342
|
||||
suspend fun new(address: String): Sapling {
|
||||
// TODO [#342]: https://github.com/zcash/zcash-android-wallet-sdk/issues/342
|
||||
// TODO [#342]: refactor SDK to enable direct calls for address verification
|
||||
return WalletAddress.ShieldedSapling(address)
|
||||
return Sapling(address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Transparent private constructor(address: String) : WalletAddress(address) {
|
||||
companion object {
|
||||
suspend fun new(address: String): WalletAddress.Transparent {
|
||||
// https://github.com/zcash/zcash-android-wallet-sdk/issues/342
|
||||
suspend fun new(address: String): Transparent {
|
||||
// TODO [#342]: https://github.com/zcash/zcash-android-wallet-sdk/issues/342
|
||||
// TODO [#342]: refactor SDK to enable direct calls for address verification
|
||||
return WalletAddress.Transparent(address)
|
||||
return Transparent(address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,50 +1,32 @@
|
|||
package cash.z.ecc.sdk.model
|
||||
|
||||
import cash.z.ecc.android.bip39.Mnemonics
|
||||
import cash.z.ecc.android.bip39.toSeed
|
||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
import cash.z.ecc.android.sdk.model.Account
|
||||
|
||||
data class WalletAddresses(
|
||||
val unified: WalletAddress.Unified,
|
||||
val shieldedSapling: WalletAddress.ShieldedSapling,
|
||||
val transparent: WalletAddress.Transparent,
|
||||
val viewingKey: String
|
||||
val sapling: WalletAddress.Sapling,
|
||||
val transparent: WalletAddress.Transparent
|
||||
) {
|
||||
// Override to prevent leaking details in logs
|
||||
override fun toString() = "WalletAddresses"
|
||||
|
||||
companion object {
|
||||
suspend fun new(persistableWallet: PersistableWallet): WalletAddresses {
|
||||
// Dispatcher needed because SecureRandom is loaded, which is slow and performs IO
|
||||
// https://github.com/zcash/kotlin-bip39/issues/13
|
||||
val bip39Seed = withContext(Dispatchers.IO) {
|
||||
Mnemonics.MnemonicCode(persistableWallet.seedPhrase.joinToString()).toSeed()
|
||||
}
|
||||
suspend fun new(synchronizer: Synchronizer): WalletAddresses {
|
||||
val saplingAddress = WalletAddress.Sapling.new(
|
||||
synchronizer.getSaplingAddress(Account.DEFAULT)
|
||||
)
|
||||
|
||||
val viewingKey = DerivationTool.deriveUnifiedViewingKeys(bip39Seed, persistableWallet.network)[0].extpub
|
||||
|
||||
val shieldedSaplingAddress = DerivationTool.deriveShieldedAddress(
|
||||
bip39Seed,
|
||||
persistableWallet.network
|
||||
).let {
|
||||
WalletAddress.ShieldedSapling.new(it)
|
||||
}
|
||||
|
||||
val transparentAddress = DerivationTool.deriveTransparentAddress(
|
||||
bip39Seed,
|
||||
persistableWallet.network
|
||||
).let {
|
||||
WalletAddress.Transparent.new(it)
|
||||
}
|
||||
val transparentAddress = WalletAddress.Transparent.new(
|
||||
synchronizer.getTransparentAddress(Account.DEFAULT)
|
||||
)
|
||||
|
||||
// TODO [#161]: Pending SDK support, fix providing correct values for the unified
|
||||
// TODO [#161]: https://github.com/zcash/secant-android-wallet/issues/161
|
||||
return WalletAddresses(
|
||||
unified = WalletAddress.Unified.new("Unified GitHub Issue #161"),
|
||||
shieldedSapling = shieldedSaplingAddress,
|
||||
transparent = transparentAddress,
|
||||
viewingKey = viewingKey
|
||||
sapling = saplingAddress,
|
||||
transparent = transparentAddress
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ data class ExtendedColors(
|
|||
val addressHighlightUnified: Color,
|
||||
val addressHighlightSapling: Color,
|
||||
val addressHighlightTransparent: Color,
|
||||
val addressHighlightViewing: Color,
|
||||
val dangerous: Color,
|
||||
val onDangerous: Color,
|
||||
val reference: Color
|
||||
|
|
|
@ -50,7 +50,6 @@ internal object Dark {
|
|||
val addressHighlightUnified = Color(0xFFFFD800)
|
||||
val addressHighlightSapling = Color(0xFF1BBFF6)
|
||||
val addressHighlightTransparent = Color(0xFF97999A)
|
||||
val addressHighlightViewing = Color(0xFF504062)
|
||||
|
||||
val dangerous = Color(0xFFEC0008)
|
||||
val onDangerous = Color(0xFFFFFFFF)
|
||||
|
@ -101,7 +100,6 @@ internal object Light {
|
|||
val addressHighlightUnified = Color(0xFFFFD800)
|
||||
val addressHighlightSapling = Color(0xFF1BBFF6)
|
||||
val addressHighlightTransparent = Color(0xFF97999A)
|
||||
val addressHighlightViewing = Color(0xFF504062)
|
||||
|
||||
val dangerous = Color(0xFFEC0008)
|
||||
val onDangerous = Color(0xFFFFFFFF)
|
||||
|
@ -148,7 +146,6 @@ internal val DarkExtendedColorPalette = ExtendedColors(
|
|||
addressHighlightUnified = Dark.addressHighlightUnified,
|
||||
addressHighlightSapling = Dark.addressHighlightSapling,
|
||||
addressHighlightTransparent = Dark.addressHighlightTransparent,
|
||||
addressHighlightViewing = Dark.addressHighlightViewing,
|
||||
dangerous = Dark.dangerous,
|
||||
onDangerous = Dark.onDangerous,
|
||||
reference = Dark.reference
|
||||
|
@ -171,7 +168,6 @@ internal val LightExtendedColorPalette = ExtendedColors(
|
|||
addressHighlightUnified = Light.addressHighlightUnified,
|
||||
addressHighlightSapling = Light.addressHighlightSapling,
|
||||
addressHighlightTransparent = Light.addressHighlightTransparent,
|
||||
addressHighlightViewing = Light.addressHighlightViewing,
|
||||
dangerous = Light.dangerous,
|
||||
onDangerous = Light.onDangerous,
|
||||
reference = Light.reference
|
||||
|
@ -195,7 +191,6 @@ internal val LocalExtendedColors = staticCompositionLocalOf {
|
|||
addressHighlightUnified = Color.Unspecified,
|
||||
addressHighlightSapling = Color.Unspecified,
|
||||
addressHighlightTransparent = Color.Unspecified,
|
||||
addressHighlightViewing = Color.Unspecified,
|
||||
dangerous = Color.Unspecified,
|
||||
onDangerous = Color.Unspecified,
|
||||
reference = Color.Unspecified
|
||||
|
|
|
@ -33,29 +33,23 @@ class WalletAddressViewTest : UiTestPrerequisites() {
|
|||
composeTestRule.onNodeWithText(getStringResource(R.string.wallet_address_unified)).also {
|
||||
it.assertExists()
|
||||
}
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.wallet_address_shielded_sapling)).also {
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.wallet_address_sapling)).also {
|
||||
it.assertExists()
|
||||
}
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.wallet_address_transparent)).also {
|
||||
it.assertExists()
|
||||
}
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.wallet_address_viewing_key)).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(walletAddresses.unified.address).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(walletAddresses.shieldedSapling.address).also {
|
||||
composeTestRule.onNodeWithText(walletAddresses.sapling.address).also {
|
||||
it.assertDoesNotExist()
|
||||
}
|
||||
composeTestRule.onNodeWithText(walletAddresses.transparent.address).also {
|
||||
it.assertDoesNotExist()
|
||||
}
|
||||
composeTestRule.onNodeWithText(walletAddresses.viewingKey).also {
|
||||
it.assertDoesNotExist()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -80,20 +74,20 @@ class WalletAddressViewTest : UiTestPrerequisites() {
|
|||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun shielded_sapling_expands() = runTest {
|
||||
fun sapling_expands() = runTest {
|
||||
val walletAddresses = WalletAddressesFixture.new()
|
||||
newTestSetup(walletAddresses)
|
||||
|
||||
composeTestRule.onNodeWithText(walletAddresses.shieldedSapling.address).also {
|
||||
composeTestRule.onNodeWithText(walletAddresses.sapling.address).also {
|
||||
it.assertDoesNotExist()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.wallet_address_shielded_sapling)).also {
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.wallet_address_sapling)).also {
|
||||
it.assertExists()
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(walletAddresses.shieldedSapling.address).also {
|
||||
composeTestRule.onNodeWithText(walletAddresses.sapling.address).also {
|
||||
it.assertExists()
|
||||
}
|
||||
}
|
||||
|
@ -118,26 +112,6 @@ class WalletAddressViewTest : UiTestPrerequisites() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun viewing_expands() = runTest {
|
||||
val walletAddresses = WalletAddressesFixture.new()
|
||||
newTestSetup(walletAddresses)
|
||||
|
||||
composeTestRule.onNodeWithText(walletAddresses.viewingKey).also {
|
||||
it.assertDoesNotExist()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.wallet_address_viewing_key)).also {
|
||||
it.assertExists()
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(walletAddresses.viewingKey).also {
|
||||
it.assertExists()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun back() = runTest {
|
||||
|
|
|
@ -13,11 +13,25 @@ class WalletAddressExtTest {
|
|||
@Test
|
||||
@SmallTest
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun testAbbreviated() = runTest {
|
||||
val actual = WalletAddressFixture.shieldedSapling().abbreviated(getAppContext())
|
||||
fun testAbbreviatedSaplingAddress() = runTest {
|
||||
val actual = WalletAddressFixture.sapling().abbreviated(getAppContext())
|
||||
|
||||
// TODO [#248]: The expected value should probably be reversed if the locale is RTL
|
||||
val expected = "ztest…rxnwg"
|
||||
// TODO [#248]: https://github.com/zcash/secant-android-wallet/issues/248
|
||||
val expected = "zs1hf…skt4u"
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun testAbbreviatedTransparentAddress() = runTest {
|
||||
val actual = WalletAddressFixture.transparent().abbreviated(getAppContext())
|
||||
|
||||
// TODO [#248]: The expected value should probably be reversed if the locale is RTL
|
||||
// TODO [#248]: https://github.com/zcash/secant-android-wallet/issues/248
|
||||
val expected = "t1QZM…3CSPK"
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
package co.electriccoin.zcash.global
|
||||
|
||||
import android.content.Context
|
||||
import cash.z.ecc.android.bip39.Mnemonics
|
||||
import cash.z.ecc.android.bip39.toSeed
|
||||
import cash.z.ecc.android.sdk.Initializer
|
||||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
import cash.z.ecc.android.sdk.ext.onFirst
|
||||
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||
import cash.z.ecc.android.sdk.model.defaultForNetwork
|
||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||
import cash.z.ecc.android.sdk.type.UnifiedViewingKey
|
||||
import cash.z.ecc.sdk.model.PersistableWallet
|
||||
import cash.z.ecc.sdk.type.fromResources
|
||||
import co.electriccoin.zcash.spackle.LazyWithArgument
|
||||
|
@ -22,7 +17,6 @@ import co.electriccoin.zcash.ui.preference.StandardPreferenceSingleton
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
|
@ -44,7 +38,6 @@ import kotlinx.coroutines.flow.stateIn
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.UUID
|
||||
|
||||
class WalletCoordinator(context: Context) {
|
||||
|
@ -81,7 +74,7 @@ class WalletCoordinator(context: Context) {
|
|||
|
||||
private sealed class InternalSynchronizerStatus {
|
||||
object NoWallet : InternalSynchronizerStatus()
|
||||
class Available(val synchronizer: cash.z.ecc.android.sdk.Synchronizer) : InternalSynchronizerStatus()
|
||||
class Available(val synchronizer: Synchronizer) : InternalSynchronizerStatus()
|
||||
class Lockout(val id: UUID) : InternalSynchronizerStatus()
|
||||
}
|
||||
|
||||
|
@ -93,9 +86,14 @@ class WalletCoordinator(context: Context) {
|
|||
flowOf(InternalSynchronizerStatus.NoWallet)
|
||||
} else {
|
||||
callbackFlow<InternalSynchronizerStatus.Available> {
|
||||
val initializer = Initializer.new(context, persistableWallet.toConfig())
|
||||
val synchronizer = synchronizerMutex.withLock {
|
||||
val synchronizer = Synchronizer.new(initializer)
|
||||
val synchronizer = Synchronizer.new(
|
||||
context = context,
|
||||
zcashNetwork = persistableWallet.network,
|
||||
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(persistableWallet.network),
|
||||
birthday = persistableWallet.birthday,
|
||||
seed = persistableWallet.seedPhrase.toByteArray()
|
||||
)
|
||||
synchronizer.start(walletScope)
|
||||
}
|
||||
|
||||
|
@ -170,11 +168,10 @@ class WalletCoordinator(context: Context) {
|
|||
.filter { it.id == lockoutId }
|
||||
.onFirst {
|
||||
synchronizerMutex.withLock {
|
||||
val didDelete = Initializer.erase(
|
||||
applicationContext,
|
||||
ZcashNetwork.fromResources(applicationContext)
|
||||
val didDelete = Synchronizer.erase(
|
||||
appContext = applicationContext,
|
||||
network = ZcashNetwork.fromResources(applicationContext)
|
||||
)
|
||||
|
||||
Twig.info { "SDK erase result: $didDelete" }
|
||||
}
|
||||
}
|
||||
|
@ -210,9 +207,9 @@ class WalletCoordinator(context: Context) {
|
|||
}
|
||||
|
||||
synchronizerMutex.withLock {
|
||||
val didDelete = Initializer.erase(
|
||||
applicationContext,
|
||||
ZcashNetwork.fromResources(applicationContext)
|
||||
val didDelete = Synchronizer.erase(
|
||||
appContext = applicationContext,
|
||||
network = ZcashNetwork.fromResources(applicationContext)
|
||||
)
|
||||
Twig.info { "SDK erase result: $didDelete" }
|
||||
}
|
||||
|
@ -223,22 +220,3 @@ class WalletCoordinator(context: Context) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun PersistableWallet.deriveViewingKey(): UnifiedViewingKey {
|
||||
// Dispatcher needed because SecureRandom is loaded, which is slow and performs IO
|
||||
// https://github.com/zcash/kotlin-bip39/issues/13
|
||||
val bip39Seed = withContext(Dispatchers.IO) {
|
||||
Mnemonics.MnemonicCode(seedPhrase.joinToString()).toSeed()
|
||||
}
|
||||
|
||||
return DerivationTool.deriveUnifiedViewingKeys(bip39Seed, network)[0]
|
||||
}
|
||||
|
||||
private suspend fun PersistableWallet.toConfig(): Initializer.Config {
|
||||
val network = network
|
||||
val vk = deriveViewingKey()
|
||||
|
||||
return Initializer.Config {
|
||||
it.importWallet(vk, birthday, network, LightWalletEndpoint.defaultForNetwork(network))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,15 +123,11 @@ private fun WalletDetailAddresses(walletAddresses: WalletAddresses) {
|
|||
ListHeader(text = stringResource(R.string.wallet_address_header_includes))
|
||||
}
|
||||
|
||||
SaplingAddress(walletAddresses.shieldedSapling.address)
|
||||
SaplingAddress(walletAddresses.sapling.address)
|
||||
TransparentAddress(walletAddresses.transparent.address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Divider(thickness = 8.dp)
|
||||
|
||||
ViewingKey(walletAddresses.viewingKey)
|
||||
}
|
||||
|
||||
// Note: The addresses code below has opportunities to be made more DRY.
|
||||
|
@ -148,7 +144,7 @@ private fun SaplingAddress(saplingAddress: String) {
|
|||
SmallIndicator(ZcashTheme.colors.addressHighlightSapling)
|
||||
|
||||
ExpandableRow(
|
||||
title = stringResource(R.string.wallet_address_shielded_sapling),
|
||||
title = stringResource(R.string.wallet_address_sapling),
|
||||
content = saplingAddress,
|
||||
isInitiallyExpanded = false
|
||||
)
|
||||
|
@ -171,27 +167,6 @@ private fun TransparentAddress(transparentAddress: String) {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ViewingKey(viewingKey: String) {
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(IntrinsicSize.Min)
|
||||
) {
|
||||
Image(
|
||||
painter = ColorPainter(ZcashTheme.colors.addressHighlightViewing),
|
||||
contentDescription = "",
|
||||
modifier = Modifier
|
||||
.width(SMALL_INDICATOR_WIDTH)
|
||||
)
|
||||
ExpandableRow(
|
||||
title = stringResource(R.string.wallet_address_viewing_key),
|
||||
content = viewingKey,
|
||||
isInitiallyExpanded = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExpandableRow(
|
||||
title: String,
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package co.electriccoin.zcash.ui.screen.home.model
|
||||
|
||||
import cash.z.ecc.android.sdk.model.PendingTransaction
|
||||
import cash.z.ecc.android.sdk.model.TransactionOverview
|
||||
|
||||
/**
|
||||
* A common transactions wrapper class to provide unified way to work with a transactions classes from our SDK.
|
||||
*/
|
||||
sealed class CommonTransaction {
|
||||
data class Pending(val data: PendingTransaction) : CommonTransaction()
|
||||
data class Overview(val data: TransactionOverview) : CommonTransaction()
|
||||
}
|
|
@ -42,7 +42,6 @@ import androidx.compose.ui.platform.testTag
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cash.z.ecc.android.sdk.db.entity.Transaction
|
||||
import cash.z.ecc.sdk.ext.ui.model.FiatCurrencyConversionRateState
|
||||
import cash.z.ecc.sdk.model.PercentDecimal
|
||||
import co.electriccoin.zcash.crash.android.CrashReporter
|
||||
|
@ -57,6 +56,7 @@ import co.electriccoin.zcash.ui.design.component.TertiaryButton
|
|||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
|
||||
import co.electriccoin.zcash.ui.screen.home.HomeTag
|
||||
import co.electriccoin.zcash.ui.screen.home.model.CommonTransaction
|
||||
import co.electriccoin.zcash.ui.screen.home.model.WalletDisplayValues
|
||||
import co.electriccoin.zcash.ui.screen.home.model.WalletSnapshot
|
||||
|
||||
|
@ -86,7 +86,7 @@ fun ComposablePreview() {
|
|||
@Composable
|
||||
fun Home(
|
||||
walletSnapshot: WalletSnapshot,
|
||||
transactionHistory: List<Transaction>,
|
||||
transactionHistory: List<CommonTransaction>,
|
||||
goScan: () -> Unit,
|
||||
goProfile: () -> Unit,
|
||||
goSend: () -> Unit,
|
||||
|
@ -179,7 +179,7 @@ private fun DebugMenu(resetSdk: () -> Unit, wipeEntireWallet: () -> Unit) {
|
|||
private fun HomeMainContent(
|
||||
paddingValues: PaddingValues,
|
||||
walletSnapshot: WalletSnapshot,
|
||||
transactionHistory: List<Transaction>,
|
||||
transactionHistory: List<CommonTransaction>,
|
||||
goScan: () -> Unit,
|
||||
goProfile: () -> Unit,
|
||||
goSend: () -> Unit,
|
||||
|
@ -321,7 +321,7 @@ private fun Status(
|
|||
|
||||
@Composable
|
||||
@Suppress("MagicNumber")
|
||||
private fun History(transactionHistory: List<Transaction>) {
|
||||
private fun History(transactionHistory: List<CommonTransaction>) {
|
||||
if (transactionHistory.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -7,13 +7,13 @@ import cash.z.ecc.android.bip39.Mnemonics
|
|||
import cash.z.ecc.android.bip39.toSeed
|
||||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
|
||||
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
|
||||
import cash.z.ecc.android.sdk.db.entity.Transaction
|
||||
import cash.z.ecc.android.sdk.db.entity.isMined
|
||||
import cash.z.ecc.android.sdk.db.entity.isSubmitSuccess
|
||||
import cash.z.ecc.android.sdk.model.Account
|
||||
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||
import cash.z.ecc.android.sdk.model.PendingTransaction
|
||||
import cash.z.ecc.android.sdk.model.WalletBalance
|
||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
import cash.z.ecc.android.sdk.model.isMined
|
||||
import cash.z.ecc.android.sdk.model.isSubmitSuccess
|
||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||
import cash.z.ecc.sdk.model.FiatCurrency
|
||||
import cash.z.ecc.sdk.model.PercentDecimal
|
||||
|
@ -26,11 +26,11 @@ import co.electriccoin.zcash.ui.preference.EncryptedPreferenceKeys
|
|||
import co.electriccoin.zcash.ui.preference.EncryptedPreferenceSingleton
|
||||
import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys
|
||||
import co.electriccoin.zcash.ui.preference.StandardPreferenceSingleton
|
||||
import co.electriccoin.zcash.ui.screen.home.model.CommonTransaction
|
||||
import co.electriccoin.zcash.ui.screen.home.model.WalletSnapshot
|
||||
import co.electriccoin.zcash.work.WorkIds
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
|
@ -41,6 +41,7 @@ import kotlinx.coroutines.flow.combine
|
|||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
@ -56,6 +57,7 @@ import kotlin.time.ExperimentalTime
|
|||
// To make this more multiplatform compatible, we need to remove the dependency on Context
|
||||
// for loading the preferences.
|
||||
// TODO [#292]: Should be moved to SDK-EXT-UI module.
|
||||
// TODO [#292]: https://github.com/zcash/secant-android-wallet/issues/292
|
||||
class WalletViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private val walletCoordinator = co.electriccoin.zcash.global.WalletCoordinator.getInstance(application)
|
||||
|
||||
|
@ -117,8 +119,11 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
|
|||
val bip39Seed = withContext(Dispatchers.IO) {
|
||||
Mnemonics.MnemonicCode(it.seedPhrase.joinToString()).toSeed()
|
||||
}
|
||||
|
||||
DerivationTool.deriveSpendingKeys(bip39Seed, it.network)[0]
|
||||
DerivationTool.deriveUnifiedSpendingKey(
|
||||
seed = bip39Seed,
|
||||
network = it.network,
|
||||
account = Account.DEFAULT
|
||||
)
|
||||
}.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
|
@ -143,7 +148,7 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
|
|||
|
||||
// This is not the right API, because the transaction list could be very long and might need UI filtering
|
||||
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
|
||||
val transactionSnapshot: StateFlow<List<Transaction>> = synchronizer
|
||||
val transactionSnapshot: StateFlow<List<CommonTransaction>> = synchronizer
|
||||
.flatMapLatest {
|
||||
if (null == it) {
|
||||
flowOf(emptyList())
|
||||
|
@ -157,11 +162,11 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
|
|||
emptyList()
|
||||
)
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
val addresses: StateFlow<WalletAddresses?> = secretState
|
||||
.filterIsInstance<SecretState.Ready>()
|
||||
.map { WalletAddresses.new(it.persistableWallet) }
|
||||
.stateIn(
|
||||
val addresses: StateFlow<WalletAddresses?> = synchronizer
|
||||
.filterNotNull()
|
||||
.map {
|
||||
WalletAddresses.new(it)
|
||||
}.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
null
|
||||
|
@ -371,15 +376,14 @@ private fun Synchronizer.toWalletSnapshot() =
|
|||
private fun Synchronizer.toTransactions() =
|
||||
combine(
|
||||
clearedTransactions.distinctUntilChanged(),
|
||||
pendingTransactions.distinctUntilChanged(),
|
||||
sentTransactions.distinctUntilChanged(),
|
||||
receivedTransactions.distinctUntilChanged()
|
||||
) { cleared, pending, sent, received ->
|
||||
pendingTransactions.distinctUntilChanged()
|
||||
) { cleared, pending ->
|
||||
// TODO [#157]: Sort the transactions to show the most recent
|
||||
buildList<Transaction> {
|
||||
addAll(cleared)
|
||||
addAll(pending)
|
||||
addAll(sent)
|
||||
addAll(received)
|
||||
// TODO [#157]: https://github.com/zcash/secant-android-wallet/issues/157
|
||||
|
||||
// Note that the list of transactions will not be sorted.
|
||||
buildList {
|
||||
addAll(cleared.map { CommonTransaction.Overview(it) })
|
||||
addAll(pending.map { CommonTransaction.Pending(it) })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,11 +6,13 @@ import androidx.activity.ComponentActivity
|
|||
import androidx.activity.viewModels
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import cash.z.ecc.sdk.send
|
||||
import co.electriccoin.zcash.ui.MainActivity
|
||||
import co.electriccoin.zcash.ui.screen.home.model.spendableBalance
|
||||
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
|
||||
import co.electriccoin.zcash.ui.screen.send.view.Send
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
internal fun MainActivity.WrapSend(
|
||||
|
@ -25,6 +27,7 @@ private fun WrapSend(
|
|||
goBack: () -> Unit
|
||||
) {
|
||||
val walletViewModel by activity.viewModels<WalletViewModel>()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val synchronizer = walletViewModel.synchronizer.collectAsState().value
|
||||
val spendableBalance = walletViewModel.walletSnapshot.collectAsState().value?.spendableBalance()
|
||||
|
@ -36,9 +39,10 @@ private fun WrapSend(
|
|||
mySpendableBalance = spendableBalance,
|
||||
goBack = goBack,
|
||||
onCreateAndSend = {
|
||||
synchronizer.send(spendingKey, it)
|
||||
|
||||
goBack()
|
||||
scope.launch {
|
||||
synchronizer.send(spendingKey, it)
|
||||
goBack()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,10 +4,8 @@
|
|||
<string name="wallet_address_back_content_description">Back</string>
|
||||
<string name="wallet_address_unified">Your Unified Address</string>
|
||||
<string name="wallet_address_header_includes">which includes</string>
|
||||
<string name="wallet_address_shielded_sapling">Shielded Sapling (NU1)</string>
|
||||
<string name="wallet_address_sapling">Shielded Sapling (NU1)</string>
|
||||
<string name="wallet_address_transparent">Transparent</string>
|
||||
<string name="wallet_address_viewing_key">Viewing Key Only (Sapling)</string>
|
||||
<string name="wallet_address_show">Show address</string>
|
||||
<string name="wallet_address_hide">Hide address</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -475,7 +475,8 @@ private fun homeScreenshots(resContext: Context, tag: String, composeTestRule: A
|
|||
}
|
||||
|
||||
private fun profileScreenshots(resContext: Context, tag: String, composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<MainActivity>, MainActivity>) {
|
||||
composeTestRule.waitUntil { composeTestRule.activity.walletViewModel.addresses.value != null }
|
||||
// Note: increased timeout limit to satisfy time needed for SDK initialization
|
||||
composeTestRule.waitUntil(2_000) { composeTestRule.activity.walletViewModel.addresses.value != null }
|
||||
|
||||
composeTestRule.onNode(hasText(resContext.getString(R.string.profile_title))).also {
|
||||
it.assertExists()
|
||||
|
|
Loading…
Reference in New Issue