Skip to content

Commit

Permalink
[#1632] Refactor Account.DEFAULT (#1645)
Browse files Browse the repository at this point in the history
* [#1632] Remove `Account.DEFAULT`

* Update Account related APIs

* Refactor balances APIs

* Adopt `getAccounts` in fragment-based Demo app

We have deprecated the old Fragment-based Demo app. See #973. So, the purpose of these changes is purely the buildability of the new Compose-based Demo app.

* Add init to `JniUnifiedSpendingKey.kt`

* Update deprecated Fragment-based Demo app

This part of the Demo app will be removed as part of #973

* Remove deprecated functions from Synchronizer

* Update WalletSnapshot and WalletVM APIs in Demo

+ necessary fixtures changes

* Update newer Compose-based Demo app

* Hide `Synchronizer.createAccount` form public API

As it was, as making it public could bring more requirements on our multi-account support in version 1

* Add all accounts flow API

* Refactor AccountFixture

- We deduplicated the fixture across the related modules and their tests
- Documentation added
  • Loading branch information
HonzaR authored Dec 4, 2024
1 parent 3558ecf commit a744a67
Show file tree
Hide file tree
Showing 47 changed files with 467 additions and 352 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- `Synchronizer.getAccounts()`
- `Synchronizer.walletBalances: StateFlow<Map<Account, AccountBalance>?>` that is replacement for the removed
`orchardBalances`, `saplingBalances`, and `transparentBalance`

### Changed
- `Synchronizer.orchardBalances`, `Synchronizer.saplingBalances`, and `Synchronizer.transparentBalance` have been
replaced by `Synchronizer.walletBalances` that provides these balances based on `Account`

### Removed
- `Synchronizer.sendToAddress` and `Synchronizer.shieldFunds` have been removed, use
`Synchronizer.createProposedTransactions` and `Synchronizer.proposeShielding` instead
- `Synchronizer.orchardBalances`, `Synchronizer.saplingBalances`, and `Synchronizer.transparentBalance`

## [2.2.6] - 2024-11-16

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import androidx.annotation.Keep
* @throws IllegalArgumentException if the values are inconsistent.
*/
@Keep
@Suppress("LongParameterList")
class JniAccount(
val accountIndex: Int,
val ufvk: String?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ package cash.z.ecc.android.sdk.internal.fixture

import cash.z.ecc.android.sdk.internal.model.JniAccountBalance

/**
* This is a test fixture for [JniAccountBalance] class. It holds mocked values that are only used within
* [JniWalletSummaryTest].
*/
object JniAccountBalanceFixture {
const val ACCOUNT_ID: Int = 0
val ACCOUNT_UUID: ByteArray = "random_uuid_16_b".toByteArray()
const val SAPLING_VERIFIED_BALANCE: Long = 0L
const val SAPLING_CHANGE_PENDING: Long = 0L
const val SAPLING_VALUE_PENDING: Long = 0L
Expand All @@ -14,7 +18,7 @@ object JniAccountBalanceFixture {

@Suppress("LongParameterList")
fun new(
account: Int = ACCOUNT_ID,
account: ByteArray = ACCOUNT_UUID,
saplingVerifiedBalance: Long = SAPLING_VERIFIED_BALANCE,
saplingChangePending: Long = SAPLING_CHANGE_PENDING,
saplingValuePending: Long = SAPLING_VALUE_PENDING,
Expand Down
2 changes: 1 addition & 1 deletion darkside-test-lib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ dependencies {
implementation(libs.androidx.multidex)
implementation(libs.bundles.grpc)

androidTestImplementation(projects.sdkIncubatorLib)
androidTestImplementation(libs.bundles.androidx.test)

androidTestImplementation(libs.zcashwalletplgn)
androidTestImplementation(libs.bip39)
}
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
available: Long = -1,
total: Long = -1
) {
val balance = synchronizer.saplingBalances.value
val balance = synchronizer.walletBalances.value?.get(wallet.account)?.sapling
if (available > 0) {
assertTrue(
"invalid available balance. Expected a minimum of $available but found ${balance?.available}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import cash.z.ecc.android.sdk.SdkSynchronizer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.WalletInitMode
import cash.z.ecc.android.sdk.ext.Darkside
import cash.z.ecc.android.sdk.fixture.AccountFixture
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork
Expand Down Expand Up @@ -55,11 +55,17 @@ class TestWallet(
// Although runBlocking isn't great, this usage is OK because this is only used within the
// automated tests

private val account = Account.DEFAULT
internal val account = AccountFixture.new()
private val context = InstrumentationRegistry.getInstrumentation().context
private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed()
private val shieldedSpendingKey =
runBlocking { DerivationTool.getInstance().deriveUnifiedSpendingKey(seed, network = network, account) }
runBlocking {
DerivationTool.getInstance().deriveUnifiedSpendingKey(
seed = seed,
network = network,
account = AccountFixture.new()
)
}
val synchronizer: SdkSynchronizer =
Synchronizer.newBlocking(
context,
Expand All @@ -72,7 +78,8 @@ class TestWallet(
walletInitMode = WalletInitMode.ExistingWallet
) as SdkSynchronizer

val available get() = synchronizer.saplingBalances.value?.available
val available
get() = synchronizer.walletBalances.value?.get(account)?.sapling?.available
val unifiedAddress =
runBlocking { synchronizer.getUnifiedAddress(account) }
val transparentAddress =
Expand Down Expand Up @@ -123,7 +130,7 @@ class TestWallet(
}

suspend fun shieldFunds(): TestWallet {
synchronizer.refreshUtxos(Account.DEFAULT, BlockHeight.new(935000L)).let { count ->
synchronizer.refreshUtxos(account, BlockHeight.new(935000L)).let { count ->
Twig.debug { "FOUND $count new UTXOs" }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import cash.z.ecc.android.sdk.demoapp.ext.defaultForNetwork
import cash.z.ecc.android.sdk.demoapp.util.fromResources
import cash.z.ecc.android.sdk.ext.convertZecToZatoshi
import cash.z.ecc.android.sdk.ext.toHex
import cash.z.ecc.android.sdk.fixture.AccountFixture
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
Expand Down Expand Up @@ -72,7 +72,7 @@ class SampleCodeTest {
@Test
fun getAddress() =
runBlocking {
val address = synchronizer.getUnifiedAddress(Account.DEFAULT)
val address = synchronizer.getUnifiedAddress(AccountFixture.new())
assertFalse(address.isBlank())
log("Address: $address")
}
Expand Down Expand Up @@ -170,7 +170,7 @@ class SampleCodeTest {
// ///////////////////////////////////////////////////
// Create a signed transaction (with memo) and broadcast
@Test
fun submitTransaction() =
fun submitTransaction(): Unit =
runBlocking {
val amount = 0.123.convertZecToZatoshi()
val address = "ztestsapling1tklsjr0wyw0d58f3p7wufvrj2cyfv6q6caumyueadq8qvqt8lda6v6tpx474rfru9y6u75u7qnw"
Expand All @@ -179,7 +179,7 @@ class SampleCodeTest {
DerivationTool.getInstance().deriveUnifiedSpendingKey(
seed,
ZcashNetwork.Mainnet,
Account.DEFAULT
AccountFixture.new()
)
synchronizer.createProposedTransactions(
synchronizer.proposeTransfer(
Expand All @@ -197,7 +197,7 @@ class SampleCodeTest {
// ////////////////////////////////////////////////////

companion object {
private val seed = "Insert seed for testing".toByteArray()
private val seed = "Inserting seed for test purposes".toByteArray()
private val lightwalletdHost = LightWalletEndpoint.Mainnet

private val context = InstrumentationRegistry.getInstrumentation().targetContext
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cash.z.ecc.android.sdk.demoapp.ui.common
package cash.z.ecc.android.sdk.demoapp

import kotlin.time.Duration.Companion.seconds

Expand All @@ -9,3 +9,7 @@ val ANDROID_STATE_FLOW_TIMEOUT = 5.seconds
* A tiny weight, useful for spacers to fill an empty space.
*/
const val MINIMAL_WEIGHT = 0.0001f

// TODO [#1644]: Refactor Account ZIP32 index across SDK
// TODO [#1644]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/1644
const val CURRENT_ZIP_32_ACCOUNT_INDEX = 0
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import cash.z.ecc.android.sdk.model.Proposal
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.type.ServerValidation
import co.electriccoin.lightwallet.client.model.LightWalletEndpoint
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

Expand All @@ -76,11 +77,14 @@ internal fun ComposeActivity.Navigation() {
NavHost(navController = navController, startDestination = HOME) {
composable(HOME) {
val walletSnapshot = walletViewModel.walletSnapshot.collectAsStateWithLifecycle().value
val allAccounts = walletViewModel.accounts.collectAsStateWithLifecycle().value
if (null == walletSnapshot) {
// Display loading indicator
} else {
Home(
walletSnapshot,
currentAccount = walletViewModel.getCurrentAccount(),
allAccounts = allAccounts.toImmutableList(),
walletSnapshot = walletSnapshot,
goBalance = { navController.navigateJustOnce(BALANCE) },
goSend = { navController.navigateJustOnce(SEND) },
goAddressDetails = { navController.navigateJustOnce(WALLET_ADDRESS_DETAILS) },
Expand All @@ -106,9 +110,11 @@ internal fun ComposeActivity.Navigation() {
if (null == synchronizer || null == walletSnapshot) {
// Display loading indicator
} else {
val balance = walletSnapshot.balanceByAccount(walletViewModel.getCurrentAccount())
val scope = rememberCoroutineScope()
Balance(
walletSnapshot,
exchangeRateUsd = walletSnapshot.exchangeRateUsd,
accountBalance = balance,
onShieldFunds = { walletViewModel.shieldFunds() },
sendState = walletViewModel.sendState.collectAsStateWithLifecycle().value,
onBack = {
Expand All @@ -129,11 +135,13 @@ internal fun ComposeActivity.Navigation() {
if (null == synchronizer) {
// Display loading indicator
} else {
val currentAccount = walletViewModel.getCurrentAccount()
val scope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
// I don't like giving synchronizer directly over to the view, but for now it isolates each of the
// demo app views
Addresses(
account = currentAccount,
synchronizer = synchronizer,
copyToClipboard = { tag, textToCopy ->
copyToClipboard(
Expand Down Expand Up @@ -161,8 +169,9 @@ internal fun ComposeActivity.Navigation() {
if (null == synchronizer || null == walletSnapshot || null == spendingKey) {
// Display loading indicator
} else {
val currentAccount = walletViewModel.getCurrentAccount()
Send(
walletSnapshot = walletSnapshot,
accountBalance = walletSnapshot.balanceByAccount(currentAccount),
sendState = walletViewModel.sendState.collectAsStateWithLifecycle().value,
onSend = {
walletViewModel.send(it)
Expand Down Expand Up @@ -292,7 +301,9 @@ internal fun ComposeActivity.Navigation() {
if (null == synchronizer) {
// Display loading indicator
} else {
val currentAccount = walletViewModel.getCurrentAccount()
Transactions(
account = currentAccount,
synchronizer = synchronizer,
onBack = { navController.popBackStackJustOnce(TRANSACTIONS) }
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
import cash.z.ecc.android.sdk.demoapp.CURRENT_ZIP_32_ACCOUNT_INDEX
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetAddressBinding
import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
import cash.z.ecc.android.sdk.demoapp.util.ProvideAddressBenchmarkTrace
import cash.z.ecc.android.sdk.demoapp.util.fromResources
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
Expand All @@ -30,23 +30,25 @@ class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
sharedViewModel.synchronizerFlow.filterNotNull().collect { synchronizer ->
val account = synchronizer.getAccounts()[CURRENT_ZIP_32_ACCOUNT_INDEX]

binding.unifiedAddress.apply {
reportTraceEvent(ProvideAddressBenchmarkTrace.Event.UNIFIED_ADDRESS_START)
val uaddress = synchronizer.getUnifiedAddress(Account.DEFAULT)
val uaddress = synchronizer.getUnifiedAddress(account)
reportTraceEvent(ProvideAddressBenchmarkTrace.Event.UNIFIED_ADDRESS_END)
text = uaddress
setOnClickListener { copyToClipboard(uaddress) }
}
binding.saplingAddress.apply {
reportTraceEvent(ProvideAddressBenchmarkTrace.Event.SAPLING_ADDRESS_START)
val sapling = synchronizer.getSaplingAddress(Account.DEFAULT)
val sapling = synchronizer.getSaplingAddress(account)
reportTraceEvent(ProvideAddressBenchmarkTrace.Event.SAPLING_ADDRESS_END)
text = sapling
setOnClickListener { copyToClipboard(sapling) }
}
binding.transparentAddress.apply {
reportTraceEvent(ProvideAddressBenchmarkTrace.Event.TRANSPARENT_ADDRESS_START)
val transparent = synchronizer.getTransparentAddress(Account.DEFAULT)
val transparent = synchronizer.getTransparentAddress(account)
reportTraceEvent(ProvideAddressBenchmarkTrace.Event.TRANSPARENT_ADDRESS_END)
text = transparent
setOnClickListener { copyToClipboard(transparent) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ 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.demoapp.BaseDemoFragment
import cash.z.ecc.android.sdk.demoapp.CURRENT_ZIP_32_ACCOUNT_INDEX
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetBalanceBinding
import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
import cash.z.ecc.android.sdk.demoapp.util.SyncBlockchainBenchmarkTrace
Expand Down Expand Up @@ -78,7 +79,7 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
DerivationTool.getInstance().deriveUnifiedSpendingKey(
seed,
network,
Account.DEFAULT
Account(CURRENT_ZIP_32_ACCOUNT_INDEX)
)
sharedViewModel.synchronizerFlow.value?.let { synchronizer ->
synchronizer.proposeShielding(usk.account, Zatoshi(100000))?.let { it1 ->
Expand Down Expand Up @@ -116,10 +117,12 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
sharedViewModel.synchronizerFlow
.filterNotNull()
.flatMapLatest {
it.saplingBalances.combine(it.exchangeRateUsd) { b, r ->
b?.let {
b to
r.currencyConversion
val account = it.getAccounts()[CURRENT_ZIP_32_ACCOUNT_INDEX]
it.walletBalances.combine(it.exchangeRateUsd) { balances, rate ->
balances?.let {
val walletBalance = balances[account]!!.sapling
walletBalance to
rate.currencyConversion
?.priceOfZec
?.toBigDecimal()
}
Expand All @@ -131,23 +134,28 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
sharedViewModel.synchronizerFlow
.filterNotNull()
.flatMapLatest {
it.orchardBalances.combine(it.exchangeRateUsd) { b, r ->
b?.let {
b to
r.currencyConversion?.priceOfZec?.toBigDecimal()
val account = it.getAccounts()[CURRENT_ZIP_32_ACCOUNT_INDEX]
it.walletBalances.combine(it.exchangeRateUsd) { balances, rate ->
balances?.let {
val walletBalance = balances[account]!!.orchard
walletBalance to
rate.currencyConversion
?.priceOfZec
?.toBigDecimal()
}
}
}
.collect { onOrchardBalance(it) }
}
launch {

sharedViewModel.synchronizerFlow
.filterNotNull()
.flatMapLatest {
it.transparentBalance.combine(it.exchangeRateUsd) { b, r ->
b?.let {
b to
r.currencyConversion
val account = it.getAccounts()[CURRENT_ZIP_32_ACCOUNT_INDEX]
it.walletBalances.combine(it.exchangeRateUsd) { balances, rate ->
balances?.let {
val walletBalance = balances[account]!!.unshielded
walletBalance to
rate.currencyConversion
?.priceOfZec
?.toBigDecimal()
}
Expand Down Expand Up @@ -208,9 +216,12 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
binding.textStatus.text = "Status: $status"
sharedViewModel.synchronizerFlow.value?.let { synchronizer ->
val rate = synchronizer.exchangeRateUsd.value.currencyConversion?.priceOfZec?.toBigDecimal()
onOrchardBalance(synchronizer.orchardBalances.value?.let { Pair(it, rate) })
onSaplingBalance(synchronizer.saplingBalances.value?.let { Pair(it, rate) })
onTransparentBalance(synchronizer.transparentBalance.value?.let { Pair(it, rate) })
viewLifecycleOwner.lifecycleScope.launch {
val account = synchronizer.getAccounts()[CURRENT_ZIP_32_ACCOUNT_INDEX]
onOrchardBalance(synchronizer.walletBalances.value?.let { Pair(it[account]!!.orchard, rate) })
onSaplingBalance(synchronizer.walletBalances.value?.let { Pair(it[account]!!.sapling, rate) })
onTransparentBalance(synchronizer.walletBalances.value?.let { Pair(it[account]!!.unshielded, rate) })
}
}
}

Expand Down
Loading

0 comments on commit a744a67

Please sign in to comment.