diff --git a/CHANGELOG.md b/CHANGELOG.md index 460e5c1fd..713fee803 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- `WalletBalance` now contains new fields `changePending` and `valuePending`. Fields `total` and `pending` are + still provided. See more in the class documentation + `sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/WalletBalance.kt` +- `Synchronizer.transparentBalances: WalletBalance` to `Synchronizer.transparentBalance: Zatoshi` +- `WalletSnapshot.transparentBalance: WalletBalance` to `WalletSnapshot.transparentBalance: Zatoshi` + +### Added +- `WalletBalanceFixture` class with mock values that are supposed to be used only for testing purposes + ## [2.0.6] - 2024-01-30 ### Fixed diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Backend.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Backend.kt index a121d6fc4..eb68e94f0 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Backend.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Backend.kt @@ -170,8 +170,6 @@ interface Backend { suspend fun rewindBlockMetadataToHeight(height: Long) - suspend fun getVerifiedTransparentBalance(address: String): Long - suspend fun getTotalTransparentBalance(address: String): Long /** diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustBackend.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustBackend.kt index 02028ce01..5cc71682f 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustBackend.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustBackend.kt @@ -161,15 +161,6 @@ class RustBackend private constructor( ) } - override suspend fun getVerifiedTransparentBalance(address: String): Long = - withContext(SdkDispatchers.DATABASE_IO) { - getVerifiedTransparentBalance( - dataDbFile.absolutePath, - address, - networkId = networkId - ) - } - override suspend fun getTotalTransparentBalance(address: String): Long = withContext(SdkDispatchers.DATABASE_IO) { getTotalTransparentBalance( @@ -628,13 +619,6 @@ class RustBackend private constructor( networkId: Int ) - @JvmStatic - private external fun getVerifiedTransparentBalance( - pathDataDb: String, - taddr: String, - networkId: Int - ): Long - @JvmStatic private external fun getTotalTransparentBalance( pathDataDb: String, diff --git a/backend-lib/src/main/rust/lib.rs b/backend-lib/src/main/rust/lib.rs index 07f4b7b36..98c53545f 100644 --- a/backend-lib/src/main/rust/lib.rs +++ b/backend-lib/src/main/rust/lib.rs @@ -671,47 +671,6 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isValidUn unwrap_exc_or(&mut env, res, JNI_FALSE) } -#[no_mangle] -pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getVerifiedTransparentBalance< - 'local, ->( - mut env: JNIEnv<'local>, - _: JClass<'local>, - db_data: JString<'local>, - address: JString<'local>, - network_id: jint, -) -> jlong { - let res = catch_unwind(&mut env, |env| { - let _span = tracing::info_span!("RustBackend.getVerifiedTransparentBalance").entered(); - let network = parse_network(network_id as u32)?; - let db_data = wallet_db(env, network, db_data)?; - let addr = utils::java_string_to_rust(env, &address); - let taddr = TransparentAddress::decode(&network, &addr).unwrap(); - - let amount = (&db_data) - .get_target_and_anchor_heights(ANCHOR_OFFSET) - .map_err(|e| format_err!("Error while fetching anchor height: {}", e)) - .and_then(|opt_anchor| { - opt_anchor - .map(|(_, a)| a) - .ok_or(format_err!("Anchor height not available; scan required.")) - }) - .and_then(|anchor| { - (&db_data) - .get_unspent_transparent_outputs(&taddr, anchor, &[]) - .map_err(|e| format_err!("Error while fetching verified balance: {}", e)) - })? - .iter() - .map(|utxo| utxo.txout().value) - .sum::>() - .ok_or_else(|| format_err!("Balance overflowed MAX_MONEY."))?; - - Ok(Amount::from(amount).into()) - }); - - unwrap_exc_or(&mut env, res, -1) -} - #[no_mangle] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getTotalTransparentBalance< 'local, 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 3457942d0..d51fbf62e 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 @@ -10,7 +10,6 @@ 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.WalletBalance import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.tool.DerivationTool @@ -81,7 +80,7 @@ class TestWallet( val birthdayHeight get() = synchronizer.latestBirthdayHeight val networkName get() = synchronizer.network.networkName - suspend fun transparentBalance(): WalletBalance { + suspend fun transparentBalance(): Zatoshi { synchronizer.refreshUtxos(account, synchronizer.latestBirthdayHeight) return synchronizer.getTransparentBalance(transparentAddress) } @@ -121,7 +120,7 @@ class TestWallet( } synchronizer.getTransparentBalance(transparentAddress).let { walletBalance -> - if (walletBalance.available.value > 0L) { + if (walletBalance.value > 0L) { synchronizer.shieldFunds(shieldedSpendingKey) } } 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 b9195f4cc..0a7e74536 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 @@ -105,7 +105,7 @@ class GetBalanceFragment : BaseDemoFragment() { launch { sharedViewModel.synchronizerFlow .filterNotNull() - .flatMapLatest { it.transparentBalances } + .flatMapLatest { it.transparentBalance } .collect { onTransparentBalance(it) } } } @@ -124,7 +124,7 @@ class GetBalanceFragment : BaseDemoFragment() { } } - private fun onTransparentBalance(transparentBalance: WalletBalance?) { + private fun onTransparentBalance(transparentBalance: Zatoshi?) { binding.transparentBalance.apply { text = transparentBalance.humanString() } @@ -133,7 +133,7 @@ class GetBalanceFragment : BaseDemoFragment() { // TODO [#776]: Support variable fees // TODO [#776]: https://github.com/zcash/zcash-android-wallet-sdk/issues/776 visibility = - if ((transparentBalance?.available ?: Zatoshi(0)) > ZcashSdk.MINERS_FEE) { + if ((transparentBalance ?: Zatoshi(0)) > ZcashSdk.MINERS_FEE) { View.VISIBLE } else { View.GONE @@ -160,7 +160,7 @@ class GetBalanceFragment : BaseDemoFragment() { sharedViewModel.synchronizerFlow.value?.let { synchronizer -> onOrchardBalance(synchronizer.orchardBalances.value) onSaplingBalance(synchronizer.saplingBalances.value) - onTransparentBalance(synchronizer.transparentBalances.value) + onTransparentBalance(synchronizer.transparentBalance.value) } } @@ -183,3 +183,13 @@ private fun WalletBalance?.humanString() = Total balance: ${total.convertZatoshiToZecString(12)} """.trimIndent() } + +@Suppress("MagicNumber") +private fun Zatoshi?.humanString() = + if (null == this) { + "Calculating balance" + } else { + """ + Balance: ${convertZatoshiToZecString(12)} + """.trimIndent() + } 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 a51954258..bf313ff09 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,6 +4,7 @@ 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.model.PercentDecimal import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.Zatoshi @@ -12,9 +13,9 @@ import cash.z.ecc.android.sdk.model.Zatoshi object WalletSnapshotFixture { val STATUS = Synchronizer.Status.SYNCED val PROGRESS = PercentDecimal.ZERO_PERCENT - val TRANSPARENT_BALANCE: WalletBalance = WalletBalance(Zatoshi(8), Zatoshi(1)) - val ORCHARD_BALANCE: WalletBalance = WalletBalance(Zatoshi(5), Zatoshi(2)) - val SAPLING_BALANCE: WalletBalance = WalletBalance(Zatoshi(4), Zatoshi(4)) + 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)) // Should fill in with non-empty values for better example values in tests and UI previews @Suppress("LongParameterList") @@ -28,7 +29,7 @@ object WalletSnapshotFixture { ), orchardBalance: WalletBalance = ORCHARD_BALANCE, saplingBalance: WalletBalance = SAPLING_BALANCE, - transparentBalance: WalletBalance = TRANSPARENT_BALANCE, + transparentBalance: Zatoshi = TRANSPARENT_BALANCE, progress: PercentDecimal = PROGRESS, synchronizerError: SynchronizerError? = null ) = WalletSnapshot( 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 6ca5ec3b1..6401db08f 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 @@ -148,20 +148,14 @@ private fun BalanceMainContent( Text( stringResource( id = R.string.balance_available_amount_format, - walletSnapshot.transparentBalance.available.toZecString() - ) - ) - Text( - stringResource( - id = R.string.balance_pending_amount_format, - walletSnapshot.transparentBalance.pending.toZecString() + walletSnapshot.transparentBalance.toZecString() ) ) // TODO [#776]: Support variable fees // TODO [#776]: https://github.com/zcash/zcash-android-wallet-sdk/issues/776 // This check will not be correct with variable fees - if (walletSnapshot.transparentBalance.available > ZcashSdk.MINERS_FEE) { + if (walletSnapshot.transparentBalance > ZcashSdk.MINERS_FEE) { // 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/home/viewmodel/WalletSnapshot.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletSnapshot.kt index b97eac3ab..db1165a7c 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 @@ -12,7 +12,7 @@ data class WalletSnapshot( val processorInfo: CompactBlockProcessor.ProcessorInfo, val orchardBalance: WalletBalance, val saplingBalance: WalletBalance, - val transparentBalance: WalletBalance, + val transparentBalance: Zatoshi, val progress: PercentDecimal, val synchronizerError: SynchronizerError? ) { @@ -27,7 +27,7 @@ data class WalletSnapshot( val isSendEnabled: Boolean get() = status == Synchronizer.Status.SYNCED && hasFunds } -fun WalletSnapshot.totalBalance() = orchardBalance.total + saplingBalance.total + transparentBalance.total +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 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 5ed2b1f7e..56257726b 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 @@ -377,7 +377,7 @@ private fun Synchronizer.toWalletSnapshot() = // 3 saplingBalances, // 4 - transparentBalances, + transparentBalance, // 5 progress, // 6 @@ -385,15 +385,15 @@ private fun Synchronizer.toWalletSnapshot() = ) { flows -> val orchardBalance = flows[2] as WalletBalance? val saplingBalance = flows[3] as WalletBalance? - val transparentBalance = flows[4] as WalletBalance? + val transparentBalance = flows[4] as Zatoshi? val progressPercentDecimal = (flows[5] as PercentDecimal) WalletSnapshot( flows[0] as Synchronizer.Status, flows[1] as CompactBlockProcessor.ProcessorInfo, - orchardBalance ?: WalletBalance(Zatoshi(0), Zatoshi(0)), - saplingBalance ?: WalletBalance(Zatoshi(0), Zatoshi(0)), - transparentBalance ?: WalletBalance(Zatoshi(0), Zatoshi(0)), + orchardBalance ?: WalletBalance(Zatoshi(0), Zatoshi(0), Zatoshi(0)), + saplingBalance ?: WalletBalance(Zatoshi(0), Zatoshi(0), Zatoshi(0)), + transparentBalance ?: Zatoshi(0), progressPercentDecimal, flows[6] as SynchronizerError? ) diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/sample/TransparentRestoreSample.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/sample/TransparentRestoreSample.kt index 6f802b9a6..7292d9104 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/sample/TransparentRestoreSample.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/sample/TransparentRestoreSample.kt @@ -60,9 +60,9 @@ class TransparentRestoreSample { val address = wallet.transparentAddress Assert.assertTrue( - "Not enough funds to run sample. Expected some Zatoshi but found ${tbalance.available}. " + + "Not enough funds to run sample. Expected some Zatoshi but found $tbalance. " + "Try adding funds to $address", - tbalance.available.value > 0 + tbalance.value > 0 ) // wallet.shieldFunds() 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 d4ba0e5d1..c7c866f0f 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 @@ -12,7 +12,6 @@ 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.WalletBalance import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.ZcashNetwork import co.electriccoin.lightwallet.client.model.LightWalletEndpoint @@ -82,7 +81,7 @@ class TestWallet( val birthdayHeight get() = synchronizer.latestBirthdayHeight val networkName get() = synchronizer.network.networkName - suspend fun transparentBalance(): WalletBalance { + suspend fun transparentBalance(): Zatoshi { synchronizer.refreshUtxos(account, synchronizer.latestBirthdayHeight) return synchronizer.getTransparentBalance(transparentAddress) } @@ -122,9 +121,9 @@ class TestWallet( } synchronizer.getTransparentBalance(transparentAddress).let { walletBalance -> - Twig.debug { "FOUND utxo balance of total: ${walletBalance.total} available: ${walletBalance.available}" } + Twig.debug { "FOUND utxo balance of total: $walletBalance" } - if (walletBalance.available.value > 0L) { + if (walletBalance.value > 0L) { synchronizer.shieldFunds(spendingKey) } } 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 b71b2ad74..631c8d983 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 @@ -49,10 +49,6 @@ internal class FakeRustBackend( override suspend fun getLatestCacheHeight(): Long = metadata.maxOf { it.height } - override suspend fun getVerifiedTransparentBalance(address: String): Long { - TODO("Not yet implemented") - } - override suspend fun getTotalTransparentBalance(address: String): Long { TODO("Not yet implemented") } 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 6269ad4ad..47a738ebf 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 @@ -44,7 +44,6 @@ import cash.z.ecc.android.sdk.model.PercentDecimal import cash.z.ecc.android.sdk.model.TransactionOverview import cash.z.ecc.android.sdk.model.TransactionRecipient 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.type.AddressType @@ -185,7 +184,7 @@ class SdkSynchronizer private constructor( override val orchardBalances = processor.orchardBalances.asStateFlow() override val saplingBalances = processor.saplingBalances.asStateFlow() - override val transparentBalances = processor.transparentBalances.asStateFlow() + override val transparentBalance = processor.transparentBalance.asStateFlow() override val transactions get() = @@ -359,7 +358,7 @@ class SdkSynchronizer private constructor( /** * Calculate the latest balance based on the blocks that have been scanned and transmit this information into the - * [transparentBalances] and [saplingBalances] flow. The [orchardBalances] flow is still not filled with proper data + * [transparentBalance] and [saplingBalances] flow. The [orchardBalances] flow is still not filled with proper data * because of the current limited Orchard support. */ suspend fun refreshAllBalances() { @@ -587,7 +586,7 @@ class SdkSynchronizer private constructor( val encodedTx = txManager.encode( usk, - tBalance.available, + tBalance, TransactionRecipient.Account(usk.account), memo, usk.account @@ -607,7 +606,7 @@ class SdkSynchronizer private constructor( return processor.refreshUtxos(account, since) } - override suspend fun getTransparentBalance(tAddr: String): WalletBalance { + override suspend fun getTransparentBalance(tAddr: String): Zatoshi { return processor.getUtxoCacheBalance(tAddr) } 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 bc28121ff..20e4f7d13 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 @@ -69,19 +69,19 @@ interface Synchronizer { val networkHeight: StateFlow /** - * A stream of balance values for the orchard pool. Includes the available and total balance. + * A stream of balance values for the orchard pool. */ val orchardBalances: StateFlow /** - * A stream of balance values for the sapling pool. Includes the available and total balance. + * A stream of balance values for the sapling pool. */ val saplingBalances: StateFlow /** - * A stream of balance values for the transparent pool. Includes the available and total balance. + * A stream of a balance for the transparent pool. */ - val transparentBalances: StateFlow + val transparentBalance: StateFlow /** * A flow of all the transactions that are on the blockchain. @@ -273,7 +273,7 @@ interface Synchronizer { /** * Returns the balance that the wallet knows about. This should be called after [refreshUtxos]. */ - suspend fun getTransparentBalance(tAddr: String): WalletBalance + suspend fun getTransparentBalance(tAddr: String): Zatoshi /** * Returns the safest height to which we can rewind, given a desire to rewind to the height 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 e7c7c3414..33ee4cae3 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 @@ -49,6 +49,7 @@ 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.WalletBalance +import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.ZcashNetwork import co.electriccoin.lightwallet.client.model.BlockHeightUnsafe import co.electriccoin.lightwallet.client.model.GetAddressUtxosReplyUnsafe @@ -155,7 +156,7 @@ class CompactBlockProcessor internal constructor( // pools internal val saplingBalances = MutableStateFlow(null) internal val orchardBalances = MutableStateFlow(null) - internal val transparentBalances = MutableStateFlow(null) + internal val transparentBalance = MutableStateFlow(null) private val processingMutex = Mutex() @@ -738,11 +739,7 @@ class CompactBlockProcessor internal constructor( // 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. - transparentBalances.value = - WalletBalance( - it.unshielded, - it.unshielded - ) + transparentBalance.value = it.unshielded } } @@ -2148,7 +2145,7 @@ class CompactBlockProcessor internal constructor( } ?: lowerBoundHeight } - suspend fun getUtxoCacheBalance(address: String): WalletBalance = backend.getDownloadedUtxoBalance(address) + suspend fun getUtxoCacheBalance(address: String): Zatoshi = backend.getDownloadedUtxoBalance(address) /** * Sealed class representing the various states of this processor. 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 new file mode 100644 index 000000000..664593970 --- /dev/null +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletBalanceFixture.kt @@ -0,0 +1,31 @@ +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 + const val VALUE_PENDING: Long = 4 + + fun new( + available: Zatoshi = Zatoshi(AVAILABLE), + changePending: Zatoshi = Zatoshi(CHANGE_PENDING), + valuePending: Zatoshi = Zatoshi(VALUE_PENDING) + ) = WalletBalance( + available = available, + changePending = changePending, + valuePending = valuePending + ) + + fun newLong( + available: Long = AVAILABLE, + changePending: Long = CHANGE_PENDING, + valuePending: Long = VALUE_PENDING + ) = WalletBalance( + available = Zatoshi(available), + changePending = Zatoshi(changePending), + valuePending = Zatoshi(valuePending) + ) +} diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackend.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackend.kt index ae8a4ed98..3c6a7fdd7 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackend.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackend.kt @@ -11,7 +11,7 @@ import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.FirstClassByteArray import cash.z.ecc.android.sdk.model.Proposal 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 @Suppress("TooManyFunctions") @@ -58,7 +58,7 @@ internal interface TypesafeBackend { suspend fun rewindBlockMetadataToHeight(height: BlockHeight) - suspend fun getDownloadedUtxoBalance(address: String): WalletBalance + suspend fun getDownloadedUtxoBalance(address: String): Zatoshi @Suppress("LongParameterList") suspend fun putUtxo( diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackendImpl.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackendImpl.kt index f4380df0a..1dbad3d4b 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackendImpl.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackendImpl.kt @@ -12,7 +12,6 @@ import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.FirstClassByteArray import cash.z.ecc.android.sdk.model.Proposal 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 kotlinx.coroutines.withContext @@ -114,21 +113,12 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke backend.rewindBlockMetadataToHeight(height.value) } - override suspend fun getDownloadedUtxoBalance(address: String): WalletBalance { - // Note this implementation is not ideal because it requires two database queries without a transaction, which - // makes the data potentially inconsistent. However the verified amount is queried first which makes this less - // bad. - val verified = - withContext(SdkDispatchers.DATABASE_IO) { - backend.getVerifiedTransparentBalance(address) - } + override suspend fun getDownloadedUtxoBalance(address: String): Zatoshi { val total = withContext(SdkDispatchers.DATABASE_IO) { - backend.getTotalTransparentBalance( - address - ) + backend.getTotalTransparentBalance(address) } - return WalletBalance(Zatoshi(total), Zatoshi(verified)) + return Zatoshi(total) } @Suppress("LongParameterList") 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/internal/model/AccountBalance.kt index 4f06f9f6c..9b363baa3 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/internal/model/AccountBalance.kt @@ -13,13 +13,15 @@ internal data class AccountBalance( return AccountBalance( sapling = WalletBalance( - Zatoshi(jni.saplingVerifiedBalance + jni.saplingChangePending + jni.saplingValuePending), - Zatoshi(jni.saplingVerifiedBalance) + available = Zatoshi(jni.saplingVerifiedBalance), + changePending = Zatoshi(jni.saplingChangePending), + valuePending = Zatoshi(jni.saplingValuePending) ), orchard = WalletBalance( - Zatoshi(jni.orchardVerifiedBalance + jni.orchardChangePending + jni.orchardValuePending), - Zatoshi(jni.orchardVerifiedBalance) + available = Zatoshi(jni.orchardVerifiedBalance), + changePending = Zatoshi(jni.orchardChangePending), + valuePending = Zatoshi(jni.orchardValuePending) ), unshielded = Zatoshi(jni.unshieldedBalance) ) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/WalletBalance.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/WalletBalance.kt index a2c4e16d4..b5dd38b56 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/WalletBalance.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/WalletBalance.kt @@ -1,28 +1,30 @@ package cash.z.ecc.android.sdk.model /** - * Data structure to hold the total and available balance of the wallet. This is what is - * received on the balance channel. + * Data structure to hold the balance of the wallet. This is what is received on the balance channel. * - * @param total the total balance, ignoring funds that cannot be used. - * @param available the amount of funds that are available for use. Typical reasons that funds + * @param available The amount of funds that are available for use. Typical reasons that funds * may be unavailable include fairly new transactions that do not have enough confirmations or * notes that are tied up because we are awaiting change from a transaction. When a note has * been spent, its change cannot be used until there are enough confirmations. + * @param changePending The value in the account of change notes that do not yet have sufficient confirmations to be + * spendable. + * @param valuePending The value in the account of all remaining received notes that either do not have sufficient + * confirmations to be spendable, or for which witnesses cannot yet be constructed without additional scanning. */ data class WalletBalance( - val total: Zatoshi, - val available: Zatoshi + val available: Zatoshi, + val changePending: Zatoshi, + val valuePending: Zatoshi ) { - init { - require(total.value >= available.value) { "Wallet total balance must be >= available balance" } - } + /** + * The current total balance is calculated as a sum of [available], [changePending], + * and [valuePending]. + */ + val total = available + changePending + valuePending + /** + * The current pending balance is calculated as the difference between [total] and [available] balances. + */ val pending = total - available - - operator fun plus(other: WalletBalance): WalletBalance = - WalletBalance( - total + other.total, - available + other.available - ) }