[#367] Fix Kotlin compiler warnings
* Fix missing room schema export directory * Fix returned unused fields from cursor * Fix missing db column index * Improved TODO. Added suppress warning * Changed parameters name to correspond to their supertype * Changed type to Kotlin variant * Use priority parameter * Unified parameter names * Suppress unchecked type warning * Removed inline function * Suppress obsolete coroutine warnings * Improve previous commit * Fix unnecessary safe call warning * Remove unused parameter * Unreachable code * toLowerCase where possible * Changed parameter name * Suppress several "unused" warnings * Fixed fromHtml() deprecation * Suppress intentionally unused parameter warning * Remove redundant initializer * Remove inline function * Suppress intentionally used deprecated code * Unreachable code * Suppress obsolete coroutine warnings * Suppress intentionally unused parameter * Remove unused expression * Supertype parameter name * Warnings of GetBlockRangeFragment.kt * Deprecated onActivityCreated * Suppress obsolete coroutine/flow warnings * Unnecessary null check * Suppress intentionally unused parameter * Suppress intentionally unused parameters * Deprecated onActivityCreated * Predetermined type * ListUtxosFragment clean code * Suppress intentionally unused parameter * Lint checks warnings fix * Add data db migration * Enable treating Kotlin compiler warnings as errors * Solve several darkside-test-lib tests warnings * Solve several demo-app tests warnings * Solve several sdk-lib tests warnings * Ktlint check result fix * Remove parentheses now that Synchronizer is not cast * Remove wildcard imports for java.util * Revert "Add data db migration" * Revert "Fix missing db column index" * Suppress missing indexes on data db entities Co-authored-by: Carter Jernigan <git@carterjernigan.com>
This commit is contained in:
parent
10d0652a8d
commit
150778f008
|
@ -1,6 +1,11 @@
|
||||||
<component name="ProjectCodeStyleConfiguration">
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
<code_scheme name="Project" version="173">
|
<code_scheme name="Project" version="173">
|
||||||
<JetCodeStyleSettings>
|
<JetCodeStyleSettings>
|
||||||
|
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||||
|
<value>
|
||||||
|
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
||||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
|
|
|
@ -43,9 +43,9 @@ class InboundTxTests : ScopedTest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addTransactions(targetHeight: BlockHeight, vararg txs: String) {
|
private fun addTransactions(targetHeight: BlockHeight, vararg txs: String) {
|
||||||
val overwriteBlockCount = 5
|
// val overwriteBlockCount = 5
|
||||||
chainMaker
|
chainMaker
|
||||||
// .stageEmptyBlocks(targetHeight, overwriteBlockCount)
|
// .stageEmptyBlocks(targetHeight, overwriteBlockCount)
|
||||||
.stageTransactions(targetHeight, *txs)
|
.stageTransactions(targetHeight, *txs)
|
||||||
.applyTipHeight(targetHeight)
|
.applyTipHeight(targetHeight)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package cash.z.ecc.android.sdk.darkside.test
|
package cash.z.ecc.android.sdk.darkside.test
|
||||||
|
|
||||||
open class DarksideTest(name: String = javaClass.simpleName) : ScopedTest() {
|
open class DarksideTest : ScopedTest() {
|
||||||
val sithLord = DarksideTestCoordinator()
|
val sithLord = DarksideTestCoordinator()
|
||||||
val validator = sithLord.validator
|
val validator = sithLord.validator
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package cash.z.ecc.android.sdk.darkside.test
|
package cash.z.ecc.android.sdk.darkside.test
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import cash.z.ecc.android.sdk.SdkSynchronizer
|
|
||||||
import cash.z.ecc.android.sdk.Synchronizer
|
import cash.z.ecc.android.sdk.Synchronizer
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
import cash.z.ecc.android.sdk.model.BlockHeight
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
@ -101,13 +100,13 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
|
||||||
twig("got processor status $it")
|
twig("got processor status $it")
|
||||||
if (it == Synchronizer.Status.DISCONNECTED) {
|
if (it == Synchronizer.Status.DISCONNECTED) {
|
||||||
twig("waiting a bit before giving up on connection...")
|
twig("waiting a bit before giving up on connection...")
|
||||||
} else if (targetHeight != null && (synchronizer as SdkSynchronizer).processor.getLastScannedHeight() < targetHeight) {
|
} else if (targetHeight != null && synchronizer.processor.getLastScannedHeight() < targetHeight) {
|
||||||
twig("awaiting new blocks from server...")
|
twig("awaiting new blocks from server...")
|
||||||
}
|
}
|
||||||
}.map {
|
}.map {
|
||||||
// whenever we're waiting for a target height, for simplicity, if we're sleeping,
|
// whenever we're waiting for a target height, for simplicity, if we're sleeping,
|
||||||
// and in between polls, then consider it that we're not synced
|
// and in between polls, then consider it that we're not synced
|
||||||
if (targetHeight != null && (synchronizer as SdkSynchronizer).processor.getLastScannedHeight() < targetHeight) {
|
if (targetHeight != null && synchronizer.processor.getLastScannedHeight() < targetHeight) {
|
||||||
twig("switching status to DOWNLOADING because we're still waiting for height $targetHeight")
|
twig("switching status to DOWNLOADING because we're still waiting for height $targetHeight")
|
||||||
Synchronizer.Status.DOWNLOADING
|
Synchronizer.Status.DOWNLOADING
|
||||||
} else {
|
} else {
|
||||||
|
@ -145,8 +144,8 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
|
||||||
|
|
||||||
fun validateHasBlock(height: BlockHeight) {
|
fun validateHasBlock(height: BlockHeight) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
assertTrue((synchronizer as SdkSynchronizer).findBlockHashAsHex(height) != null)
|
assertTrue(synchronizer.findBlockHashAsHex(height) != null)
|
||||||
assertTrue((synchronizer as SdkSynchronizer).findBlockHash(height)?.size ?: 0 > 0)
|
assertTrue(synchronizer.findBlockHash(height)?.size ?: 0 > 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,7 +192,7 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validateBlockHash(height: BlockHeight, expectedHash: String) {
|
fun validateBlockHash(height: BlockHeight, expectedHash: String) {
|
||||||
val hash = runBlocking { (synchronizer as SdkSynchronizer).findBlockHashAsHex(height) }
|
val hash = runBlocking { synchronizer.findBlockHashAsHex(height) }
|
||||||
assertEquals(expectedHash, hash)
|
assertEquals(expectedHash, hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,7 +201,7 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validateTxCount(count: Int) {
|
fun validateTxCount(count: Int) {
|
||||||
val txCount = runBlocking { (synchronizer as SdkSynchronizer).getTransactionCount() }
|
val txCount = runBlocking { synchronizer.getTransactionCount() }
|
||||||
assertEquals("Expected $count transactions but found $txCount instead!", count, txCount)
|
assertEquals("Expected $count transactions but found $txCount instead!", count, txCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,7 +215,7 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
suspend fun validateBalance(available: Long = -1, total: Long = -1, accountIndex: Int = 0) {
|
suspend fun validateBalance(available: Long = -1, total: Long = -1, accountIndex: Int = 0) {
|
||||||
val balance = (synchronizer as SdkSynchronizer).processor.getBalanceInfo(accountIndex)
|
val balance = synchronizer.processor.getBalanceInfo(accountIndex)
|
||||||
if (available > 0) {
|
if (available > 0) {
|
||||||
assertEquals("invalid available balance", available, balance.available)
|
assertEquals("invalid available balance", available, balance.available)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
||||||
import cash.z.ecc.android.sdk.internal.Twig
|
import cash.z.ecc.android.sdk.internal.Twig
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
|
@ -19,6 +20,7 @@ import org.junit.Before
|
||||||
import org.junit.BeforeClass
|
import org.junit.BeforeClass
|
||||||
import java.util.concurrent.TimeoutException
|
import java.util.concurrent.TimeoutException
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
open class ScopedTest(val defaultTimeout: Long = 2000L) : DarksideTestPrerequisites() {
|
open class ScopedTest(val defaultTimeout: Long = 2000L) : DarksideTestPrerequisites() {
|
||||||
protected lateinit var testScope: CoroutineScope
|
protected lateinit var testScope: CoroutineScope
|
||||||
|
|
||||||
|
@ -60,7 +62,7 @@ open class ScopedTest(val defaultTimeout: Long = 2000L) : DarksideTestPrerequisi
|
||||||
fun createScope() {
|
fun createScope() {
|
||||||
twig("======================= CLASS STARTED ===============================")
|
twig("======================= CLASS STARTED ===============================")
|
||||||
classScope = CoroutineScope(
|
classScope = CoroutineScope(
|
||||||
SupervisorJob() + newFixedThreadPoolContext(2, this.javaClass.simpleName)
|
SupervisorJob() + newFixedThreadPoolContext(2, this::class.java.simpleName)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,9 @@ class SimpleMnemonics : MnemonicPlugin {
|
||||||
override fun fullWordList(languageCode: String) = Mnemonics.getCachedWords(Locale.ENGLISH.language)
|
override fun fullWordList(languageCode: String) = Mnemonics.getCachedWords(Locale.ENGLISH.language)
|
||||||
override fun nextEntropy(): ByteArray = WordCount.COUNT_24.toEntropy()
|
override fun nextEntropy(): ByteArray = WordCount.COUNT_24.toEntropy()
|
||||||
override fun nextMnemonic(): CharArray = MnemonicCode(WordCount.COUNT_24).chars
|
override fun nextMnemonic(): CharArray = MnemonicCode(WordCount.COUNT_24).chars
|
||||||
override fun nextMnemonic(entropy: ByteArray): CharArray = MnemonicCode(entropy).chars
|
override fun nextMnemonic(seed: ByteArray): CharArray = MnemonicCode(seed).chars
|
||||||
override fun nextMnemonicList(): List<CharArray> = MnemonicCode(WordCount.COUNT_24).words
|
override fun nextMnemonicList(): List<CharArray> = MnemonicCode(WordCount.COUNT_24).words
|
||||||
override fun nextMnemonicList(entropy: ByteArray): List<CharArray> = MnemonicCode(entropy).words
|
override fun nextMnemonicList(seed: ByteArray): List<CharArray> = MnemonicCode(seed).words
|
||||||
override fun toSeed(mnemonic: CharArray): ByteArray = MnemonicCode(mnemonic).toSeed()
|
override fun toSeed(mnemonic: CharArray): ByteArray = MnemonicCode(mnemonic).toSeed()
|
||||||
override fun toWordList(mnemonic: CharArray): List<CharArray> = MnemonicCode(mnemonic).words
|
override fun toWordList(mnemonic: CharArray): List<CharArray> = MnemonicCode(mnemonic).words
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ 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.tool.DerivationTool
|
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
|
@ -34,6 +35,7 @@ import java.util.concurrent.TimeoutException
|
||||||
* A simple wallet that connects to testnet for integration testing. The intention is that it is
|
* A simple wallet that connects to testnet for integration testing. The intention is that it is
|
||||||
* easy to drive and nice to use.
|
* easy to drive and nice to use.
|
||||||
*/
|
*/
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
class TestWallet(
|
class TestWallet(
|
||||||
val seedPhrase: String,
|
val seedPhrase: String,
|
||||||
val alias: String = "TestWallet",
|
val alias: String = "TestWallet",
|
||||||
|
|
|
@ -70,14 +70,14 @@ class SampleCodeTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
assertEquals(1, spendingKeys.size)
|
assertEquals(1, spendingKeys.size)
|
||||||
log("Spending Key: ${spendingKeys?.get(0)}")
|
log("Spending Key: ${spendingKeys[0]}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ///////////////////////////////////////////////////
|
// ///////////////////////////////////////////////////
|
||||||
// Get Address
|
// Get Address
|
||||||
@Test fun getAddress() = runBlocking {
|
@Test fun getAddress() = runBlocking {
|
||||||
val address = synchronizer.getAddress()
|
val address = synchronizer.getAddress()
|
||||||
assertFalse(address.isNullOrBlank())
|
assertFalse(address.isBlank())
|
||||||
log("Address: $address")
|
log("Address: $address")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,11 @@ import com.google.android.material.snackbar.Snackbar
|
||||||
abstract class BaseDemoFragment<T : ViewBinding> : Fragment() {
|
abstract class BaseDemoFragment<T : ViewBinding> : Fragment() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Since the lightwalletservice is not a component that apps typically use, directly, we provide
|
* Since the lightWalletService is not a component that apps typically use, directly, we provide
|
||||||
* this from one place. Everything that can be done with the service can/should be done with the
|
* this from one place. Everything that can be done with the service can/should be done with the
|
||||||
* synchronizer because it wraps the service.
|
* synchronizer because it wraps the service.
|
||||||
*/
|
*/
|
||||||
val lightwalletService get() = mainActivity()?.lightwalletService
|
val lightWalletService get() = mainActivity()?.lightWalletService
|
||||||
|
|
||||||
// contains view information provided by the user
|
// contains view information provided by the user
|
||||||
val sharedViewModel: SharedViewModel by activityViewModels()
|
val sharedViewModel: SharedViewModel by activityViewModels()
|
||||||
|
@ -76,9 +76,8 @@ abstract class BaseDemoFragment<T : ViewBinding> : Fragment() {
|
||||||
* Convenience function to the given text to the clipboard.
|
* Convenience function to the given text to the clipboard.
|
||||||
*/
|
*/
|
||||||
open fun copyToClipboard(text: String, description: String = "Copied to clipboard!") {
|
open fun copyToClipboard(text: String, description: String = "Copied to clipboard!") {
|
||||||
(activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)?.let { cm ->
|
(activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
|
||||||
cm.setPrimaryClip(ClipData.newPlainText("DemoAppClip", text))
|
.setPrimaryClip(ClipData.newPlainText("DemoAppClip", text))
|
||||||
}
|
|
||||||
toast(description)
|
toast(description)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ class MainActivity :
|
||||||
* this object because it would utilize the synchronizer, instead, which exposes APIs that
|
* this object because it would utilize the synchronizer, instead, which exposes APIs that
|
||||||
* automatically sync with the server.
|
* automatically sync with the server.
|
||||||
*/
|
*/
|
||||||
var lightwalletService: LightWalletService? = null
|
var lightWalletService: LightWalletService? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
@ -53,8 +53,8 @@ class MainActivity :
|
||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(toolbar)
|
||||||
|
|
||||||
val fab: FloatingActionButton = findViewById(R.id.fab)
|
val fab: FloatingActionButton = findViewById(R.id.fab)
|
||||||
fab.setOnClickListener { view ->
|
fab.setOnClickListener {
|
||||||
onFabClicked(view)
|
onFabClicked()
|
||||||
}
|
}
|
||||||
val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
|
val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
|
||||||
val navView: NavigationView = findViewById(R.id.nav_view)
|
val navView: NavigationView = findViewById(R.id.nav_view)
|
||||||
|
@ -78,7 +78,7 @@ class MainActivity :
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
lightwalletService?.shutdown()
|
lightWalletService?.shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
@ -107,17 +107,17 @@ class MainActivity :
|
||||||
//
|
//
|
||||||
|
|
||||||
private fun initService() {
|
private fun initService() {
|
||||||
if (lightwalletService != null) {
|
if (lightWalletService != null) {
|
||||||
lightwalletService?.shutdown()
|
lightWalletService?.shutdown()
|
||||||
}
|
}
|
||||||
val network = ZcashNetwork.fromResources(applicationContext)
|
val network = ZcashNetwork.fromResources(applicationContext)
|
||||||
lightwalletService = LightWalletGrpcService.new(
|
lightWalletService = LightWalletGrpcService.new(
|
||||||
applicationContext,
|
applicationContext,
|
||||||
LightWalletEndpoint.defaultForNetwork(network)
|
LightWalletEndpoint.defaultForNetwork(network)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onFabClicked(view: View) {
|
private fun onFabClicked() {
|
||||||
fabListener?.onActionButtonClicked()
|
fabListener?.onActionButtonClicked()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,8 +126,10 @@ class MainActivity :
|
||||||
//
|
//
|
||||||
|
|
||||||
fun getClipboardText(): String? {
|
fun getClipboardText(): String? {
|
||||||
return with(clipboard) {
|
with(clipboard) {
|
||||||
if (!hasPrimaryClip()) return null
|
if (!hasPrimaryClip()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
return primaryClip!!.getItemAt(0)?.coerceToText(this@MainActivity)?.toString()
|
return primaryClip!!.getItemAt(0)?.coerceToText(this@MainActivity)?.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package cash.z.ecc.android.sdk.demoapp.demos.getblock
|
package cash.z.ecc.android.sdk.demoapp.demos.getblock
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Html
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.core.text.HtmlCompat
|
||||||
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
||||||
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetBlockBinding
|
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetBlockBinding
|
||||||
import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
|
import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
|
||||||
|
@ -26,10 +26,10 @@ class GetBlockFragment : BaseDemoFragment<FragmentGetBlockBinding>() {
|
||||||
|
|
||||||
private fun setBlockHeight(blockHeight: BlockHeight) {
|
private fun setBlockHeight(blockHeight: BlockHeight) {
|
||||||
val blocks =
|
val blocks =
|
||||||
lightwalletService?.getBlockRange(blockHeight..blockHeight)
|
lightWalletService?.getBlockRange(blockHeight..blockHeight)
|
||||||
val block = blocks?.firstOrNull()
|
val block = blocks?.firstOrNull()
|
||||||
binding.textInfo.visibility = View.VISIBLE
|
binding.textInfo.visibility = View.VISIBLE
|
||||||
binding.textInfo.text = Html.fromHtml(
|
binding.textInfo.text = HtmlCompat.fromHtml(
|
||||||
"""
|
"""
|
||||||
<b>block height:</b> ${block?.height.withCommas()}
|
<b>block height:</b> ${block?.height.withCommas()}
|
||||||
<br/><b>block time:</b> ${block?.time.toRelativeTime(requireApplicationContext())}
|
<br/><b>block time:</b> ${block?.time.toRelativeTime(requireApplicationContext())}
|
||||||
|
@ -37,10 +37,12 @@ class GetBlockFragment : BaseDemoFragment<FragmentGetBlockBinding>() {
|
||||||
<br/><b>hash:</b> ${block?.hash?.toByteArray()?.toHex()}
|
<br/><b>hash:</b> ${block?.hash?.toByteArray()?.toHex()}
|
||||||
<br/><b>prevHash:</b> ${block?.prevHash?.toByteArray()?.toHex()}
|
<br/><b>prevHash:</b> ${block?.prevHash?.toByteArray()?.toHex()}
|
||||||
${block?.vtxList.toHtml()}
|
${block?.vtxList.toHtml()}
|
||||||
""".trimIndent()
|
""".trimIndent(),
|
||||||
|
HtmlCompat.FROM_HTML_MODE_LEGACY
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
private fun onApply(_unused: View? = null) {
|
private fun onApply(_unused: View? = null) {
|
||||||
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
||||||
val newHeight = min(binding.textBlockHeight.text.toString().toLongOrNull() ?: network.saplingActivationHeight.value, network.saplingActivationHeight.value)
|
val newHeight = min(binding.textBlockHeight.text.toString().toLongOrNull() ?: network.saplingActivationHeight.value, network.saplingActivationHeight.value)
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package cash.z.ecc.android.sdk.demoapp.demos.getblockrange
|
package cash.z.ecc.android.sdk.demoapp.demos.getblockrange
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Html
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.core.text.HtmlCompat
|
||||||
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
||||||
import cash.z.ecc.android.sdk.demoapp.R
|
import cash.z.ecc.android.sdk.demoapp.R
|
||||||
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetBlockRangeBinding
|
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetBlockRangeBinding
|
||||||
|
@ -27,13 +27,13 @@ class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
|
||||||
private fun setBlockRange(blockRange: ClosedRange<BlockHeight>) {
|
private fun setBlockRange(blockRange: ClosedRange<BlockHeight>) {
|
||||||
val start = System.currentTimeMillis()
|
val start = System.currentTimeMillis()
|
||||||
val blocks =
|
val blocks =
|
||||||
lightwalletService?.getBlockRange(blockRange)
|
lightWalletService?.getBlockRange(blockRange)
|
||||||
val fetchDelta = System.currentTimeMillis() - start
|
val fetchDelta = System.currentTimeMillis() - start
|
||||||
|
|
||||||
// Note: This is a demo so we won't worry about iterating efficiently over these blocks
|
// Note: This is a demo so we won't worry about iterating efficiently over these blocks
|
||||||
// Note: Converting the blocks sequence to a list can consume a lot of memory and may
|
// Note: Converting the blocks sequence to a list can consume a lot of memory and may
|
||||||
// cause OOM.
|
// cause OOM.
|
||||||
binding.textInfo.text = Html.fromHtml(
|
binding.textInfo.text = HtmlCompat.fromHtml(
|
||||||
blocks?.toList()?.run {
|
blocks?.toList()?.run {
|
||||||
val count = size
|
val count = size
|
||||||
val emptyCount = count { it.vtxCount == 0 }
|
val emptyCount = count { it.vtxCount == 0 }
|
||||||
|
@ -66,13 +66,15 @@ class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
|
||||||
<br/><b>avg OUTs [per block / per TX]:</b> ${"%.1f / %.1f".format(outCount.toDouble() / (count - emptyCount), outCount.toDouble() / txCount)}
|
<br/><b>avg OUTs [per block / per TX]:</b> ${"%.1f / %.1f".format(outCount.toDouble() / (count - emptyCount), outCount.toDouble() / txCount)}
|
||||||
<br/><b>avg INs [per block / per TX]:</b> ${"%.1f / %.1f".format(inCount.toDouble() / (count - emptyCount), inCount.toDouble() / txCount)}
|
<br/><b>avg INs [per block / per TX]:</b> ${"%.1f / %.1f".format(inCount.toDouble() / (count - emptyCount), inCount.toDouble() / txCount)}
|
||||||
<br/><b>most shielded TXs:</b> ${if (maxTxs == null) "none" else "${maxTxs.vtxCount} in block ${maxTxs.height.withCommas()}"}
|
<br/><b>most shielded TXs:</b> ${if (maxTxs == null) "none" else "${maxTxs.vtxCount} in block ${maxTxs.height.withCommas()}"}
|
||||||
<br/><b>most shielded INs:</b> ${if (maxInTx == null) "none" else "${maxInTx.spendsCount} in block ${maxIns?.height.withCommas()} at tx index ${maxInTx.index}"}
|
<br/><b>most shielded INs:</b> ${if (maxInTx == null) "none" else "${maxInTx.spendsCount} in block ${maxIns.height.withCommas()} at tx index ${maxInTx.index}"}
|
||||||
<br/><b>most shielded OUTs:</b> ${if (maxOutTx == null) "none" else "${maxOutTx?.outputsCount} in block ${maxOuts?.height.withCommas()} at tx index ${maxOutTx?.index}"}
|
<br/><b>most shielded OUTs:</b> ${if (maxOutTx == null) "none" else "${maxOutTx.outputsCount} in block ${maxOuts.height.withCommas()} at tx index ${maxOutTx.index}"}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
} ?: "No blocks found in that range."
|
} ?: "No blocks found in that range.",
|
||||||
|
HtmlCompat.FROM_HTML_MODE_LEGACY
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
private fun onApply(_unused: View) {
|
private fun onApply(_unused: View) {
|
||||||
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
||||||
val start = max(binding.textStartHeight.text.toString().toLongOrNull() ?: network.saplingActivationHeight.value, network.saplingActivationHeight.value)
|
val start = max(binding.textStartHeight.text.toString().toLongOrNull() ?: network.saplingActivationHeight.value, network.saplingActivationHeight.value)
|
||||||
|
|
|
@ -15,7 +15,7 @@ class GetLatestHeightFragment : BaseDemoFragment<FragmentGetLatestHeightBinding>
|
||||||
private fun displayLatestHeight() {
|
private fun displayLatestHeight() {
|
||||||
// note: this is a blocking call, a real app wouldn't do this on the main thread
|
// note: this is a blocking call, a real app wouldn't do this on the main thread
|
||||||
// instead, a production app would leverage the synchronizer like in the other demos
|
// instead, a production app would leverage the synchronizer like in the other demos
|
||||||
binding.textInfo.text = lightwalletService?.getLatestBlockHeight().toString()
|
binding.textInfo.text = lightWalletService?.getLatestBlockHeight().toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
@ -2,6 +2,8 @@ package cash.z.ecc.android.sdk.demoapp.demos.getprivatekey
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import cash.z.ecc.android.bip39.Mnemonics
|
import cash.z.ecc.android.bip39.Mnemonics
|
||||||
import cash.z.ecc.android.bip39.toSeed
|
import cash.z.ecc.android.bip39.toSeed
|
||||||
|
@ -61,9 +63,14 @@ class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
|
||||||
// Android Lifecycle overrides
|
// Android Lifecycle overrides
|
||||||
//
|
//
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onCreateView(
|
||||||
super.onActivityCreated(savedInstanceState)
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
setup()
|
setup()
|
||||||
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
|
|
@ -49,12 +49,14 @@ class HomeFragment : BaseDemoFragment<FragmentHomeBinding>() {
|
||||||
mainActivity()?.removeClipboardListener()
|
mainActivity()?.removeClipboardListener()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
private fun onEditSeedPhrase(unused: View) {
|
private fun onEditSeedPhrase(unused: View) {
|
||||||
setEditShown(true)
|
setEditShown(true)
|
||||||
binding.inputSeedPhrase.setText(sharedViewModel.seedPhrase.value)
|
binding.inputSeedPhrase.setText(sharedViewModel.seedPhrase.value)
|
||||||
binding.textLayoutSeedPhrase.helperText = ""
|
binding.textLayoutSeedPhrase.helperText = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
private fun onAcceptSeedPhrase(unused: View) {
|
private fun onAcceptSeedPhrase(unused: View) {
|
||||||
if (applySeedPhrase()) {
|
if (applySeedPhrase()) {
|
||||||
setEditShown(false)
|
setEditShown(false)
|
||||||
|
@ -62,10 +64,12 @@ class HomeFragment : BaseDemoFragment<FragmentHomeBinding>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
private fun onCancelSeedPhrase(unused: View) {
|
private fun onCancelSeedPhrase(unused: View) {
|
||||||
setEditShown(false)
|
setEditShown(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
private fun onPasteSeedPhrase(unused: View) {
|
private fun onPasteSeedPhrase(unused: View) {
|
||||||
mainActivity()?.getClipboardText().let { clipboardText ->
|
mainActivity()?.getClipboardText().let { clipboardText ->
|
||||||
binding.inputSeedPhrase.setText(clipboardText)
|
binding.inputSeedPhrase.setText(clipboardText)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package cash.z.ecc.android.sdk.demoapp.demos.listtransactions
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import cash.z.ecc.android.bip39.Mnemonics
|
import cash.z.ecc.android.bip39.Mnemonics
|
||||||
|
@ -33,7 +34,7 @@ import kotlinx.coroutines.runBlocking
|
||||||
class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBinding>() {
|
class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBinding>() {
|
||||||
private lateinit var initializer: Initializer
|
private lateinit var initializer: Initializer
|
||||||
private lateinit var synchronizer: Synchronizer
|
private lateinit var synchronizer: Synchronizer
|
||||||
private lateinit var adapter: TransactionAdapter<ConfirmedTransaction>
|
private lateinit var adapter: TransactionAdapter
|
||||||
private lateinit var address: String
|
private lateinit var address: String
|
||||||
private var status: Synchronizer.Status? = null
|
private var status: Synchronizer.Status? = null
|
||||||
private val isSynced get() = status == Synchronizer.Status.SYNCED
|
private val isSynced get() = status == Synchronizer.Status.SYNCED
|
||||||
|
@ -134,9 +135,14 @@ class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBindin
|
||||||
// Android Lifecycle overrides
|
// Android Lifecycle overrides
|
||||||
//
|
//
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onCreateView(
|
||||||
super.onActivityCreated(savedInstanceState)
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
setup()
|
setup()
|
||||||
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
|
|
@ -10,30 +10,29 @@ import cash.z.ecc.android.sdk.demoapp.R
|
||||||
/**
|
/**
|
||||||
* Simple adapter implementation that knows how to bind a recyclerview to ClearedTransactions.
|
* Simple adapter implementation that knows how to bind a recyclerview to ClearedTransactions.
|
||||||
*/
|
*/
|
||||||
class TransactionAdapter<T : ConfirmedTransaction> :
|
class TransactionAdapter : ListAdapter<ConfirmedTransaction, TransactionViewHolder>(
|
||||||
ListAdapter<T, TransactionViewHolder<T>>(
|
object : DiffUtil.ItemCallback<ConfirmedTransaction>() {
|
||||||
object : DiffUtil.ItemCallback<T>() {
|
override fun areItemsTheSame(
|
||||||
override fun areItemsTheSame(
|
oldItem: ConfirmedTransaction,
|
||||||
oldItem: T,
|
newItem: ConfirmedTransaction
|
||||||
newItem: T
|
) = oldItem.minedHeight == newItem.minedHeight
|
||||||
) = oldItem.minedHeight == newItem.minedHeight
|
|
||||||
|
|
||||||
override fun areContentsTheSame(
|
override fun areContentsTheSame(
|
||||||
oldItem: T,
|
oldItem: ConfirmedTransaction,
|
||||||
newItem: T
|
newItem: ConfirmedTransaction
|
||||||
) = oldItem == newItem
|
) = oldItem == newItem
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|
||||||
override fun onCreateViewHolder(
|
override fun onCreateViewHolder(
|
||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
viewType: Int
|
viewType: Int
|
||||||
) = TransactionViewHolder<T>(
|
) = TransactionViewHolder(
|
||||||
LayoutInflater.from(parent.context).inflate(R.layout.item_transaction, parent, false)
|
LayoutInflater.from(parent.context).inflate(R.layout.item_transaction, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun onBindViewHolder(
|
override fun onBindViewHolder(
|
||||||
holder: TransactionViewHolder<T>,
|
holder: TransactionViewHolder,
|
||||||
position: Int
|
position: Int
|
||||||
) = holder.bindTo(getItem(position))
|
) = holder.bindTo(getItem(position))
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,18 +15,18 @@ import java.util.Locale
|
||||||
/**
|
/**
|
||||||
* Simple view holder for displaying confirmed transactions in the recyclerview.
|
* Simple view holder for displaying confirmed transactions in the recyclerview.
|
||||||
*/
|
*/
|
||||||
class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
class TransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
private val amountText = itemView.findViewById<TextView>(R.id.text_transaction_amount)
|
private val amountText = itemView.findViewById<TextView>(R.id.text_transaction_amount)
|
||||||
private val infoText = itemView.findViewById<TextView>(R.id.text_transaction_info)
|
private val infoText = itemView.findViewById<TextView>(R.id.text_transaction_info)
|
||||||
private val timeText = itemView.findViewById<TextView>(R.id.text_transaction_timestamp)
|
private val timeText = itemView.findViewById<TextView>(R.id.text_transaction_timestamp)
|
||||||
private val icon = itemView.findViewById<ImageView>(R.id.image_transaction_type)
|
private val icon = itemView.findViewById<ImageView>(R.id.image_transaction_type)
|
||||||
private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault())
|
private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault())
|
||||||
|
|
||||||
fun bindTo(transaction: T?) {
|
fun bindTo(transaction: ConfirmedTransaction?) {
|
||||||
val isInbound = transaction?.toAddress.isNullOrEmpty()
|
val isInbound = transaction?.toAddress.isNullOrEmpty()
|
||||||
amountText.text = transaction?.valueInZatoshi.convertZatoshiToZecString()
|
amountText.text = transaction?.valueInZatoshi.convertZatoshiToZecString()
|
||||||
timeText.text =
|
timeText.text =
|
||||||
if (transaction == null || transaction?.blockTimeInSeconds == 0L) "Pending"
|
if (transaction == null || transaction.blockTimeInSeconds == 0L) "Pending"
|
||||||
else formatter.format(transaction.blockTimeInSeconds * 1000L)
|
else formatter.format(transaction.blockTimeInSeconds * 1000L)
|
||||||
infoText.text = getMemoString(transaction)
|
infoText.text = getMemoString(transaction)
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
||||||
icon.setColorFilter(ContextCompat.getColor(itemView.context, if (isInbound) R.color.tx_inbound else R.color.tx_outbound))
|
icon.setColorFilter(ContextCompat.getColor(itemView.context, if (isInbound) R.color.tx_inbound else R.color.tx_outbound))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMemoString(transaction: T?): String {
|
private fun getMemoString(transaction: ConfirmedTransaction?): String {
|
||||||
return transaction?.memo?.takeUnless { it[0] < 0 }?.let { String(it) } ?: "no memo"
|
return transaction?.memo?.takeUnless { it[0] < 0 }?.let { String(it) } ?: "no memo"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
||||||
private lateinit var seed: ByteArray
|
private lateinit var seed: ByteArray
|
||||||
private lateinit var initializer: Initializer
|
private lateinit var initializer: Initializer
|
||||||
private lateinit var synchronizer: Synchronizer
|
private lateinit var synchronizer: Synchronizer
|
||||||
private lateinit var adapter: UtxoAdapter<ConfirmedTransaction>
|
private lateinit var adapter: UtxoAdapter
|
||||||
private val address: String = "t1RwbKka1CnktvAJ1cSqdn7c6PXWG4tZqgd"
|
private val address: String = "t1RwbKka1CnktvAJ1cSqdn7c6PXWG4tZqgd"
|
||||||
private var status: Synchronizer.Status? = null
|
private var status: Synchronizer.Status? = null
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
||||||
setup()
|
setup()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun initUi() {
|
private fun initUi() {
|
||||||
binding.inputAddress.setText(address)
|
binding.inputAddress.setText(address)
|
||||||
binding.inputRangeStart.setText(ZcashNetwork.fromResources(requireApplicationContext()).saplingActivationHeight.toString())
|
binding.inputRangeStart.setText(ZcashNetwork.fromResources(requireApplicationContext()).saplingActivationHeight.toString())
|
||||||
binding.inputRangeEnd.setText(getUxtoEndHeight(requireApplicationContext()).value.toString())
|
binding.inputRangeEnd.setText(getUxtoEndHeight(requireApplicationContext()).value.toString())
|
||||||
|
@ -99,7 +99,7 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
||||||
initTransactionUi()
|
initTransactionUi()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun downloadTransactions() {
|
private fun downloadTransactions() {
|
||||||
binding.textStatus.text = "loading..."
|
binding.textStatus.text = "loading..."
|
||||||
binding.textStatus.post {
|
binding.textStatus.post {
|
||||||
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
||||||
|
@ -114,7 +114,7 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
||||||
?: getUxtoEndHeight(requireApplicationContext()).value
|
?: getUxtoEndHeight(requireApplicationContext()).value
|
||||||
var allStart = now
|
var allStart = now
|
||||||
twig("loading transactions in range $startToUse..$endToUse")
|
twig("loading transactions in range $startToUse..$endToUse")
|
||||||
val txids = lightwalletService?.getTAddressTransactions(
|
val txids = lightWalletService?.getTAddressTransactions(
|
||||||
addressToUse,
|
addressToUse,
|
||||||
BlockHeight.new(network, startToUse)..BlockHeight.new(network, endToUse)
|
BlockHeight.new(network, startToUse)..BlockHeight.new(network, endToUse)
|
||||||
)
|
)
|
||||||
|
@ -131,18 +131,16 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
||||||
// twig("failed to decrypt and store transaction due to: $t")
|
// twig("failed to decrypt and store transaction due to: $t")
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
}?.let { txData ->
|
}?.let { _ ->
|
||||||
// Disabled during migration to newer SDK version; this appears to have been
|
// Disabled during migration to newer SDK version; this appears to have been
|
||||||
// leveraging non-public APIs in the SDK so perhaps should be removed
|
// leveraging non-public APIs in the SDK so perhaps should be removed
|
||||||
// val parseStart = now
|
// val parseStart = now
|
||||||
// val tList = LocalRpcTypes.TransactionDataList.newBuilder().addAllData(txData).build()
|
// val tList = LocalRpcTypes.TransactionDataList.newBuilder().addAllData(txData).build()
|
||||||
// val parsedTransactions = initializer.rustBackend.parseTransactionDataList(tList)
|
// val parsedTransactions = initializer.rustBackend.parseTransactionDataList(tList)
|
||||||
// delta = now - parseStart
|
// delta = now - parseStart
|
||||||
// updateStatus("parsed txs in ${delta}ms.")
|
// updateStatus("parsed txs in ${delta}ms.")
|
||||||
}
|
}
|
||||||
(synchronizer as SdkSynchronizer).refreshTransactions()
|
(synchronizer as SdkSynchronizer).refreshTransactions()
|
||||||
// val finalCount = (synchronizer as SdkSynchronizer).getTransactionCount()
|
|
||||||
// "found ${finalCount - initialCount} shielded outputs.
|
|
||||||
delta = now - allStart
|
delta = now - allStart
|
||||||
updateStatus("Total time ${delta}ms.")
|
updateStatus("Total time ${delta}ms.")
|
||||||
|
|
||||||
|
@ -199,7 +197,7 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
synchronizer.clearedTransactions.collectWith(lifecycleScope, ::onTransactionsUpdated)
|
synchronizer.clearedTransactions.collectWith(lifecycleScope, ::onTransactionsUpdated)
|
||||||
// synchronizer.receivedTransactions.collectWith(lifecycleScope, ::onTransactionsUpdated)
|
// synchronizer.receivedTransactions.collectWith(lifecycleScope, ::onTransactionsUpdated)
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
twig("failed to start the synchronizer!!! due to : $t")
|
twig("failed to start the synchronizer!!! due to : $t")
|
||||||
}
|
}
|
||||||
|
@ -220,12 +218,12 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
||||||
LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)
|
LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)
|
||||||
adapter = UtxoAdapter()
|
adapter = UtxoAdapter()
|
||||||
binding.recyclerTransactions.adapter = adapter
|
binding.recyclerTransactions.adapter = adapter
|
||||||
// lifecycleScope.launch {
|
// lifecycleScope.launch {
|
||||||
// // address = synchronizer.getAddress()
|
// address = synchronizer.getAddress()
|
||||||
// synchronizer.receivedTransactions.onEach {
|
// synchronizer.receivedTransactions.onEach {
|
||||||
// onTransactionsUpdated(it)
|
// onTransactionsUpdated(it)
|
||||||
// }.launchIn(this)
|
// }.launchIn(this)
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startSynchronizer() {
|
private fun startSynchronizer() {
|
||||||
|
|
|
@ -10,30 +10,29 @@ import cash.z.ecc.android.sdk.demoapp.R
|
||||||
/**
|
/**
|
||||||
* Simple adapter implementation that knows how to bind a recyclerview to ClearedTransactions.
|
* Simple adapter implementation that knows how to bind a recyclerview to ClearedTransactions.
|
||||||
*/
|
*/
|
||||||
class UtxoAdapter<T : ConfirmedTransaction> :
|
class UtxoAdapter : ListAdapter<ConfirmedTransaction, UtxoViewHolder>(
|
||||||
ListAdapter<T, UtxoViewHolder<T>>(
|
object : DiffUtil.ItemCallback<ConfirmedTransaction>() {
|
||||||
object : DiffUtil.ItemCallback<T>() {
|
override fun areItemsTheSame(
|
||||||
override fun areItemsTheSame(
|
oldItem: ConfirmedTransaction,
|
||||||
oldItem: T,
|
newItem: ConfirmedTransaction
|
||||||
newItem: T
|
) = oldItem.minedHeight == newItem.minedHeight
|
||||||
) = oldItem.minedHeight == newItem.minedHeight
|
|
||||||
|
|
||||||
override fun areContentsTheSame(
|
override fun areContentsTheSame(
|
||||||
oldItem: T,
|
oldItem: ConfirmedTransaction,
|
||||||
newItem: T
|
newItem: ConfirmedTransaction
|
||||||
) = oldItem == newItem
|
) = oldItem == newItem
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|
||||||
override fun onCreateViewHolder(
|
override fun onCreateViewHolder(
|
||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
viewType: Int
|
viewType: Int
|
||||||
) = UtxoViewHolder<T>(
|
) = UtxoViewHolder(
|
||||||
LayoutInflater.from(parent.context).inflate(R.layout.item_transaction, parent, false)
|
LayoutInflater.from(parent.context).inflate(R.layout.item_transaction, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun onBindViewHolder(
|
override fun onBindViewHolder(
|
||||||
holder: UtxoViewHolder<T>,
|
holder: UtxoViewHolder,
|
||||||
position: Int
|
position: Int
|
||||||
) = holder.bindTo(getItem(position))
|
) = holder.bindTo(getItem(position))
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,21 +13,21 @@ import java.util.Locale
|
||||||
/**
|
/**
|
||||||
* Simple view holder for displaying confirmed transactions in the recyclerview.
|
* Simple view holder for displaying confirmed transactions in the recyclerview.
|
||||||
*/
|
*/
|
||||||
class UtxoViewHolder<T : ConfirmedTransaction>(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
class UtxoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
private val amountText = itemView.findViewById<TextView>(R.id.text_transaction_amount)
|
private val amountText = itemView.findViewById<TextView>(R.id.text_transaction_amount)
|
||||||
private val infoText = itemView.findViewById<TextView>(R.id.text_transaction_info)
|
private val infoText = itemView.findViewById<TextView>(R.id.text_transaction_info)
|
||||||
private val timeText = itemView.findViewById<TextView>(R.id.text_transaction_timestamp)
|
private val timeText = itemView.findViewById<TextView>(R.id.text_transaction_timestamp)
|
||||||
private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault())
|
private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault())
|
||||||
|
|
||||||
fun bindTo(transaction: T?) {
|
fun bindTo(transaction: ConfirmedTransaction?) {
|
||||||
amountText.text = transaction?.valueInZatoshi.convertZatoshiToZecString()
|
amountText.text = transaction?.valueInZatoshi.convertZatoshiToZecString()
|
||||||
timeText.text =
|
timeText.text =
|
||||||
if (transaction == null || transaction?.blockTimeInSeconds == 0L) "Pending"
|
if (transaction == null || transaction.blockTimeInSeconds == 0L) "Pending"
|
||||||
else formatter.format(transaction.blockTimeInSeconds * 1000L)
|
else formatter.format(transaction.blockTimeInSeconds * 1000L)
|
||||||
infoText.text = getMemoString(transaction)
|
infoText.text = getMemoString(transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMemoString(transaction: T?): String {
|
private fun getMemoString(transaction: ConfirmedTransaction?): String {
|
||||||
return transaction?.memo?.takeUnless { it[0] < 0 }?.let { String(it) } ?: "no memo"
|
return transaction?.memo?.takeUnless { it[0] < 0 }?.let { String(it) } ?: "no memo"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package cash.z.ecc.android.sdk.demoapp.demos.send
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import cash.z.ecc.android.bip39.Mnemonics
|
import cash.z.ecc.android.bip39.Mnemonics
|
||||||
|
@ -162,6 +163,7 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
private fun onSend(unused: View) {
|
private fun onSend(unused: View) {
|
||||||
isSending = true
|
isSending = true
|
||||||
val amount = amountInput.text.toString().toDouble().convertZecToZatoshi()
|
val amount = amountInput.text.toString().toDouble().convertZecToZatoshi()
|
||||||
|
@ -221,9 +223,14 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
|
||||||
// Android Lifecycle overrides
|
// Android Lifecycle overrides
|
||||||
//
|
//
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onCreateView(
|
||||||
super.onActivityCreated(savedInstanceState)
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
setup()
|
setup()
|
||||||
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
|
|
@ -5,7 +5,7 @@ package cash.z.ecc.android.sdk.demoapp.util
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import cash.z.ecc.android.sdk.demoapp.R
|
import cash.z.ecc.android.sdk.demoapp.R
|
||||||
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import java.util.*
|
import java.util.Locale
|
||||||
|
|
||||||
fun ZcashNetwork.Companion.fromResources(context: Context): ZcashNetwork {
|
fun ZcashNetwork.Companion.fromResources(context: Context): ZcashNetwork {
|
||||||
val networkNameFromResources = context.getString(R.string.network_name).lowercase(Locale.ROOT)
|
val networkNameFromResources = context.getString(R.string.network_name).lowercase(Locale.ROOT)
|
||||||
|
|
|
@ -31,6 +31,7 @@ class SampleStorage(context: Context) {
|
||||||
* the SDK. This class delegates to the storage object. For demo purposes, we're using an insecure
|
* the SDK. This class delegates to the storage object. For demo purposes, we're using an insecure
|
||||||
* SampleStorage implementation but this can easily be swapped for a truly secure storage solution.
|
* SampleStorage implementation but this can easily be swapped for a truly secure storage solution.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("deprecation")
|
||||||
class SampleStorageBridge(context: Context) {
|
class SampleStorageBridge(context: Context) {
|
||||||
private val delegate = SampleStorage(context.applicationContext)
|
private val delegate = SampleStorage(context.applicationContext)
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,9 @@ class SimpleMnemonics : MnemonicPlugin {
|
||||||
override fun fullWordList(languageCode: String) = Mnemonics.getCachedWords(Locale.ENGLISH.language)
|
override fun fullWordList(languageCode: String) = Mnemonics.getCachedWords(Locale.ENGLISH.language)
|
||||||
override fun nextEntropy(): ByteArray = WordCount.COUNT_24.toEntropy()
|
override fun nextEntropy(): ByteArray = WordCount.COUNT_24.toEntropy()
|
||||||
override fun nextMnemonic(): CharArray = MnemonicCode(WordCount.COUNT_24).chars
|
override fun nextMnemonic(): CharArray = MnemonicCode(WordCount.COUNT_24).chars
|
||||||
override fun nextMnemonic(entropy: ByteArray): CharArray = MnemonicCode(entropy).chars
|
override fun nextMnemonic(seed: ByteArray): CharArray = MnemonicCode(seed).chars
|
||||||
override fun nextMnemonicList(): List<CharArray> = MnemonicCode(WordCount.COUNT_24).words
|
override fun nextMnemonicList(): List<CharArray> = MnemonicCode(WordCount.COUNT_24).words
|
||||||
override fun nextMnemonicList(entropy: ByteArray): List<CharArray> = MnemonicCode(entropy).words
|
override fun nextMnemonicList(seed: ByteArray): List<CharArray> = MnemonicCode(seed).words
|
||||||
override fun toSeed(mnemonic: CharArray): ByteArray = MnemonicCode(mnemonic).toSeed()
|
override fun toSeed(mnemonic: CharArray): ByteArray = MnemonicCode(mnemonic).toSeed()
|
||||||
override fun toWordList(mnemonic: CharArray): List<CharArray> = MnemonicCode(mnemonic).words
|
override fun toWordList(mnemonic: CharArray): List<CharArray> = MnemonicCode(mnemonic).words
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,8 +25,7 @@ IS_SNAPSHOT=true
|
||||||
LIBRARY_VERSION=1.9.0-beta03
|
LIBRARY_VERSION=1.9.0-beta03
|
||||||
|
|
||||||
# Kotlin compiler warnings can be considered errors, failing the build.
|
# Kotlin compiler warnings can be considered errors, failing the build.
|
||||||
# Currently set to false, because this project has a lot of warnings to fix first.
|
ZCASH_IS_TREAT_WARNINGS_AS_ERRORS=true
|
||||||
ZCASH_IS_TREAT_WARNINGS_AS_ERRORS=false
|
|
||||||
|
|
||||||
# Optionally configure code coverage, as historically Jacoco has at times been buggy with respect to new Kotlin versions
|
# Optionally configure code coverage, as historically Jacoco has at times been buggy with respect to new Kotlin versions
|
||||||
IS_ANDROID_INSTRUMENTATION_TEST_COVERAGE_ENABLED=false
|
IS_ANDROID_INSTRUMENTATION_TEST_COVERAGE_ENABLED=false
|
||||||
|
|
|
@ -122,10 +122,8 @@ android {
|
||||||
useLibrary("android.test.runner")
|
useLibrary("android.test.runner")
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
javaCompileOptions {
|
ksp {
|
||||||
annotationProcessorOptions {
|
arg("room.schemaLocation", "$projectDir/schemas")
|
||||||
argument("room.schemaLocation", "$projectDir/schemas")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
consumerProguardFiles("proguard-consumer.txt")
|
consumerProguardFiles("proguard-consumer.txt")
|
||||||
|
@ -301,6 +299,7 @@ dependencies {
|
||||||
androidTestImplementation(libs.androidx.test.junit)
|
androidTestImplementation(libs.androidx.test.junit)
|
||||||
androidTestImplementation(libs.androidx.test.core)
|
androidTestImplementation(libs.androidx.test.core)
|
||||||
androidTestImplementation(libs.coroutines.okhttp)
|
androidTestImplementation(libs.coroutines.okhttp)
|
||||||
|
androidTestImplementation(libs.kotlin.test)
|
||||||
androidTestImplementation(libs.kotlinx.coroutines.test)
|
androidTestImplementation(libs.kotlinx.coroutines.test)
|
||||||
// used by 'ru.gildor.corutines.okhttp.await' (to make simple suspended requests) and breaks on versions higher than 3.8.0
|
// used by 'ru.gildor.corutines.okhttp.await' (to make simple suspended requests) and breaks on versions higher than 3.8.0
|
||||||
androidTestImplementation(libs.okhttp)
|
androidTestImplementation(libs.okhttp)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import ru.gildor.coroutines.okhttp.await
|
import ru.gildor.coroutines.okhttp.await
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
|
||||||
fun Initializer.Config.seedPhrase(seedPhrase: String, network: ZcashNetwork) {
|
fun Initializer.Config.seedPhrase(seedPhrase: String, network: ZcashNetwork) {
|
||||||
runBlocking { setSeed(SimpleMnemonics().toSeed(seedPhrase.toCharArray()), network) }
|
runBlocking { setSeed(SimpleMnemonics().toSeed(seedPhrase.toCharArray()), network) }
|
||||||
|
@ -21,6 +22,7 @@ object BlockExplorer {
|
||||||
.build()
|
.build()
|
||||||
val result = client.newCall(request).await()
|
val result = client.newCall(request).await()
|
||||||
val body = result.body?.string()
|
val body = result.body?.string()
|
||||||
|
assertNotNull(body, "Body can not be null.")
|
||||||
return JSONObject(body).getJSONArray("data").getJSONObject(0).getLong("id")
|
return JSONObject(body).getJSONArray("data").getJSONObject(0).getLong("id")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,11 @@ import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.util.TestWallet
|
import cash.z.ecc.android.sdk.util.TestWallet
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Assert.assertTrue
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.Parameterized
|
import org.junit.runners.Parameterized
|
||||||
|
import kotlin.test.DefaultAsserter.assertEquals
|
||||||
|
import kotlin.test.DefaultAsserter.assertTrue
|
||||||
|
|
||||||
// TODO [#650]: https://github.com/zcash/zcash-android-wallet-sdk/issues/650
|
// TODO [#650]: https://github.com/zcash/zcash-android-wallet-sdk/issues/650
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ class SanityTest(
|
||||||
twig(it)
|
twig(it)
|
||||||
}.getOrElse { return@runBlocking }
|
}.getOrElse { return@runBlocking }
|
||||||
|
|
||||||
val downloaderHeight = runCatching {
|
runCatching {
|
||||||
wallet.service.getLatestBlockHeight()
|
wallet.service.getLatestBlockHeight()
|
||||||
}.getOrNull() ?: return@runBlocking
|
}.getOrNull() ?: return@runBlocking
|
||||||
assertTrue("$networkName failed to return a proper block. Height was ${block.height} but we expected $height", block.height == height.value)
|
assertTrue("$networkName failed to return a proper block. Height was ${block.height} but we expected $height", block.height == height.value)
|
||||||
|
|
|
@ -25,12 +25,12 @@ import kotlinx.coroutines.flow.filterNotNull
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Assert.assertTrue
|
|
||||||
import org.junit.BeforeClass
|
import org.junit.BeforeClass
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class TestnetIntegrationTest : ScopedTest() {
|
class TestnetIntegrationTest : ScopedTest() {
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ class TestnetIntegrationTest : ScopedTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testLoadBirthday() {
|
fun testLoadBirthday() {
|
||||||
val (height, hash, time, tree) = runBlocking {
|
val (height) = runBlocking {
|
||||||
CheckpointTool.loadNearest(
|
CheckpointTool.loadNearest(
|
||||||
context,
|
context,
|
||||||
synchronizer.network,
|
synchronizer.network,
|
||||||
|
@ -81,8 +81,8 @@ class TestnetIntegrationTest : ScopedTest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTrue(
|
assertTrue(
|
||||||
"No funds available when we expected a balance greater than zero!",
|
availableBalance!!.value > 0,
|
||||||
availableBalance!!.value > 0
|
"No funds available when we expected a balance greater than zero!"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ class TestnetIntegrationTest : ScopedTest() {
|
||||||
ZcashSdk.MINERS_FEE,
|
ZcashSdk.MINERS_FEE,
|
||||||
toAddress,
|
toAddress,
|
||||||
"first mainnet tx from the SDK"
|
"first mainnet tx from the SDK"
|
||||||
).filter { it?.isSubmitSuccess() == true }.onFirst {
|
).filter { it.isSubmitSuccess() }.onFirst {
|
||||||
log("DONE SENDING!!!")
|
log("DONE SENDING!!!")
|
||||||
}
|
}
|
||||||
log("returning true from sendFunds")
|
log("returning true from sendFunds")
|
||||||
|
|
|
@ -24,10 +24,6 @@ import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertFalse
|
|
||||||
import org.junit.Assert.assertNotNull
|
|
||||||
import org.junit.Assert.assertNull
|
|
||||||
import org.junit.Assert.assertTrue
|
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.BeforeClass
|
import org.junit.BeforeClass
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -35,6 +31,10 @@ import org.junit.runner.RunWith
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
import kotlin.test.assertNull
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
@MaintainedTest(TestPurpose.REGRESSION)
|
@MaintainedTest(TestPurpose.REGRESSION)
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@ -69,11 +69,11 @@ class PersistentTransactionManagerTest : ScopedTest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initMocks() {
|
private fun initMocks() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.openMocks(this)
|
||||||
mockEncoder.stub {
|
mockEncoder.stub {
|
||||||
onBlocking {
|
onBlocking {
|
||||||
createTransaction(any(), any(), any(), any(), any())
|
createTransaction(any(), any(), any(), any(), any())
|
||||||
}.thenAnswer { invocation ->
|
}.thenAnswer {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
delay(200)
|
delay(200)
|
||||||
EncodedTransaction(byteArrayOf(1, 2, 3), byteArrayOf(8, 9), 5_000_000)
|
EncodedTransaction(byteArrayOf(1, 2, 3), byteArrayOf(8, 9), 5_000_000)
|
||||||
|
@ -100,7 +100,7 @@ class PersistentTransactionManagerTest : ScopedTest() {
|
||||||
|
|
||||||
txFlow.drop(2).onEach {
|
txFlow.drop(2).onEach {
|
||||||
twig("found tx: $it")
|
twig("found tx: $it")
|
||||||
assertTrue("Expected the encoded tx to be cancelled but it wasn't", it.isCancelled())
|
assertTrue(it.isCancelled(), "Expected the encoded tx to be cancelled but it wasn't")
|
||||||
twig("found it to be successfully cancelled")
|
twig("found it to be successfully cancelled")
|
||||||
testScope.cancel()
|
testScope.cancel()
|
||||||
}.launchIn(testScope).join()
|
}.launchIn(testScope).join()
|
||||||
|
@ -112,16 +112,16 @@ class PersistentTransactionManagerTest : ScopedTest() {
|
||||||
assertFalse(tx.isCancelled())
|
assertFalse(tx.isCancelled())
|
||||||
manager.cancel(tx.id)
|
manager.cancel(tx.id)
|
||||||
tx = manager.findById(tx.id)!!
|
tx = manager.findById(tx.id)!!
|
||||||
assertTrue("Transaction was not cancelled", tx.isCancelled())
|
assertTrue(tx.isCancelled(), "Transaction was not cancelled")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testAbort() = runBlocking {
|
fun testAbort() = runBlocking {
|
||||||
var tx: PendingTransaction? = manager.initSpend(Zatoshi(1234), "a", "b", 0)
|
var tx: PendingTransaction? = manager.initSpend(Zatoshi(1234), "a", "b", 0)
|
||||||
assertNotNull(tx)
|
assertNotNull(tx)
|
||||||
manager.abort(tx!!)
|
manager.abort(tx)
|
||||||
tx = manager.findById(tx.id)
|
tx = manager.findById(tx.id)
|
||||||
assertNull("Transaction was not removed from the DB", tx)
|
assertNull(tx, "Transaction was not removed from the DB")
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
||||||
import cash.z.ecc.android.sdk.internal.Twig
|
import cash.z.ecc.android.sdk.internal.Twig
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
|
@ -19,6 +20,7 @@ import org.junit.Before
|
||||||
import org.junit.BeforeClass
|
import org.junit.BeforeClass
|
||||||
import java.util.concurrent.TimeoutException
|
import java.util.concurrent.TimeoutException
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
open class ScopedTest(val defaultTimeout: Long = 2000L) {
|
open class ScopedTest(val defaultTimeout: Long = 2000L) {
|
||||||
protected lateinit var testScope: CoroutineScope
|
protected lateinit var testScope: CoroutineScope
|
||||||
|
|
||||||
|
@ -60,7 +62,7 @@ open class ScopedTest(val defaultTimeout: Long = 2000L) {
|
||||||
fun createScope() {
|
fun createScope() {
|
||||||
twig("======================= CLASS STARTED ===============================")
|
twig("======================= CLASS STARTED ===============================")
|
||||||
classScope = CoroutineScope(
|
classScope = CoroutineScope(
|
||||||
SupervisorJob() + newFixedThreadPoolContext(2, this.javaClass.simpleName)
|
SupervisorJob() + newFixedThreadPoolContext(2, this::class.java.simpleName)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,9 @@ class SimpleMnemonics : MnemonicPlugin {
|
||||||
override fun fullWordList(languageCode: String) = Mnemonics.getCachedWords(Locale.ENGLISH.language)
|
override fun fullWordList(languageCode: String) = Mnemonics.getCachedWords(Locale.ENGLISH.language)
|
||||||
override fun nextEntropy(): ByteArray = WordCount.COUNT_24.toEntropy()
|
override fun nextEntropy(): ByteArray = WordCount.COUNT_24.toEntropy()
|
||||||
override fun nextMnemonic(): CharArray = MnemonicCode(WordCount.COUNT_24).chars
|
override fun nextMnemonic(): CharArray = MnemonicCode(WordCount.COUNT_24).chars
|
||||||
override fun nextMnemonic(entropy: ByteArray): CharArray = MnemonicCode(entropy).chars
|
override fun nextMnemonic(seed: ByteArray): CharArray = MnemonicCode(seed).chars
|
||||||
override fun nextMnemonicList(): List<CharArray> = MnemonicCode(WordCount.COUNT_24).words
|
override fun nextMnemonicList(): List<CharArray> = MnemonicCode(WordCount.COUNT_24).words
|
||||||
override fun nextMnemonicList(entropy: ByteArray): List<CharArray> = MnemonicCode(entropy).words
|
override fun nextMnemonicList(seed: ByteArray): List<CharArray> = MnemonicCode(seed).words
|
||||||
override fun toSeed(mnemonic: CharArray): ByteArray = MnemonicCode(mnemonic).toSeed()
|
override fun toSeed(mnemonic: CharArray): ByteArray = MnemonicCode(mnemonic).toSeed()
|
||||||
override fun toWordList(mnemonic: CharArray): List<CharArray> = MnemonicCode(mnemonic).words
|
override fun toWordList(mnemonic: CharArray): List<CharArray> = MnemonicCode(mnemonic).words
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ 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.tool.DerivationTool
|
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
|
@ -34,6 +35,7 @@ import java.util.concurrent.TimeoutException
|
||||||
* A simple wallet that connects to testnet for integration testing. The intention is that it is
|
* A simple wallet that connects to testnet for integration testing. The intention is that it is
|
||||||
* easy to drive and nice to use.
|
* easy to drive and nice to use.
|
||||||
*/
|
*/
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
class TestWallet(
|
class TestWallet(
|
||||||
val seedPhrase: String,
|
val seedPhrase: String,
|
||||||
val alias: String = "TestWallet",
|
val alias: String = "TestWallet",
|
||||||
|
|
|
@ -20,7 +20,7 @@ import java.io.File
|
||||||
/**
|
/**
|
||||||
* Simplified Initializer focused on starting from a ViewingKey.
|
* Simplified Initializer focused on starting from a ViewingKey.
|
||||||
*/
|
*/
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList", "unused")
|
||||||
class Initializer private constructor(
|
class Initializer private constructor(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
internal val rustBackend: RustBackend,
|
internal val rustBackend: RustBackend,
|
||||||
|
@ -147,12 +147,10 @@ class Initializer private constructor(
|
||||||
/**
|
/**
|
||||||
* Set the server and the network property at the same time to prevent them from getting out
|
* Set the server and the network property at the same time to prevent them from getting out
|
||||||
* of sync. Ultimately, this determines which host a synchronizer will use in order to
|
* of sync. Ultimately, this determines which host a synchronizer will use in order to
|
||||||
* connect to lightwalletd. In most cases, the default host is sufficient but an override
|
* connect to lightwalletd.
|
||||||
* can be provided. The host cannot be changed without explicitly setting the network.
|
|
||||||
*
|
*
|
||||||
* @param network the Zcash network to use. Either testnet or mainnet.
|
* @param network the Zcash network to use. Either testnet or mainnet.
|
||||||
* @param host the lightwalletd host to use.
|
* @param lightWalletEndpoint the light wallet endpoint to use.
|
||||||
* @param port the lightwalletd port to use.
|
|
||||||
*/
|
*/
|
||||||
fun setNetwork(
|
fun setNetwork(
|
||||||
network: ZcashNetwork,
|
network: ZcashNetwork,
|
||||||
|
@ -311,6 +309,7 @@ class Initializer private constructor(
|
||||||
block: (Config) -> Unit
|
block: (Config) -> Unit
|
||||||
) = new(appContext, onCriticalErrorHandler, Config(block))
|
) = new(appContext, onCriticalErrorHandler, Config(block))
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
suspend fun new(
|
suspend fun new(
|
||||||
context: Context,
|
context: Context,
|
||||||
onCriticalErrorHandler: ((Throwable?) -> Boolean)?,
|
onCriticalErrorHandler: ((Throwable?) -> Boolean)?,
|
||||||
|
|
|
@ -62,7 +62,6 @@ import io.grpc.ManagedChannel
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
@ -96,7 +95,7 @@ import kotlin.coroutines.EmptyCoroutineContext
|
||||||
* @property processor saves the downloaded compact blocks to the cache and then scans those blocks for
|
* @property processor saves the downloaded compact blocks to the cache and then scans those blocks for
|
||||||
* data related to this wallet.
|
* data related to this wallet.
|
||||||
*/
|
*/
|
||||||
@ExperimentalCoroutinesApi
|
@OptIn(kotlinx.coroutines.ObsoleteCoroutinesApi::class)
|
||||||
@FlowPreview
|
@FlowPreview
|
||||||
class SdkSynchronizer internal constructor(
|
class SdkSynchronizer internal constructor(
|
||||||
private val storage: TransactionRepository,
|
private val storage: TransactionRepository,
|
||||||
|
@ -109,6 +108,8 @@ class SdkSynchronizer internal constructor(
|
||||||
private val _saplingBalances = MutableStateFlow<WalletBalance?>(null)
|
private val _saplingBalances = MutableStateFlow<WalletBalance?>(null)
|
||||||
private val _transparentBalances = MutableStateFlow<WalletBalance?>(null)
|
private val _transparentBalances = MutableStateFlow<WalletBalance?>(null)
|
||||||
|
|
||||||
|
// TODO [#288]: Remove Deprecated Usage of ConflatedBroadcastChannel
|
||||||
|
// TODO [#288]: https://github.com/zcash/zcash-android-wallet-sdk/issues/288
|
||||||
private val _status = ConflatedBroadcastChannel<Synchronizer.Status>(DISCONNECTED)
|
private val _status = ConflatedBroadcastChannel<Synchronizer.Status>(DISCONNECTED)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -171,6 +172,9 @@ class SdkSynchronizer internal constructor(
|
||||||
* processor is finished scanning, the synchronizer updates transaction and balance info and
|
* processor is finished scanning, the synchronizer updates transaction and balance info and
|
||||||
* then emits a [SYNCED] status.
|
* then emits a [SYNCED] status.
|
||||||
*/
|
*/
|
||||||
|
// TODO [#658] Replace ComputableFlow and asFlow() obsolete Coroutine usage
|
||||||
|
// TODO [#658] https://github.com/zcash/zcash-android-wallet-sdk/issues/658
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
override val status = _status.asFlow()
|
override val status = _status.asFlow()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -414,6 +418,7 @@ class SdkSynchronizer internal constructor(
|
||||||
twig("Synchronizer onReady complete. Processor start has exited!")
|
twig("Synchronizer onReady complete. Processor start has exited!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
private fun onCriticalError(unused: CoroutineContext?, error: Throwable) {
|
private fun onCriticalError(unused: CoroutineContext?, error: Throwable) {
|
||||||
twig("********")
|
twig("********")
|
||||||
twig("******** ERROR: $error")
|
twig("******** ERROR: $error")
|
||||||
|
@ -641,8 +646,9 @@ class SdkSynchronizer internal constructor(
|
||||||
// only submit if it wasn't cancelled. Otherwise cleanup, immediately for best UX.
|
// only submit if it wasn't cancelled. Otherwise cleanup, immediately for best UX.
|
||||||
if (encodedTx.isCancelled()) {
|
if (encodedTx.isCancelled()) {
|
||||||
twig("[cleanup] this tx has been cancelled so we will cleanup instead of submitting")
|
twig("[cleanup] this tx has been cancelled so we will cleanup instead of submitting")
|
||||||
if (cleanupCancelledTx(encodedTx)) refreshAllBalances()
|
if (cleanupCancelledTx(encodedTx)) {
|
||||||
encodedTx
|
refreshAllBalances()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
txManager.submit(encodedTx)
|
txManager.submit(encodedTx)
|
||||||
}
|
}
|
||||||
|
@ -673,8 +679,9 @@ class SdkSynchronizer internal constructor(
|
||||||
// only submit if it wasn't cancelled. Otherwise cleanup, immediately for best UX.
|
// only submit if it wasn't cancelled. Otherwise cleanup, immediately for best UX.
|
||||||
if (encodedTx.isCancelled()) {
|
if (encodedTx.isCancelled()) {
|
||||||
twig("[cleanup] this shielding tx has been cancelled so we will cleanup instead of submitting")
|
twig("[cleanup] this shielding tx has been cancelled so we will cleanup instead of submitting")
|
||||||
if (cleanupCancelledTx(encodedTx)) refreshAllBalances()
|
if (cleanupCancelledTx(encodedTx)) {
|
||||||
encodedTx
|
refreshAllBalances()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
txManager.submit(encodedTx)
|
txManager.submit(encodedTx)
|
||||||
}
|
}
|
||||||
|
@ -685,8 +692,8 @@ class SdkSynchronizer internal constructor(
|
||||||
txManager.monitorById(it.id)
|
txManager.monitorById(it.id)
|
||||||
}.distinctUntilChanged()
|
}.distinctUntilChanged()
|
||||||
|
|
||||||
override suspend fun refreshUtxos(address: String, startHeight: BlockHeight): Int? {
|
override suspend fun refreshUtxos(tAddr: String, since: BlockHeight): Int? {
|
||||||
return processor.refreshUtxos(address, startHeight)
|
return processor.refreshUtxos(tAddr, since)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getTransparentBalance(tAddr: String): WalletBalance {
|
override suspend fun getTransparentBalance(tAddr: String): WalletBalance {
|
||||||
|
|
|
@ -76,6 +76,7 @@ import kotlin.math.roundToInt
|
||||||
* in when considering initial range to download. In most cases, this should be the birthday height
|
* in when considering initial range to download. In most cases, this should be the birthday height
|
||||||
* of the current wallet--the height before which we do not need to scan for transactions.
|
* of the current wallet--the height before which we do not need to scan for transactions.
|
||||||
*/
|
*/
|
||||||
|
@OptIn(kotlinx.coroutines.ObsoleteCoroutinesApi::class)
|
||||||
@OpenForTesting
|
@OpenForTesting
|
||||||
class CompactBlockProcessor internal constructor(
|
class CompactBlockProcessor internal constructor(
|
||||||
val downloader: CompactBlockDownloader,
|
val downloader: CompactBlockDownloader,
|
||||||
|
@ -126,6 +127,8 @@ class CompactBlockProcessor internal constructor(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO [#288]: Remove Deprecated Usage of ConflatedBroadcastChannel
|
||||||
|
// TODO [#288]: https://github.com/zcash/zcash-android-wallet-sdk/issues/288
|
||||||
private val _state: ConflatedBroadcastChannel<State> = ConflatedBroadcastChannel(Initialized)
|
private val _state: ConflatedBroadcastChannel<State> = ConflatedBroadcastChannel(Initialized)
|
||||||
private val _progress = ConflatedBroadcastChannel(0)
|
private val _progress = ConflatedBroadcastChannel(0)
|
||||||
private val _processorInfo =
|
private val _processorInfo =
|
||||||
|
@ -161,18 +164,27 @@ class CompactBlockProcessor internal constructor(
|
||||||
* The flow of state values so that a wallet can monitor the state of this class without needing
|
* The flow of state values so that a wallet can monitor the state of this class without needing
|
||||||
* to poll.
|
* to poll.
|
||||||
*/
|
*/
|
||||||
|
// TODO [#658] Replace ComputableFlow and asFlow() obsolete Coroutine usage
|
||||||
|
// TODO [#658] https://github.com/zcash/zcash-android-wallet-sdk/issues/658
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
val state = _state.asFlow()
|
val state = _state.asFlow()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The flow of progress values so that a wallet can monitor how much downloading remains
|
* The flow of progress values so that a wallet can monitor how much downloading remains
|
||||||
* without needing to poll.
|
* without needing to poll.
|
||||||
*/
|
*/
|
||||||
|
// TODO [#658] Replace ComputableFlow and asFlow() obsolete Coroutine usage
|
||||||
|
// TODO [#658] https://github.com/zcash/zcash-android-wallet-sdk/issues/658
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
val progress = _progress.asFlow()
|
val progress = _progress.asFlow()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The flow of detailed processorInfo like the range of blocks that shall be downloaded and
|
* The flow of detailed processorInfo like the range of blocks that shall be downloaded and
|
||||||
* scanned. This gives the wallet a lot of insight into the work of this processor.
|
* scanned. This gives the wallet a lot of insight into the work of this processor.
|
||||||
*/
|
*/
|
||||||
|
// TODO [#658] Replace ComputableFlow and asFlow() obsolete Coroutine usage
|
||||||
|
// TODO [#658] https://github.com/zcash/zcash-android-wallet-sdk/issues/658
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
val processorInfo = _processorInfo.asFlow()
|
val processorInfo = _processorInfo.asFlow()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -393,12 +405,8 @@ class CompactBlockProcessor internal constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newTxs?.onEach { newTransaction ->
|
newTxs.onEach { newTransaction ->
|
||||||
if (newTransaction == null) {
|
enhance(newTransaction)
|
||||||
twig("somehow, new transaction was null!!!")
|
|
||||||
} else {
|
|
||||||
enhance(newTransaction)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
twig("Done enhancing transaction details")
|
twig("Done enhancing transaction details")
|
||||||
BlockProcessingResult.Success
|
BlockProcessingResult.Success
|
||||||
|
@ -864,6 +872,7 @@ class CompactBlockProcessor internal constructor(
|
||||||
* when we unexpectedly lose server connection or are waiting for an event to happen on the
|
* when we unexpectedly lose server connection or are waiting for an event to happen on the
|
||||||
* chain. We can pass this desire along now and later figure out how to handle it, privately.
|
* chain. We can pass this desire along now and later figure out how to handle it, privately.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
private fun calculatePollInterval(fastIntervalDesired: Boolean = false): Long {
|
private fun calculatePollInterval(fastIntervalDesired: Boolean = false): Long {
|
||||||
val interval = POLL_INTERVAL
|
val interval = POLL_INTERVAL
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
|
@ -1119,7 +1128,7 @@ class CompactBlockProcessor internal constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Service.LightdInfo.matchingNetwork(network: String): Boolean {
|
private fun Service.LightdInfo.matchingNetwork(network: String): Boolean {
|
||||||
fun String.toId() = toLowerCase(Locale.US).run {
|
fun String.toId() = lowercase(Locale.US).run {
|
||||||
when {
|
when {
|
||||||
contains("main") -> "mainnet"
|
contains("main") -> "mainnet"
|
||||||
contains("test") -> "testnet"
|
contains("test") -> "testnet"
|
||||||
|
@ -1140,7 +1149,6 @@ class CompactBlockProcessor internal constructor(
|
||||||
twig("$name MUTEX: releasing lock", -1)
|
twig("$name MUTEX: releasing lock", -1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
twig("$name MUTEX: withLock complete", -1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package cash.z.ecc.android.sdk.db.entity
|
||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.ForeignKey
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.RoomWarnings
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
tableName = "received_notes",
|
tableName = "received_notes",
|
||||||
|
@ -25,6 +26,7 @@ import androidx.room.ForeignKey
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@SuppressWarnings(RoomWarnings.MISSING_INDEX_ON_FOREIGN_KEY_CHILD)
|
||||||
data class Received(
|
data class Received(
|
||||||
@ColumnInfo(name = "id_note")
|
@ColumnInfo(name = "id_note")
|
||||||
val id: Int? = 0,
|
val id: Int? = 0,
|
||||||
|
|
|
@ -3,6 +3,7 @@ package cash.z.ecc.android.sdk.db.entity
|
||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.ForeignKey
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.RoomWarnings
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
tableName = "sent_notes",
|
tableName = "sent_notes",
|
||||||
|
@ -19,6 +20,7 @@ import androidx.room.ForeignKey
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@SuppressWarnings(RoomWarnings.MISSING_INDEX_ON_FOREIGN_KEY_CHILD)
|
||||||
data class Sent(
|
data class Sent(
|
||||||
@ColumnInfo(name = "id_note")
|
@ColumnInfo(name = "id_note")
|
||||||
val id: Int? = 0,
|
val id: Int? = 0,
|
||||||
|
|
|
@ -5,6 +5,7 @@ import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.ForeignKey
|
import androidx.room.ForeignKey
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
import androidx.room.RoomWarnings
|
||||||
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
|
||||||
|
|
||||||
|
@ -23,6 +24,7 @@ import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@SuppressWarnings(RoomWarnings.MISSING_INDEX_ON_FOREIGN_KEY_CHILD)
|
||||||
data class TransactionEntity(
|
data class TransactionEntity(
|
||||||
@ColumnInfo(name = "id_tx")
|
@ColumnInfo(name = "id_tx")
|
||||||
val id: Long?,
|
val id: Long?,
|
||||||
|
|
|
@ -3,6 +3,7 @@ package cash.z.ecc.android.sdk.db.entity
|
||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.ForeignKey
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.RoomWarnings
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
tableName = "utxos",
|
tableName = "utxos",
|
||||||
|
@ -15,6 +16,7 @@ import androidx.room.ForeignKey
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@SuppressWarnings(RoomWarnings.MISSING_INDEX_ON_FOREIGN_KEY_CHILD)
|
||||||
data class Utxo(
|
data class Utxo(
|
||||||
@ColumnInfo(name = "id_utxo")
|
@ColumnInfo(name = "id_utxo")
|
||||||
val id: Long? = 0L,
|
val id: Long? = 0L,
|
||||||
|
|
|
@ -10,8 +10,8 @@ class BatchMetrics(val range: ClosedRange<BlockHeight>, val batchSize: Int, priv
|
||||||
private var batchStartTime = 0L
|
private var batchStartTime = 0L
|
||||||
private var batchEndTime = 0L
|
private var batchEndTime = 0L
|
||||||
private var rangeSize = range.endInclusive.value - range.start.value + 1
|
private var rangeSize = range.endInclusive.value - range.start.value + 1
|
||||||
private inline fun now() = System.currentTimeMillis()
|
private fun now() = System.currentTimeMillis()
|
||||||
private inline fun ips(blocks: Long, time: Long) = 1000.0f * blocks / time
|
private fun ips(blocks: Long, time: Long) = 1000.0f * blocks / time
|
||||||
|
|
||||||
val isComplete get() = completedBatches * batchSize >= rangeSize
|
val isComplete get() = completedBatches * batchSize >= rangeSize
|
||||||
val isBatchComplete get() = batchEndTime > batchStartTime
|
val isBatchComplete get() = batchEndTime > batchStartTime
|
||||||
|
@ -29,8 +29,6 @@ class BatchMetrics(val range: ClosedRange<BlockHeight>, val batchSize: Int, priv
|
||||||
fun endBatch() {
|
fun endBatch() {
|
||||||
completedBatches++
|
completedBatches++
|
||||||
batchEndTime = now()
|
batchEndTime = now()
|
||||||
onMetricComplete?.let {
|
onMetricComplete?.invoke(this, isComplete)
|
||||||
it.invoke(this, isComplete)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ enum class ConsensusBranchId(val displayName: String, val id: Long, val hexId: S
|
||||||
fun fromId(id: Long): ConsensusBranchId? = values().firstOrNull { it.id == id }
|
fun fromId(id: Long): ConsensusBranchId? = values().firstOrNull { it.id == id }
|
||||||
|
|
||||||
fun fromHex(hex: String): ConsensusBranchId? = values().firstOrNull { branch ->
|
fun fromHex(hex: String): ConsensusBranchId? = values().firstOrNull { branch ->
|
||||||
hex.toLowerCase(Locale.US).replace("_", "").replaceFirst("0x", "").let { sanitized ->
|
hex.lowercase(Locale.US).replace("_", "").replaceFirst("0x", "").let { sanitized ->
|
||||||
branch.hexId.equals(sanitized, true)
|
branch.hexId.equals(sanitized, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import androidx.room.Dao
|
||||||
import androidx.room.Database
|
import androidx.room.Database
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
|
import androidx.room.RoomWarnings
|
||||||
import androidx.room.Transaction
|
import androidx.room.Transaction
|
||||||
import androidx.room.migration.Migration
|
import androidx.room.migration.Migration
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
@ -302,6 +303,7 @@ interface TransactionDao {
|
||||||
LIMIT :limit
|
LIMIT :limit
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
|
||||||
fun getSentTransactions(limit: Int = Int.MAX_VALUE): DataSource.Factory<Int, ConfirmedTransaction>
|
fun getSentTransactions(limit: Int = Int.MAX_VALUE): DataSource.Factory<Int, ConfirmedTransaction>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -328,6 +330,7 @@ interface TransactionDao {
|
||||||
LIMIT :limit
|
LIMIT :limit
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
|
||||||
fun getReceivedTransactions(limit: Int = Int.MAX_VALUE): DataSource.Factory<Int, ConfirmedTransaction>
|
fun getReceivedTransactions(limit: Int = Int.MAX_VALUE): DataSource.Factory<Int, ConfirmedTransaction>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -430,7 +433,6 @@ interface TransactionDao {
|
||||||
var success = false
|
var success = false
|
||||||
try {
|
try {
|
||||||
var hasInitialMatch = false
|
var hasInitialMatch = false
|
||||||
var hasFinalMatch = true
|
|
||||||
twig("[cleanup] cleanupCancelledTx starting...")
|
twig("[cleanup] cleanupCancelledTx starting...")
|
||||||
findUnminedTransactionIds(rawTransactionId).also {
|
findUnminedTransactionIds(rawTransactionId).also {
|
||||||
twig("[cleanup] cleanupCancelledTx found ${it.size} matching transactions to cleanup")
|
twig("[cleanup] cleanupCancelledTx found ${it.size} matching transactions to cleanup")
|
||||||
|
@ -438,7 +440,7 @@ interface TransactionDao {
|
||||||
hasInitialMatch = true
|
hasInitialMatch = true
|
||||||
removeInvalidOutboundTransaction(transactionId)
|
removeInvalidOutboundTransaction(transactionId)
|
||||||
}
|
}
|
||||||
hasFinalMatch = findMatchingTransactionId(rawTransactionId) != null
|
val hasFinalMatch = findMatchingTransactionId(rawTransactionId) != null
|
||||||
success = hasInitialMatch && !hasFinalMatch
|
success = hasInitialMatch && !hasFinalMatch
|
||||||
twig("[cleanup] cleanupCancelledTx Done. success? $success")
|
twig("[cleanup] cleanupCancelledTx Done. success? $success")
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
|
|
|
@ -28,11 +28,11 @@ internal inline fun <R> tryWarn(
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
val shouldThrowAnyway = (
|
val shouldThrowAnyway = (
|
||||||
unlessContains != null &&
|
unlessContains != null &&
|
||||||
(t.message?.toLowerCase()?.contains(unlessContains.toLowerCase()) == true)
|
(t.message?.lowercase()?.contains(unlessContains.lowercase()) == true)
|
||||||
) ||
|
) ||
|
||||||
(
|
(
|
||||||
ifContains != null &&
|
ifContains != null &&
|
||||||
(t.message?.toLowerCase()?.contains(ifContains.toLowerCase()) == false)
|
(t.message?.lowercase()?.contains(ifContains.lowercase()) == false)
|
||||||
)
|
)
|
||||||
if (shouldThrowAnyway) {
|
if (shouldThrowAnyway) {
|
||||||
throw t
|
throw t
|
||||||
|
|
|
@ -3,6 +3,7 @@ package cash.z.ecc.android.sdk.internal.ext.android
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ObsoleteCoroutinesApi
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
|
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
|
||||||
|
@ -12,6 +13,9 @@ import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/* Adapted from ComputableLiveData */
|
/* Adapted from ComputableLiveData */
|
||||||
|
// TODO [#658] https://github.com/zcash/zcash-android-wallet-sdk/issues/658
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
@OptIn(ObsoleteCoroutinesApi::class)
|
||||||
abstract class ComputableFlow<T>(dispatcher: CoroutineDispatcher = Dispatchers.IO) {
|
abstract class ComputableFlow<T>(dispatcher: CoroutineDispatcher = Dispatchers.IO) {
|
||||||
private val computationScope: CoroutineScope = CoroutineScope(dispatcher + SupervisorJob())
|
private val computationScope: CoroutineScope = CoroutineScope(dispatcher + SupervisorJob())
|
||||||
private val computationChannel: ConflatedBroadcastChannel<T> = ConflatedBroadcastChannel()
|
private val computationChannel: ConflatedBroadcastChannel<T> = ConflatedBroadcastChannel()
|
||||||
|
|
|
@ -49,8 +49,7 @@ fun <Key, Value> DataSource.Factory<Key, Value>.toFlowPagedList(
|
||||||
*
|
*
|
||||||
* @see FlowPagedListBuilder
|
* @see FlowPagedListBuilder
|
||||||
*/
|
*/
|
||||||
@SuppressLint("RestrictedApi")
|
fun <Key, Value> DataSource.Factory<Key, Value>.toFlowPagedList(
|
||||||
inline fun <Key, Value> DataSource.Factory<Key, Value>.toFlowPagedList(
|
|
||||||
pageSize: Int,
|
pageSize: Int,
|
||||||
initialLoadKey: Key? = null,
|
initialLoadKey: Key? = null,
|
||||||
boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
|
boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package cash.z.ecc.android.sdk.internal.ext.android
|
package cash.z.ecc.android.sdk.internal.ext.android
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import androidx.paging.Config
|
import androidx.paging.Config
|
||||||
|
@ -29,7 +28,7 @@ class FlowPagedListBuilder<Key, Value>(
|
||||||
* Creates a FlowPagedListBuilder with required parameters.
|
* Creates a FlowPagedListBuilder with required parameters.
|
||||||
*
|
*
|
||||||
* @param dataSourceFactory DataSource factory providing DataSource generations.
|
* @param dataSourceFactory DataSource factory providing DataSource generations.
|
||||||
* @param config Paging configuration.
|
* @param pageSize List page size.
|
||||||
*/
|
*/
|
||||||
constructor(dataSourceFactory: DataSource.Factory<Key, Value>, pageSize: Int) : this(
|
constructor(dataSourceFactory: DataSource.Factory<Key, Value>, pageSize: Int) : this(
|
||||||
dataSourceFactory,
|
dataSourceFactory,
|
||||||
|
@ -44,7 +43,6 @@ class FlowPagedListBuilder<Key, Value>(
|
||||||
*
|
*
|
||||||
* @return The Flow of PagedLists
|
* @return The Flow of PagedLists
|
||||||
*/
|
*/
|
||||||
@SuppressLint("RestrictedApi")
|
|
||||||
fun build(): Flow<List<Value>> {
|
fun build(): Flow<List<Value>> {
|
||||||
return object : ComputableFlow<List<Value>>(fetchContext) {
|
return object : ComputableFlow<List<Value>>(fetchContext) {
|
||||||
private lateinit var dataSource: DataSource<Key, Value>
|
private lateinit var dataSource: DataSource<Key, Value>
|
||||||
|
@ -56,6 +54,7 @@ class FlowPagedListBuilder<Key, Value>(
|
||||||
var initializeKey = initialLoadKey
|
var initializeKey = initialLoadKey
|
||||||
if (::list.isInitialized) {
|
if (::list.isInitialized) {
|
||||||
twig("list is initialized")
|
twig("list is initialized")
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
initializeKey = list.lastKey as Key
|
initializeKey = list.lastKey as Key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,14 +73,14 @@ class PersistentTransactionManager(
|
||||||
//
|
//
|
||||||
|
|
||||||
override suspend fun initSpend(
|
override suspend fun initSpend(
|
||||||
value: Zatoshi,
|
zatoshi: Zatoshi,
|
||||||
toAddress: String,
|
toAddress: String,
|
||||||
memo: String,
|
memo: String,
|
||||||
fromAccountIndex: Int
|
fromAccountIndex: Int
|
||||||
): PendingTransaction = withContext(Dispatchers.IO) {
|
): PendingTransaction = withContext(Dispatchers.IO) {
|
||||||
twig("constructing a placeholder transaction")
|
twig("constructing a placeholder transaction")
|
||||||
var tx = PendingTransactionEntity(
|
var tx = PendingTransactionEntity(
|
||||||
value = value.value,
|
value = zatoshi.value,
|
||||||
toAddress = toAddress,
|
toAddress = toAddress,
|
||||||
memo = memo.toByteArray(),
|
memo = memo.toByteArray(),
|
||||||
accountIndex = fromAccountIndex
|
accountIndex = fromAccountIndex
|
||||||
|
@ -264,12 +264,14 @@ class PersistentTransactionManager(
|
||||||
/**
|
/**
|
||||||
* Remove a transaction and pretend it never existed.
|
* Remove a transaction and pretend it never existed.
|
||||||
*
|
*
|
||||||
|
* @param transaction the transaction to be processed.
|
||||||
|
*
|
||||||
* @return the final number of transactions that were removed from the database.
|
* @return the final number of transactions that were removed from the database.
|
||||||
*/
|
*/
|
||||||
override suspend fun abort(existingTransaction: PendingTransaction): Int {
|
override suspend fun abort(transaction: PendingTransaction): Int {
|
||||||
return pendingTransactionDao {
|
return pendingTransactionDao {
|
||||||
twig("[cleanup] Deleting pendingTxId: ${existingTransaction.id}")
|
twig("[cleanup] Deleting pendingTxId: ${transaction.id}")
|
||||||
delete(existingTransaction as PendingTransactionEntity)
|
delete(transaction as PendingTransactionEntity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,13 +288,14 @@ class PersistentTransactionManager(
|
||||||
*/
|
*/
|
||||||
private suspend fun <R> safeUpdate(logMessage: String = "", priority: Int = 0, block: suspend PendingTransactionDao.() -> R): R? {
|
private suspend fun <R> safeUpdate(logMessage: String = "", priority: Int = 0, block: suspend PendingTransactionDao.() -> R): R? {
|
||||||
return try {
|
return try {
|
||||||
twig(logMessage)
|
twig(logMessage, priority)
|
||||||
pendingTransactionDao { block() }
|
pendingTransactionDao { block() }
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
val stacktrace = StringWriter().also { t.printStackTrace(PrintWriter(it)) }.toString()
|
val stacktrace = StringWriter().also { t.printStackTrace(PrintWriter(it)) }.toString()
|
||||||
twig(
|
twig(
|
||||||
"Unknown error while attempting to '$logMessage':" +
|
"Unknown error while attempting to '$logMessage':" +
|
||||||
" ${t.message} caused by: ${t.cause} stacktrace: $stacktrace"
|
" ${t.message} caused by: ${t.cause} stacktrace: $stacktrace",
|
||||||
|
priority
|
||||||
)
|
)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,9 +109,11 @@ interface OutboundTransactionManager {
|
||||||
/**
|
/**
|
||||||
* Delete the given transaction but return 0 if it did not exist.
|
* Delete the given transaction but return 0 if it did not exist.
|
||||||
*
|
*
|
||||||
|
* @param transaction the transaction to be processed.
|
||||||
|
*
|
||||||
* @return the total number of transactions successfully removed from storage.
|
* @return the total number of transactions successfully removed from storage.
|
||||||
*/
|
*/
|
||||||
suspend fun abort(it: PendingTransaction): Int
|
suspend fun abort(transaction: PendingTransaction): Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all pending transactions known to this wallet as a flow that is updated anytime the list
|
* Get all pending transactions known to this wallet as a flow that is updated anytime the list
|
||||||
|
|
|
@ -12,7 +12,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.Locale
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tool for loading checkpoints for the wallet, based on the height at which the wallet was born.
|
* Tool for loading checkpoints for the wallet, based on the height at which the wallet was born.
|
||||||
|
@ -65,11 +65,7 @@ internal object CheckpointTool {
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
internal fun checkpointDirectory(network: ZcashNetwork) =
|
internal fun checkpointDirectory(network: ZcashNetwork) =
|
||||||
"co.electriccoin.zcash/checkpoint/${
|
"co.electriccoin.zcash/checkpoint/${network.networkName.lowercase(Locale.ROOT)}"
|
||||||
(network.networkName as java.lang.String).toLowerCase(
|
|
||||||
Locale.ROOT
|
|
||||||
)
|
|
||||||
}"
|
|
||||||
|
|
||||||
internal fun checkpointHeightFromFilename(zcashNetwork: ZcashNetwork, fileName: String) =
|
internal fun checkpointHeightFromFilename(zcashNetwork: ZcashNetwork, fileName: String) =
|
||||||
BlockHeight.new(zcashNetwork, fileName.split('.').first().toLong())
|
BlockHeight.new(zcashNetwork, fileName.split('.').first().toLong())
|
||||||
|
|
|
@ -83,20 +83,21 @@ class DerivationTool {
|
||||||
deriveTransparentAddressFromSeed(seed, account, index, networkId = network.id)
|
deriveTransparentAddressFromSeed(seed, account, index, networkId = network.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deriveTransparentAddressFromPublicKey(transparentPublicKey: String, network: ZcashNetwork): String = withRustBackendLoaded {
|
override suspend fun deriveTransparentAddressFromPublicKey(publicKey: String, network: ZcashNetwork): String = withRustBackendLoaded {
|
||||||
deriveTransparentAddressFromPubKey(transparentPublicKey, networkId = network.id)
|
deriveTransparentAddressFromPubKey(pk = publicKey, networkId = network.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deriveTransparentAddressFromPrivateKey(transparentPrivateKey: String, network: ZcashNetwork): String = withRustBackendLoaded {
|
override suspend fun deriveTransparentAddressFromPrivateKey(privateKey: String, network: ZcashNetwork): String = withRustBackendLoaded {
|
||||||
deriveTransparentAddressFromPrivKey(transparentPrivateKey, networkId = network.id)
|
deriveTransparentAddressFromPrivKey(sk = privateKey, networkId = network.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deriveTransparentSecretKey(seed: ByteArray, network: ZcashNetwork, account: Int, index: Int): String = withRustBackendLoaded {
|
override suspend fun deriveTransparentSecretKey(seed: ByteArray, network: ZcashNetwork, account: Int, index: Int): String = withRustBackendLoaded {
|
||||||
deriveTransparentSecretKeyFromSeed(seed, account, index, networkId = network.id)
|
deriveTransparentSecretKeyFromSeed(seed, account, index, networkId = network.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun validateUnifiedViewingKey(viewingKey: UnifiedViewingKey, networkId: Int = ZcashNetwork.Mainnet.id) {
|
fun validateUnifiedViewingKey(viewingKey: UnifiedViewingKey, networkId: Int = ZcashNetwork.Mainnet.id) {
|
||||||
// TODO
|
// TODO [#654] https://github.com/zcash/zcash-android-wallet-sdk/issues/654
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue