From 078e76a941dd9a7de76346b22eeee82e4bba82da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Honza=20Rychnovsk=C3=BD?= Date: Wed, 13 Mar 2024 10:03:53 +0100 Subject: [PATCH] [#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 --- CHANGELOG.md | 4 ++ .../android/sdk/internal/jni/RustBackend.kt | 2 +- .../z/ecc/android/sdk/demoapp/Navigation.kt | 10 +++- .../demos/getbalance/GetBalanceFragment.kt | 7 ++- .../ui/screen/balance/view/BalanceView.kt | 9 ++-- .../screen/home/viewmodel/WalletSnapshot.kt | 11 ++-- .../screen/home/viewmodel/WalletViewModel.kt | 31 ++++++++++- .../demoapp/ui/screen/send/view/SendView.kt | 52 ++++++++++++++++--- demo-app/src/main/res/values/strings.xml | 3 ++ gradle.properties | 2 +- .../cash/z/ecc/android/sdk/model/ZecSend.kt | 10 ++++ .../sdk/integration/TestnetIntegrationTest.kt | 3 +- .../sdk/sample/TransparentRestoreSample.kt | 3 +- .../cash/z/ecc/android/sdk/ext/ZcashSdk.kt | 10 +++- .../sdk/fixture/TransactionOverviewFixture.kt | 5 +- .../cash/z/ecc/android/sdk/model/Proposal.kt | 4 ++ 16 files changed, 131 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 875341da..0255547d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [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 ### Fixed 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 82f5f0bc..1cd78c56 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 @@ -376,7 +376,7 @@ class RustBackend private constructor( */ companion object { 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() { rustLibraryLoader.load { 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 497dd443..e75eb833 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 @@ -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.fromResources 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.type.ServerValidation import kotlinx.coroutines.CoroutineScope @@ -131,6 +132,9 @@ internal fun ComposeActivity.Navigation() { val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value val walletSnapshot = walletViewModel.walletSnapshot.collectAsStateWithLifecycle().value val spendingKey = walletViewModel.spendingKey.collectAsStateWithLifecycle().value + + val sendTransactionProposal = remember { mutableStateOf(null) } + if (null == synchronizer || null == walletSnapshot || null == spendingKey) { // Display loading indicator } else { @@ -140,10 +144,14 @@ internal fun ComposeActivity.Navigation() { onSend = { walletViewModel.send(it) }, + onGetProposal = { + sendTransactionProposal.value = walletViewModel.getSendProposal(it) + }, onBack = { walletViewModel.clearSendOrShieldState() navController.popBackStackJustOnce(SEND) - } + }, + sendTransactionProposal = sendTransactionProposal.value ) } } 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 d221e9ad..74686337 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 @@ -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.util.SyncBlockchainBenchmarkTrace 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.internal.Twig import cash.z.ecc.android.sdk.model.Account @@ -138,10 +137,10 @@ class GetBalanceFragment : BaseDemoFragment() { } binding.shield.apply { - // TODO [#776]: Support variable fees - // TODO [#776]: https://github.com/zcash/zcash-android-wallet-sdk/issues/776 + // This check is not entirely correct - it does not calculate the resulting fee with the new Proposal API + // Note that the entire fragment-based old Demo app will be removed as part of [#973] visibility = - if ((transparentBalance ?: Zatoshi(0)) > ZcashSdk.MINERS_FEE) { + if ((transparentBalance ?: Zatoshi(0)).value > 0L) { View.VISIBLE } else { View.GONE 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 6401db08..c529936e 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 @@ -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.ui.screen.home.viewmodel.SendState 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 @Preview(name = "Balance") -@Composable @Suppress("ktlint:standard:function-naming") +@Composable private fun ComposablePreview() { MaterialTheme { Balance( @@ -152,10 +151,8 @@ private fun BalanceMainContent( ) ) - // 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 > ZcashSdk.MINERS_FEE) { + // 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)) 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 db1165a7..11ade79e 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,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.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.WalletBalance import cash.z.ecc.android.sdk.model.Zatoshi @@ -16,13 +15,11 @@ data class WalletSnapshot( val progress: PercentDecimal, 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 - val hasFunds = - saplingBalance.available.value > - (ZcashSdk.MINERS_FEE.value.toDouble() / Zatoshi.ZATOSHI_PER_ZEC) // 0.0001 - val hasSaplingBalance = saplingBalance.total.value > 0 + // 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 } 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 df66cb47..69261dd0 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 @@ -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.PercentDecimal 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 +import cash.z.ecc.android.sdk.model.proposeSend import cash.z.ecc.android.sdk.model.send import cash.z.ecc.android.sdk.tool.DerivationTool 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.toList import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock 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 // for loading the preferences. +@Suppress("TooManyFunctions") class WalletViewModel(application: Application) : AndroidViewModel(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. * * Observe the result via [sendState]. */ - @Suppress("MagicNumber") fun shieldFunds() { if (sendState.value is SendState.Sending) { return @@ -230,6 +258,7 @@ class WalletViewModel(application: Application) : AndroidViewModel(application) viewModelScope.launch { val spendingKey = spendingKey.filterNotNull().first() kotlin.runCatching { + @Suppress("MagicNumber") synchronizer.proposeShielding(spendingKey.account, Zatoshi(100000))?.let { synchronizer.createProposedTransactions( it, 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 b5d6e246..7d018214 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 @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size 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.model.Memo 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.ZecSend import cash.z.ecc.android.sdk.model.ZecSendExt @@ -60,18 +62,22 @@ private fun ComposablePreview() { walletSnapshot = WalletSnapshotFixture.new(), sendState = SendState.None, onSend = {}, - onBack = {} + onGetProposal = {}, + onBack = {}, + sendTransactionProposal = null ) } } @Composable -@Suppress("ktlint:standard:function-naming") +@Suppress("ktlint:standard:function-naming", "LongParameterList") fun Send( walletSnapshot: WalletSnapshot, sendState: SendState, onSend: (ZecSend) -> Unit, - onBack: () -> Unit + onGetProposal: (ZecSend) -> Unit, + onBack: () -> Unit, + sendTransactionProposal: Proposal?, ) { Scaffold(topBar = { SendTopAppBar(onBack) @@ -80,7 +86,9 @@ fun Send( paddingValues = paddingValues, walletSnapshot = walletSnapshot, sendState = sendState, - onSend = onSend + onSend = onSend, + onGetProposal = onGetProposal, + sendTransactionProposal = sendTransactionProposal ) } } @@ -105,12 +113,14 @@ private fun SendTopAppBar(onBack: () -> Unit) { } @Composable -@Suppress("LongMethod", "ktlint:standard:function-naming") +@Suppress("LongMethod", "ktlint:standard:function-naming", "LongParameterList") private fun SendMainContent( paddingValues: PaddingValues, walletSnapshot: WalletSnapshot, sendState: SendState, - onSend: (ZecSend) -> Unit + onSend: (ZecSend) -> Unit, + onGetProposal: (ZecSend) -> Unit, + sendTransactionProposal: Proposal?, ) { val context = LocalContext.current val monetarySeparators = MonetarySeparators.current(locale = Locale.US) @@ -229,6 +239,36 @@ private fun SendMainContent( 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( onClick = { val zecSendValidation = diff --git a/demo-app/src/main/res/values/strings.xml b/demo-app/src/main/res/values/strings.xml index b37064b5..64f5900b 100644 --- a/demo-app/src/main/res/values/strings.xml +++ b/demo-app/src/main/res/values/strings.xml @@ -73,6 +73,9 @@ Memo Send + Get Proposal + Proposal:\n%1$s + %1$s:%2$d <host>:<port> Invalid server diff --git a/gradle.properties b/gradle.properties index 5d8f6f47..cdc37f9f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,7 +26,7 @@ ZCASH_ASCII_GPG_KEY= # Configures whether release is an unstable snapshot, therefore published to the snapshot repository. IS_SNAPSHOT=true -LIBRARY_VERSION=2.0.7 +LIBRARY_VERSION=2.0.8 # Kotlin compiler warnings can be considered errors, failing the build. ZCASH_IS_TREAT_WARNINGS_AS_ERRORS=true diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecSend.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecSend.kt index 2acb6767..919ae4c3 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecSend.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecSend.kt @@ -18,3 +18,13 @@ suspend fun Synchronizer.send( ), spendingKey ) + +suspend fun Synchronizer.proposeSend( + spendingKey: UnifiedSpendingKey, + send: ZecSend +) = proposeTransfer( + spendingKey.account, + send.destination.address, + send.amount, + send.memo.value +) 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 98638f66..5de76c55 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 @@ -5,7 +5,6 @@ import androidx.test.platform.app.InstrumentationRegistry 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.ZcashSdk import cash.z.ecc.android.sdk.ext.onFirst import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.model.Account @@ -116,7 +115,7 @@ class TestnetIntegrationTest : ScopedTest() { synchronizer.proposeTransfer( spendingKey.account, toAddress, - ZcashSdk.MINERS_FEE, + Zatoshi(10_000L), "first mainnet tx from the SDK" ), spendingKey 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 7292d910..0f494c1a 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 @@ -3,7 +3,6 @@ package cash.z.ecc.android.sdk.sample 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.Zatoshi import cash.z.ecc.android.sdk.model.ZcashNetwork @@ -20,7 +19,7 @@ import org.junit.Test * the same data. */ class TransparentRestoreSample { - val txValue = Zatoshi(ZcashSdk.MINERS_FEE.value / 2) + val txValue = Zatoshi(Zatoshi(10_000L).value / 2) // val walletA = SimpleWallet(SEED_PHRASE, "WalletA") diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/ZcashSdk.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/ZcashSdk.kt index d7276099..58ddd17c 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/ZcashSdk.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/ZcashSdk.kt @@ -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 * relates to network requests. */ -@Suppress("MagicNumber") object ZcashSdk { /** * 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) /** diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/TransactionOverviewFixture.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/TransactionOverviewFixture.kt index 34a77b30..a8453743 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/TransactionOverviewFixture.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/fixture/TransactionOverviewFixture.kt @@ -1,12 +1,12 @@ 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.FirstClassByteArray import cash.z.ecc.android.sdk.model.TransactionOverview import cash.z.ecc.android.sdk.model.TransactionState import cash.z.ecc.android.sdk.model.Zatoshi +@Suppress("MagicNumber") object TransactionOverviewFixture { const val ID: Long = 1 val RAW_ID: FirstClassByteArray get() = FirstClassByteArray("rawId".toByteArray()) @@ -16,9 +16,8 @@ object TransactionOverviewFixture { val RAW: FirstClassByteArray get() = FirstClassByteArray("raw".toByteArray()) const val IS_SENT_TRANSACTION: Boolean = false - @Suppress("MagicNumber") 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 RECEIVED_NOTE_COUNT: Int = 1 const val SENT_NOTE_COUNT: Int = 0 diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Proposal.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Proposal.kt index 0906dafb..8ae77d69 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Proposal.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Proposal.kt @@ -51,4 +51,8 @@ class Proposal( fun totalFeeRequired(): Zatoshi { return Zatoshi(inner.totalFeeRequired()) } + + fun toPrettyString(): String { + return "Transaction count: ${transactionCount()}, Total fee required: ${totalFeeRequired()}" + } }