[#1385] Adopt `AccountBalance` fields in Kotlin layer

* [#1385] Adopt `AccountBalance` fields in Kotlin layer

- Adds the Kotlin side changes for #1380
- Changed WalletBalance to contain these three fields: available, changePending, and valuePending, instead of total and available, and change the transparent flow to StateFlow<Zatoshi>, as we don't distinguish there.
- Related connected APIs changed
- Closes #1385

* Add WalletBalanceFixture

Placed in the public directory to be visible for clients as well

* Changelog update

* Remove `getVerifiedTransparentBalance ` API entirely

Co-authored-by: str4d <jack@electriccoin.co>
This commit is contained in:
Honza Rychnovský 2024-02-07 17:33:47 +01:00 committed by GitHub
parent b617eb1bb3
commit 19cca515fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 117 additions and 146 deletions

View File

@ -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

View File

@ -170,8 +170,6 @@ interface Backend {
suspend fun rewindBlockMetadataToHeight(height: Long)
suspend fun getVerifiedTransparentBalance(address: String): Long
suspend fun getTotalTransparentBalance(address: String): Long
/**

View File

@ -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,

View File

@ -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::<Option<NonNegativeAmount>>()
.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,

View File

@ -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)
}
}

View File

@ -105,7 +105,7 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
launch {
sharedViewModel.synchronizerFlow
.filterNotNull()
.flatMapLatest { it.transparentBalances }
.flatMapLatest { it.transparentBalance }
.collect { onTransparentBalance(it) }
}
}
@ -124,7 +124,7 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
}
}
private fun onTransparentBalance(transparentBalance: WalletBalance?) {
private fun onTransparentBalance(transparentBalance: Zatoshi?) {
binding.transparentBalance.apply {
text = transparentBalance.humanString()
}
@ -133,7 +133,7 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
// 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<FragmentGetBalanceBinding>() {
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()
}

View File

@ -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(

View File

@ -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))

View File

@ -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

View File

@ -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?
)

View File

@ -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()

View File

@ -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)
}
}

View File

@ -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")
}

View File

@ -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)
}

View File

@ -69,19 +69,19 @@ interface Synchronizer {
val networkHeight: StateFlow<BlockHeight?>
/**
* 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<WalletBalance?>
/**
* 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<WalletBalance?>
/**
* 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<WalletBalance?>
val transparentBalance: StateFlow<Zatoshi?>
/**
* 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

View File

@ -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<WalletBalance?>(null)
internal val orchardBalances = MutableStateFlow<WalletBalance?>(null)
internal val transparentBalances = MutableStateFlow<WalletBalance?>(null)
internal val transparentBalance = MutableStateFlow<Zatoshi?>(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.

View File

@ -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)
)
}

View File

@ -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(

View File

@ -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")

View File

@ -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)
)

View File

@ -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
)
}