From 09f4489fdba78f12e2983037d094a0dd69a3735c Mon Sep 17 00:00:00 2001 From: Honza Date: Fri, 22 Nov 2024 11:12:32 +0100 Subject: [PATCH 01/25] [#1632] Remove `Account.DEFAULT` --- .../android/sdk/internal/model/JniAccount.kt | 1 - .../demoapp/ui/screen/keys/view/KeysView.kt | 151 ++++++++++++++++++ demo-app/src/main/res/values/strings.xml | 9 +- .../sdk/internal/DerivationToolImplTest.kt | 1 - .../ecc/android/sdk/fixture/WalletFixture.kt | 6 +- .../ecc/android/sdk/model/WalletAddresses.kt | 8 +- .../ecc/android/sdk/fixture/AccountFixture.kt | 13 ++ .../sdk/fixture/WalletBalanceFixture.kt | 1 - .../z/ecc/android/sdk/tool/DerivationTool.kt | 3 +- 9 files changed, 182 insertions(+), 11 deletions(-) create mode 100644 demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/keys/view/KeysView.kt create mode 100644 sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniAccount.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniAccount.kt index a79a9e002..545228ca0 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniAccount.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniAccount.kt @@ -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?, diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/keys/view/KeysView.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/keys/view/KeysView.kt new file mode 100644 index 000000000..7ae768aaf --- /dev/null +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/keys/view/KeysView.kt @@ -0,0 +1,151 @@ +package cash.z.ecc.android.sdk.demoapp.ui.screen.keys.view + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.outlined.Autorenew +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +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.demoapp.R +import cash.z.ecc.android.sdk.demoapp.fixture.WalletSnapshotFixture +import cash.z.ecc.android.sdk.demoapp.ui.screen.home.viewmodel.SendState +import cash.z.ecc.android.sdk.demoapp.ui.screen.home.viewmodel.WalletSnapshot +import cash.z.ecc.android.sdk.ext.convertZatoshiToZec +import cash.z.ecc.android.sdk.ext.toUsdString +import cash.z.ecc.android.sdk.model.toZecString + +@Preview(name = "Keys") +@Composable +private fun ComposablePreview() { + MaterialTheme { + Keys( + keysState = KeysState(), + onBack = {}, + ) + } +} + +@Composable +fun Keys( + keysState: KeysState, + onBack: () -> Unit, +) { + Scaffold(topBar = { + KeysTopAppBar( + onBack, + ) + }) { paddingValues -> + KeysMainContent( + paddingValues = paddingValues, + keysState = keysState, + ) + } +} + +@Composable +private fun KeysTopAppBar( + onBack: () -> Unit, +) { + TopAppBar( + title = { Text(text = stringResource(id = R.string.menu_keys)) }, + navigationIcon = { + IconButton( + onClick = onBack + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = null + ) + } + } + ) +} + +@Composable +private fun KeysMainContent( + paddingValues: PaddingValues, + keysState: KeysState, +) { + Column( + Modifier + .verticalScroll(rememberScrollState()) + .padding(top = paddingValues.calculateTopPadding()) + ) { + Text(stringResource(id = R.string.balance_orchard)) + Text( + stringResource( + id = R.string.balance_available_amount_format, + walletSnapshot.orchardBalance.available.toZecString(), + walletSnapshot.exchangeRateUsd?.multiply(walletSnapshot.orchardBalance.available.convertZatoshiToZec()) + .toUsdString() + ) + ) + Text( + stringResource( + id = R.string.balance_pending_amount_format, + walletSnapshot.orchardBalance.pending.toZecString(), + walletSnapshot.exchangeRateUsd?.multiply(walletSnapshot.orchardBalance.pending.convertZatoshiToZec()) + .toUsdString() + ) + ) + + Spacer(Modifier.padding(8.dp)) + + Text(stringResource(id = R.string.balance_sapling)) + Text( + stringResource( + id = R.string.balance_available_amount_format, + walletSnapshot.saplingBalance.available.toZecString(), + walletSnapshot.exchangeRateUsd?.multiply(walletSnapshot.saplingBalance.available.convertZatoshiToZec()) + .toUsdString() + ) + ) + Text( + stringResource( + id = R.string.balance_pending_amount_format, + walletSnapshot.saplingBalance.pending.toZecString(), + walletSnapshot.exchangeRateUsd?.multiply(walletSnapshot.saplingBalance.pending.convertZatoshiToZec()) + .toUsdString() + ) + ) + + Spacer(Modifier.padding(8.dp)) + + Text(stringResource(id = R.string.balance_transparent)) + Text( + stringResource( + id = R.string.balance_available_amount_format, + walletSnapshot.transparentBalance.toZecString(), + walletSnapshot.exchangeRateUsd?.multiply(walletSnapshot.transparentBalance.convertZatoshiToZec()) + .toUsdString() + ) + ) + + // This check is not entirely correct - it does not calculate the resulting fee with the new Proposal API + if (walletSnapshot.transparentBalance.value > 0L) { + // Note this implementation does not guard against multiple clicks + Button(onClick = onShieldFunds) { + Text(stringResource(id = R.string.action_shield)) + } + } + + // Eventually there should be something to clear the status + Text(stringResource(id = R.string.send_status, sendState.toString())) + } +} diff --git a/demo-app/src/main/res/values/strings.xml b/demo-app/src/main/res/values/strings.xml index f23a81eef..99658c149 100644 --- a/demo-app/src/main/res/values/strings.xml +++ b/demo-app/src/main/res/values/strings.xml @@ -14,10 +14,10 @@ Home - Get Private Key Get Address Server Get Balance + Private Keys Get Latest Height Get Block Get Block Range @@ -102,4 +102,11 @@ Unavailable Status: + Note that we strongly discouraged wallets from displaying the private keys the UI. + This is only for testing purposes. + Unified Full View Key: + Unified Spending Key: + ZIP 32 Arbitrary Wallet Key: + ZIP 32 Arbitrary Account Key: + diff --git a/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/internal/DerivationToolImplTest.kt b/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/internal/DerivationToolImplTest.kt index 62ea17b34..9fc2efeab 100644 --- a/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/internal/DerivationToolImplTest.kt +++ b/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/internal/DerivationToolImplTest.kt @@ -2,7 +2,6 @@ package cash.z.ecc.android.sdk.internal import cash.z.ecc.android.sdk.ext.toHex import cash.z.ecc.android.sdk.fixture.WalletFixture -import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.tool.DerivationTool import kotlinx.coroutines.test.runTest diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt index 1dc303168..842fa5654 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt @@ -12,6 +12,8 @@ import cash.z.ecc.android.sdk.tool.DerivationTool sealed class WalletFixture { abstract val seedPhrase: String + abstract val accounts: List + abstract fun getBirthday(zcashNetwork: ZcashNetwork): BlockHeight abstract fun getAddresses(zcashNetwork: ZcashNetwork): Addresses @@ -19,7 +21,7 @@ sealed class WalletFixture { suspend fun getUnifiedSpendingKey( seed: String = seedPhrase, network: ZcashNetwork, - account: Account = Account.DEFAULT + account: Account ) = DerivationTool.getInstance().deriveUnifiedSpendingKey( Mnemonics.MnemonicCode(seed).toEntropy(), network, @@ -28,6 +30,8 @@ sealed class WalletFixture { @Suppress("MaxLineLength") data object Ben : WalletFixture() { + override val accounts: List + get() = listOf(Account()) override val seedPhrase: String get() = "kitchen renew wide common vague fold vacuum tilt amazing pear square gossip jewel month tree" + diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/WalletAddresses.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/WalletAddresses.kt index 674247312..c6b6999da 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/WalletAddresses.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/WalletAddresses.kt @@ -11,20 +11,20 @@ data class WalletAddresses( override fun toString() = "WalletAddresses" companion object { - suspend fun new(synchronizer: Synchronizer): WalletAddresses { + suspend fun new(synchronizer: Synchronizer, account: Account): WalletAddresses { val unified = WalletAddress.Unified.new( - synchronizer.getUnifiedAddress(Account.DEFAULT) + synchronizer.getUnifiedAddress(account) ) val saplingAddress = WalletAddress.Sapling.new( - synchronizer.getSaplingAddress(Account.DEFAULT) + synchronizer.getSaplingAddress(account) ) val transparentAddress = WalletAddress.Transparent.new( - synchronizer.getTransparentAddress(Account.DEFAULT) + synchronizer.getTransparentAddress(account) ) return WalletAddresses( diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt new file mode 100644 index 000000000..b89475b22 --- /dev/null +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt @@ -0,0 +1,13 @@ +package cash.z.ecc.android.sdk.fixture + +import cash.z.ecc.android.sdk.model.Account + +object AccountFixture { + val DEFAULT_UUID: ByteArray = "test_uuid".toByteArray() + + fun new( + accountUuid: ByteArray = DEFAULT_UUID + ) = Account( + accountUuid = accountUuid + ) +} diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletBalanceFixture.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletBalanceFixture.kt index 664593970..011af6e7c 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletBalanceFixture.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletBalanceFixture.kt @@ -3,7 +3,6 @@ package cash.z.ecc.android.sdk.fixture import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.Zatoshi -@Suppress("MagicNumber") object WalletBalanceFixture { const val AVAILABLE: Long = 8L const val CHANGE_PENDING: Long = 4 diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/DerivationTool.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/DerivationTool.kt index a21eeaa42..0129a66fa 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/DerivationTool.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/DerivationTool.kt @@ -14,8 +14,7 @@ interface DerivationTool { * Given a seed and a number of accounts, return the associated Unified Full Viewing Keys. * * @param seed the seed from which to derive viewing keys. - * @param numberOfAccounts the number of accounts to use. Multiple accounts are not fully - * supported so the default value of 1 is recommended. + * @param numberOfAccounts the number of accounts to use. * * @return the UFVKs derived from the seed, encoded as Strings. */ From edf12e02863bc1601ff8e416bce432322cb05477 Mon Sep 17 00:00:00 2001 From: Honza Date: Mon, 25 Nov 2024 14:31:51 +0100 Subject: [PATCH 02/25] Update Account related APIs --- CHANGELOG.md | 3 +++ .../ecc/android/sdk/fixture/WalletFixture.kt | 24 +++++++++---------- .../cash/z/ecc/android/sdk/SdkSynchronizer.kt | 19 +++++++++++---- .../cash/z/ecc/android/sdk/Synchronizer.kt | 19 ++++++++++++--- .../z/ecc/android/sdk/exception/Exceptions.kt | 6 ++++- .../sdk/internal/model/WalletSummary.kt | 1 + .../{internal => }/model/AccountBalance.kt | 7 +++--- 7 files changed, 54 insertions(+), 25 deletions(-) rename sdk-lib/src/main/java/cash/z/ecc/android/sdk/{internal => }/model/AccountBalance.kt (83%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 247845dfb..a1135ba7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- `Synchronizer.getAccounts` +- `Synchronizer.createAccount` ## [2.2.6] - 2024-11-16 ### Added diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt index 842fa5654..18fb81a5a 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt @@ -1,10 +1,8 @@ package cash.z.ecc.android.sdk.fixture -import cash.z.ecc.android.bip39.Mnemonics 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 /** * Provides two default wallets, making it easy to test sending funds back and forth between them. @@ -18,20 +16,14 @@ sealed class WalletFixture { abstract fun getAddresses(zcashNetwork: ZcashNetwork): Addresses - suspend fun getUnifiedSpendingKey( - seed: String = seedPhrase, - network: ZcashNetwork, - account: Account - ) = DerivationTool.getInstance().deriveUnifiedSpendingKey( - Mnemonics.MnemonicCode(seed).toEntropy(), - network, - account - ) - @Suppress("MaxLineLength") data object Ben : WalletFixture() { override val accounts: List - get() = listOf(Account()) + get() = listOf( + // fixme + Account(byteArrayOf()) + ) + override val seedPhrase: String get() = "kitchen renew wide common vague fold vacuum tilt amazing pear square gossip jewel month tree" + @@ -66,6 +58,12 @@ sealed class WalletFixture { @Suppress("MaxLineLength") data object Alice : WalletFixture() { + override val accounts: List + get() = listOf( + // fixme + Account(byteArrayOf()) + ) + override val seedPhrase: String get() = "wish puppy smile loan doll curve hole maze file ginger hair nose key relax knife witness cannon" + diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt index c46f15932..e03c6796a 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt @@ -637,12 +637,11 @@ class SdkSynchronizer private constructor( // Account management // - // Not ready to be a public API; internal for testing only - internal suspend fun createAccount( + override suspend fun createAccount( seed: ByteArray, treeState: TreeState, recoverUntil: BlockHeight? - ): UnifiedSpendingKey? { + ): UnifiedSpendingKey { return runCatching { backend.createAccountAndGetSpendingKey( seed = seed, @@ -651,7 +650,19 @@ class SdkSynchronizer private constructor( ) }.onFailure { Twig.error(it) { "Create account failed." } - }.getOrNull() + }.getOrElse { + throw InitializeException.CreateAccountException(it) + } + } + + override suspend fun getAccounts(): List { + return runCatching { + backend.getAccounts() + }.onFailure { + Twig.error(it) { "Get wallet accounts failed." } + }.getOrElse { + throw InitializeException.GetAccountsException(it) + } } /** diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt index b59ce29af..bfbd74cfd 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt @@ -123,6 +123,14 @@ interface Synchronizer { // Operations // + /** + * Returns all the wallet accounts or throws [InitializeException.GetAccountsException] + * + * @return List of all wallet accounts + * @throws [InitializeException.GetAccountsException] in case of the operation failure + */ + suspend fun getAccounts(): List + /** * Measure connection quality and speed of given [servers]. * @@ -133,7 +141,6 @@ interface Synchronizer { servers: List ): Flow - @Suppress("ktlint:standard:no-consecutive-comments") /** * Adds the next available account-level spend authority, given the current set of * [ZIP 316](https://zips.z.cash/zip-0316) account identifiers known, to the wallet @@ -154,13 +161,19 @@ interface Synchronizer { * automated account recovery). * * @param seed the wallet's seed phrase. + * @param treeState + * @param recoverUntil * * @return the newly created ZIP 316 account identifier, along with the binary * encoding of the `UnifiedSpendingKey` for the newly created account. * - * This is not yet ready to be a public API! - * suspend fun createAccount(seed: ByteArray): UnifiedSpendingKey + * @throws [InitializeException.CreateAccountException] in case of the operation failure **/ + suspend fun createAccount( + seed: ByteArray, + treeState: TreeState, + recoverUntil: BlockHeight? + ): UnifiedSpendingKey /** * Gets the current unified address for the given account. diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/exception/Exceptions.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/exception/Exceptions.kt index 7cd75089a..1e9bb905d 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/exception/Exceptions.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/exception/Exceptions.kt @@ -204,7 +204,11 @@ sealed class InitializeException(message: String, cause: Throwable? = null) : Sd private fun readResolve(): Any = SeedNotRelevant } - class FalseStart(cause: Throwable?) : InitializeException("Failed to initialize accounts due to: $cause", cause) + class GetAccountsException(cause: Throwable?) : InitializeException( + "Failed to get accounts due to: ${cause?.message}", cause) + + class CreateAccountException(cause: Throwable?) : InitializeException( + "Failed to create new account due to: ${cause?.message}", cause) class AlreadyInitializedException(cause: Throwable, dbPath: String) : InitializeException( "Failed to initialize the blocks table" + diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/WalletSummary.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/WalletSummary.kt index c28946007..94c12e01d 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/WalletSummary.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/WalletSummary.kt @@ -1,6 +1,7 @@ package cash.z.ecc.android.sdk.internal.model import cash.z.ecc.android.sdk.model.Account +import cash.z.ecc.android.sdk.model.AccountBalance import cash.z.ecc.android.sdk.model.BlockHeight internal data class WalletSummary( diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/AccountBalance.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/AccountBalance.kt similarity index 83% rename from sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/AccountBalance.kt rename to sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/AccountBalance.kt index 9b363baa3..5293de208 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/AccountBalance.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/AccountBalance.kt @@ -1,9 +1,8 @@ -package cash.z.ecc.android.sdk.internal.model +package cash.z.ecc.android.sdk.model -import cash.z.ecc.android.sdk.model.WalletBalance -import cash.z.ecc.android.sdk.model.Zatoshi +import cash.z.ecc.android.sdk.internal.model.JniAccountBalance -internal data class AccountBalance( +data class AccountBalance( val sapling: WalletBalance, val orchard: WalletBalance, val unshielded: Zatoshi From 7ba8578d016cf69c83d482b633ba9c2452243a49 Mon Sep 17 00:00:00 2001 From: Honza Date: Mon, 25 Nov 2024 14:33:43 +0100 Subject: [PATCH 03/25] Refactor balances APIs --- CHANGELOG.md | 5 +++ .../cash/z/ecc/android/sdk/SdkSynchronizer.kt | 14 +++++--- .../cash/z/ecc/android/sdk/Synchronizer.kt | 29 +++++----------- .../block/processor/CompactBlockProcessor.kt | 34 +++++++------------ 4 files changed, 35 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1135ba7f..0138a7902 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `Synchronizer.getAccounts` - `Synchronizer.createAccount` + +### Changed +- `Synchronizer.orchardBalances`, `Synchronizer.saplingBalances`, and `Synchronizer.transparentBalance` have been + replaced by `Synchronizer.walletBalances` that provides these balances based on `Account` + ## [2.2.6] - 2024-11-16 ### Added diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt index e03c6796a..ea8a1ecef 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt @@ -215,9 +215,7 @@ class SdkSynchronizer private constructor( val coroutineScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) - override val orchardBalances = processor.orchardBalances.asStateFlow() - override val saplingBalances = processor.saplingBalances.asStateFlow() - override val transparentBalance = processor.transparentBalance.asStateFlow() + override val walletBalances = processor.walletBalances.asStateFlow() private val refreshExchangeRateUsd = MutableSharedFlow(replay = 1).apply { tryEmit(Unit) } @@ -511,7 +509,8 @@ class SdkSynchronizer private constructor( // Triggering UTXOs and transactions fetching at the beginning of the block synchronization right after the // app starts makes the transparent transactions appear faster. launch(CoroutineExceptionHandler(::onCriticalError)) { - refreshUtxos(Account.DEFAULT) + // Refresh UTXOs and transactions for all the wallet's accounts + refreshAllAccountsUtxos() refreshTransactions() } @@ -621,9 +620,10 @@ class SdkSynchronizer private constructor( val shouldRefresh = !scannedRange.isNullOrEmpty() || elapsedMillis > (ZcashSdk.POLL_INTERVAL * 5) val reason = if (scannedRange.isNullOrEmpty()) "it's been a while" else "new blocks were scanned" + // Refresh UTXOs, balances, and transactions for all the wallet's accounts if (shouldRefresh) { Twig.debug { "Triggering utxo refresh since $reason!" } - refreshUtxos(Account.DEFAULT) + refreshAllAccountsUtxos() Twig.debug { "Triggering balance refresh since $reason!" } refreshAllBalances() @@ -633,6 +633,10 @@ class SdkSynchronizer private constructor( } } + private suspend fun refreshAllAccountsUtxos() { + getAccounts().forEach { refreshUtxos(it) } + } + // // Account management // diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt index bfbd74cfd..f5fca276d 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt @@ -14,8 +14,10 @@ import cash.z.ecc.android.sdk.internal.SaplingParamTool import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.internal.db.DatabaseCoordinator import cash.z.ecc.android.sdk.internal.exchange.UsdExchangeRateFetcher +import cash.z.ecc.android.sdk.internal.model.TreeState import cash.z.ecc.android.sdk.internal.model.ext.toBlockHeight import cash.z.ecc.android.sdk.model.Account +import cash.z.ecc.android.sdk.model.AccountBalance import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.FastestServersResult import cash.z.ecc.android.sdk.model.ObserveFiatCurrencyResult @@ -26,7 +28,6 @@ import cash.z.ecc.android.sdk.model.TransactionOverview import cash.z.ecc.android.sdk.model.TransactionRecipient import cash.z.ecc.android.sdk.model.TransactionSubmitResult import cash.z.ecc.android.sdk.model.UnifiedSpendingKey -import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.tool.CheckpointTool @@ -78,19 +79,9 @@ interface Synchronizer { val networkHeight: StateFlow /** - * A stream of balance values for the orchard pool. + * A stream of wallet balances */ - val orchardBalances: StateFlow - - /** - * A stream of balance values for the sapling pool. - */ - val saplingBalances: StateFlow - - /** - * A stream of a balance for the transparent pool. - */ - val transparentBalance: StateFlow + val walletBalances: StateFlow?> /** * The latest known USD/ZEC exchange rate, paired with the time it was queried. @@ -178,8 +169,7 @@ interface Synchronizer { /** * Gets the current unified address for the given account. * - * @param account the account whose address is of interest. Use Account.DEFAULT to get a result for the first - * account. + * @param account the account whose address is of interest. * * @return the current unified address for the given account. */ @@ -188,8 +178,7 @@ interface Synchronizer { /** * Gets the legacy Sapling address corresponding to the current unified address for the given account. * - * @param account the account whose address is of interest. Use Account.DEFAULT to get a result for the first - * account. + * @param account the account whose address is of interest. * * @return a legacy Sapling address for the given account. */ @@ -198,8 +187,7 @@ interface Synchronizer { /** * Gets the legacy transparent address corresponding to the current unified address for the given account. * - * @param account the account whose address is of interest. Use Account.DEFAULT to get a result for the first - * account. + * @param account the account whose address is of interest. * * @return a legacy transparent address for the given account. */ @@ -422,8 +410,7 @@ interface Synchronizer { /** * Download all UTXOs for the given account addresses and store any new ones in the database. * - * @param account The Account, for which all addresses blocks will be downloaded. Use Account.DEFAULT to get a - * result for the first account. + * @param account The Account, for which all addresses blocks will be downloaded. * @param since The BlockHeight, from which blocks will be downloaded. * * @return the number of utxos that were downloaded and added to the UTXO table. diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessor.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessor.kt index f04d4f420..50d076b07 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessor.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessor.kt @@ -39,6 +39,7 @@ import cash.z.ecc.android.sdk.internal.ext.retryUpToAndThrow import cash.z.ecc.android.sdk.internal.ext.retryWithBackoff import cash.z.ecc.android.sdk.internal.ext.toHexReversed import cash.z.ecc.android.sdk.internal.metrics.TraceScope +import cash.z.ecc.android.sdk.model.AccountBalance import cash.z.ecc.android.sdk.internal.model.BlockBatch import cash.z.ecc.android.sdk.internal.model.JniBlockMeta import cash.z.ecc.android.sdk.internal.model.RewindResult @@ -59,7 +60,6 @@ import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.PercentDecimal import cash.z.ecc.android.sdk.model.RawTransaction import cash.z.ecc.android.sdk.model.TransactionSubmitResult -import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.Zatoshi import co.electriccoin.lightwallet.client.model.BlockHeightUnsafe import co.electriccoin.lightwallet.client.model.GetAddressUtxosReplyUnsafe @@ -165,11 +165,7 @@ class CompactBlockProcessor internal constructor( private val _processorInfo = MutableStateFlow(ProcessorInfo(null, null, null)) private val _networkHeight = MutableStateFlow(null) private val _fullyScannedHeight = MutableStateFlow(null) - - // pools - internal val saplingBalances = MutableStateFlow(null) - internal val orchardBalances = MutableStateFlow(null) - internal val transparentBalance = MutableStateFlow(null) + internal val walletBalances = MutableStateFlow?>(null) private val processingMutex = Mutex() @@ -511,12 +507,12 @@ class CompactBlockProcessor internal constructor( if (fullyScannedHeight.value == null) { Twig.info { "Postponing UTXOs fetching because fullyScannedHeight is null" } } else { - val fetchedCount = + backend.getAccounts().forEach { refreshUtxos( - account = Account.DEFAULT, + account = it, startHeight = fullyScannedHeight.value!! ) - Twig.info { "UTXOs fetched count: $fetchedCount" } + } } } is SyncingResult.Failure -> { @@ -621,12 +617,12 @@ class CompactBlockProcessor internal constructor( if (fullyScannedHeight.value == null) { Twig.info { "Postponing UTXOs fetching because fullyScannedHeight is null" } } else { - val fetchedCount = + backend.getAccounts().forEach { refreshUtxos( - account = Account.DEFAULT, + account = it, startHeight = fullyScannedHeight.value!! ) - Twig.debug { "UTXOs fetched count: $fetchedCount" } + } } SyncingResult.AllSuccess } @@ -773,17 +769,13 @@ class CompactBlockProcessor internal constructor( /** * Update the latest balances using the given wallet summary, and transmit this information - * into the related internal flows. + * into the related internal flow. */ internal suspend fun updateAllBalances(summary: WalletSummary) { - summary.accountBalances[Account.DEFAULT]?.let { - Twig.debug { "Updating balances" } - saplingBalances.value = it.sapling - orchardBalances.value = it.orchard - // We only allow stored transparent balance to be shielded, and we do so with - // a zero-conf transaction, so treat all unshielded balance as available. - transparentBalance.value = it.unshielded - } + // We only allow stored transparent balance to be shielded, and we do so with + // a zero-conf transaction, so treat all unshielded balance as available. + Twig.debug { "Updating balances" } + walletBalances.value = summary.accountBalances } /** From e8be36a0a70606b5fdbe470027c8c5e871608370 Mon Sep 17 00:00:00 2001 From: Honza Date: Mon, 25 Nov 2024 16:51:48 +0100 Subject: [PATCH 04/25] 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. --- .../sdk/demoapp/{ui/common => }/Constants.kt | 4 +- .../demos/getaddress/GetAddressFragment.kt | 11 +++-- .../demos/getbalance/GetBalanceFragment.kt | 47 ++++++++++++------- .../getprivatekey/GetPrivateKeyFragment.kt | 3 +- .../demos/listutxos/ListUtxosFragment.kt | 4 +- .../sdk/demoapp/demos/send/SendFragment.kt | 12 ++++- .../main/res/menu/activity_main_drawer.xml | 2 +- .../main/res/navigation/mobile_navigation.xml | 2 +- 8 files changed, 55 insertions(+), 30 deletions(-) rename demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/{ui/common => }/Constants.kt (80%) diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/common/Constants.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Constants.kt similarity index 80% rename from demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/common/Constants.kt rename to demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Constants.kt index 51a13bc42..15e51b731 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/common/Constants.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Constants.kt @@ -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 @@ -9,3 +9,5 @@ val ANDROID_STATE_FLOW_TIMEOUT = 5.seconds * A tiny weight, useful for spacers to fill an empty space. */ const val MINIMAL_WEIGHT = 0.0001f + +const val CURRENT_ACCOUNT = 0 \ No newline at end of file diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getaddress/GetAddressFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getaddress/GetAddressFragment.kt index 32f00d3f8..49478d214 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getaddress/GetAddressFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getaddress/GetAddressFragment.kt @@ -1,17 +1,18 @@ package cash.z.ecc.android.sdk.demoapp.demos.getaddress import android.os.Bundle +import android.provider.SyncStateContract import android.view.LayoutInflater import android.view.View 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_ACCOUNT 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 @@ -30,23 +31,25 @@ class GetAddressFragment : BaseDemoFragment() { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { launch { sharedViewModel.synchronizerFlow.filterNotNull().collect { synchronizer -> + val account = synchronizer.getAccounts()[CURRENT_ACCOUNT] + 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) } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt index 4469f3576..9144e5ee3 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt @@ -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_ACCOUNT 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 @@ -78,7 +79,7 @@ class GetBalanceFragment : BaseDemoFragment() { DerivationTool.getInstance().deriveUnifiedSpendingKey( seed, network, - Account.DEFAULT + CURRENT_ACCOUNT ) sharedViewModel.synchronizerFlow.value?.let { synchronizer -> synchronizer.proposeShielding(usk.account, Zatoshi(100000))?.let { it1 -> @@ -116,10 +117,12 @@ class GetBalanceFragment : BaseDemoFragment() { sharedViewModel.synchronizerFlow .filterNotNull() .flatMapLatest { - it.saplingBalances.combine(it.exchangeRateUsd) { b, r -> - b?.let { - b to - r.currencyConversion + val account = it.getAccounts()[CURRENT_ACCOUNT] + it.walletBalances.combine(it.exchangeRateUsd) { balances, rate -> + balances?.let { + val walletBalance = balances[account]!!.sapling + walletBalance to + rate.currencyConversion ?.priceOfZec ?.toBigDecimal() } @@ -131,23 +134,28 @@ class GetBalanceFragment : BaseDemoFragment() { sharedViewModel.synchronizerFlow .filterNotNull() .flatMapLatest { - it.orchardBalances.combine(it.exchangeRateUsd) { b, r -> - b?.let { - b to - r.currencyConversion?.priceOfZec?.toBigDecimal() + val account = it.getAccounts()[CURRENT_ACCOUNT] + 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_ACCOUNT] + it.walletBalances.combine(it.exchangeRateUsd) { balances, rate -> + balances?.let { + val walletBalance = balances[account]!!.unshielded + walletBalance to + rate.currencyConversion ?.priceOfZec ?.toBigDecimal() } @@ -208,9 +216,12 @@ class GetBalanceFragment : BaseDemoFragment() { 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_ACCOUNT] + 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) }) + } } } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getprivatekey/GetPrivateKeyFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getprivatekey/GetPrivateKeyFragment.kt index 20ba6d7e3..66ae8fde3 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getprivatekey/GetPrivateKeyFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getprivatekey/GetPrivateKeyFragment.kt @@ -13,7 +13,6 @@ import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetPrivateKeyBinding import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext import cash.z.ecc.android.sdk.demoapp.util.fromResources -import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.tool.DerivationTool import kotlinx.coroutines.launch @@ -50,7 +49,7 @@ class GetPrivateKeyFragment : BaseDemoFragment() { DerivationTool.getInstance().deriveUnifiedSpendingKey( seed, ZcashNetwork.fromResources(requireApplicationContext()), - Account(5) + 5 ) // derive the key that allows you to view but not spend transactions diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/ListUtxosFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/ListUtxosFragment.kt index a21501495..f057b2037 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/ListUtxosFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/ListUtxosFragment.kt @@ -10,12 +10,12 @@ import androidx.recyclerview.widget.LinearLayoutManager import cash.z.ecc.android.sdk.SdkSynchronizer import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment +import cash.z.ecc.android.sdk.demoapp.CURRENT_ACCOUNT import cash.z.ecc.android.sdk.demoapp.databinding.FragmentListUtxosBinding import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext import cash.z.ecc.android.sdk.demoapp.util.fromResources import cash.z.ecc.android.sdk.demoapp.util.mainActivity 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.PercentDecimal import cash.z.ecc.android.sdk.model.TransactionOverview @@ -188,7 +188,7 @@ class ListUtxosFragment : BaseDemoFragment() { .filterNotNull() .collect { binding.inputAddress.setText( - it.getTransparentAddress(Account.DEFAULT) + it.getTransparentAddress(it.getAccounts()[CURRENT_ACCOUNT]) ) } } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt index 170cff3c5..21bd22501 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt @@ -10,6 +10,7 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment +import cash.z.ecc.android.sdk.demoapp.CURRENT_ACCOUNT import cash.z.ecc.android.sdk.demoapp.DemoConstants import cash.z.ecc.android.sdk.demoapp.databinding.FragmentSendBinding import cash.z.ecc.android.sdk.demoapp.util.mainActivity @@ -22,6 +23,7 @@ import cash.z.ecc.android.sdk.model.WalletBalance import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.launch /** @@ -96,7 +98,15 @@ class SendFragment : BaseDemoFragment() { launch { sharedViewModel.synchronizerFlow .filterNotNull() - .flatMapLatest { it.saplingBalances } + .flatMapLatest { + val account = it.getAccounts()[CURRENT_ACCOUNT] + it.walletBalances.mapLatest { balances -> + balances?.let { + val walletBalance = balances[account]!!.sapling + walletBalance + } + } + } .collect { onBalance(it) } } } diff --git a/demo-app/src/main/res/menu/activity_main_drawer.xml b/demo-app/src/main/res/menu/activity_main_drawer.xml index 0c15f8151..41dcdd581 100644 --- a/demo-app/src/main/res/menu/activity_main_drawer.xml +++ b/demo-app/src/main/res/menu/activity_main_drawer.xml @@ -12,7 +12,7 @@ + android:title="@string/menu_keys" /> Date: Thu, 28 Nov 2024 14:11:37 +0100 Subject: [PATCH 05/25] Add init to `JniUnifiedSpendingKey.kt` --- .../ecc/android/sdk/internal/model/JniUnifiedSpendingKey.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniUnifiedSpendingKey.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniUnifiedSpendingKey.kt index 5eaa1ffe0..766c87736 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniUnifiedSpendingKey.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniUnifiedSpendingKey.kt @@ -27,6 +27,12 @@ class JniUnifiedSpendingKey( */ val bytes: ByteArray ) { + init { + require(accountUuid.size == 16) { + "Account UUID must be 16 bytes" + } + } + // Override to prevent leaking key to logs override fun toString() = "JniUnifiedSpendingKey(account=$account, bytes=***)" From 1ed11c025b2d9b9fcfb91c84bba4601dbb26ebee Mon Sep 17 00:00:00 2001 From: Honza Date: Thu, 28 Nov 2024 14:12:52 +0100 Subject: [PATCH 06/25] File and link followup --- .../src/main/java/cash/z/ecc/android/sdk/demoapp/Constants.kt | 4 +++- .../z/ecc/android/sdk/internal/db/derived/TxOutputsView.kt | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Constants.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Constants.kt index 15e51b731..e46a96da2 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Constants.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Constants.kt @@ -10,4 +10,6 @@ val ANDROID_STATE_FLOW_TIMEOUT = 5.seconds */ const val MINIMAL_WEIGHT = 0.0001f -const val CURRENT_ACCOUNT = 0 \ No newline at end of file +// 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 \ No newline at end of file diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/TxOutputsView.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/TxOutputsView.kt index d70fe4cf4..263bcf42c 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/TxOutputsView.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/TxOutputsView.kt @@ -71,7 +71,9 @@ internal class TxOutputsView(private val sqliteDatabase: SupportSQLiteDatabase) val toAddressIndex = it.getColumnIndex(TxOutputsViewDefinition.COLUMN_STRING_TO_ADDRESS) if (!it.isNull(toAccountIndex)) { - TransactionRecipient.Account(Account(it.getInt(toAccountIndex))) + // TODO [1644]: Refactor Account ZIP32 index across SDK + // TODO [1644]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/1644 + TransactionRecipient.Account(0) } else { TransactionRecipient.Address(it.getString(toAddressIndex)) } From 2a0906722f4facc6d4393fccb22f2d86cf233838 Mon Sep 17 00:00:00 2001 From: Honza Date: Thu, 28 Nov 2024 14:16:57 +0100 Subject: [PATCH 07/25] Update deprecated Fragment-based Demo app This part of the Demo app will be removed as part of #973 --- .../demoapp/demos/getaddress/GetAddressFragment.kt | 5 ++--- .../demoapp/demos/getbalance/GetBalanceFragment.kt | 13 ++++++------- .../demoapp/demos/listutxos/ListUtxosFragment.kt | 4 ++-- .../android/sdk/demoapp/demos/send/SendFragment.kt | 4 ++-- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getaddress/GetAddressFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getaddress/GetAddressFragment.kt index 49478d214..47c3e9ef1 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getaddress/GetAddressFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getaddress/GetAddressFragment.kt @@ -1,14 +1,13 @@ package cash.z.ecc.android.sdk.demoapp.demos.getaddress import android.os.Bundle -import android.provider.SyncStateContract import android.view.LayoutInflater import android.view.View 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_ACCOUNT +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 @@ -31,7 +30,7 @@ class GetAddressFragment : BaseDemoFragment() { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { launch { sharedViewModel.synchronizerFlow.filterNotNull().collect { synchronizer -> - val account = synchronizer.getAccounts()[CURRENT_ACCOUNT] + val account = synchronizer.getAccounts()[CURRENT_ZIP_32_ACCOUNT_INDEX] binding.unifiedAddress.apply { reportTraceEvent(ProvideAddressBenchmarkTrace.Event.UNIFIED_ADDRESS_START) diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt index 9144e5ee3..5f97ab4c3 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt @@ -10,7 +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_ACCOUNT +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 @@ -19,7 +19,6 @@ import cash.z.ecc.android.sdk.ext.convertZatoshiToZec import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString import cash.z.ecc.android.sdk.ext.toUsdString import cash.z.ecc.android.sdk.internal.Twig -import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.PercentDecimal import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.Zatoshi @@ -79,7 +78,7 @@ class GetBalanceFragment : BaseDemoFragment() { DerivationTool.getInstance().deriveUnifiedSpendingKey( seed, network, - CURRENT_ACCOUNT + CURRENT_ZIP_32_ACCOUNT_INDEX ) sharedViewModel.synchronizerFlow.value?.let { synchronizer -> synchronizer.proposeShielding(usk.account, Zatoshi(100000))?.let { it1 -> @@ -117,7 +116,7 @@ class GetBalanceFragment : BaseDemoFragment() { sharedViewModel.synchronizerFlow .filterNotNull() .flatMapLatest { - val account = it.getAccounts()[CURRENT_ACCOUNT] + val account = it.getAccounts()[CURRENT_ZIP_32_ACCOUNT_INDEX] it.walletBalances.combine(it.exchangeRateUsd) { balances, rate -> balances?.let { val walletBalance = balances[account]!!.sapling @@ -134,7 +133,7 @@ class GetBalanceFragment : BaseDemoFragment() { sharedViewModel.synchronizerFlow .filterNotNull() .flatMapLatest { - val account = it.getAccounts()[CURRENT_ACCOUNT] + val account = it.getAccounts()[CURRENT_ZIP_32_ACCOUNT_INDEX] it.walletBalances.combine(it.exchangeRateUsd) { balances, rate -> balances?.let { val walletBalance = balances[account]!!.orchard @@ -150,7 +149,7 @@ class GetBalanceFragment : BaseDemoFragment() { sharedViewModel.synchronizerFlow .filterNotNull() .flatMapLatest { - val account = it.getAccounts()[CURRENT_ACCOUNT] + val account = it.getAccounts()[CURRENT_ZIP_32_ACCOUNT_INDEX] it.walletBalances.combine(it.exchangeRateUsd) { balances, rate -> balances?.let { val walletBalance = balances[account]!!.unshielded @@ -217,7 +216,7 @@ class GetBalanceFragment : BaseDemoFragment() { sharedViewModel.synchronizerFlow.value?.let { synchronizer -> val rate = synchronizer.exchangeRateUsd.value.currencyConversion?.priceOfZec?.toBigDecimal() viewLifecycleOwner.lifecycleScope.launch { - val account = synchronizer.getAccounts()[CURRENT_ACCOUNT] + 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) }) diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/ListUtxosFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/ListUtxosFragment.kt index f057b2037..b5dd07e75 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/ListUtxosFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/ListUtxosFragment.kt @@ -10,7 +10,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import cash.z.ecc.android.sdk.SdkSynchronizer import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment -import cash.z.ecc.android.sdk.demoapp.CURRENT_ACCOUNT +import cash.z.ecc.android.sdk.demoapp.CURRENT_ZIP_32_ACCOUNT_INDEX import cash.z.ecc.android.sdk.demoapp.databinding.FragmentListUtxosBinding import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext import cash.z.ecc.android.sdk.demoapp.util.fromResources @@ -188,7 +188,7 @@ class ListUtxosFragment : BaseDemoFragment() { .filterNotNull() .collect { binding.inputAddress.setText( - it.getTransparentAddress(it.getAccounts()[CURRENT_ACCOUNT]) + it.getTransparentAddress(it.getAccounts()[CURRENT_ZIP_32_ACCOUNT_INDEX]) ) } } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt index 21bd22501..6924f4554 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt @@ -10,7 +10,7 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment -import cash.z.ecc.android.sdk.demoapp.CURRENT_ACCOUNT +import cash.z.ecc.android.sdk.demoapp.CURRENT_ZIP_32_ACCOUNT_INDEX import cash.z.ecc.android.sdk.demoapp.DemoConstants import cash.z.ecc.android.sdk.demoapp.databinding.FragmentSendBinding import cash.z.ecc.android.sdk.demoapp.util.mainActivity @@ -99,7 +99,7 @@ class SendFragment : BaseDemoFragment() { sharedViewModel.synchronizerFlow .filterNotNull() .flatMapLatest { - val account = it.getAccounts()[CURRENT_ACCOUNT] + val account = it.getAccounts()[CURRENT_ZIP_32_ACCOUNT_INDEX] it.walletBalances.mapLatest { balances -> balances?.let { val walletBalance = balances[account]!!.sapling From 1a9662caff9a1e8d15ec8282695d4d3a9ebb51dc Mon Sep 17 00:00:00 2001 From: Honza Date: Thu, 28 Nov 2024 14:18:22 +0100 Subject: [PATCH 08/25] Remove deprecated functions from Synchronizer --- .../cash/z/ecc/android/sdk/SdkSynchronizer.kt | 68 ------------------- .../cash/z/ecc/android/sdk/Synchronizer.kt | 39 ----------- 2 files changed, 107 deletions(-) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt index ea8a1ecef..59e6b1aa2 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt @@ -771,74 +771,6 @@ class SdkSynchronizer private constructor( } } - @Deprecated( - message = "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.", - replaceWith = - ReplaceWith( - "createProposedTransactions(proposeTransfer(usk.account, toAddress, amount, memo), usk)" - ) - ) - @Throws(TransactionEncoderException::class, LightWalletException.TransactionSubmitException::class) - override suspend fun sendToAddress( - usk: UnifiedSpendingKey, - amount: Zatoshi, - toAddress: String, - memo: String - ): Long { - val encodedTx = - txManager.encode( - usk, - amount, - TransactionRecipient.Address(toAddress), - memo, - usk.account - ) - - when (txManager.submit(encodedTx)) { - is TransactionSubmitResult.Success -> { - return storage.findMatchingTransactionId(encodedTx.txId.byteArray)!! - } - else -> { - throw LightWalletException.TransactionSubmitException() - } - } - } - - @Deprecated( - message = "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.", - replaceWith = - ReplaceWith( - "proposeShielding(usk.account, shieldingThreshold, memo)?.let { createProposedTransactions(it, usk) }" - ) - ) - @Throws(TransactionEncoderException::class, LightWalletException.TransactionSubmitException::class) - override suspend fun shieldFunds( - usk: UnifiedSpendingKey, - memo: String - ): Long { - Twig.debug { "Initializing shielding transaction" } - val tAddr = CompactBlockProcessor.getTransparentAddress(backend, usk.account) - val tBalance = processor.getUtxoCacheBalance(tAddr) - - val encodedTx = - txManager.encode( - usk, - tBalance, - TransactionRecipient.Account(usk.account), - memo, - usk.account - ) - - when (txManager.submit(encodedTx)) { - is TransactionSubmitResult.Success -> { - return storage.findMatchingTransactionId(encodedTx.txId.byteArray)!! - } - else -> { - throw LightWalletException.TransactionSubmitException() - } - } - } - override suspend fun refreshUtxos( account: Account, since: BlockHeight diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt index f5fca276d..d860c6484 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt @@ -269,45 +269,6 @@ interface Synchronizer { usk: UnifiedSpendingKey ): Flow - /** - * Sends zatoshi. - * - * @param usk the unified spending key associated with the notes that will be spent. - * @param amount the amount of zatoshi to send. - * @param toAddress the recipient's address. - * @param memo the optional memo to include as part of the transaction. - * - * @return a flow of PendingTransaction objects representing changes to the state of the - * transaction. Any time the state changes a new instance will be emitted by this flow. This is - * useful for updating the UI without needing to poll. Of course, polling is always an option - * for any wallet that wants to ignore this return value. - */ - @Deprecated( - message = "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.", - replaceWith = - ReplaceWith( - "createProposedTransactions(proposeTransfer(usk.account, toAddress, amount, memo), usk)" - ) - ) - suspend fun sendToAddress( - usk: UnifiedSpendingKey, - amount: Zatoshi, - toAddress: String, - memo: String = "" - ): Long - - @Deprecated( - message = "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.", - replaceWith = - ReplaceWith( - "proposeShielding(usk.account, shieldingThreshold, memo)?.let { createProposedTransactions(it, usk) }" - ) - ) - suspend fun shieldFunds( - usk: UnifiedSpendingKey, - memo: String = ZcashSdk.DEFAULT_SHIELD_FUNDS_MEMO_PREFIX - ): Long - // TODO [#1534]: Add RustLayerException.ValidateAddressException // TODO [#1534]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/1534 From 12125dea784b23e8169107ebed6b84571e6ae602 Mon Sep 17 00:00:00 2001 From: Honza Date: Thu, 28 Nov 2024 14:21:13 +0100 Subject: [PATCH 09/25] Update WalletSnapshot and WalletVM APIs in Demo + necessary fixtures changes --- .../demoapp/fixture/WalletSnapshotFixture.kt | 22 ++++---- .../screen/home/viewmodel/WalletSnapshot.kt | 27 +++------- .../screen/home/viewmodel/WalletViewModel.kt | 54 +++++++++++-------- .../sdk/fixture/AccountBalanceFixture.kt | 22 ++++++++ .../ecc/android/sdk/fixture/AccountFixture.kt | 4 +- .../ecc/android/sdk/fixture/WalletFixture.kt | 10 +--- .../ecc/android/sdk/model/WalletAddresses.kt | 5 +- .../cash/z/ecc/android/sdk/model/Account.kt | 17 ++++++ 8 files changed, 95 insertions(+), 66 deletions(-) create mode 100644 sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountBalanceFixture.kt rename {sdk-lib => sdk-incubator-lib}/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt (61%) diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/fixture/WalletSnapshotFixture.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/fixture/WalletSnapshotFixture.kt index 36f5a89dc..66135a52d 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/fixture/WalletSnapshotFixture.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/fixture/WalletSnapshotFixture.kt @@ -4,20 +4,22 @@ import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.block.processor.CompactBlockProcessor import cash.z.ecc.android.sdk.demoapp.ui.screen.home.viewmodel.SynchronizerError import cash.z.ecc.android.sdk.demoapp.ui.screen.home.viewmodel.WalletSnapshot -import cash.z.ecc.android.sdk.fixture.WalletBalanceFixture +import cash.z.ecc.android.sdk.fixture.AccountBalanceFixture +import cash.z.ecc.android.sdk.fixture.AccountFixture +import cash.z.ecc.android.sdk.model.Account +import cash.z.ecc.android.sdk.model.AccountBalance import cash.z.ecc.android.sdk.model.PercentDecimal -import cash.z.ecc.android.sdk.model.WalletBalance -import cash.z.ecc.android.sdk.model.Zatoshi import java.math.BigDecimal @Suppress("MagicNumber") object WalletSnapshotFixture { val STATUS = Synchronizer.Status.SYNCED val PROGRESS = PercentDecimal.ZERO_PERCENT - val TRANSPARENT_BALANCE: Zatoshi = Zatoshi(8) - val ORCHARD_BALANCE: WalletBalance = WalletBalanceFixture.new(Zatoshi(5), Zatoshi(2), Zatoshi(1)) - val SAPLING_BALANCE: WalletBalance = WalletBalanceFixture.new(Zatoshi(4), Zatoshi(4), Zatoshi(2)) val EXCHANGE_RATE_USD: BigDecimal = BigDecimal(37.4850) + val ACCOUNT = AccountFixture.new() + val WALLET_BALANCES: Map = mapOf( + ACCOUNT to AccountBalanceFixture.new() + ) // Should fill in with non-empty values for better example values in tests and UI previews @Suppress("LongParameterList") @@ -29,18 +31,14 @@ object WalletSnapshotFixture { null, null ), - orchardBalance: WalletBalance = ORCHARD_BALANCE, - saplingBalance: WalletBalance = SAPLING_BALANCE, - transparentBalance: Zatoshi = TRANSPARENT_BALANCE, + walletBalances: Map = WALLET_BALANCES, exchangeRateUsd: BigDecimal? = EXCHANGE_RATE_USD, progress: PercentDecimal = PROGRESS, synchronizerError: SynchronizerError? = null ) = WalletSnapshot( status, processorInfo, - orchardBalance, - saplingBalance, - transparentBalance, + walletBalances, exchangeRateUsd, progress, synchronizerError diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletSnapshot.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletSnapshot.kt index 08370fa46..dc9c49ea2 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletSnapshot.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletSnapshot.kt @@ -2,33 +2,20 @@ package cash.z.ecc.android.sdk.demoapp.ui.screen.home.viewmodel import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.block.processor.CompactBlockProcessor +import cash.z.ecc.android.sdk.model.Account +import cash.z.ecc.android.sdk.model.AccountBalance import cash.z.ecc.android.sdk.model.PercentDecimal -import cash.z.ecc.android.sdk.model.WalletBalance -import cash.z.ecc.android.sdk.model.Zatoshi import java.math.BigDecimal data class WalletSnapshot( val status: Synchronizer.Status, val processorInfo: CompactBlockProcessor.ProcessorInfo, - val orchardBalance: WalletBalance, - val saplingBalance: WalletBalance, - val transparentBalance: Zatoshi, + val walletBalances: Map, val exchangeRateUsd: BigDecimal?, val progress: PercentDecimal, val synchronizerError: SynchronizerError? ) { - // Note: the wallet is effectively empty if it cannot cover the miner's fee - // This check is not entirely correct - it does not calculate the resulting fee with the new Proposal API - val hasFunds = saplingBalance.available.value > 0L - - val hasSaplingBalance = saplingBalance.total.value > 0L - - val isSendEnabled: Boolean get() = status == Synchronizer.Status.SYNCED && hasFunds -} - -fun WalletSnapshot.totalBalance() = orchardBalance.total + saplingBalance.total + transparentBalance - -// Note that considering both to be spendable is subject to change. -// The user experience could be confusing, and in the future we might prefer to ask users -// to transfer their balance to the latest balance type to make it spendable. -fun WalletSnapshot.spendableBalance() = orchardBalance.available + saplingBalance.available + fun balanceByAccount(account: Account): AccountBalance { + return walletBalances[account] ?: error("Balance of $account could not be find.") + } +} \ No newline at end of file diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletViewModel.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletViewModel.kt index 1053a16b6..c7920f9f0 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletViewModel.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletViewModel.kt @@ -10,15 +10,17 @@ import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.WalletCoordinator import cash.z.ecc.android.sdk.WalletInitMode import cash.z.ecc.android.sdk.block.processor.CompactBlockProcessor +import cash.z.ecc.android.sdk.demoapp.ANDROID_STATE_FLOW_TIMEOUT +import cash.z.ecc.android.sdk.demoapp.CURRENT_ZIP_32_ACCOUNT_INDEX import cash.z.ecc.android.sdk.demoapp.ext.defaultForNetwork import cash.z.ecc.android.sdk.demoapp.getInstance import cash.z.ecc.android.sdk.demoapp.preference.EncryptedPreferenceKeys import cash.z.ecc.android.sdk.demoapp.preference.EncryptedPreferenceSingleton -import cash.z.ecc.android.sdk.demoapp.ui.common.ANDROID_STATE_FLOW_TIMEOUT import cash.z.ecc.android.sdk.demoapp.ui.common.throttle import cash.z.ecc.android.sdk.demoapp.util.fromResources import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.model.Account +import cash.z.ecc.android.sdk.model.AccountBalance import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.ObserveFiatCurrencyResult import cash.z.ecc.android.sdk.model.PercentDecimal @@ -26,7 +28,6 @@ import cash.z.ecc.android.sdk.model.PersistableWallet import cash.z.ecc.android.sdk.model.Proposal import cash.z.ecc.android.sdk.model.TransactionSubmitResult import cash.z.ecc.android.sdk.model.WalletAddresses -import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZecSend @@ -107,7 +108,7 @@ class WalletViewModel(application: Application) : AndroidViewModel(application) DerivationTool.getInstance().deriveUnifiedSpendingKey( seed = bip39Seed, network = it.network, - account = Account.DEFAULT + account = getCurrentAccount() ) }.stateIn( viewModelScope, @@ -137,7 +138,7 @@ class WalletViewModel(application: Application) : AndroidViewModel(application) .filterNotNull() .map { runCatching { - WalletAddresses.new(it) + WalletAddresses.new(getCurrentAccount(), it) }.onFailure { Twig.warn { "Wait until the SDK starts providing the addresses" } }.getOrNull() @@ -353,6 +354,24 @@ class WalletViewModel(application: Application) : AndroidViewModel(application) } } + fun getAccounts(): List { + val synchronizer = synchronizer.value + + return if (null != synchronizer) { + runBlocking { + kotlin.runCatching { + synchronizer.getAccounts() + }.onFailure { + Twig.error(it) { "Failed to get wallet accounts" } + }.getOrThrow() + } + } else { + error("Unable get wallet accounts.") + } + } + + fun getCurrentAccount(): Account = getAccounts()[CURRENT_ZIP_32_ACCOUNT_INDEX] + companion object { private const val QUICK_REWIND_BLOCKS = 100 } @@ -454,7 +473,7 @@ private fun Synchronizer.toCommonError(): Flow = } // No good way around needing magic numbers for the indices -@Suppress("MagicNumber") +@Suppress("MagicNumber", "UNCHECKED_CAST") private fun Synchronizer.toWalletSnapshot() = combine( // 0 @@ -462,34 +481,23 @@ private fun Synchronizer.toWalletSnapshot() = // 1 processorInfo, // 2 - orchardBalances, + walletBalances.filterNotNull(), // 3 - saplingBalances, - // 4 - transparentBalance, - // 5 exchangeRateUsd, - // 6 + // 4 progress, - // 7 + // 5 toCommonError() ) { flows -> - val orchardBalance = flows[2] as WalletBalance? - val saplingBalance = flows[3] as WalletBalance? - val transparentBalance = flows[4] as Zatoshi? - - @Suppress("UNCHECKED_CAST") - val exchangeRateUsd = flows[5] as ObserveFiatCurrencyResult - val progressPercentDecimal = (flows[6] as PercentDecimal) + val exchangeRateUsd = flows[3] as ObserveFiatCurrencyResult + val progressPercentDecimal = (flows[4] as PercentDecimal) WalletSnapshot( flows[0] as Synchronizer.Status, flows[1] as CompactBlockProcessor.ProcessorInfo, - orchardBalance ?: WalletBalance(Zatoshi(0), Zatoshi(0), Zatoshi(0)), - saplingBalance ?: WalletBalance(Zatoshi(0), Zatoshi(0), Zatoshi(0)), - transparentBalance ?: Zatoshi(0), + flows[2] as Map, exchangeRateUsd.currencyConversion?.priceOfZec?.toBigDecimal(), progressPercentDecimal, - flows[7] as SynchronizerError? + flows[5] as SynchronizerError? ) } diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountBalanceFixture.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountBalanceFixture.kt new file mode 100644 index 000000000..ab5abec8f --- /dev/null +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountBalanceFixture.kt @@ -0,0 +1,22 @@ +package cash.z.ecc.android.sdk.fixture + +import cash.z.ecc.android.sdk.model.AccountBalance +import cash.z.ecc.android.sdk.model.WalletBalance +import cash.z.ecc.android.sdk.model.Zatoshi + +@Suppress("MagicNumber") +object AccountBalanceFixture { + val TRANSPARENT_BALANCE: Zatoshi = Zatoshi(8) + val SAPLING_BALANCE: WalletBalance = WalletBalanceFixture.new(Zatoshi(4), Zatoshi(4), Zatoshi(2)) + val ORCHARD_BALANCE: WalletBalance = WalletBalanceFixture.new(Zatoshi(5), Zatoshi(2), Zatoshi(1)) + + fun new( + orchardBalance: WalletBalance = ORCHARD_BALANCE, + saplingBalance: WalletBalance = SAPLING_BALANCE, + transparentBalance: Zatoshi = TRANSPARENT_BALANCE + ) = AccountBalance( + saplingBalance, + orchardBalance, + transparentBalance + ) +} diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt similarity index 61% rename from sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt rename to sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt index b89475b22..1b413e1b6 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt @@ -3,10 +3,10 @@ package cash.z.ecc.android.sdk.fixture import cash.z.ecc.android.sdk.model.Account object AccountFixture { - val DEFAULT_UUID: ByteArray = "test_uuid".toByteArray() + val ACCOUNT_UUID = WalletFixture.Alice.accounts[0].accountUuid fun new( - accountUuid: ByteArray = DEFAULT_UUID + accountUuid: ByteArray = ACCOUNT_UUID ) = Account( accountUuid = accountUuid ) diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt index 18fb81a5a..81ed27cc1 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt @@ -19,10 +19,7 @@ sealed class WalletFixture { @Suppress("MaxLineLength") data object Ben : WalletFixture() { override val accounts: List - get() = listOf( - // fixme - Account(byteArrayOf()) - ) + get() = listOf(Account("a1b2c3d4-e5f6-7890-1234-567890abcdef".toByteArray())) override val seedPhrase: String get() = @@ -59,10 +56,7 @@ sealed class WalletFixture { @Suppress("MaxLineLength") data object Alice : WalletFixture() { override val accounts: List - get() = listOf( - // fixme - Account(byteArrayOf()) - ) + get() = listOf(Account("f47ac10b-58cc-4372-a567-0e02b2c3d479".toByteArray())) override val seedPhrase: String get() = diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/WalletAddresses.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/WalletAddresses.kt index c6b6999da..6abdd7cc6 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/WalletAddresses.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/WalletAddresses.kt @@ -11,7 +11,10 @@ data class WalletAddresses( override fun toString() = "WalletAddresses" companion object { - suspend fun new(synchronizer: Synchronizer, account: Account): WalletAddresses { + suspend fun new( + account: Account, + synchronizer: Synchronizer + ): WalletAddresses { val unified = WalletAddress.Unified.new( synchronizer.getUnifiedAddress(account) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Account.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Account.kt index 0c82fad26..5f14c9e3a 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Account.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Account.kt @@ -13,4 +13,21 @@ data class Account(val value: Int) { companion object { val DEFAULT = Account(0) } + + override fun toString(): String { + return "Account(accountUuid=${accountUuid.contentToString()})" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Account + + return accountUuid.contentEquals(other.accountUuid) + } + + override fun hashCode(): Int { + return accountUuid.contentHashCode() + } } From 5958dd8bb7d7d433b9d8990cf7afe054e662d5c0 Mon Sep 17 00:00:00 2001 From: Honza Date: Thu, 28 Nov 2024 14:21:49 +0100 Subject: [PATCH 10/25] Update newer Compose-based Demo app --- .../z/ecc/android/sdk/demoapp/Navigation.kt | 11 +- .../ui/screen/addresses/view/AddressesView.kt | 4 +- .../ui/screen/balance/view/BalanceView.kt | 41 ++--- .../demoapp/ui/screen/keys/view/KeysView.kt | 151 ------------------ .../demoapp/ui/screen/send/view/SendView.kt | 16 +- .../transactions/view/TransactionsView.kt | 6 +- demo-app/src/main/res/values/strings.xml | 9 -- .../android/sdk/model/PendingTransaction.kt | 2 +- 8 files changed, 49 insertions(+), 191 deletions(-) delete mode 100644 demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/keys/view/KeysView.kt diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Navigation.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Navigation.kt index 646baf08c..609d02c5e 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Navigation.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Navigation.kt @@ -106,9 +106,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 = { @@ -129,11 +131,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( @@ -161,8 +165,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) @@ -292,7 +297,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) } ) diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/addresses/view/AddressesView.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/addresses/view/AddressesView.kt index c43c7da5a..6a14440de 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/addresses/view/AddressesView.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/addresses/view/AddressesView.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.unit.dp import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.demoapp.R import cash.z.ecc.android.sdk.internal.Twig +import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.WalletAddresses import kotlinx.coroutines.flow.flow @@ -48,6 +49,7 @@ private fun ComposablePreview() { @Suppress("ktlint:standard:function-naming") fun Addresses( synchronizer: Synchronizer, + account: Account, copyToClipboard: (String, String) -> Unit, onBack: () -> Unit, snackbarHostState: SnackbarHostState @@ -62,7 +64,7 @@ fun Addresses( flow { emit( runCatching { - WalletAddresses.new(synchronizer) + WalletAddresses.new(account, synchronizer) }.onFailure { Twig.warn { "Wait until the SDK starts providing the addresses" } }.getOrNull() diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/balance/view/BalanceView.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/balance/view/BalanceView.kt index 131de428f..d2e2771b9 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/balance/view/BalanceView.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/balance/view/BalanceView.kt @@ -23,12 +23,13 @@ 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.demoapp.R -import cash.z.ecc.android.sdk.demoapp.fixture.WalletSnapshotFixture import cash.z.ecc.android.sdk.demoapp.ui.screen.home.viewmodel.SendState -import cash.z.ecc.android.sdk.demoapp.ui.screen.home.viewmodel.WalletSnapshot import cash.z.ecc.android.sdk.ext.convertZatoshiToZec import cash.z.ecc.android.sdk.ext.toUsdString +import cash.z.ecc.android.sdk.fixture.AccountBalanceFixture +import cash.z.ecc.android.sdk.model.AccountBalance import cash.z.ecc.android.sdk.model.toZecString +import java.math.BigDecimal @Preview(name = "Balance") @Suppress("ktlint:standard:function-naming") @@ -36,7 +37,8 @@ import cash.z.ecc.android.sdk.model.toZecString private fun ComposablePreview() { MaterialTheme { Balance( - walletSnapshot = WalletSnapshotFixture.new(), + exchangeRateUsd = BigDecimal(50), + accountBalance = AccountBalanceFixture.new(), sendState = SendState.None, onBack = {}, onShieldFunds = {}, @@ -48,7 +50,8 @@ private fun ComposablePreview() { @Composable @Suppress("ktlint:standard:function-naming") fun Balance( - walletSnapshot: WalletSnapshot, + exchangeRateUsd: BigDecimal?, + accountBalance: AccountBalance, sendState: SendState, onShieldFunds: () -> Unit, onBack: () -> Unit, @@ -63,8 +66,9 @@ fun Balance( BalanceMainContent( paddingValues = paddingValues, - walletSnapshot, - sendState, + exchangeRateUsd = exchangeRateUsd, + accountBalance = accountBalance, + sendState = sendState, onShieldFunds = onShieldFunds ) } @@ -103,8 +107,9 @@ private fun BalanceTopAppBar( @Composable @Suppress("ktlint:standard:function-naming") private fun BalanceMainContent( + exchangeRateUsd: BigDecimal?, + accountBalance: AccountBalance, paddingValues: PaddingValues, - walletSnapshot: WalletSnapshot, sendState: SendState, onShieldFunds: () -> Unit ) { @@ -117,16 +122,16 @@ private fun BalanceMainContent( Text( stringResource( id = R.string.balance_available_amount_format, - walletSnapshot.orchardBalance.available.toZecString(), - walletSnapshot.exchangeRateUsd?.multiply(walletSnapshot.orchardBalance.available.convertZatoshiToZec()) + accountBalance.orchard.available.toZecString(), + exchangeRateUsd?.multiply(accountBalance.orchard.available.convertZatoshiToZec()) .toUsdString() ) ) Text( stringResource( id = R.string.balance_pending_amount_format, - walletSnapshot.orchardBalance.pending.toZecString(), - walletSnapshot.exchangeRateUsd?.multiply(walletSnapshot.orchardBalance.pending.convertZatoshiToZec()) + accountBalance.orchard.pending.toZecString(), + exchangeRateUsd?.multiply(accountBalance.orchard.pending.convertZatoshiToZec()) .toUsdString() ) ) @@ -137,16 +142,16 @@ private fun BalanceMainContent( Text( stringResource( id = R.string.balance_available_amount_format, - walletSnapshot.saplingBalance.available.toZecString(), - walletSnapshot.exchangeRateUsd?.multiply(walletSnapshot.saplingBalance.available.convertZatoshiToZec()) + accountBalance.sapling.available.toZecString(), + exchangeRateUsd?.multiply(accountBalance.sapling.available.convertZatoshiToZec()) .toUsdString() ) ) Text( stringResource( id = R.string.balance_pending_amount_format, - walletSnapshot.saplingBalance.pending.toZecString(), - walletSnapshot.exchangeRateUsd?.multiply(walletSnapshot.saplingBalance.pending.convertZatoshiToZec()) + accountBalance.sapling.pending.toZecString(), + exchangeRateUsd?.multiply(accountBalance.sapling.pending.convertZatoshiToZec()) .toUsdString() ) ) @@ -157,14 +162,14 @@ private fun BalanceMainContent( Text( stringResource( id = R.string.balance_available_amount_format, - walletSnapshot.transparentBalance.toZecString(), - walletSnapshot.exchangeRateUsd?.multiply(walletSnapshot.transparentBalance.convertZatoshiToZec()) + accountBalance.unshielded.toZecString(), + exchangeRateUsd?.multiply(accountBalance.unshielded.convertZatoshiToZec()) .toUsdString() ) ) // This check is not entirely correct - it does not calculate the resulting fee with the new Proposal API - if (walletSnapshot.transparentBalance.value > 0L) { + if (accountBalance.unshielded.value > 0L) { // Note this implementation does not guard against multiple clicks Button(onClick = onShieldFunds) { Text(stringResource(id = R.string.action_shield)) diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/keys/view/KeysView.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/keys/view/KeysView.kt deleted file mode 100644 index 7ae768aaf..000000000 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/keys/view/KeysView.kt +++ /dev/null @@ -1,151 +0,0 @@ -package cash.z.ecc.android.sdk.demoapp.ui.screen.keys.view - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.outlined.Autorenew -import androidx.compose.material3.Button -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -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.demoapp.R -import cash.z.ecc.android.sdk.demoapp.fixture.WalletSnapshotFixture -import cash.z.ecc.android.sdk.demoapp.ui.screen.home.viewmodel.SendState -import cash.z.ecc.android.sdk.demoapp.ui.screen.home.viewmodel.WalletSnapshot -import cash.z.ecc.android.sdk.ext.convertZatoshiToZec -import cash.z.ecc.android.sdk.ext.toUsdString -import cash.z.ecc.android.sdk.model.toZecString - -@Preview(name = "Keys") -@Composable -private fun ComposablePreview() { - MaterialTheme { - Keys( - keysState = KeysState(), - onBack = {}, - ) - } -} - -@Composable -fun Keys( - keysState: KeysState, - onBack: () -> Unit, -) { - Scaffold(topBar = { - KeysTopAppBar( - onBack, - ) - }) { paddingValues -> - KeysMainContent( - paddingValues = paddingValues, - keysState = keysState, - ) - } -} - -@Composable -private fun KeysTopAppBar( - onBack: () -> Unit, -) { - TopAppBar( - title = { Text(text = stringResource(id = R.string.menu_keys)) }, - navigationIcon = { - IconButton( - onClick = onBack - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = null - ) - } - } - ) -} - -@Composable -private fun KeysMainContent( - paddingValues: PaddingValues, - keysState: KeysState, -) { - Column( - Modifier - .verticalScroll(rememberScrollState()) - .padding(top = paddingValues.calculateTopPadding()) - ) { - Text(stringResource(id = R.string.balance_orchard)) - Text( - stringResource( - id = R.string.balance_available_amount_format, - walletSnapshot.orchardBalance.available.toZecString(), - walletSnapshot.exchangeRateUsd?.multiply(walletSnapshot.orchardBalance.available.convertZatoshiToZec()) - .toUsdString() - ) - ) - Text( - stringResource( - id = R.string.balance_pending_amount_format, - walletSnapshot.orchardBalance.pending.toZecString(), - walletSnapshot.exchangeRateUsd?.multiply(walletSnapshot.orchardBalance.pending.convertZatoshiToZec()) - .toUsdString() - ) - ) - - Spacer(Modifier.padding(8.dp)) - - Text(stringResource(id = R.string.balance_sapling)) - Text( - stringResource( - id = R.string.balance_available_amount_format, - walletSnapshot.saplingBalance.available.toZecString(), - walletSnapshot.exchangeRateUsd?.multiply(walletSnapshot.saplingBalance.available.convertZatoshiToZec()) - .toUsdString() - ) - ) - Text( - stringResource( - id = R.string.balance_pending_amount_format, - walletSnapshot.saplingBalance.pending.toZecString(), - walletSnapshot.exchangeRateUsd?.multiply(walletSnapshot.saplingBalance.pending.convertZatoshiToZec()) - .toUsdString() - ) - ) - - Spacer(Modifier.padding(8.dp)) - - Text(stringResource(id = R.string.balance_transparent)) - Text( - stringResource( - id = R.string.balance_available_amount_format, - walletSnapshot.transparentBalance.toZecString(), - walletSnapshot.exchangeRateUsd?.multiply(walletSnapshot.transparentBalance.convertZatoshiToZec()) - .toUsdString() - ) - ) - - // This check is not entirely correct - it does not calculate the resulting fee with the new Proposal API - if (walletSnapshot.transparentBalance.value > 0L) { - // Note this implementation does not guard against multiple clicks - Button(onClick = onShieldFunds) { - Text(stringResource(id = R.string.action_shield)) - } - } - - // Eventually there should be something to clear the status - Text(stringResource(id = R.string.send_status, sendState.toString())) - } -} diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/send/view/SendView.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/send/view/SendView.kt index 555403938..8fa1723f8 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/send/view/SendView.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/send/view/SendView.kt @@ -36,13 +36,13 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import cash.z.ecc.android.sdk.demoapp.MINIMAL_WEIGHT import cash.z.ecc.android.sdk.demoapp.R -import cash.z.ecc.android.sdk.demoapp.fixture.WalletSnapshotFixture -import cash.z.ecc.android.sdk.demoapp.ui.common.MINIMAL_WEIGHT import cash.z.ecc.android.sdk.demoapp.ui.screen.home.viewmodel.SendState -import cash.z.ecc.android.sdk.demoapp.ui.screen.home.viewmodel.WalletSnapshot import cash.z.ecc.android.sdk.demoapp.util.fromResources +import cash.z.ecc.android.sdk.fixture.AccountBalanceFixture import cash.z.ecc.android.sdk.fixture.WalletFixture +import cash.z.ecc.android.sdk.model.AccountBalance import cash.z.ecc.android.sdk.model.Memo import cash.z.ecc.android.sdk.model.MonetarySeparators import cash.z.ecc.android.sdk.model.Proposal @@ -60,7 +60,7 @@ import java.util.Locale private fun ComposablePreview() { MaterialTheme { Send( - walletSnapshot = WalletSnapshotFixture.new(), + accountBalance = AccountBalanceFixture.new(), sendState = SendState.None, onSend = {}, onGetProposal = {}, @@ -75,7 +75,7 @@ private fun ComposablePreview() { @Composable @Suppress("ktlint:standard:function-naming", "LongParameterList") fun Send( - walletSnapshot: WalletSnapshot, + accountBalance: AccountBalance, sendState: SendState, onSend: (ZecSend) -> Unit, onGetProposal: (ZecSend) -> Unit, @@ -89,7 +89,7 @@ fun Send( }) { paddingValues -> SendMainContent( paddingValues = paddingValues, - walletSnapshot = walletSnapshot, + accountBalance = accountBalance, sendState = sendState, onSend = onSend, onGetProposal = onGetProposal, @@ -123,7 +123,7 @@ private fun SendTopAppBar(onBack: () -> Unit) { @Suppress("LongMethod", "ktlint:standard:function-naming", "LongParameterList") private fun SendMainContent( paddingValues: PaddingValues, - walletSnapshot: WalletSnapshot, + accountBalance: AccountBalance, sendState: SendState, onSend: (ZecSend) -> Unit, onGetProposal: (ZecSend) -> Unit, @@ -157,7 +157,7 @@ private fun SendMainContent( ) { Text(text = stringResource(id = R.string.send_available_balance)) Row(Modifier.fillMaxWidth()) { - Text(text = walletSnapshot.saplingBalance.available.toZecString()) + Text(text = accountBalance.sapling.available.toZecString()) } TextField( diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/transactions/view/TransactionsView.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/transactions/view/TransactionsView.kt index 7fcb817bd..34722928c 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/transactions/view/TransactionsView.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/transactions/view/TransactionsView.kt @@ -32,6 +32,7 @@ import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.demoapp.R import cash.z.ecc.android.sdk.demoapp.util.toTransactionState import cash.z.ecc.android.sdk.internal.Twig +import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.WalletAddresses import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -43,6 +44,7 @@ import kotlinx.coroutines.launch @Composable @Suppress("ktlint:standard:function-naming", "standard:function-naming") fun Transactions( + account: Account, synchronizer: Synchronizer, onBack: () -> Unit ) { @@ -53,7 +55,9 @@ fun Transactions( val stateFlow by remember(synchronizer) { mutableStateOf( - flow { emit(WalletAddresses.new(synchronizer)) }.catch { emit(null) } + flow { + emit(WalletAddresses.new(account, synchronizer)) + }.catch { emit(null) } ) } val walletAddresses by stateFlow.collectAsStateWithLifecycle(initialValue = null) diff --git a/demo-app/src/main/res/values/strings.xml b/demo-app/src/main/res/values/strings.xml index 99658c149..6d3d6e86f 100644 --- a/demo-app/src/main/res/values/strings.xml +++ b/demo-app/src/main/res/values/strings.xml @@ -1,5 +1,4 @@ - Pending Unknown @@ -101,12 +100,4 @@ Fee: Unavailable Status: - - Note that we strongly discouraged wallets from displaying the private keys the UI. - This is only for testing purposes. - Unified Full View Key: - Unified Spending Key: - ZIP 32 Arbitrary Wallet Key: - ZIP 32 Arbitrary Account Key: - diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/PendingTransaction.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/PendingTransaction.kt index 657f336ac..a23ee66f3 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/PendingTransaction.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/PendingTransaction.kt @@ -31,7 +31,7 @@ sealed class TransactionRecipient { override fun toString() = "TransactionRecipient.Address" } - data class Account(val accountValue: cash.z.ecc.android.sdk.model.Account) : TransactionRecipient() { + data class Account(val accountZip32Index: Int) : TransactionRecipient() { override fun toString() = "TransactionRecipient.Account" } From dc59e2cf06fed07249280be14c49d0ad30579b69 Mon Sep 17 00:00:00 2001 From: Honza Date: Fri, 29 Nov 2024 11:40:12 +0100 Subject: [PATCH 11/25] Static code analysis warnings --- .../java/cash/z/ecc/android/sdk/demoapp/Constants.kt | 6 +++--- .../android/sdk/demoapp/fixture/WalletSnapshotFixture.kt | 7 ++++--- .../sdk/demoapp/ui/screen/balance/view/BalanceView.kt | 4 ++-- .../demoapp/ui/screen/home/viewmodel/WalletSnapshot.kt | 2 +- .../cash/z/ecc/android/sdk/fixture/AccountFixture.kt | 9 ++++----- .../main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt | 1 - .../src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt | 4 ++-- .../android/sdk/block/processor/CompactBlockProcessor.kt | 2 +- .../java/cash/z/ecc/android/sdk/exception/Exceptions.kt | 8 ++++++-- .../ecc/android/sdk/internal/db/derived/TxOutputsView.kt | 4 ++-- 10 files changed, 25 insertions(+), 22 deletions(-) diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Constants.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Constants.kt index e46a96da2..61983fac5 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Constants.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Constants.kt @@ -10,6 +10,6 @@ val ANDROID_STATE_FLOW_TIMEOUT = 5.seconds */ 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 \ No newline at end of file +// 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 diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/fixture/WalletSnapshotFixture.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/fixture/WalletSnapshotFixture.kt index 66135a52d..1afcbb573 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/fixture/WalletSnapshotFixture.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/fixture/WalletSnapshotFixture.kt @@ -17,9 +17,10 @@ object WalletSnapshotFixture { val PROGRESS = PercentDecimal.ZERO_PERCENT val EXCHANGE_RATE_USD: BigDecimal = BigDecimal(37.4850) val ACCOUNT = AccountFixture.new() - val WALLET_BALANCES: Map = mapOf( - ACCOUNT to AccountBalanceFixture.new() - ) + val WALLET_BALANCES: Map = + mapOf( + ACCOUNT to AccountBalanceFixture.new() + ) // Should fill in with non-empty values for better example values in tests and UI previews @Suppress("LongParameterList") diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/balance/view/BalanceView.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/balance/view/BalanceView.kt index d2e2771b9..fea810761 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/balance/view/BalanceView.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/balance/view/BalanceView.kt @@ -32,7 +32,7 @@ import cash.z.ecc.android.sdk.model.toZecString import java.math.BigDecimal @Preview(name = "Balance") -@Suppress("ktlint:standard:function-naming") +@Suppress("ktlint:standard:function-naming", "MagicNumber") @Composable private fun ComposablePreview() { MaterialTheme { @@ -48,7 +48,7 @@ private fun ComposablePreview() { } @Composable -@Suppress("ktlint:standard:function-naming") +@Suppress("ktlint:standard:function-naming", "LongParameterList") fun Balance( exchangeRateUsd: BigDecimal?, accountBalance: AccountBalance, diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletSnapshot.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletSnapshot.kt index dc9c49ea2..0b90e24c1 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletSnapshot.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletSnapshot.kt @@ -18,4 +18,4 @@ data class WalletSnapshot( fun balanceByAccount(account: Account): AccountBalance { return walletBalances[account] ?: error("Balance of $account could not be find.") } -} \ No newline at end of file +} diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt index 1b413e1b6..62f8f16a2 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt @@ -5,9 +5,8 @@ import cash.z.ecc.android.sdk.model.Account object AccountFixture { val ACCOUNT_UUID = WalletFixture.Alice.accounts[0].accountUuid - fun new( - accountUuid: ByteArray = ACCOUNT_UUID - ) = Account( - accountUuid = accountUuid - ) + fun new(accountUuid: ByteArray = ACCOUNT_UUID) = + Account( + accountUuid = accountUuid + ) } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt index 59e6b1aa2..fc40b53dc 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt @@ -13,7 +13,6 @@ import cash.z.ecc.android.sdk.block.processor.CompactBlockProcessor.State.Synced import cash.z.ecc.android.sdk.block.processor.CompactBlockProcessor.State.Syncing import cash.z.ecc.android.sdk.exception.CompactBlockProcessorException import cash.z.ecc.android.sdk.exception.InitializeException -import cash.z.ecc.android.sdk.exception.LightWalletException import cash.z.ecc.android.sdk.exception.TransactionEncoderException import cash.z.ecc.android.sdk.ext.ConsensusBranchId import cash.z.ecc.android.sdk.ext.ZcashSdk diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt index d860c6484..6e19509a1 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt @@ -160,11 +160,11 @@ interface Synchronizer { * * @throws [InitializeException.CreateAccountException] in case of the operation failure **/ - suspend fun createAccount( + suspend fun createAccount( seed: ByteArray, treeState: TreeState, recoverUntil: BlockHeight? - ): UnifiedSpendingKey + ): UnifiedSpendingKey /** * Gets the current unified address for the given account. diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessor.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessor.kt index 50d076b07..951845454 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessor.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessor.kt @@ -39,7 +39,6 @@ import cash.z.ecc.android.sdk.internal.ext.retryUpToAndThrow import cash.z.ecc.android.sdk.internal.ext.retryWithBackoff import cash.z.ecc.android.sdk.internal.ext.toHexReversed import cash.z.ecc.android.sdk.internal.metrics.TraceScope -import cash.z.ecc.android.sdk.model.AccountBalance import cash.z.ecc.android.sdk.internal.model.BlockBatch import cash.z.ecc.android.sdk.internal.model.JniBlockMeta import cash.z.ecc.android.sdk.internal.model.RewindResult @@ -56,6 +55,7 @@ import cash.z.ecc.android.sdk.internal.model.ext.toTransactionStatus import cash.z.ecc.android.sdk.internal.repository.DerivedDataRepository import cash.z.ecc.android.sdk.internal.transaction.OutboundTransactionManager import cash.z.ecc.android.sdk.model.Account +import cash.z.ecc.android.sdk.model.AccountBalance import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.PercentDecimal import cash.z.ecc.android.sdk.model.RawTransaction diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/exception/Exceptions.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/exception/Exceptions.kt index 1e9bb905d..34b485be8 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/exception/Exceptions.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/exception/Exceptions.kt @@ -205,10 +205,14 @@ sealed class InitializeException(message: String, cause: Throwable? = null) : Sd } class GetAccountsException(cause: Throwable?) : InitializeException( - "Failed to get accounts due to: ${cause?.message}", cause) + "Failed to get accounts due to: ${cause?.message}", + cause + ) class CreateAccountException(cause: Throwable?) : InitializeException( - "Failed to create new account due to: ${cause?.message}", cause) + "Failed to create new account due to: ${cause?.message}", + cause + ) class AlreadyInitializedException(cause: Throwable, dbPath: String) : InitializeException( "Failed to initialize the blocks table" + diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/TxOutputsView.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/TxOutputsView.kt index 263bcf42c..cdb77a8b4 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/TxOutputsView.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/TxOutputsView.kt @@ -71,8 +71,8 @@ internal class TxOutputsView(private val sqliteDatabase: SupportSQLiteDatabase) val toAddressIndex = it.getColumnIndex(TxOutputsViewDefinition.COLUMN_STRING_TO_ADDRESS) if (!it.isNull(toAccountIndex)) { - // TODO [1644]: Refactor Account ZIP32 index across SDK - // TODO [1644]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/1644 + // TODO [#1644]: Refactor Account ZIP32 index across SDK + // TODO [#1644]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/1644 TransactionRecipient.Account(0) } else { TransactionRecipient.Address(it.getString(toAddressIndex)) From ed14aa1ce2b79581cec4ac5e806905c65aefe006 Mon Sep 17 00:00:00 2001 From: Honza Date: Fri, 29 Nov 2024 11:56:49 +0100 Subject: [PATCH 12/25] Changelog update --- CHANGELOG.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0138a7902..aa49beb14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,22 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- `Synchronizer.getAccounts` -- `Synchronizer.createAccount` +- `Synchronizer.getAccounts()` +- `Synchronizer.createAccount()` +- `Synchronizer.walletBalances: StateFlow?>` that is replacement for the removed + `orchardBalances`, `saplingBalances`, and `transparentBalance` ### Changed +- `Account` data class works with `accountUuid: ByteArray` instead of the previous ZIP 32 account index - `Synchronizer.orchardBalances`, `Synchronizer.saplingBalances`, and `Synchronizer.transparentBalance` have been replaced by `Synchronizer.walletBalances` that provides these balances based on `Account` +- These functions from `DerivationTool` have been refactored to work with the ZIP 32 account index instead of the + `Account` data class: `deriveUnifiedSpendingKey`, `deriveUnifiedAddress`, `deriveArbitraryAccountKey` + +### 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 From c4a53e1a456c37a2dc04c700fd5a45d2aa238337 Mon Sep 17 00:00:00 2001 From: Honza Date: Fri, 29 Nov 2024 16:50:42 +0100 Subject: [PATCH 13/25] Update fixture values + tests --- .../internal/fixture/JniAccountBalanceFixture.kt | 4 ++-- .../android/sdk/darkside/test/AccountFixture.kt | 14 ++++++++++++++ .../sdk/darkside/test/DarksideTestCoordinator.kt | 2 +- .../ecc/android/sdk/darkside/test/TestWallet.kt | 16 +++++++++++----- .../z/ecc/android/sdk/fixture/WalletFixture.kt | 4 ++-- .../z/ecc/android/sdk/fixture/WalletFixture.kt | 13 ++++++++++--- .../sdk/integration/TestnetIntegrationTest.kt | 13 +++++++------ .../android/sdk/internal/SdkSynchronizerTest.kt | 4 ++-- .../android/sdk/model/UnifiedSpendingKeyTest.kt | 4 ---- .../ecc/android/sdk/util/AddressGeneratorUtil.kt | 8 ++++++-- .../cash/z/ecc/android/sdk/util/TestWallet.kt | 16 +++++++++++----- .../java/cash/z/ecc/fixture/AccountFixture.kt | 13 +++++++++++++ .../java/cash/z/ecc/fixture/FakeRustBackend.kt | 10 +++++----- .../cash/z/ecc/android/sdk/model/AccountTest.kt | 2 +- 14 files changed, 85 insertions(+), 38 deletions(-) create mode 100644 darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/AccountFixture.kt create mode 100644 sdk-lib/src/androidTest/java/cash/z/ecc/fixture/AccountFixture.kt diff --git a/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/fixture/JniAccountBalanceFixture.kt b/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/fixture/JniAccountBalanceFixture.kt index 937d146ed..15f49375b 100644 --- a/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/fixture/JniAccountBalanceFixture.kt +++ b/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/fixture/JniAccountBalanceFixture.kt @@ -3,7 +3,7 @@ package cash.z.ecc.android.sdk.internal.fixture import cash.z.ecc.android.sdk.internal.model.JniAccountBalance 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 @@ -14,7 +14,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, diff --git a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/AccountFixture.kt b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/AccountFixture.kt new file mode 100644 index 000000000..f1769f0d6 --- /dev/null +++ b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/AccountFixture.kt @@ -0,0 +1,14 @@ +package cash.z.ecc.android.sdk.darkside.test + +import cash.z.ecc.android.sdk.model.Account + +@Suppress("MagicNumber") +object AccountFixture { + val ACCOUNT_UUID = "random_uuid_16_b".toByteArray() + const val ZIP_32_ACCOUNT_INDEX = 0 + + fun new(accountUuid: ByteArray = ACCOUNT_UUID) = + Account( + accountUuid = accountUuid + ) +} diff --git a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/DarksideTestCoordinator.kt b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/DarksideTestCoordinator.kt index 5ef23c105..4fedab2a6 100644 --- a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/DarksideTestCoordinator.kt +++ b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/DarksideTestCoordinator.kt @@ -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}", diff --git a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt index 7f5a12314..b7974c3a9 100644 --- a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt +++ b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt @@ -8,7 +8,6 @@ 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.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 @@ -55,11 +54,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, + accountIndex = AccountFixture.ZIP_32_ACCOUNT_INDEX + ) + } val synchronizer: SdkSynchronizer = Synchronizer.newBlocking( context, @@ -72,7 +77,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 = @@ -123,7 +129,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" } } diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt index 81ed27cc1..dee02d31f 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt @@ -19,7 +19,7 @@ sealed class WalletFixture { @Suppress("MaxLineLength") data object Ben : WalletFixture() { override val accounts: List - get() = listOf(Account("a1b2c3d4-e5f6-7890-1234-567890abcdef".toByteArray())) + get() = listOf(Account("ben_uuid_16_byte".toByteArray())) override val seedPhrase: String get() = @@ -56,7 +56,7 @@ sealed class WalletFixture { @Suppress("MaxLineLength") data object Alice : WalletFixture() { override val accounts: List - get() = listOf(Account("f47ac10b-58cc-4372-a567-0e02b2c3d479".toByteArray())) + get() = listOf(Account("alice_uuid_16_by".toByteArray())) override val seedPhrase: String get() = diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt index cb7b30a9e..59e6df48c 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt @@ -3,10 +3,13 @@ package cash.z.ecc.android.sdk.fixture import cash.z.ecc.android.bip39.Mnemonics import cash.z.ecc.android.sdk.internal.deriveUnifiedSpendingKey import cash.z.ecc.android.sdk.internal.jni.RustDerivationTool -import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.ZcashNetwork object WalletFixture { + // TODO [#1644]: Refactor Account ZIP32 index across SDK + // TODO [#1644]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/1644 + const val ACCOUNT_INDEX = 0 + val NETWORK = ZcashNetwork.Mainnet // This is the "Ben" wallet phrase from sdk-incubator-lib. @@ -22,6 +25,10 @@ object WalletFixture { suspend fun getUnifiedSpendingKey( seed: String = SEED_PHRASE, network: ZcashNetwork = NETWORK, - account: Account = Account.DEFAULT - ) = RustDerivationTool.new().deriveUnifiedSpendingKey(Mnemonics.MnemonicCode(seed).toEntropy(), network, account) + accountIndex: Int = ACCOUNT_INDEX + ) = RustDerivationTool.new().deriveUnifiedSpendingKey( + Mnemonics.MnemonicCode(seed).toEntropy(), + network, + accountIndex + ) } diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/TestnetIntegrationTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/TestnetIntegrationTest.kt index 2ef137bd2..4f87a5f7b 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/TestnetIntegrationTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/TestnetIntegrationTest.kt @@ -7,13 +7,13 @@ import cash.z.ecc.android.sdk.Synchronizer.Status.SYNCED import cash.z.ecc.android.sdk.WalletInitMode import cash.z.ecc.android.sdk.ext.onFirst 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 import cash.z.ecc.android.sdk.test.ScopedTest import cash.z.ecc.android.sdk.tool.CheckpointTool import cash.z.ecc.android.sdk.tool.DerivationTool +import cash.z.ecc.fixture.AccountFixture import co.electriccoin.lightwallet.client.LightWalletClient import co.electriccoin.lightwallet.client.model.BlockHeightUnsafe import co.electriccoin.lightwallet.client.model.LightWalletEndpoint @@ -36,6 +36,7 @@ import java.util.concurrent.CountDownLatch class TestnetIntegrationTest : ScopedTest() { var stopWatch = CountDownLatch(1) val saplingActivation = synchronizer.network.saplingActivationHeight + val account = AccountFixture.new() @Test @Ignore("This test is broken") @@ -68,7 +69,7 @@ class TestnetIntegrationTest : ScopedTest() { @Ignore("This test is broken") fun getAddress() = runBlocking { - assertEquals(address, synchronizer.getUnifiedAddress(Account.DEFAULT)) + assertEquals(address, synchronizer.getUnifiedAddress(AccountFixture.new())) } // This is an extremely slow test; it is disabled so that we can get CI set up @@ -78,8 +79,8 @@ class TestnetIntegrationTest : ScopedTest() { fun testBalance() = runBlocking { var availableBalance: Zatoshi? = null - synchronizer.saplingBalances.onFirst { - availableBalance = it?.available + synchronizer.walletBalances.onFirst { + availableBalance = it?.get(account)?.sapling?.available } synchronizer.status.filter { it == SYNCED }.onFirst { @@ -96,7 +97,7 @@ class TestnetIntegrationTest : ScopedTest() { fun testSpend() = runBlocking { var success = false - synchronizer.saplingBalances.filterNotNull().onEach { + synchronizer.walletBalances.filterNotNull().onEach { success = sendFunds() }.first() log("asserting $success") @@ -108,7 +109,7 @@ class TestnetIntegrationTest : ScopedTest() { DerivationTool.getInstance().deriveUnifiedSpendingKey( seed, synchronizer.network, - Account.DEFAULT + AccountFixture.ZIP_32_ACCOUNT_INDEX ) log("sending to address") synchronizer.createProposedTransactions( diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SdkSynchronizerTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SdkSynchronizerTest.kt index d4d2a19b4..ce928f0d4 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SdkSynchronizerTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SdkSynchronizerTest.kt @@ -8,8 +8,8 @@ import cash.z.ecc.android.sdk.WalletInitMode import cash.z.ecc.android.sdk.exception.InitializeException import cash.z.ecc.android.sdk.fixture.LightWalletEndpointFixture import cash.z.ecc.android.sdk.fixture.WalletFixture -import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.ZcashNetwork +import cash.z.ecc.fixture.AccountFixture import kotlinx.coroutines.test.runTest import java.util.UUID import kotlin.test.Test @@ -107,7 +107,7 @@ class SdkSynchronizerTest { // Using existing wallet init mode as simplification for the test walletInitMode = WalletInitMode.ExistingWallet ).use { - it.getSaplingAddress(Account.DEFAULT) + it.getSaplingAddress(AccountFixture.new()) } // Second instance should fail because the seed is not relevant to the wallet. diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/model/UnifiedSpendingKeyTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/model/UnifiedSpendingKeyTest.kt index 5b5bf15a9..a1b6c5895 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/model/UnifiedSpendingKeyTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/model/UnifiedSpendingKeyTest.kt @@ -2,7 +2,6 @@ package cash.z.ecc.android.sdk.model import androidx.test.filters.SmallTest import cash.z.ecc.android.sdk.fixture.WalletFixture -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test import kotlin.test.assertContentEquals @@ -11,7 +10,6 @@ import kotlin.test.assertEquals class UnifiedSpendingKeyTest { @Test @SmallTest - @OptIn(ExperimentalCoroutinesApi::class) fun factory_copies_bytes() = runTest { val spendingKey = WalletFixture.getUnifiedSpendingKey() @@ -26,7 +24,6 @@ class UnifiedSpendingKeyTest { @Test @SmallTest - @OptIn(ExperimentalCoroutinesApi::class) fun get_copies_bytes() = runTest { val spendingKey = WalletFixture.getUnifiedSpendingKey() @@ -41,7 +38,6 @@ class UnifiedSpendingKeyTest { @Test @SmallTest - @OptIn(ExperimentalCoroutinesApi::class) fun toString_does_not_leak() = runTest { assertEquals( diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/AddressGeneratorUtil.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/AddressGeneratorUtil.kt index ef9b1907d..b3d59c681 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/AddressGeneratorUtil.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/AddressGeneratorUtil.kt @@ -2,9 +2,9 @@ package cash.z.ecc.android.sdk.util import cash.z.ecc.android.sdk.internal.deriveUnifiedAddress import cash.z.ecc.android.sdk.internal.jni.RustDerivationTool -import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.test.readFileLinesInFlow +import cash.z.ecc.fixture.AccountFixture import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.map import kotlinx.coroutines.runBlocking @@ -32,7 +32,11 @@ class AddressGeneratorUtil { .map { seedPhrase -> mnemonics.toSeed(seedPhrase.toCharArray()) }.map { seed -> - RustDerivationTool.new().deriveUnifiedAddress(seed, ZcashNetwork.Mainnet, Account.DEFAULT) + RustDerivationTool.new().deriveUnifiedAddress( + seed = seed, + network = ZcashNetwork.Mainnet, + accountIndex = AccountFixture.ZIP_32_ACCOUNT_INDEX + ) }.collect { address -> println("xrxrx2\t$address") assertTrue(address.startsWith("u1")) diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt index 5c9410a83..80681ea7a 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt @@ -10,10 +10,10 @@ import cash.z.ecc.android.sdk.fixture.LightWalletEndpointFixture import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.internal.deriveUnifiedSpendingKey import cash.z.ecc.android.sdk.internal.jni.RustDerivationTool -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 +import cash.z.ecc.fixture.AccountFixture import co.electriccoin.lightwallet.client.model.LightWalletEndpoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi @@ -56,11 +56,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 + private val account = AccountFixture.new() private val context = InstrumentationRegistry.getInstrumentation().context private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed() private val spendingKey = - runBlocking { RustDerivationTool.new().deriveUnifiedSpendingKey(seed, network = network, account) } + runBlocking { + RustDerivationTool.new().deriveUnifiedSpendingKey( + seed = seed, + network = network, + accountIndex = AccountFixture.ZIP_32_ACCOUNT_INDEX + ) + } val synchronizer: SdkSynchronizer = Synchronizer.newBlocking( context, @@ -73,7 +79,7 @@ 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 = @@ -124,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" } } diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/AccountFixture.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/AccountFixture.kt new file mode 100644 index 000000000..cf4b3c487 --- /dev/null +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/AccountFixture.kt @@ -0,0 +1,13 @@ +package cash.z.ecc.fixture + +import cash.z.ecc.android.sdk.model.Account + +object AccountFixture { + val ACCOUNT_UUID = "random_uuid_16_b".toByteArray() + const val ZIP_32_ACCOUNT_INDEX = 0 + + fun new(accountUuid: ByteArray = ACCOUNT_UUID) = + Account( + accountUuid = accountUuid + ) +} diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackend.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackend.kt index 31394ab01..a5c25b524 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackend.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackend.kt @@ -89,14 +89,14 @@ internal class FakeRustBackend( } override suspend fun proposeTransferFromUri( - accountIndex: Int, + accountUuid: ByteArray, uri: String ): ProposalUnsafe { error("Intentionally not implemented yet.") } override suspend fun proposeTransfer( - accountIndex: Int, + accountUuid: ByteArray, to: String, value: Long, memo: ByteArray? @@ -105,7 +105,7 @@ internal class FakeRustBackend( } override suspend fun proposeShielding( - accountIndex: Int, + accountUuid: ByteArray, shieldingThreshold: Long, memo: ByteArray?, transparentReceiver: String? @@ -159,7 +159,7 @@ internal class FakeRustBackend( error("Intentionally not implemented in mocked FakeRustBackend implementation.") } - override suspend fun getCurrentAddress(accountIndex: Int): String { + override suspend fun getCurrentAddress(accountUuid: ByteArray): String { error("Intentionally not implemented yet.") } @@ -171,7 +171,7 @@ internal class FakeRustBackend( error("Intentionally not implemented yet.") } - override suspend fun listTransparentReceivers(accountIndex: Int): List { + override suspend fun listTransparentReceivers(accountUuid: ByteArray): List { error("Intentionally not implemented yet.") } diff --git a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/model/AccountTest.kt b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/model/AccountTest.kt index c2b481fc5..636a1b90e 100644 --- a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/model/AccountTest.kt +++ b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/model/AccountTest.kt @@ -7,7 +7,7 @@ class AccountTest { @Test fun out_of_bounds() { assertFailsWith(IllegalArgumentException::class) { - Account(-1) + Account("random".toByteArray()) } } } From 7c1e40a14f456ace4ef0cf7a8cd3fe3d73b0d675 Mon Sep 17 00:00:00 2001 From: Honza Date: Sun, 1 Dec 2024 18:49:24 +0100 Subject: [PATCH 14/25] Fix some Demo app tests --- .../cash/z/wallet/sdk/sample/demoapp/SampleCodeTest.kt | 10 +++++----- .../cash/z/ecc/android/sdk/fixture/AccountFixture.kt | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/SampleCodeTest.kt b/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/SampleCodeTest.kt index ee0dff0df..97451fa59 100644 --- a/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/SampleCodeTest.kt +++ b/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/SampleCodeTest.kt @@ -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 @@ -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") } @@ -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" @@ -179,7 +179,7 @@ class SampleCodeTest { DerivationTool.getInstance().deriveUnifiedSpendingKey( seed, ZcashNetwork.Mainnet, - Account.DEFAULT + AccountFixture.ZIP_32_ACCOUNT_INDEX ) synchronizer.createProposedTransactions( synchronizer.proposeTransfer( @@ -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 diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt index 62f8f16a2..2a61066b2 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt @@ -4,6 +4,7 @@ import cash.z.ecc.android.sdk.model.Account object AccountFixture { val ACCOUNT_UUID = WalletFixture.Alice.accounts[0].accountUuid + const val ZIP_32_ACCOUNT_INDEX = 0 fun new(accountUuid: ByteArray = ACCOUNT_UUID) = Account( From 731f2d5f915545926ab244c1272027a6826464cb Mon Sep 17 00:00:00 2001 From: Honza Date: Sun, 1 Dec 2024 20:12:43 +0100 Subject: [PATCH 15/25] Fix shield funds sample test --- .../java/cash/z/ecc/android/sdk/sample/ShieldFundsSample.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/sample/ShieldFundsSample.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/sample/ShieldFundsSample.kt index ab1a9b13a..cca051668 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/sample/ShieldFundsSample.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/sample/ShieldFundsSample.kt @@ -3,6 +3,7 @@ package cash.z.ecc.android.sdk.sample import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.util.TestWallet +import cash.z.ecc.fixture.AccountFixture import kotlinx.coroutines.runBlocking import org.junit.Assert import org.junit.Ignore @@ -34,6 +35,9 @@ class ShieldFundsSample { Assert.assertEquals("foo", "${wallet.unifiedAddress} ${wallet.transparentAddress}") // wallet.shieldFunds() - Assert.assertEquals(Zatoshi(5), wallet.synchronizer.saplingBalances.value?.available) + Assert.assertEquals( + Zatoshi(5), + wallet.synchronizer.walletBalances.value?.get(AccountFixture.new())?.sapling?.available + ) } } From 77d80b0fca8ea3d4130806fb34f48e0098c7607b Mon Sep 17 00:00:00 2001 From: Honza Date: Mon, 2 Dec 2024 14:10:26 +0100 Subject: [PATCH 16/25] 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 --- CHANGELOG.md | 1 - .../src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt | 3 ++- sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa49beb14..29ec7fa24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,6 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `Synchronizer.getAccounts()` -- `Synchronizer.createAccount()` - `Synchronizer.walletBalances: StateFlow?>` that is replacement for the removed `orchardBalances`, `saplingBalances`, and `transparentBalance` diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt index fc40b53dc..8233bd084 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt @@ -640,7 +640,8 @@ class SdkSynchronizer private constructor( // Account management // - override suspend fun createAccount( + // Not ready to be a public API; internal for testing only + internal suspend fun createAccount( seed: ByteArray, treeState: TreeState, recoverUntil: BlockHeight? diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt index 6e19509a1..59f0cfa42 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt @@ -14,7 +14,6 @@ import cash.z.ecc.android.sdk.internal.SaplingParamTool import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.internal.db.DatabaseCoordinator import cash.z.ecc.android.sdk.internal.exchange.UsdExchangeRateFetcher -import cash.z.ecc.android.sdk.internal.model.TreeState import cash.z.ecc.android.sdk.internal.model.ext.toBlockHeight import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.AccountBalance @@ -160,11 +159,13 @@ interface Synchronizer { * * @throws [InitializeException.CreateAccountException] in case of the operation failure **/ + /* Not ready to be a public API; internal for testing only suspend fun createAccount( seed: ByteArray, treeState: TreeState, recoverUntil: BlockHeight? ): UnifiedSpendingKey + */ /** * Gets the current unified address for the given account. From 5dd8ec6b16868980c0f79de0d484f83a5799cfbb Mon Sep 17 00:00:00 2001 From: Honza Date: Mon, 2 Dec 2024 15:21:44 +0100 Subject: [PATCH 17/25] Add all accounts flow API --- .../z/ecc/android/sdk/demoapp/Navigation.kt | 6 +++- .../demoapp/ui/screen/home/view/HomeView.kt | 31 ++++++++++++++++--- .../screen/home/viewmodel/WalletSnapshot.kt | 2 +- .../screen/home/viewmodel/WalletViewModel.kt | 13 ++++++++ demo-app/src/main/res/values/strings.xml | 8 +++++ .../cash/z/ecc/android/sdk/SdkSynchronizer.kt | 21 ++++++++++++- .../cash/z/ecc/android/sdk/Synchronizer.kt | 11 +++++++ 7 files changed, 85 insertions(+), 7 deletions(-) diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Navigation.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Navigation.kt index 609d02c5e..7127ac0ca 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Navigation.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Navigation.kt @@ -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 @@ -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) }, diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/view/HomeView.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/view/HomeView.kt index 2b3f888d9..7b0e30914 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/view/HomeView.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/view/HomeView.kt @@ -2,6 +2,8 @@ package cash.z.ecc.android.sdk.demoapp.ui.screen.home.view import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -25,11 +27,16 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier 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.Synchronizer import cash.z.ecc.android.sdk.demoapp.R import cash.z.ecc.android.sdk.demoapp.fixture.WalletSnapshotFixture import cash.z.ecc.android.sdk.demoapp.ui.common.DisableScreenTimeout import cash.z.ecc.android.sdk.demoapp.ui.screen.home.viewmodel.WalletSnapshot +import cash.z.ecc.android.sdk.fixture.AccountFixture +import cash.z.ecc.android.sdk.model.Account +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf @Preview(name = "Home") @Composable @@ -37,14 +44,16 @@ import cash.z.ecc.android.sdk.demoapp.ui.screen.home.viewmodel.WalletSnapshot private fun ComposablePreviewHome() { MaterialTheme { Home( - WalletSnapshotFixture.new(), + allAccounts = persistentListOf(AccountFixture.new()), + currentAccount = AccountFixture.new(), + walletSnapshot = WalletSnapshotFixture.new(), isTestnet = true, goBalance = {}, goSend = {}, goAddressDetails = {}, goTransactions = {}, - goTestnetFaucet = {}, goServer = {}, + goTestnetFaucet = {}, resetSdk = {}, rewind = {}, ) @@ -54,6 +63,8 @@ private fun ComposablePreviewHome() { @Composable @Suppress("LongParameterList", "ktlint:standard:function-naming") fun Home( + allAccounts: ImmutableList, + currentAccount: Account, walletSnapshot: WalletSnapshot, isTestnet: Boolean, goBalance: () -> Unit, @@ -75,12 +86,14 @@ fun Home( }) { paddingValues -> HomeMainContent( paddingValues = paddingValues, - walletSnapshot, + walletSnapshot = walletSnapshot, goBalance = goBalance, goSend = goSend, goServer = goServer, goAddressDetails = goAddressDetails, - goTransactions = goTransactions + goTransactions = goTransactions, + currentAccount = currentAccount, + allAccounts = allAccounts ) } } @@ -154,6 +167,8 @@ private fun DebugMenu( @Composable @Suppress("LongParameterList", "ktlint:standard:function-naming") private fun HomeMainContent( + allAccounts: ImmutableList, + currentAccount: Account, paddingValues: PaddingValues, walletSnapshot: WalletSnapshot, goBalance: () -> Unit, @@ -198,5 +213,13 @@ private fun HomeMainContent( // is different from a longer sync. DisableScreenTimeout() } + + Spacer(modifier = Modifier.height(12.dp)) + + Text(text = stringResource(id = R.string.home_accounts, allAccounts)) + + Spacer(modifier = Modifier.height(6.dp)) + + Text(text = stringResource(id = R.string.home_current_account, currentAccount)) } } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletSnapshot.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletSnapshot.kt index 0b90e24c1..854397dae 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletSnapshot.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletSnapshot.kt @@ -16,6 +16,6 @@ data class WalletSnapshot( val synchronizerError: SynchronizerError? ) { fun balanceByAccount(account: Account): AccountBalance { - return walletBalances[account] ?: error("Balance of $account could not be find.") + return walletBalances[account] ?: error("Balance of $account could not be found.") } } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletViewModel.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletViewModel.kt index c7920f9f0..0652a7a33 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletViewModel.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletViewModel.kt @@ -370,6 +370,19 @@ class WalletViewModel(application: Application) : AndroidViewModel(application) } } + @OptIn(ExperimentalCoroutinesApi::class) + val accounts: StateFlow> = + synchronizer + .filterNotNull() + .flatMapLatest { + it.accountsFlow.filterNotNull() + } + .stateIn( + viewModelScope, + SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), + emptyList() + ) + fun getCurrentAccount(): Account = getAccounts()[CURRENT_ZIP_32_ACCOUNT_INDEX] companion object { diff --git a/demo-app/src/main/res/values/strings.xml b/demo-app/src/main/res/values/strings.xml index 6d3d6e86f..4c6178b0d 100644 --- a/demo-app/src/main/res/values/strings.xml +++ b/demo-app/src/main/res/values/strings.xml @@ -52,6 +52,14 @@ Generate a new random secret phrase Restore wallet + + The wallet\'s accounts: + %1$s + + + Selected account: + %1$s + Status: %1$s Progress: %1$.2f%% diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt index 8233bd084..e4cde8496 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt @@ -640,6 +640,8 @@ class SdkSynchronizer private constructor( // Account management // + private val refreshAccountsBus = MutableSharedFlow() + // Not ready to be a public API; internal for testing only internal suspend fun createAccount( seed: ByteArray, @@ -651,7 +653,9 @@ class SdkSynchronizer private constructor( seed = seed, treeState = treeState, recoverUntil = recoverUntil - ) + ).also { + refreshAccountsBus.emit(Unit) + } }.onFailure { Twig.error(it) { "Create account failed." } }.getOrElse { @@ -669,6 +673,21 @@ class SdkSynchronizer private constructor( } } + override val accountsFlow: Flow?> = + channelFlow { + send(getAccounts()) + launch { + refreshAccountsBus.collect { + send(getAccounts()) + } + } + awaitClose() + }.stateIn( + coroutineScope, + SharingStarted.WhileSubscribed(), + null + ) + /** * Returns the current Unified Address for this account. */ diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt index 59f0cfa42..163e0742d 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt @@ -121,6 +121,16 @@ interface Synchronizer { */ suspend fun getAccounts(): List + /** + * Returns all the wallet accounts or throws [InitializeException.GetAccountsException] + * + * It's a Flow version of [getAccounts] + * + * @return Flow of all wallet accounts + * @throws [InitializeException.GetAccountsException] in case of the operation failure + */ + val accountsFlow: Flow?> + /** * Measure connection quality and speed of given [servers]. * @@ -159,6 +169,7 @@ interface Synchronizer { * * @throws [InitializeException.CreateAccountException] in case of the operation failure **/ + @Suppress("standard:no-consecutive-comments") /* Not ready to be a public API; internal for testing only suspend fun createAccount( seed: ByteArray, From 9f66836307f656dea6f105f55d54f78043fbe346 Mon Sep 17 00:00:00 2001 From: Honza Date: Tue, 3 Dec 2024 17:49:11 +0100 Subject: [PATCH 18/25] Refactor AccountFixture - We deduplicated the fixture across the related modules and their tests - It works with UUID instead of String converted to ByteArray - Documentation added --- .../fixture/JniAccountBalanceFixture.kt | 4 +++ darkside-test-lib/build.gradle.kts | 2 +- .../sdk/darkside/test/AccountFixture.kt | 14 --------- .../android/sdk/darkside/test/TestWallet.kt | 1 + .../ecc/android/sdk/fixture/AccountFixture.kt | 13 --------- .../ecc/android/sdk/fixture/WalletFixture.kt | 5 ++-- .../sdk/integration/TestnetIntegrationTest.kt | 2 +- .../sdk/internal/SdkSynchronizerTest.kt | 2 +- .../android/sdk/sample/ShieldFundsSample.kt | 2 +- .../android/sdk/util/AddressGeneratorUtil.kt | 2 +- .../cash/z/ecc/android/sdk/util/TestWallet.kt | 2 +- .../java/cash/z/ecc/fixture/AccountFixture.kt | 13 --------- .../ecc/android/sdk/fixture/AccountFixture.kt | 29 +++++++++++++++++++ 13 files changed, 43 insertions(+), 48 deletions(-) delete mode 100644 darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/AccountFixture.kt delete mode 100644 sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt delete mode 100644 sdk-lib/src/androidTest/java/cash/z/ecc/fixture/AccountFixture.kt create mode 100644 sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt diff --git a/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/fixture/JniAccountBalanceFixture.kt b/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/fixture/JniAccountBalanceFixture.kt index 15f49375b..8466aec5f 100644 --- a/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/fixture/JniAccountBalanceFixture.kt +++ b/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/fixture/JniAccountBalanceFixture.kt @@ -2,6 +2,10 @@ 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 { val ACCOUNT_UUID: ByteArray = "random_uuid_16_b".toByteArray() const val SAPLING_VERIFIED_BALANCE: Long = 0L diff --git a/darkside-test-lib/build.gradle.kts b/darkside-test-lib/build.gradle.kts index 727e49e2f..1b59f3c12 100644 --- a/darkside-test-lib/build.gradle.kts +++ b/darkside-test-lib/build.gradle.kts @@ -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) } diff --git a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/AccountFixture.kt b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/AccountFixture.kt deleted file mode 100644 index f1769f0d6..000000000 --- a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/AccountFixture.kt +++ /dev/null @@ -1,14 +0,0 @@ -package cash.z.ecc.android.sdk.darkside.test - -import cash.z.ecc.android.sdk.model.Account - -@Suppress("MagicNumber") -object AccountFixture { - val ACCOUNT_UUID = "random_uuid_16_b".toByteArray() - const val ZIP_32_ACCOUNT_INDEX = 0 - - fun new(accountUuid: ByteArray = ACCOUNT_UUID) = - Account( - accountUuid = accountUuid - ) -} diff --git a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt index b7974c3a9..c1019408b 100644 --- a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt +++ b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt @@ -7,6 +7,7 @@ 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.BlockHeight import cash.z.ecc.android.sdk.model.Zatoshi diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt deleted file mode 100644 index 2a61066b2..000000000 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt +++ /dev/null @@ -1,13 +0,0 @@ -package cash.z.ecc.android.sdk.fixture - -import cash.z.ecc.android.sdk.model.Account - -object AccountFixture { - val ACCOUNT_UUID = WalletFixture.Alice.accounts[0].accountUuid - const val ZIP_32_ACCOUNT_INDEX = 0 - - fun new(accountUuid: ByteArray = ACCOUNT_UUID) = - Account( - accountUuid = accountUuid - ) -} diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt index dee02d31f..9eae42922 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt @@ -3,6 +3,7 @@ package cash.z.ecc.android.sdk.fixture 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 java.util.UUID /** * Provides two default wallets, making it easy to test sending funds back and forth between them. @@ -19,7 +20,7 @@ sealed class WalletFixture { @Suppress("MaxLineLength") data object Ben : WalletFixture() { override val accounts: List - get() = listOf(Account("ben_uuid_16_byte".toByteArray())) + get() = listOf(AccountFixture.new(UUID.fromString("52175368-821a-4664-8a7c-6a75d850f71c"))) override val seedPhrase: String get() = @@ -56,7 +57,7 @@ sealed class WalletFixture { @Suppress("MaxLineLength") data object Alice : WalletFixture() { override val accounts: List - get() = listOf(Account("alice_uuid_16_by".toByteArray())) + get() = listOf(AccountFixture.new(UUID.fromString("8a204240-73a5-4e7a-93c2-a6e05711a000"))) override val seedPhrase: String get() = diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/TestnetIntegrationTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/TestnetIntegrationTest.kt index 4f87a5f7b..b2c66f1bc 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/TestnetIntegrationTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/TestnetIntegrationTest.kt @@ -6,6 +6,7 @@ import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.Synchronizer.Status.SYNCED import cash.z.ecc.android.sdk.WalletInitMode import cash.z.ecc.android.sdk.ext.onFirst +import cash.z.ecc.android.sdk.fixture.AccountFixture import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.Zatoshi @@ -13,7 +14,6 @@ import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.test.ScopedTest import cash.z.ecc.android.sdk.tool.CheckpointTool import cash.z.ecc.android.sdk.tool.DerivationTool -import cash.z.ecc.fixture.AccountFixture import co.electriccoin.lightwallet.client.LightWalletClient import co.electriccoin.lightwallet.client.model.BlockHeightUnsafe import co.electriccoin.lightwallet.client.model.LightWalletEndpoint diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SdkSynchronizerTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SdkSynchronizerTest.kt index ce928f0d4..db3461143 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SdkSynchronizerTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SdkSynchronizerTest.kt @@ -6,10 +6,10 @@ import cash.z.ecc.android.bip39.Mnemonics import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.WalletInitMode import cash.z.ecc.android.sdk.exception.InitializeException +import cash.z.ecc.android.sdk.fixture.AccountFixture import cash.z.ecc.android.sdk.fixture.LightWalletEndpointFixture import cash.z.ecc.android.sdk.fixture.WalletFixture import cash.z.ecc.android.sdk.model.ZcashNetwork -import cash.z.ecc.fixture.AccountFixture import kotlinx.coroutines.test.runTest import java.util.UUID import kotlin.test.Test diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/sample/ShieldFundsSample.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/sample/ShieldFundsSample.kt index cca051668..e3608e9e3 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/sample/ShieldFundsSample.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/sample/ShieldFundsSample.kt @@ -1,9 +1,9 @@ package cash.z.ecc.android.sdk.sample +import cash.z.ecc.android.sdk.fixture.AccountFixture import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.util.TestWallet -import cash.z.ecc.fixture.AccountFixture import kotlinx.coroutines.runBlocking import org.junit.Assert import org.junit.Ignore diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/AddressGeneratorUtil.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/AddressGeneratorUtil.kt index b3d59c681..0d3219b34 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/AddressGeneratorUtil.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/AddressGeneratorUtil.kt @@ -1,10 +1,10 @@ package cash.z.ecc.android.sdk.util +import cash.z.ecc.android.sdk.fixture.AccountFixture import cash.z.ecc.android.sdk.internal.deriveUnifiedAddress import cash.z.ecc.android.sdk.internal.jni.RustDerivationTool import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.test.readFileLinesInFlow -import cash.z.ecc.fixture.AccountFixture import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.map import kotlinx.coroutines.runBlocking diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt index 80681ea7a..00d5e7f12 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt @@ -6,6 +6,7 @@ import cash.z.ecc.android.bip39.toSeed 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.fixture.AccountFixture import cash.z.ecc.android.sdk.fixture.LightWalletEndpointFixture import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.internal.deriveUnifiedSpendingKey @@ -13,7 +14,6 @@ import cash.z.ecc.android.sdk.internal.jni.RustDerivationTool import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.ZcashNetwork -import cash.z.ecc.fixture.AccountFixture import co.electriccoin.lightwallet.client.model.LightWalletEndpoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/AccountFixture.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/AccountFixture.kt deleted file mode 100644 index cf4b3c487..000000000 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/AccountFixture.kt +++ /dev/null @@ -1,13 +0,0 @@ -package cash.z.ecc.fixture - -import cash.z.ecc.android.sdk.model.Account - -object AccountFixture { - val ACCOUNT_UUID = "random_uuid_16_b".toByteArray() - const val ZIP_32_ACCOUNT_INDEX = 0 - - fun new(accountUuid: ByteArray = ACCOUNT_UUID) = - Account( - accountUuid = accountUuid - ) -} diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt new file mode 100644 index 000000000..ffb32025f --- /dev/null +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt @@ -0,0 +1,29 @@ +package cash.z.ecc.android.sdk.fixture + +import cash.z.ecc.android.sdk.model.Account +import java.nio.ByteBuffer +import java.util.UUID + +/** + * This test fixture class provides a unified way for getting a fixture account for test purposes across the SDK's + * modules. + * + * Note that these values are used in the automated tests only and are not passed across the JNI. + */ +object AccountFixture { + val ACCOUNT_UUID = UUID.fromString("01234567-89ab-cdef-0123-456789abcdef") + const val ZIP_32_ACCOUNT_INDEX = 0 + + fun new(accountUuid: UUID = ACCOUNT_UUID) = + Account( + accountUuid = accountUuid.toByteArray() + ) +} + +// This provides us with a way to convert [UUID] to [ByteArray] +private fun UUID.toByteArray(): ByteArray = + ByteBuffer + .allocate(16) + .putLong(mostSignificantBits) + .putLong(leastSignificantBits) + .array() \ No newline at end of file From 9a7dbbd134d14ece1bb90ab2ff27aca71bc961a1 Mon Sep 17 00:00:00 2001 From: Honza Date: Tue, 3 Dec 2024 17:51:10 +0100 Subject: [PATCH 19/25] Move Account changes to account-uuids --- sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Account.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Account.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Account.kt index 5f14c9e3a..860629017 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Account.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Account.kt @@ -14,10 +14,6 @@ data class Account(val value: Int) { val DEFAULT = Account(0) } - override fun toString(): String { - return "Account(accountUuid=${accountUuid.contentToString()})" - } - override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false From 496ba8c42bc65c5b7fefa47b5a2065d61fe1ddcb Mon Sep 17 00:00:00 2001 From: Honza Date: Tue, 3 Dec 2024 17:55:21 +0100 Subject: [PATCH 20/25] Move AccountTest changes to account-uuids --- .../src/test/java/cash/z/ecc/android/sdk/model/AccountTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/model/AccountTest.kt b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/model/AccountTest.kt index 636a1b90e..c2b481fc5 100644 --- a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/model/AccountTest.kt +++ b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/model/AccountTest.kt @@ -7,7 +7,7 @@ class AccountTest { @Test fun out_of_bounds() { assertFailsWith(IllegalArgumentException::class) { - Account("random".toByteArray()) + Account(-1) } } } From a412b9a99524e1ade97de654ca095ac1f6afcf11 Mon Sep 17 00:00:00 2001 From: Honza Date: Tue, 3 Dec 2024 17:58:19 +0100 Subject: [PATCH 21/25] Move FakeRustBackend changes to account-uuids --- .../java/cash/z/ecc/fixture/FakeRustBackend.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackend.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackend.kt index a5c25b524..31394ab01 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackend.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackend.kt @@ -89,14 +89,14 @@ internal class FakeRustBackend( } override suspend fun proposeTransferFromUri( - accountUuid: ByteArray, + accountIndex: Int, uri: String ): ProposalUnsafe { error("Intentionally not implemented yet.") } override suspend fun proposeTransfer( - accountUuid: ByteArray, + accountIndex: Int, to: String, value: Long, memo: ByteArray? @@ -105,7 +105,7 @@ internal class FakeRustBackend( } override suspend fun proposeShielding( - accountUuid: ByteArray, + accountIndex: Int, shieldingThreshold: Long, memo: ByteArray?, transparentReceiver: String? @@ -159,7 +159,7 @@ internal class FakeRustBackend( error("Intentionally not implemented in mocked FakeRustBackend implementation.") } - override suspend fun getCurrentAddress(accountUuid: ByteArray): String { + override suspend fun getCurrentAddress(accountIndex: Int): String { error("Intentionally not implemented yet.") } @@ -171,7 +171,7 @@ internal class FakeRustBackend( error("Intentionally not implemented yet.") } - override suspend fun listTransparentReceivers(accountUuid: ByteArray): List { + override suspend fun listTransparentReceivers(accountIndex: Int): List { error("Intentionally not implemented yet.") } From 5f7cac18db0928cc97170625119a377e61a0687e Mon Sep 17 00:00:00 2001 From: Honza Date: Tue, 3 Dec 2024 20:32:05 +0100 Subject: [PATCH 22/25] Revert TransactionRecipient.Account parameter change --- .../java/cash/z/ecc/android/sdk/model/PendingTransaction.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/PendingTransaction.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/PendingTransaction.kt index a23ee66f3..b054fe464 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/PendingTransaction.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/PendingTransaction.kt @@ -31,7 +31,7 @@ sealed class TransactionRecipient { override fun toString() = "TransactionRecipient.Address" } - data class Account(val accountZip32Index: Int) : TransactionRecipient() { + data class Account(val accountId: Int) : TransactionRecipient() { override fun toString() = "TransactionRecipient.Account" } From 152d1ae5f59f66209d338f3da37375baaaf83ab9 Mon Sep 17 00:00:00 2001 From: Honza Date: Tue, 3 Dec 2024 21:54:43 +0100 Subject: [PATCH 23/25] Fix static code analysis warnings --- .../sdk/internal/fixture/JniAccountBalanceFixture.kt | 2 +- .../java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/fixture/JniAccountBalanceFixture.kt b/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/fixture/JniAccountBalanceFixture.kt index 8466aec5f..5dd9cde9c 100644 --- a/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/fixture/JniAccountBalanceFixture.kt +++ b/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/fixture/JniAccountBalanceFixture.kt @@ -5,7 +5,7 @@ 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 { val ACCOUNT_UUID: ByteArray = "random_uuid_16_b".toByteArray() const val SAPLING_VERIFIED_BALANCE: Long = 0L diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt index ffb32025f..8f0f8e199 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt @@ -20,10 +20,12 @@ object AccountFixture { ) } +private const val UUID_V4_BYTE_SIZE = 16 + // This provides us with a way to convert [UUID] to [ByteArray] private fun UUID.toByteArray(): ByteArray = ByteBuffer - .allocate(16) + .allocate(UUID_V4_BYTE_SIZE) .putLong(mostSignificantBits) .putLong(leastSignificantBits) - .array() \ No newline at end of file + .array() From 6f51c74fc1858cba287ec40a9511fb760e7f3f81 Mon Sep 17 00:00:00 2001 From: Honza Date: Tue, 3 Dec 2024 22:03:44 +0100 Subject: [PATCH 24/25] Changelog cleanup --- CHANGELOG.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29ec7fa24..e204f922b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,12 +11,9 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Synchronizer.walletBalances: StateFlow?>` that is replacement for the removed `orchardBalances`, `saplingBalances`, and `transparentBalance` -### Changed -- `Account` data class works with `accountUuid: ByteArray` instead of the previous ZIP 32 account index +### Changed - `Synchronizer.orchardBalances`, `Synchronizer.saplingBalances`, and `Synchronizer.transparentBalance` have been - replaced by `Synchronizer.walletBalances` that provides these balances based on `Account` -- These functions from `DerivationTool` have been refactored to work with the ZIP 32 account index instead of the - `Account` data class: `deriveUnifiedSpendingKey`, `deriveUnifiedAddress`, `deriveArbitraryAccountKey` + replaced by `Synchronizer.walletBalances` that provides these balances based on `Account` ### Removed - `Synchronizer.sendToAddress` and `Synchronizer.shieldFunds` have been removed, use From 2f4ac4c7319533771de03cfca53678c4fd686eb7 Mon Sep 17 00:00:00 2001 From: Honza Date: Wed, 4 Dec 2024 14:33:43 +0100 Subject: [PATCH 25/25] Final 1640 changes stirp out The rest of the changes (tests changes) will be used by 1640 though --- .../sdk/internal/model/JniUnifiedSpendingKey.kt | 6 ------ .../ecc/android/sdk/darkside/test/TestWallet.kt | 2 +- .../wallet/sdk/sample/demoapp/SampleCodeTest.kt | 2 +- .../demos/getbalance/GetBalanceFragment.kt | 3 ++- .../getprivatekey/GetPrivateKeyFragment.kt | 3 ++- .../z/ecc/android/sdk/fixture/WalletFixture.kt | 5 ++--- .../z/ecc/android/sdk/fixture/WalletFixture.kt | 2 +- .../z/ecc/android/sdk/fixture/AccountFixture.kt | 6 ++++-- .../cash/z/ecc/android/sdk/model/Account.kt | 17 ----------------- 9 files changed, 13 insertions(+), 33 deletions(-) diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniUnifiedSpendingKey.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniUnifiedSpendingKey.kt index 766c87736..5eaa1ffe0 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniUnifiedSpendingKey.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniUnifiedSpendingKey.kt @@ -27,12 +27,6 @@ class JniUnifiedSpendingKey( */ val bytes: ByteArray ) { - init { - require(accountUuid.size == 16) { - "Account UUID must be 16 bytes" - } - } - // Override to prevent leaking key to logs override fun toString() = "JniUnifiedSpendingKey(account=$account, bytes=***)" diff --git a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt index c1019408b..98a7355e6 100644 --- a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt +++ b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt @@ -63,7 +63,7 @@ class TestWallet( DerivationTool.getInstance().deriveUnifiedSpendingKey( seed = seed, network = network, - accountIndex = AccountFixture.ZIP_32_ACCOUNT_INDEX + account = AccountFixture.new() ) } val synchronizer: SdkSynchronizer = diff --git a/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/SampleCodeTest.kt b/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/SampleCodeTest.kt index 97451fa59..c8b433670 100644 --- a/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/SampleCodeTest.kt +++ b/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/SampleCodeTest.kt @@ -179,7 +179,7 @@ class SampleCodeTest { DerivationTool.getInstance().deriveUnifiedSpendingKey( seed, ZcashNetwork.Mainnet, - AccountFixture.ZIP_32_ACCOUNT_INDEX + AccountFixture.new() ) synchronizer.createProposedTransactions( synchronizer.proposeTransfer( diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt index 5f97ab4c3..9f76c2eb2 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt @@ -19,6 +19,7 @@ import cash.z.ecc.android.sdk.ext.convertZatoshiToZec import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString import cash.z.ecc.android.sdk.ext.toUsdString import cash.z.ecc.android.sdk.internal.Twig +import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.PercentDecimal import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.Zatoshi @@ -78,7 +79,7 @@ class GetBalanceFragment : BaseDemoFragment() { DerivationTool.getInstance().deriveUnifiedSpendingKey( seed, network, - CURRENT_ZIP_32_ACCOUNT_INDEX + Account(CURRENT_ZIP_32_ACCOUNT_INDEX) ) sharedViewModel.synchronizerFlow.value?.let { synchronizer -> synchronizer.proposeShielding(usk.account, Zatoshi(100000))?.let { it1 -> diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getprivatekey/GetPrivateKeyFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getprivatekey/GetPrivateKeyFragment.kt index 66ae8fde3..20ba6d7e3 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getprivatekey/GetPrivateKeyFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getprivatekey/GetPrivateKeyFragment.kt @@ -13,6 +13,7 @@ import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetPrivateKeyBinding import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext import cash.z.ecc.android.sdk.demoapp.util.fromResources +import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.tool.DerivationTool import kotlinx.coroutines.launch @@ -49,7 +50,7 @@ class GetPrivateKeyFragment : BaseDemoFragment() { DerivationTool.getInstance().deriveUnifiedSpendingKey( seed, ZcashNetwork.fromResources(requireApplicationContext()), - 5 + Account(5) ) // derive the key that allows you to view but not spend transactions diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt index 9eae42922..c5f645265 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt @@ -3,7 +3,6 @@ package cash.z.ecc.android.sdk.fixture 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 java.util.UUID /** * Provides two default wallets, making it easy to test sending funds back and forth between them. @@ -20,7 +19,7 @@ sealed class WalletFixture { @Suppress("MaxLineLength") data object Ben : WalletFixture() { override val accounts: List - get() = listOf(AccountFixture.new(UUID.fromString("52175368-821a-4664-8a7c-6a75d850f71c"))) + get() = listOf(AccountFixture.new(0)) override val seedPhrase: String get() = @@ -57,7 +56,7 @@ sealed class WalletFixture { @Suppress("MaxLineLength") data object Alice : WalletFixture() { override val accounts: List - get() = listOf(AccountFixture.new(UUID.fromString("8a204240-73a5-4e7a-93c2-a6e05711a000"))) + get() = listOf(AccountFixture.new(1)) override val seedPhrase: String get() = diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt index 59e6df48c..6bfb39772 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt @@ -28,7 +28,7 @@ object WalletFixture { accountIndex: Int = ACCOUNT_INDEX ) = RustDerivationTool.new().deriveUnifiedSpendingKey( Mnemonics.MnemonicCode(seed).toEntropy(), - network, + network.id, accountIndex ) } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt index 8f0f8e199..6f3799ba0 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/AccountFixture.kt @@ -11,18 +11,20 @@ import java.util.UUID * Note that these values are used in the automated tests only and are not passed across the JNI. */ object AccountFixture { + @Suppress("UnusedPrivateMember") val ACCOUNT_UUID = UUID.fromString("01234567-89ab-cdef-0123-456789abcdef") const val ZIP_32_ACCOUNT_INDEX = 0 - fun new(accountUuid: UUID = ACCOUNT_UUID) = + fun new(accountId: Int = ZIP_32_ACCOUNT_INDEX) = Account( - accountUuid = accountUuid.toByteArray() + value = accountId ) } private const val UUID_V4_BYTE_SIZE = 16 // This provides us with a way to convert [UUID] to [ByteArray] +@Suppress("UnusedPrivateMember") private fun UUID.toByteArray(): ByteArray = ByteBuffer .allocate(UUID_V4_BYTE_SIZE) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Account.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Account.kt index 860629017..571aa73bb 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Account.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Account.kt @@ -9,21 +9,4 @@ data class Account(val value: Int) { init { require(value >= 0) { "Account index must be >= 0 but actually is $value" } } - - companion object { - val DEFAULT = Account(0) - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Account - - return accountUuid.contentEquals(other.accountUuid) - } - - override fun hashCode(): Int { - return accountUuid.contentHashCode() - } }