[#776] Enable ZIP 317 fees support

* [#776] Enable ZIP 317 fees support

* Deprecate ZcashSdk.MINERS_FEE

* Replace MINERS_FEE with Proposal API in Demo app

* Changelog update

* Bump SDK to 2.0.8 to produce snapshot version
This commit is contained in:
Honza Rychnovský 2024-03-13 10:03:53 +01:00 committed by GitHub
parent 5b04cbc579
commit 078e76a941
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 131 additions and 35 deletions

View File

@ -6,6 +6,10 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Changed
- The SDK uses ZIP-317 fee system internally
- `ZcashSdk.MINERS_FEE` has been deprecated, and will be removed in 2.1.0
## [2.0.7] - 2024-03-08 ## [2.0.7] - 2024-03-08
### Fixed ### Fixed

View File

@ -376,7 +376,7 @@ class RustBackend private constructor(
*/ */
companion object { companion object {
internal val rustLibraryLoader = NativeLibraryLoader("zcashwalletsdk") internal val rustLibraryLoader = NativeLibraryLoader("zcashwalletsdk")
private const val IS_USE_ZIP_317_FEES = false private const val IS_USE_ZIP_317_FEES = true
suspend fun loadLibrary() { suspend fun loadLibrary() {
rustLibraryLoader.load { rustLibraryLoader.load {

View File

@ -40,6 +40,7 @@ import cash.z.ecc.android.sdk.demoapp.ui.screen.transactions.view.Transactions
import cash.z.ecc.android.sdk.demoapp.util.AndroidApiVersion import cash.z.ecc.android.sdk.demoapp.util.AndroidApiVersion
import cash.z.ecc.android.sdk.demoapp.util.fromResources import cash.z.ecc.android.sdk.demoapp.util.fromResources
import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.model.Proposal
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.type.ServerValidation import cash.z.ecc.android.sdk.type.ServerValidation
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -131,6 +132,9 @@ internal fun ComposeActivity.Navigation() {
val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value
val walletSnapshot = walletViewModel.walletSnapshot.collectAsStateWithLifecycle().value val walletSnapshot = walletViewModel.walletSnapshot.collectAsStateWithLifecycle().value
val spendingKey = walletViewModel.spendingKey.collectAsStateWithLifecycle().value val spendingKey = walletViewModel.spendingKey.collectAsStateWithLifecycle().value
val sendTransactionProposal = remember { mutableStateOf<Proposal?>(null) }
if (null == synchronizer || null == walletSnapshot || null == spendingKey) { if (null == synchronizer || null == walletSnapshot || null == spendingKey) {
// Display loading indicator // Display loading indicator
} else { } else {
@ -140,10 +144,14 @@ internal fun ComposeActivity.Navigation() {
onSend = { onSend = {
walletViewModel.send(it) walletViewModel.send(it)
}, },
onGetProposal = {
sendTransactionProposal.value = walletViewModel.getSendProposal(it)
},
onBack = { onBack = {
walletViewModel.clearSendOrShieldState() walletViewModel.clearSendOrShieldState()
navController.popBackStackJustOnce(SEND) navController.popBackStackJustOnce(SEND)
} },
sendTransactionProposal = sendTransactionProposal.value
) )
} }
} }

View File

@ -14,7 +14,6 @@ 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.ext.requireApplicationContext
import cash.z.ecc.android.sdk.demoapp.util.SyncBlockchainBenchmarkTrace import cash.z.ecc.android.sdk.demoapp.util.SyncBlockchainBenchmarkTrace
import cash.z.ecc.android.sdk.demoapp.util.fromResources import cash.z.ecc.android.sdk.demoapp.util.fromResources
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.Account
@ -138,10 +137,10 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
} }
binding.shield.apply { binding.shield.apply {
// TODO [#776]: Support variable fees // This check is not entirely correct - it does not calculate the resulting fee with the new Proposal API
// TODO [#776]: https://github.com/zcash/zcash-android-wallet-sdk/issues/776 // Note that the entire fragment-based old Demo app will be removed as part of [#973]
visibility = visibility =
if ((transparentBalance ?: Zatoshi(0)) > ZcashSdk.MINERS_FEE) { if ((transparentBalance ?: Zatoshi(0)).value > 0L) {
View.VISIBLE View.VISIBLE
} else { } else {
View.GONE View.GONE

View File

@ -26,12 +26,11 @@ import cash.z.ecc.android.sdk.demoapp.R
import cash.z.ecc.android.sdk.demoapp.fixture.WalletSnapshotFixture 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.SendState
import cash.z.ecc.android.sdk.demoapp.ui.screen.home.viewmodel.WalletSnapshot import cash.z.ecc.android.sdk.demoapp.ui.screen.home.viewmodel.WalletSnapshot
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.model.toZecString import cash.z.ecc.android.sdk.model.toZecString
@Preview(name = "Balance") @Preview(name = "Balance")
@Composable
@Suppress("ktlint:standard:function-naming") @Suppress("ktlint:standard:function-naming")
@Composable
private fun ComposablePreview() { private fun ComposablePreview() {
MaterialTheme { MaterialTheme {
Balance( Balance(
@ -152,10 +151,8 @@ private fun BalanceMainContent(
) )
) )
// TODO [#776]: Support variable fees // This check is not entirely correct - it does not calculate the resulting fee with the new Proposal API
// TODO [#776]: https://github.com/zcash/zcash-android-wallet-sdk/issues/776 if (walletSnapshot.transparentBalance.value > 0L) {
// This check will not be correct with variable fees
if (walletSnapshot.transparentBalance > ZcashSdk.MINERS_FEE) {
// Note this implementation does not guard against multiple clicks // Note this implementation does not guard against multiple clicks
Button(onClick = onShieldFunds) { Button(onClick = onShieldFunds) {
Text(stringResource(id = R.string.action_shield)) Text(stringResource(id = R.string.action_shield))

View File

@ -2,7 +2,6 @@ package cash.z.ecc.android.sdk.demoapp.ui.screen.home.viewmodel
import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.block.processor.CompactBlockProcessor import cash.z.ecc.android.sdk.block.processor.CompactBlockProcessor
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.model.PercentDecimal import cash.z.ecc.android.sdk.model.PercentDecimal
import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.Zatoshi
@ -16,13 +15,11 @@ data class WalletSnapshot(
val progress: PercentDecimal, val progress: PercentDecimal,
val synchronizerError: SynchronizerError? val synchronizerError: SynchronizerError?
) { ) {
// TODO [#776]: Support variable fees
// TODO [#776]: https://github.com/zcash/zcash-android-wallet-sdk/issues/776
// Note: the wallet is effectively empty if it cannot cover the miner's fee // Note: the wallet is effectively empty if it cannot cover the miner's fee
val hasFunds = // This check is not entirely correct - it does not calculate the resulting fee with the new Proposal API
saplingBalance.available.value > val hasFunds = saplingBalance.available.value > 0L
(ZcashSdk.MINERS_FEE.value.toDouble() / Zatoshi.ZATOSHI_PER_ZEC) // 0.0001
val hasSaplingBalance = saplingBalance.total.value > 0 val hasSaplingBalance = saplingBalance.total.value > 0L
val isSendEnabled: Boolean get() = status == Synchronizer.Status.SYNCED && hasFunds val isSendEnabled: Boolean get() = status == Synchronizer.Status.SYNCED && hasFunds
} }

View File

@ -22,12 +22,14 @@ import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.PercentDecimal import cash.z.ecc.android.sdk.model.PercentDecimal
import cash.z.ecc.android.sdk.model.PersistableWallet 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.TransactionSubmitResult
import cash.z.ecc.android.sdk.model.WalletAddresses import cash.z.ecc.android.sdk.model.WalletAddresses
import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.model.ZecSend import cash.z.ecc.android.sdk.model.ZecSend
import cash.z.ecc.android.sdk.model.proposeSend
import cash.z.ecc.android.sdk.model.send import cash.z.ecc.android.sdk.model.send
import cash.z.ecc.android.sdk.tool.DerivationTool import cash.z.ecc.android.sdk.tool.DerivationTool
import co.electriccoin.lightwallet.client.model.LightWalletEndpoint import co.electriccoin.lightwallet.client.model.LightWalletEndpoint
@ -50,6 +52,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.toList import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -57,6 +60,7 @@ import kotlin.time.Duration.Companion.seconds
// To make this more multiplatform compatible, we need to remove the dependency on Context // To make this more multiplatform compatible, we need to remove the dependency on Context
// for loading the preferences. // for loading the preferences.
@Suppress("TooManyFunctions")
class WalletViewModel(application: Application) : AndroidViewModel(application) { class WalletViewModel(application: Application) : AndroidViewModel(application) {
private val walletCoordinator = WalletCoordinator.getInstance(application) private val walletCoordinator = WalletCoordinator.getInstance(application)
@ -212,12 +216,36 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
} }
} }
/**
* Synchronously provides proposal object for the given [spendingKey] and [zecSend] objects
*/
fun getSendProposal(zecSend: ZecSend): Proposal? {
if (sendState.value is SendState.Sending) {
return null
}
val synchronizer = synchronizer.value
return if (null != synchronizer) {
// Calling the proposal API within a blocking coroutine should be fine for the showcase purpose
runBlocking {
val spendingKey = spendingKey.filterNotNull().first()
kotlin.runCatching {
synchronizer.proposeSend(spendingKey, zecSend)
}.onFailure {
Twig.error(it) { "Failed to get transaction proposal" }
}.getOrNull()
}
} else {
error("Unable to send funds because synchronizer is not loaded.")
}
}
/** /**
* Asynchronously shields transparent funds. Note that two shielding operations cannot occur at the same time. * Asynchronously shields transparent funds. Note that two shielding operations cannot occur at the same time.
* *
* Observe the result via [sendState]. * Observe the result via [sendState].
*/ */
@Suppress("MagicNumber")
fun shieldFunds() { fun shieldFunds() {
if (sendState.value is SendState.Sending) { if (sendState.value is SendState.Sending) {
return return
@ -230,6 +258,7 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
viewModelScope.launch { viewModelScope.launch {
val spendingKey = spendingKey.filterNotNull().first() val spendingKey = spendingKey.filterNotNull().first()
kotlin.runCatching { kotlin.runCatching {
@Suppress("MagicNumber")
synchronizer.proposeShielding(spendingKey.account, Zatoshi(100000))?.let { synchronizer.proposeShielding(spendingKey.account, Zatoshi(100000))?.let {
synchronizer.createProposedTransactions( synchronizer.createProposedTransactions(
it, it,

View File

@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
@ -43,6 +44,7 @@ import cash.z.ecc.android.sdk.demoapp.util.fromResources
import cash.z.ecc.android.sdk.fixture.WalletFixture import cash.z.ecc.android.sdk.fixture.WalletFixture
import cash.z.ecc.android.sdk.model.Memo import cash.z.ecc.android.sdk.model.Memo
import cash.z.ecc.android.sdk.model.MonetarySeparators import cash.z.ecc.android.sdk.model.MonetarySeparators
import cash.z.ecc.android.sdk.model.Proposal
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.model.ZecSend import cash.z.ecc.android.sdk.model.ZecSend
import cash.z.ecc.android.sdk.model.ZecSendExt import cash.z.ecc.android.sdk.model.ZecSendExt
@ -60,18 +62,22 @@ private fun ComposablePreview() {
walletSnapshot = WalletSnapshotFixture.new(), walletSnapshot = WalletSnapshotFixture.new(),
sendState = SendState.None, sendState = SendState.None,
onSend = {}, onSend = {},
onBack = {} onGetProposal = {},
onBack = {},
sendTransactionProposal = null
) )
} }
} }
@Composable @Composable
@Suppress("ktlint:standard:function-naming") @Suppress("ktlint:standard:function-naming", "LongParameterList")
fun Send( fun Send(
walletSnapshot: WalletSnapshot, walletSnapshot: WalletSnapshot,
sendState: SendState, sendState: SendState,
onSend: (ZecSend) -> Unit, onSend: (ZecSend) -> Unit,
onBack: () -> Unit onGetProposal: (ZecSend) -> Unit,
onBack: () -> Unit,
sendTransactionProposal: Proposal?,
) { ) {
Scaffold(topBar = { Scaffold(topBar = {
SendTopAppBar(onBack) SendTopAppBar(onBack)
@ -80,7 +86,9 @@ fun Send(
paddingValues = paddingValues, paddingValues = paddingValues,
walletSnapshot = walletSnapshot, walletSnapshot = walletSnapshot,
sendState = sendState, sendState = sendState,
onSend = onSend onSend = onSend,
onGetProposal = onGetProposal,
sendTransactionProposal = sendTransactionProposal
) )
} }
} }
@ -105,12 +113,14 @@ private fun SendTopAppBar(onBack: () -> Unit) {
} }
@Composable @Composable
@Suppress("LongMethod", "ktlint:standard:function-naming") @Suppress("LongMethod", "ktlint:standard:function-naming", "LongParameterList")
private fun SendMainContent( private fun SendMainContent(
paddingValues: PaddingValues, paddingValues: PaddingValues,
walletSnapshot: WalletSnapshot, walletSnapshot: WalletSnapshot,
sendState: SendState, sendState: SendState,
onSend: (ZecSend) -> Unit onSend: (ZecSend) -> Unit,
onGetProposal: (ZecSend) -> Unit,
sendTransactionProposal: Proposal?,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val monetarySeparators = MonetarySeparators.current(locale = Locale.US) val monetarySeparators = MonetarySeparators.current(locale = Locale.US)
@ -229,6 +239,36 @@ private fun SendMainContent(
Text(validation.joinToString(", ")) Text(validation.joinToString(", "))
} }
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = {
val zecSendValidation =
ZecSendExt.new(
context,
recipientAddressString,
amountZecString,
memoString,
monetarySeparators
)
when (zecSendValidation) {
is ZecSendExt.ZecSendValidation.Valid -> onGetProposal(zecSendValidation.zecSend)
is ZecSendExt.ZecSendValidation.Invalid -> validation = zecSendValidation.validationErrors
}
},
// Needs actual validation
enabled = amountZecString.isNotBlank() && recipientAddressString.isNotBlank()
) {
Text(stringResource(id = R.string.send_proposal_button))
}
if (sendTransactionProposal != null) {
Text(stringResource(id = R.string.send_proposal_status, sendTransactionProposal.toPrettyString()))
Spacer(modifier = Modifier.height(16.dp))
}
Button( Button(
onClick = { onClick = {
val zecSendValidation = val zecSendValidation =

View File

@ -73,6 +73,9 @@
<string name="send_memo">Memo</string> <string name="send_memo">Memo</string>
<string name="send_button">Send</string> <string name="send_button">Send</string>
<string name="send_proposal_button">Get Proposal</string>
<string name="send_proposal_status">Proposal:\n<xliff:g id="proposal" example="Fee:0.001...">%1$s</xliff:g></string>
<string name="server_textfield_value"><xliff:g id="host" example="example.com">%1$s</xliff:g>:<xliff:g id="port" example="508">%2$d</xliff:g></string> <string name="server_textfield_value"><xliff:g id="host" example="example.com">%1$s</xliff:g>:<xliff:g id="port" example="508">%2$d</xliff:g></string>
<string name="server_textfield_hint">&lt;host&gt;:&lt;port&gt;</string> <string name="server_textfield_hint">&lt;host&gt;:&lt;port&gt;</string>
<string name="server_textfield_error">Invalid server</string> <string name="server_textfield_error">Invalid server</string>

View File

@ -26,7 +26,7 @@ ZCASH_ASCII_GPG_KEY=
# Configures whether release is an unstable snapshot, therefore published to the snapshot repository. # Configures whether release is an unstable snapshot, therefore published to the snapshot repository.
IS_SNAPSHOT=true IS_SNAPSHOT=true
LIBRARY_VERSION=2.0.7 LIBRARY_VERSION=2.0.8
# Kotlin compiler warnings can be considered errors, failing the build. # Kotlin compiler warnings can be considered errors, failing the build.
ZCASH_IS_TREAT_WARNINGS_AS_ERRORS=true ZCASH_IS_TREAT_WARNINGS_AS_ERRORS=true

View File

@ -18,3 +18,13 @@ suspend fun Synchronizer.send(
), ),
spendingKey spendingKey
) )
suspend fun Synchronizer.proposeSend(
spendingKey: UnifiedSpendingKey,
send: ZecSend
) = proposeTransfer(
spendingKey.account,
send.destination.address,
send.amount,
send.memo.value
)

View File

@ -5,7 +5,6 @@ import androidx.test.platform.app.InstrumentationRegistry
import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.Synchronizer.Status.SYNCED import cash.z.ecc.android.sdk.Synchronizer.Status.SYNCED
import cash.z.ecc.android.sdk.WalletInitMode import cash.z.ecc.android.sdk.WalletInitMode
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.ext.onFirst import cash.z.ecc.android.sdk.ext.onFirst
import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.Account
@ -116,7 +115,7 @@ class TestnetIntegrationTest : ScopedTest() {
synchronizer.proposeTransfer( synchronizer.proposeTransfer(
spendingKey.account, spendingKey.account,
toAddress, toAddress,
ZcashSdk.MINERS_FEE, Zatoshi(10_000L),
"first mainnet tx from the SDK" "first mainnet tx from the SDK"
), ),
spendingKey spendingKey

View File

@ -3,7 +3,6 @@
package cash.z.ecc.android.sdk.sample package cash.z.ecc.android.sdk.sample
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
@ -20,7 +19,7 @@ import org.junit.Test
* the same data. * the same data.
*/ */
class TransparentRestoreSample { class TransparentRestoreSample {
val txValue = Zatoshi(ZcashSdk.MINERS_FEE.value / 2) val txValue = Zatoshi(Zatoshi(10_000L).value / 2)
// val walletA = SimpleWallet(SEED_PHRASE, "WalletA") // val walletA = SimpleWallet(SEED_PHRASE, "WalletA")

View File

@ -8,11 +8,19 @@ import cash.z.ecc.android.sdk.model.Zatoshi
* becomes easier to reduce privacy by segmenting the anonymity set of users, particularly as it * becomes easier to reduce privacy by segmenting the anonymity set of users, particularly as it
* relates to network requests. * relates to network requests.
*/ */
@Suppress("MagicNumber")
object ZcashSdk { object ZcashSdk {
/** /**
* Miner's fee in zatoshi. * Miner's fee in zatoshi.
*/ */
@Suppress("MagicNumber")
@Deprecated(
message = "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.",
replaceWith =
ReplaceWith(
"proposeTransfer(usk.account, toAddress, amount, memo) OR " +
"proposeShielding(usk.account, shieldingThreshold, memo)"
)
)
val MINERS_FEE = Zatoshi(10_000L) val MINERS_FEE = Zatoshi(10_000L)
/** /**

View File

@ -1,12 +1,12 @@
package cash.z.ecc.android.sdk.fixture package cash.z.ecc.android.sdk.fixture
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.FirstClassByteArray import cash.z.ecc.android.sdk.model.FirstClassByteArray
import cash.z.ecc.android.sdk.model.TransactionOverview import cash.z.ecc.android.sdk.model.TransactionOverview
import cash.z.ecc.android.sdk.model.TransactionState import cash.z.ecc.android.sdk.model.TransactionState
import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.Zatoshi
@Suppress("MagicNumber")
object TransactionOverviewFixture { object TransactionOverviewFixture {
const val ID: Long = 1 const val ID: Long = 1
val RAW_ID: FirstClassByteArray get() = FirstClassByteArray("rawId".toByteArray()) val RAW_ID: FirstClassByteArray get() = FirstClassByteArray("rawId".toByteArray())
@ -16,9 +16,8 @@ object TransactionOverviewFixture {
val RAW: FirstClassByteArray get() = FirstClassByteArray("raw".toByteArray()) val RAW: FirstClassByteArray get() = FirstClassByteArray("raw".toByteArray())
const val IS_SENT_TRANSACTION: Boolean = false const val IS_SENT_TRANSACTION: Boolean = false
@Suppress("MagicNumber")
val NET_VALUE: Zatoshi = Zatoshi(10_000) val NET_VALUE: Zatoshi = Zatoshi(10_000)
val FEE_PAID: Zatoshi = ZcashSdk.MINERS_FEE val FEE_PAID: Zatoshi = Zatoshi(10_000)
const val IS_CHANGE: Boolean = false const val IS_CHANGE: Boolean = false
const val RECEIVED_NOTE_COUNT: Int = 1 const val RECEIVED_NOTE_COUNT: Int = 1
const val SENT_NOTE_COUNT: Int = 0 const val SENT_NOTE_COUNT: Int = 0

View File

@ -51,4 +51,8 @@ class Proposal(
fun totalFeeRequired(): Zatoshi { fun totalFeeRequired(): Zatoshi {
return Zatoshi(inner.totalFeeRequired()) return Zatoshi(inner.totalFeeRequired())
} }
fun toPrettyString(): String {
return "Transaction count: ${transactionCount()}, Total fee required: ${totalFeeRequired()}"
}
} }