parent
597cc43886
commit
3b826f8f6a
|
@ -3,7 +3,6 @@ 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.bip39.Mnemonics
|
import cash.z.ecc.android.bip39.Mnemonics
|
||||||
import cash.z.ecc.android.bip39.toSeed
|
import cash.z.ecc.android.bip39.toSeed
|
||||||
import cash.z.ecc.android.sdk.Initializer
|
|
||||||
import cash.z.ecc.android.sdk.SdkSynchronizer
|
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.db.entity.isPending
|
import cash.z.ecc.android.sdk.db.entity.isPending
|
||||||
|
@ -67,12 +66,16 @@ class TestWallet(
|
||||||
runBlocking { DerivationTool.deriveSpendingKeys(seed, network = network)[0] }
|
runBlocking { DerivationTool.deriveSpendingKeys(seed, network = network)[0] }
|
||||||
private val transparentAccountPrivateKey =
|
private val transparentAccountPrivateKey =
|
||||||
runBlocking { DerivationTool.deriveTransparentAccountPrivateKey(seed, network = network) }
|
runBlocking { DerivationTool.deriveTransparentAccountPrivateKey(seed, network = network) }
|
||||||
val initializer = runBlocking {
|
val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(
|
||||||
Initializer.new(context) { config ->
|
context,
|
||||||
runBlocking { config.importWallet(seed, startHeight, network, endpoint, alias = alias) }
|
network,
|
||||||
}
|
alias,
|
||||||
}
|
endpoint,
|
||||||
val synchronizer: SdkSynchronizer = runBlocking { Synchronizer.new(initializer) } as SdkSynchronizer
|
seed,
|
||||||
|
startHeight
|
||||||
|
)
|
||||||
|
as
|
||||||
|
SdkSynchronizer
|
||||||
val service = (synchronizer.processor.downloader.lightWalletService as LightWalletGrpcService)
|
val service = (synchronizer.processor.downloader.lightWalletService as LightWalletGrpcService)
|
||||||
|
|
||||||
val available get() = synchronizer.saplingBalances.value?.available
|
val available get() = synchronizer.saplingBalances.value?.available
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package cash.z.wallet.sdk.sample.demoapp
|
package cash.z.wallet.sdk.sample.demoapp
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import cash.z.ecc.android.sdk.Initializer
|
|
||||||
import cash.z.ecc.android.sdk.Synchronizer
|
import cash.z.ecc.android.sdk.Synchronizer
|
||||||
import cash.z.ecc.android.sdk.db.entity.isFailure
|
import cash.z.ecc.android.sdk.db.entity.isFailure
|
||||||
|
import cash.z.ecc.android.sdk.demoapp.util.fromResources
|
||||||
import cash.z.ecc.android.sdk.ext.convertZecToZatoshi
|
import cash.z.ecc.android.sdk.ext.convertZecToZatoshi
|
||||||
import cash.z.ecc.android.sdk.ext.toHex
|
import cash.z.ecc.android.sdk.ext.toHex
|
||||||
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
||||||
|
@ -14,6 +14,7 @@ import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
import cash.z.ecc.android.sdk.model.Mainnet
|
import cash.z.ecc.android.sdk.model.Mainnet
|
||||||
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
|
import cash.z.ecc.android.sdk.model.defaultForNetwork
|
||||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
@ -160,8 +161,14 @@ class SampleCodeTest {
|
||||||
|
|
||||||
private val context = InstrumentationRegistry.getInstrumentation().targetContext
|
private val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
private val synchronizer: Synchronizer = run {
|
private val synchronizer: Synchronizer = run {
|
||||||
val initializer = runBlocking { Initializer.new(context) {} }
|
val network = ZcashNetwork.fromResources(context)
|
||||||
Synchronizer.newBlocking(initializer)
|
Synchronizer.newBlocking(
|
||||||
|
context,
|
||||||
|
network,
|
||||||
|
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
|
||||||
|
seed = seed,
|
||||||
|
birthday = null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
|
|
|
@ -5,7 +5,6 @@ import android.view.LayoutInflater
|
||||||
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
|
||||||
import cash.z.ecc.android.sdk.Initializer
|
|
||||||
import cash.z.ecc.android.sdk.Synchronizer
|
import cash.z.ecc.android.sdk.Synchronizer
|
||||||
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
||||||
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetAddressBinding
|
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetAddressBinding
|
||||||
|
@ -49,20 +48,15 @@ class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
|
||||||
).first()
|
).first()
|
||||||
}
|
}
|
||||||
|
|
||||||
// using the ViewingKey to initialize
|
|
||||||
runBlocking {
|
|
||||||
Initializer.new(requireApplicationContext(), null) {
|
|
||||||
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
||||||
it.newWallet(
|
synchronizer = Synchronizer.newBlocking(
|
||||||
viewingKey,
|
requireApplicationContext(),
|
||||||
network = network,
|
network,
|
||||||
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network)
|
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
|
||||||
|
seed = seed,
|
||||||
|
birthday = network.saplingActivationHeight
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}.let { initializer ->
|
|
||||||
synchronizer = Synchronizer.newBlocking(initializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun displayAddress() {
|
private fun displayAddress() {
|
||||||
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
|
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
|
||||||
|
|
|
@ -5,7 +5,6 @@ import android.view.LayoutInflater
|
||||||
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
|
||||||
import cash.z.ecc.android.sdk.Initializer
|
|
||||||
import cash.z.ecc.android.sdk.Synchronizer
|
import cash.z.ecc.android.sdk.Synchronizer
|
||||||
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
|
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
|
||||||
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
||||||
|
@ -18,9 +17,7 @@ import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
import cash.z.ecc.android.sdk.model.WalletBalance
|
import cash.z.ecc.android.sdk.model.WalletBalance
|
||||||
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.model.defaultForNetwork
|
import cash.z.ecc.android.sdk.model.defaultForNetwork
|
||||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the available balance && total balance associated with the seed defined by the default config.
|
* Displays the available balance && total balance associated with the seed defined by the default config.
|
||||||
|
@ -46,28 +43,15 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
|
||||||
// have the seed stored
|
// have the seed stored
|
||||||
val seed = Mnemonics.MnemonicCode(seedPhrase).toSeed()
|
val seed = Mnemonics.MnemonicCode(seedPhrase).toSeed()
|
||||||
|
|
||||||
// converting seed into viewingKey
|
|
||||||
val viewingKey = runBlocking {
|
|
||||||
DerivationTool.deriveUnifiedFullViewingKeys(
|
|
||||||
seed,
|
|
||||||
ZcashNetwork.fromResources(requireApplicationContext())
|
|
||||||
).first()
|
|
||||||
}
|
|
||||||
|
|
||||||
// using the ViewingKey to initialize
|
|
||||||
runBlocking {
|
|
||||||
Initializer.new(requireApplicationContext(), null) {
|
|
||||||
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
||||||
it.newWallet(
|
synchronizer = Synchronizer.newBlocking(
|
||||||
viewingKey,
|
requireApplicationContext(),
|
||||||
network = network,
|
network,
|
||||||
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network)
|
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
|
||||||
|
seed = seed,
|
||||||
|
birthday = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}.let { initializer ->
|
|
||||||
synchronizer = Synchronizer.newBlocking(initializer, seed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
|
@ -8,7 +8,6 @@ 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
|
||||||
import cash.z.ecc.android.bip39.toSeed
|
import cash.z.ecc.android.bip39.toSeed
|
||||||
import cash.z.ecc.android.sdk.Initializer
|
|
||||||
import cash.z.ecc.android.sdk.Synchronizer
|
import cash.z.ecc.android.sdk.Synchronizer
|
||||||
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
|
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
|
||||||
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
||||||
|
@ -33,7 +32,6 @@ import kotlinx.coroutines.runBlocking
|
||||||
*/
|
*/
|
||||||
@Suppress("TooManyFunctions")
|
@Suppress("TooManyFunctions")
|
||||||
class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBinding>() {
|
class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBinding>() {
|
||||||
private lateinit var initializer: Initializer
|
|
||||||
private lateinit var synchronizer: Synchronizer
|
private lateinit var synchronizer: Synchronizer
|
||||||
private lateinit var adapter: TransactionAdapter
|
private lateinit var adapter: TransactionAdapter
|
||||||
private lateinit var address: String
|
private lateinit var address: String
|
||||||
|
@ -51,28 +49,20 @@ class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBindin
|
||||||
// Use a BIP-39 library to convert a seed phrase into a byte array. Most wallets already
|
// Use a BIP-39 library to convert a seed phrase into a byte array. Most wallets already
|
||||||
// have the seed stored
|
// have the seed stored
|
||||||
val seed = Mnemonics.MnemonicCode(seedPhrase).toSeed()
|
val seed = Mnemonics.MnemonicCode(seedPhrase).toSeed()
|
||||||
|
|
||||||
initializer = Initializer.newBlocking(
|
|
||||||
requireApplicationContext(),
|
|
||||||
Initializer.Config {
|
|
||||||
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
||||||
runBlocking {
|
|
||||||
it.importWallet(
|
|
||||||
seed,
|
|
||||||
birthday = null,
|
|
||||||
network = network,
|
|
||||||
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
address = runBlocking {
|
address = runBlocking {
|
||||||
DerivationTool.deriveUnifiedAddress(
|
DerivationTool.deriveUnifiedAddress(
|
||||||
seed,
|
seed,
|
||||||
ZcashNetwork.fromResources(requireApplicationContext())
|
ZcashNetwork.fromResources(requireApplicationContext())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
synchronizer = Synchronizer.newBlocking(initializer, seed)
|
synchronizer = Synchronizer.newBlocking(
|
||||||
|
requireApplicationContext(),
|
||||||
|
network,
|
||||||
|
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
|
||||||
|
seed = seed,
|
||||||
|
birthday = null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initTransactionUI() {
|
private fun initTransactionUI() {
|
||||||
|
|
|
@ -8,7 +8,6 @@ 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
|
||||||
import cash.z.ecc.android.bip39.toSeed
|
import cash.z.ecc.android.bip39.toSeed
|
||||||
import cash.z.ecc.android.sdk.Initializer
|
|
||||||
import cash.z.ecc.android.sdk.SdkSynchronizer
|
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.block.CompactBlockProcessor
|
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
|
||||||
|
@ -28,7 +27,6 @@ import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
|
@ -47,7 +45,6 @@ import kotlin.math.max
|
||||||
@Suppress("TooManyFunctions")
|
@Suppress("TooManyFunctions")
|
||||||
class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
||||||
private lateinit var seed: ByteArray
|
private lateinit var seed: ByteArray
|
||||||
private lateinit var initializer: Initializer
|
|
||||||
private lateinit var synchronizer: Synchronizer
|
private lateinit var synchronizer: Synchronizer
|
||||||
private lateinit var adapter: UtxoAdapter
|
private lateinit var adapter: UtxoAdapter
|
||||||
private val address: String = "t1RwbKka1CnktvAJ1cSqdn7c6PXWG4tZqgd"
|
private val address: String = "t1RwbKka1CnktvAJ1cSqdn7c6PXWG4tZqgd"
|
||||||
|
@ -66,21 +63,16 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
||||||
// Use a BIP-39 library to convert a seed phrase into a byte array. Most wallets already
|
// Use a BIP-39 library to convert a seed phrase into a byte array. Most wallets already
|
||||||
// have the seed stored
|
// have the seed stored
|
||||||
seed = Mnemonics.MnemonicCode(sharedViewModel.seedPhrase.value).toSeed()
|
seed = Mnemonics.MnemonicCode(sharedViewModel.seedPhrase.value).toSeed()
|
||||||
initializer = runBlocking {
|
|
||||||
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
||||||
Initializer.new(requireApplicationContext()) {
|
synchronizer = Synchronizer.newBlocking(
|
||||||
runBlocking {
|
requireApplicationContext(),
|
||||||
it.newWallet(
|
network,
|
||||||
seed,
|
alias = "Demo_Utxos",
|
||||||
network = network,
|
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
|
||||||
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network)
|
seed = seed,
|
||||||
|
birthday = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
it.alias = "Demo_Utxos"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
synchronizer = runBlocking { Synchronizer.new(initializer, seed) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
|
@ -8,7 +8,6 @@ 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
|
||||||
import cash.z.ecc.android.bip39.toSeed
|
import cash.z.ecc.android.bip39.toSeed
|
||||||
import cash.z.ecc.android.sdk.Initializer
|
|
||||||
import cash.z.ecc.android.sdk.Synchronizer
|
import cash.z.ecc.android.sdk.Synchronizer
|
||||||
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
|
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
|
||||||
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
|
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
|
||||||
|
@ -68,20 +67,14 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
|
||||||
// have the seed stored
|
// have the seed stored
|
||||||
val seed = Mnemonics.MnemonicCode(seedPhrase).toSeed()
|
val seed = Mnemonics.MnemonicCode(seedPhrase).toSeed()
|
||||||
|
|
||||||
runBlocking {
|
|
||||||
Initializer.new(requireApplicationContext()) {
|
|
||||||
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
||||||
runBlocking {
|
synchronizer = Synchronizer.newBlocking(
|
||||||
it.newWallet(
|
requireApplicationContext(),
|
||||||
seed,
|
network,
|
||||||
network = network,
|
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
|
||||||
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network)
|
seed = seed,
|
||||||
|
birthday = null
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
}.let { initializer ->
|
|
||||||
synchronizer = Synchronizer.newBlocking(initializer, seed)
|
|
||||||
}
|
|
||||||
spendingKey = runBlocking {
|
spendingKey = runBlocking {
|
||||||
DerivationTool.deriveSpendingKeys(seed, ZcashNetwork.fromResources(requireApplicationContext())).first()
|
DerivationTool.deriveSpendingKeys(seed, ZcashNetwork.fromResources(requireApplicationContext())).first()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,11 @@
|
||||||
package cash.z.ecc.android.sdk.ext
|
package cash.z.ecc.android.sdk.ext
|
||||||
|
|
||||||
import cash.z.ecc.android.sdk.Initializer
|
|
||||||
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
|
||||||
import cash.z.ecc.android.sdk.util.SimpleMnemonics
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import okhttp3.OkHttpClient
|
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
|
import kotlin.test.assertNotNull
|
||||||
|
|
||||||
fun Initializer.Config.seedPhrase(seedPhrase: String, network: ZcashNetwork) {
|
|
||||||
runBlocking { setSeed(SimpleMnemonics().toSeed(seedPhrase.toCharArray()), network) }
|
|
||||||
}
|
|
||||||
|
|
||||||
object BlockExplorer {
|
object BlockExplorer {
|
||||||
suspend fun fetchLatestHeight(): Long {
|
suspend fun fetchLatestHeight(): Long {
|
||||||
val client = OkHttpClient()
|
val client = OkHttpClient()
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package cash.z.ecc.android.sdk.integration
|
package cash.z.ecc.android.sdk.integration
|
||||||
|
|
||||||
|
import androidx.test.core.app.ApplicationProvider
|
||||||
|
import cash.z.ecc.android.sdk.DefaultSynchronizerFactory
|
||||||
import cash.z.ecc.android.sdk.annotation.MaintainedTest
|
import cash.z.ecc.android.sdk.annotation.MaintainedTest
|
||||||
import cash.z.ecc.android.sdk.annotation.TestPurpose
|
import cash.z.ecc.android.sdk.annotation.TestPurpose
|
||||||
import cash.z.ecc.android.sdk.db.DatabaseCoordinator
|
import cash.z.ecc.android.sdk.db.DatabaseCoordinator
|
||||||
|
@ -13,7 +15,6 @@ import org.junit.Ignore
|
||||||
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
|
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
|
||||||
|
@ -36,44 +37,35 @@ class SanityTest(
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testFilePaths() {
|
fun testFilePaths() {
|
||||||
|
val rustBackend = runBlocking {
|
||||||
|
DefaultSynchronizerFactory.defaultRustBackend(
|
||||||
|
ApplicationProvider.getApplicationContext(),
|
||||||
|
ZcashNetwork.Testnet,
|
||||||
|
"TestWallet",
|
||||||
|
TestWallet.Backups.SAMPLE_WALLET.testnetBirthday
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
assertTrue(
|
assertTrue(
|
||||||
"$name has invalid DataDB file",
|
"$name has invalid DataDB file",
|
||||||
wallet.initializer.rustBackend.dataDbFile.absolutePath.endsWith(
|
rustBackend.dataDbFile.absolutePath.endsWith(
|
||||||
"no_backup/co.electricoin.zcash/TestWallet_${networkName}_${DatabaseCoordinator.DB_DATA_NAME}"
|
"no_backup/co.electricoin.zcash/TestWallet_${networkName}_${DatabaseCoordinator.DB_DATA_NAME}"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
assertTrue(
|
assertTrue(
|
||||||
"$name has invalid CacheDB file",
|
"$name has invalid CacheDB file",
|
||||||
wallet.initializer.rustBackend.cacheDbFile.absolutePath.endsWith(
|
rustBackend.cacheDbFile.absolutePath.endsWith(
|
||||||
"no_backup/co.electricoin.zcash/TestWallet_${networkName}_${DatabaseCoordinator.DB_CACHE_NAME}"
|
"no_backup/co.electricoin.zcash/TestWallet_${networkName}_${DatabaseCoordinator.DB_CACHE_NAME}"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
assertTrue(
|
assertTrue(
|
||||||
"$name has invalid CacheDB params dir",
|
"$name has invalid CacheDB params dir",
|
||||||
wallet.initializer.rustBackend.pathParamsDir.endsWith(
|
rustBackend.pathParamsDir.endsWith(
|
||||||
"cache/params"
|
"cache/params"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testBirthday() {
|
|
||||||
assertEquals(
|
|
||||||
"$name has invalid birthday height",
|
|
||||||
birthday,
|
|
||||||
wallet.initializer.checkpoint.height
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testViewingKeys() {
|
|
||||||
assertEquals(
|
|
||||||
"$name has invalid encoding",
|
|
||||||
encoding,
|
|
||||||
wallet.initializer.viewingKeys[0].encoding
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore(
|
@Ignore(
|
||||||
"This test needs to be refactored to a separate test module. It causes SSLHandshakeException: Chain " +
|
"This test needs to be refactored to a separate test module. It causes SSLHandshakeException: Chain " +
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
package cash.z.ecc.android.sdk.integration
|
package cash.z.ecc.android.sdk.integration
|
||||||
|
|
||||||
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import androidx.test.filters.LargeTest
|
import androidx.test.filters.LargeTest
|
||||||
import androidx.test.filters.MediumTest
|
import androidx.test.filters.MediumTest
|
||||||
|
import cash.z.ecc.android.sdk.DefaultSynchronizerFactory
|
||||||
import cash.z.ecc.android.sdk.annotation.MaintainedTest
|
import cash.z.ecc.android.sdk.annotation.MaintainedTest
|
||||||
import cash.z.ecc.android.sdk.annotation.TestPurpose
|
import cash.z.ecc.android.sdk.annotation.TestPurpose
|
||||||
import cash.z.ecc.android.sdk.db.DatabaseCoordinator
|
import cash.z.ecc.android.sdk.db.DatabaseCoordinator
|
||||||
|
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.Assert.assertTrue
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -22,38 +24,32 @@ class SmokeTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testFilePaths() {
|
fun testFilePaths() {
|
||||||
|
val rustBackend = runBlocking {
|
||||||
|
DefaultSynchronizerFactory.defaultRustBackend(
|
||||||
|
ApplicationProvider.getApplicationContext(),
|
||||||
|
ZcashNetwork.Testnet,
|
||||||
|
"TestWallet",
|
||||||
|
TestWallet.Backups.SAMPLE_WALLET.testnetBirthday
|
||||||
|
)
|
||||||
|
}
|
||||||
assertTrue(
|
assertTrue(
|
||||||
"Invalid DataDB file",
|
"Invalid DataDB file",
|
||||||
wallet.initializer.rustBackend.dataDbFile.absolutePath.endsWith(
|
rustBackend.dataDbFile.absolutePath.endsWith(
|
||||||
"no_backup/co.electricoin.zcash/TestWallet_testnet_${DatabaseCoordinator.DB_DATA_NAME}"
|
"no_backup/co.electricoin.zcash/TestWallet_testnet_${DatabaseCoordinator.DB_DATA_NAME}"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
assertTrue(
|
assertTrue(
|
||||||
"Invalid CacheDB file",
|
"Invalid CacheDB file",
|
||||||
wallet.initializer.rustBackend.cacheDbFile.absolutePath.endsWith(
|
rustBackend.cacheDbFile.absolutePath.endsWith(
|
||||||
"no_backup/co.electricoin.zcash/TestWallet_testnet_${DatabaseCoordinator.DB_CACHE_NAME}"
|
"no_backup/co.electricoin.zcash/TestWallet_testnet_${DatabaseCoordinator.DB_CACHE_NAME}"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
assertTrue(
|
assertTrue(
|
||||||
"Invalid CacheDB params dir",
|
"Invalid CacheDB params dir",
|
||||||
wallet.initializer.rustBackend.pathParamsDir.endsWith("cache/params")
|
rustBackend.pathParamsDir.endsWith("cache/params")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testBirthday() {
|
|
||||||
assertEquals(
|
|
||||||
"Invalid birthday height",
|
|
||||||
1_330_000,
|
|
||||||
wallet.initializer.checkpoint.height.value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testViewingKeys() {
|
|
||||||
assertEquals("Invalid encoding", "uviewtest1m3cyp6tdy3rewtpqazdxlsqkmu7xjedtqmp4da8mvxm87h4as38v5kz4ulw7x7nmgv5d8uwk743a5zt7aurtz2z2g74fu740ecp5fhdgakm6hgzr5jzcl75cmddlufmjpykrpkzj84yz8j5qe9c5935qt2tvd9dpx3m0zw5dwn3t2dtsdyqvy5jstf88w799qre549yyxw7dvk3murm3568ah6wqg5tdjka2ujtgct4q62hw7mfcxcyaeu8l6882hxkt9x4025mx3w35whcrmpxy8fqsh62esatczj8awxtrgnj8h2vj65r8595qt9jl4gz84w4mja74tymt8xxaguckeam", wallet.initializer.viewingKeys[0].encoding)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This test takes an extremely long time
|
// This test takes an extremely long time
|
||||||
// Does its runtime grow over time based on growth of the blockchain?
|
// Does its runtime grow over time based on growth of the blockchain?
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -2,7 +2,6 @@ package cash.z.ecc.android.sdk.integration
|
||||||
|
|
||||||
import androidx.test.filters.LargeTest
|
import androidx.test.filters.LargeTest
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import cash.z.ecc.android.sdk.Initializer
|
|
||||||
import cash.z.ecc.android.sdk.Synchronizer
|
import cash.z.ecc.android.sdk.Synchronizer
|
||||||
import cash.z.ecc.android.sdk.Synchronizer.Status.SYNCED
|
import cash.z.ecc.android.sdk.Synchronizer.Status.SYNCED
|
||||||
import cash.z.ecc.android.sdk.db.entity.isSubmitSuccess
|
import cash.z.ecc.android.sdk.db.entity.isSubmitSuccess
|
||||||
|
@ -63,7 +62,7 @@ class TestnetIntegrationTest : ScopedTest() {
|
||||||
@Test
|
@Test
|
||||||
@Ignore("This test is broken")
|
@Ignore("This test is broken")
|
||||||
fun getAddress() = runBlocking {
|
fun getAddress() = runBlocking {
|
||||||
assertEquals(address, synchronizer.getAddress())
|
assertEquals(address, synchronizer.getCurrentAddress())
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is an extremely slow test; it is disabled so that we can get CI set up
|
// This is an extremely slow test; it is disabled so that we can get CI set up
|
||||||
|
@ -117,29 +116,33 @@ class TestnetIntegrationTest : ScopedTest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
init { Twig.plant(TroubleshootingTwig()) }
|
init {
|
||||||
|
Twig.plant(TroubleshootingTwig())
|
||||||
|
}
|
||||||
|
|
||||||
val lightWalletEndpoint = LightWalletEndpoint("lightwalletd.testnet.z.cash", 9087, true)
|
val lightWalletEndpoint = LightWalletEndpoint("lightwalletd.testnet.z.cash", 9087, true)
|
||||||
private const val birthdayHeight = 963150L
|
private const val birthdayHeight = 963150L
|
||||||
private const val targetHeight = 663250
|
private const val targetHeight = 663250
|
||||||
private const val seedPhrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"
|
private const val seedPhrase =
|
||||||
|
"still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"
|
||||||
val seed = "cash.z.ecc.android.sdk.integration.IntegrationTest.seed.value.64bytes".toByteArray()
|
val seed = "cash.z.ecc.android.sdk.integration.IntegrationTest.seed.value.64bytes".toByteArray()
|
||||||
val address = "zs1m30y59wxut4zk9w24d6ujrdnfnl42hpy0ugvhgyhr8s0guszutqhdj05c7j472dndjstulph74m"
|
val address = "zs1m30y59wxut4zk9w24d6ujrdnfnl42hpy0ugvhgyhr8s0guszutqhdj05c7j472dndjstulph74m"
|
||||||
val toAddress = "zs1vp7kvlqr4n9gpehztr76lcn6skkss9p8keqs3nv8avkdtjrcctrvmk9a7u494kluv756jeee5k0"
|
val toAddress = "zs1vp7kvlqr4n9gpehztr76lcn6skkss9p8keqs3nv8avkdtjrcctrvmk9a7u494kluv756jeee5k0"
|
||||||
|
|
||||||
private val context = InstrumentationRegistry.getInstrumentation().context
|
private val context = InstrumentationRegistry.getInstrumentation().context
|
||||||
private val initializer = runBlocking {
|
|
||||||
Initializer.new(context) { config ->
|
|
||||||
config.setNetwork(ZcashNetwork.Testnet, lightWalletEndpoint)
|
|
||||||
runBlocking { config.importWallet(seed, BlockHeight.new(ZcashNetwork.Testnet, birthdayHeight), ZcashNetwork.Testnet, lightWalletEndpoint) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private lateinit var synchronizer: Synchronizer
|
private lateinit var synchronizer: Synchronizer
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
fun startUp() {
|
fun startUp() {
|
||||||
synchronizer = Synchronizer.newBlocking(initializer)
|
synchronizer = Synchronizer.newBlocking(
|
||||||
|
context,
|
||||||
|
ZcashNetwork.Testnet,
|
||||||
|
lightWalletEndpoint =
|
||||||
|
lightWalletEndpoint,
|
||||||
|
seed = seed,
|
||||||
|
birthday = BlockHeight.new(ZcashNetwork.Testnet, birthdayHeight)
|
||||||
|
)
|
||||||
synchronizer.start(classScope)
|
synchronizer.start(classScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package cash.z.ecc.android.sdk.util
|
package cash.z.ecc.android.sdk.util
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import cash.z.ecc.android.sdk.Initializer
|
|
||||||
import cash.z.ecc.android.sdk.Synchronizer
|
import cash.z.ecc.android.sdk.Synchronizer
|
||||||
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
||||||
import cash.z.ecc.android.sdk.internal.Twig
|
import cash.z.ecc.android.sdk.internal.Twig
|
||||||
|
@ -14,11 +13,9 @@ import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.model.defaultForNetwork
|
import cash.z.ecc.android.sdk.model.defaultForNetwork
|
||||||
import cash.z.ecc.android.sdk.tool.CheckpointTool
|
import cash.z.ecc.android.sdk.tool.CheckpointTool
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.asFlow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import okio.buffer
|
|
||||||
import okio.source
|
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -83,11 +80,7 @@ class BalancePrinterUtil {
|
||||||
mnemonics.toSeed(seedPhrase.toCharArray())
|
mnemonics.toSeed(seedPhrase.toCharArray())
|
||||||
}.collect { seed ->
|
}.collect { seed ->
|
||||||
// TODO: clear the dataDb but leave the cacheDb
|
// TODO: clear the dataDb but leave the cacheDb
|
||||||
val initializer = Initializer.new(context) { config ->
|
|
||||||
val endpoint = LightWalletEndpoint.defaultForNetwork(network)
|
|
||||||
runBlocking { config.importWallet(seed, birthdayHeight, network, endpoint) }
|
|
||||||
config.alias = alias
|
|
||||||
}
|
|
||||||
/*
|
/*
|
||||||
what I need to do right now
|
what I need to do right now
|
||||||
- for each seed
|
- for each seed
|
||||||
|
@ -103,7 +96,14 @@ class BalancePrinterUtil {
|
||||||
- can we be more stateless and thereby improve the flexibility of this code?!!!
|
- can we be more stateless and thereby improve the flexibility of this code?!!!
|
||||||
*/
|
*/
|
||||||
synchronizer?.stop()
|
synchronizer?.stop()
|
||||||
synchronizer = Synchronizer.new(initializer).apply {
|
synchronizer = Synchronizer.new(
|
||||||
|
context,
|
||||||
|
network,
|
||||||
|
lightWalletEndpoint = LightWalletEndpoint
|
||||||
|
.defaultForNetwork(network),
|
||||||
|
seed = seed,
|
||||||
|
birthday = birthdayHeight
|
||||||
|
).apply {
|
||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,16 +142,7 @@ class BalancePrinterUtil {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun readLines() = flow<String> {
|
fun readLines() = javaClass.getResourceAsStream("/utils/seeds.txt")!!.bufferedReader().lineSequence().asFlow()
|
||||||
val seedFile = javaClass.getResourceAsStream("/utils/seeds.txt")!!
|
|
||||||
seedFile.source().buffer().use { source ->
|
|
||||||
var line: String? = source.readUtf8Line()
|
|
||||||
while (line != null) {
|
|
||||||
emit(line)
|
|
||||||
line = source.readUtf8Line()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// private fun initWallet(seed: String): Wallet {
|
// private fun initWallet(seed: String): Wallet {
|
||||||
// val spendingKeyProvider = Delegates.notNull<String>()
|
// val spendingKeyProvider = Delegates.notNull<String>()
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
package cash.z.ecc.android.sdk.util
|
package cash.z.ecc.android.sdk.util
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import cash.z.ecc.android.sdk.Initializer
|
|
||||||
import cash.z.ecc.android.sdk.SdkSynchronizer
|
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.TroubleshootingTwig
|
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.model.BlockHeight
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
|
import cash.z.ecc.android.sdk.model.defaultForNetwork
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
@ -65,27 +66,17 @@ class DataDbScannerUtil {
|
||||||
@Test
|
@Test
|
||||||
@Ignore("This test is broken")
|
@Ignore("This test is broken")
|
||||||
fun scanExistingDb() {
|
fun scanExistingDb() {
|
||||||
synchronizer = run {
|
synchronizer = Synchronizer.newBlocking(
|
||||||
val initializer = runBlocking {
|
context,
|
||||||
Initializer.new(context) {
|
ZcashNetwork.Mainnet,
|
||||||
it.setBirthdayHeight(
|
lightWalletEndpoint = LightWalletEndpoint
|
||||||
BlockHeight.new(
|
.defaultForNetwork(ZcashNetwork.Mainnet),
|
||||||
|
seed = byteArrayOf(),
|
||||||
|
birthday = BlockHeight.new(
|
||||||
ZcashNetwork.Mainnet,
|
ZcashNetwork.Mainnet,
|
||||||
birthdayHeight
|
birthdayHeight
|
||||||
),
|
|
||||||
false
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val synchronizer = runBlocking {
|
|
||||||
Synchronizer.new(
|
|
||||||
initializer
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
synchronizer
|
|
||||||
}
|
|
||||||
|
|
||||||
println("sync!")
|
println("sync!")
|
||||||
synchronizer.start()
|
synchronizer.start()
|
||||||
|
|
|
@ -3,7 +3,6 @@ package cash.z.ecc.android.sdk.util
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
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
|
||||||
import cash.z.ecc.android.sdk.Initializer
|
|
||||||
import cash.z.ecc.android.sdk.SdkSynchronizer
|
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.db.entity.isPending
|
import cash.z.ecc.android.sdk.db.entity.isPending
|
||||||
|
@ -67,12 +66,14 @@ class TestWallet(
|
||||||
runBlocking { DerivationTool.deriveSpendingKeys(seed, network = network)[0] }
|
runBlocking { DerivationTool.deriveSpendingKeys(seed, network = network)[0] }
|
||||||
private val transparentAccountPrivateKey =
|
private val transparentAccountPrivateKey =
|
||||||
runBlocking { DerivationTool.deriveTransparentAccountPrivateKey(seed, network = network) }
|
runBlocking { DerivationTool.deriveTransparentAccountPrivateKey(seed, network = network) }
|
||||||
val initializer = runBlocking {
|
val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(
|
||||||
Initializer.new(context) { config ->
|
context,
|
||||||
runBlocking { config.importWallet(seed, startHeight, network, endpoint, alias = alias) }
|
network,
|
||||||
}
|
alias,
|
||||||
}
|
lightWalletEndpoint = endpoint,
|
||||||
val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(initializer) as SdkSynchronizer
|
seed = seed,
|
||||||
|
startHeight
|
||||||
|
) as SdkSynchronizer
|
||||||
val service = (synchronizer.processor.downloader.lightWalletService as LightWalletGrpcService)
|
val service = (synchronizer.processor.downloader.lightWalletService as LightWalletGrpcService)
|
||||||
|
|
||||||
val available get() = synchronizer.saplingBalances.value?.available
|
val available get() = synchronizer.saplingBalances.value?.available
|
||||||
|
@ -109,7 +110,12 @@ class TestWallet(
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun send(address: String = transparentAddress, memo: String = "", amount: Zatoshi = Zatoshi(500L), fromAccountIndex: Int = 0): TestWallet {
|
suspend fun send(
|
||||||
|
address: String = transparentAddress,
|
||||||
|
memo: String = "",
|
||||||
|
amount: Zatoshi = Zatoshi(500L),
|
||||||
|
fromAccountIndex: Int = 0
|
||||||
|
): TestWallet {
|
||||||
Twig.sprout("$alias sending")
|
Twig.sprout("$alias sending")
|
||||||
synchronizer.sendToAddress(shieldedSpendingKey, amount, address, memo, fromAccountIndex)
|
synchronizer.sendToAddress(shieldedSpendingKey, amount, address, memo, fromAccountIndex)
|
||||||
.takeWhile { it.isPending(null) }
|
.takeWhile { it.isPending(null) }
|
||||||
|
|
|
@ -1,434 +0,0 @@
|
||||||
package cash.z.ecc.android.sdk
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import cash.z.ecc.android.sdk.db.DatabaseCoordinator
|
|
||||||
import cash.z.ecc.android.sdk.exception.InitializerException
|
|
||||||
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
|
||||||
import cash.z.ecc.android.sdk.internal.ext.getCacheDirSuspend
|
|
||||||
import cash.z.ecc.android.sdk.internal.model.Checkpoint
|
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
|
||||||
import cash.z.ecc.android.sdk.jni.RustBackend
|
|
||||||
import cash.z.ecc.android.sdk.model.BlockHeight
|
|
||||||
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
|
||||||
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
|
||||||
import cash.z.ecc.android.sdk.tool.CheckpointTool
|
|
||||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
|
||||||
import cash.z.ecc.android.sdk.type.UnifiedFullViewingKey
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simplified Initializer focused on starting from a ViewingKey.
|
|
||||||
*/
|
|
||||||
@Suppress("LongParameterList", "unused")
|
|
||||||
class Initializer private constructor(
|
|
||||||
val context: Context,
|
|
||||||
internal val rustBackend: RustBackend,
|
|
||||||
val network: ZcashNetwork,
|
|
||||||
val alias: String,
|
|
||||||
val lightWalletEndpoint: LightWalletEndpoint,
|
|
||||||
val viewingKeys: List<UnifiedFullViewingKey>,
|
|
||||||
val overwriteVks: Boolean,
|
|
||||||
internal val checkpoint: Checkpoint
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun erase() = erase(context, network, alias)
|
|
||||||
|
|
||||||
@Suppress("TooManyFunctions")
|
|
||||||
class Config private constructor(
|
|
||||||
val viewingKeys: MutableList<UnifiedFullViewingKey> = mutableListOf(),
|
|
||||||
var alias: String = ZcashSdk.DEFAULT_ALIAS
|
|
||||||
) {
|
|
||||||
var birthdayHeight: BlockHeight? = null
|
|
||||||
private set
|
|
||||||
|
|
||||||
lateinit var network: ZcashNetwork
|
|
||||||
private set
|
|
||||||
|
|
||||||
lateinit var lightWalletEndpoint: LightWalletEndpoint
|
|
||||||
private set
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines the default behavior for null birthdays. When null, nothing has been specified
|
|
||||||
* so a null birthdayHeight value is an error. When false, null birthdays will be replaced
|
|
||||||
* with the most recent checkpoint height available (typically, the latest `*.json` file in
|
|
||||||
* `assets/co.electriccoin.zcash/checkpoint/`). When true, null birthdays will be replaced with the oldest
|
|
||||||
* reasonable height where a transaction could exist (typically, sapling activation but
|
|
||||||
* better approximations could be devised in the future, such as the date when the first
|
|
||||||
* BIP-39 zcash wallets came online).
|
|
||||||
*/
|
|
||||||
var defaultToOldestHeight: Boolean? = null
|
|
||||||
private set
|
|
||||||
|
|
||||||
var overwriteVks: Boolean = false
|
|
||||||
private set
|
|
||||||
|
|
||||||
constructor(block: (Config) -> Unit) : this() {
|
|
||||||
block(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Birthday functions
|
|
||||||
//
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the birthday height for this configuration. When the height is not known, the wallet
|
|
||||||
* can either default to the latest known birthday (in order to sync new wallets faster) or
|
|
||||||
* the oldest possible birthday (in order to import a wallet with an unknown birthday
|
|
||||||
* without skipping old transactions).
|
|
||||||
*
|
|
||||||
* @param height nullable birthday height to use for this configuration.
|
|
||||||
* @param defaultToOldestHeight determines how a null birthday height will be
|
|
||||||
* interpreted. Typically, `false` for new wallets and `true` for restored wallets because
|
|
||||||
* new wallets want to load quickly but restored wallets want to find all possible
|
|
||||||
* transactions. Again, this value is only considered when [height] is null.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
fun setBirthdayHeight(height: BlockHeight?, defaultToOldestHeight: Boolean): Config =
|
|
||||||
apply {
|
|
||||||
this.birthdayHeight = height
|
|
||||||
this.defaultToOldestHeight = defaultToOldestHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the most recent checkpoint available. This is useful for new wallets.
|
|
||||||
*/
|
|
||||||
fun newWalletBirthday(): Config = apply {
|
|
||||||
birthdayHeight = null
|
|
||||||
defaultToOldestHeight = false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the birthday checkpoint closest to the given wallet birthday. This is useful when
|
|
||||||
* importing a pre-existing wallet. It is the same as calling
|
|
||||||
* `birthdayHeight = importedHeight`.
|
|
||||||
*/
|
|
||||||
fun importedWalletBirthday(importedHeight: BlockHeight?): Config = apply {
|
|
||||||
birthdayHeight = importedHeight
|
|
||||||
defaultToOldestHeight = true
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Viewing key functions
|
|
||||||
//
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add viewing keys to the set of accounts to monitor. Note: Using more than one viewing key
|
|
||||||
* is not currently well supported. Consider it an alpha-preview feature that might work but
|
|
||||||
* probably has serious bugs.
|
|
||||||
*/
|
|
||||||
fun setViewingKeys(
|
|
||||||
vararg unifiedFullViewingKeys: UnifiedFullViewingKey,
|
|
||||||
overwrite: Boolean = false
|
|
||||||
): Config = apply {
|
|
||||||
overwriteVks = overwrite
|
|
||||||
viewingKeys.apply {
|
|
||||||
clear()
|
|
||||||
addAll(unifiedFullViewingKeys)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setOverwriteKeys(isOverwrite: Boolean) {
|
|
||||||
overwriteVks = isOverwrite
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add viewing key to the set of accounts to monitor. Note: Using more than one viewing key
|
|
||||||
* is not currently well supported. Consider it an alpha-preview feature that might work but
|
|
||||||
* probably has serious bugs.
|
|
||||||
*/
|
|
||||||
fun addViewingKey(unifiedFullViewingKey: UnifiedFullViewingKey): Config = apply {
|
|
||||||
viewingKeys.add(unifiedFullViewingKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Convenience functions
|
|
||||||
//
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
* connect to lightwalletd.
|
|
||||||
*
|
|
||||||
* @param network the Zcash network to use. Either testnet or mainnet.
|
|
||||||
* @param lightWalletEndpoint the light wallet endpoint to use.
|
|
||||||
*/
|
|
||||||
fun setNetwork(
|
|
||||||
network: ZcashNetwork,
|
|
||||||
lightWalletEndpoint: LightWalletEndpoint
|
|
||||||
): Config = apply {
|
|
||||||
this.network = network
|
|
||||||
this.lightWalletEndpoint = lightWalletEndpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Import a wallet using the first viewing key derived from the given seed.
|
|
||||||
*/
|
|
||||||
suspend fun importWallet(
|
|
||||||
seed: ByteArray,
|
|
||||||
birthday: BlockHeight?,
|
|
||||||
network: ZcashNetwork,
|
|
||||||
lightWalletEndpoint: LightWalletEndpoint,
|
|
||||||
alias: String = ZcashSdk.DEFAULT_ALIAS
|
|
||||||
): Config =
|
|
||||||
importWallet(
|
|
||||||
DerivationTool.deriveUnifiedFullViewingKeys(seed, network = network)[0],
|
|
||||||
birthday,
|
|
||||||
network,
|
|
||||||
lightWalletEndpoint,
|
|
||||||
alias
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default function for importing a wallet.
|
|
||||||
*/
|
|
||||||
@Suppress("LongParameterList")
|
|
||||||
fun importWallet(
|
|
||||||
viewingKey: UnifiedFullViewingKey,
|
|
||||||
birthday: BlockHeight?,
|
|
||||||
network: ZcashNetwork,
|
|
||||||
lightWalletEndpoint: LightWalletEndpoint,
|
|
||||||
alias: String = ZcashSdk.DEFAULT_ALIAS
|
|
||||||
): Config = apply {
|
|
||||||
setViewingKeys(viewingKey)
|
|
||||||
setNetwork(network, lightWalletEndpoint)
|
|
||||||
importedWalletBirthday(birthday)
|
|
||||||
this.alias = alias
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new wallet using the first viewing key derived from the given seed.
|
|
||||||
*/
|
|
||||||
suspend fun newWallet(
|
|
||||||
seed: ByteArray,
|
|
||||||
network: ZcashNetwork,
|
|
||||||
lightWalletEndpoint: LightWalletEndpoint,
|
|
||||||
alias: String = ZcashSdk.DEFAULT_ALIAS
|
|
||||||
): Config = newWallet(
|
|
||||||
DerivationTool.deriveUnifiedFullViewingKeys(seed, network)[0],
|
|
||||||
network,
|
|
||||||
lightWalletEndpoint,
|
|
||||||
alias
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default function for creating a new wallet.
|
|
||||||
*/
|
|
||||||
fun newWallet(
|
|
||||||
viewingKey: UnifiedFullViewingKey,
|
|
||||||
network: ZcashNetwork,
|
|
||||||
lightWalletEndpoint: LightWalletEndpoint,
|
|
||||||
alias: String = ZcashSdk.DEFAULT_ALIAS
|
|
||||||
): Config = apply {
|
|
||||||
setViewingKeys(viewingKey)
|
|
||||||
setNetwork(network, lightWalletEndpoint)
|
|
||||||
newWalletBirthday()
|
|
||||||
this.alias = alias
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience method for setting thew viewingKeys from a given seed. This is the same as
|
|
||||||
* calling `setViewingKeys` with the keys that match this seed.
|
|
||||||
*/
|
|
||||||
@Suppress("SpreadOperator")
|
|
||||||
suspend fun setSeed(
|
|
||||||
seed: ByteArray,
|
|
||||||
network: ZcashNetwork,
|
|
||||||
numberOfAccounts: Int = 1
|
|
||||||
): Config =
|
|
||||||
apply {
|
|
||||||
@Suppress("SpreadOperator")
|
|
||||||
setViewingKeys(
|
|
||||||
*DerivationTool.deriveUnifiedFullViewingKeys(
|
|
||||||
seed,
|
|
||||||
network,
|
|
||||||
numberOfAccounts
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the network from a network id, throwing an exception if the id is not recognized.
|
|
||||||
*
|
|
||||||
* @param networkId the ID of the network corresponding to the [ZcashNetwork] enum.
|
|
||||||
* Typically, it is 0 for testnet and 1 for mainnet.
|
|
||||||
*/
|
|
||||||
fun setNetworkId(networkId: Int): Config = apply {
|
|
||||||
network = ZcashNetwork.from(networkId)
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Validation helpers
|
|
||||||
//
|
|
||||||
|
|
||||||
fun validate(): Config = apply {
|
|
||||||
validateAlias(alias)
|
|
||||||
validateViewingKeys()
|
|
||||||
validateBirthday()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun validateBirthday() {
|
|
||||||
// if birthday is missing then we need to know how to interpret it
|
|
||||||
// so defaultToOldestHeight ought to be set, in that case
|
|
||||||
if (birthdayHeight == null && defaultToOldestHeight == null) {
|
|
||||||
throw InitializerException.MissingDefaultBirthdayException
|
|
||||||
}
|
|
||||||
// allow either null or a value greater than the activation height
|
|
||||||
if (
|
|
||||||
(birthdayHeight?.value ?: network.saplingActivationHeight.value)
|
|
||||||
< network.saplingActivationHeight.value
|
|
||||||
) {
|
|
||||||
throw InitializerException.InvalidBirthdayHeightException(birthdayHeight, network)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun validateViewingKeys() {
|
|
||||||
require(viewingKeys.isNotEmpty()) {
|
|
||||||
"Unified Viewing keys are required. Ensure that the unified viewing keys or seed" +
|
|
||||||
" have been set on this Initializer."
|
|
||||||
}
|
|
||||||
viewingKeys.forEach {
|
|
||||||
DerivationTool.validateUnifiedFullViewingKey(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object : SdkSynchronizer.Erasable {
|
|
||||||
|
|
||||||
suspend fun new(appContext: Context, config: Config) = new(appContext, null, config)
|
|
||||||
|
|
||||||
fun newBlocking(appContext: Context, config: Config) = runBlocking {
|
|
||||||
new(
|
|
||||||
appContext,
|
|
||||||
null,
|
|
||||||
config
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun new(
|
|
||||||
appContext: Context,
|
|
||||||
onCriticalErrorHandler: ((Throwable?) -> Boolean)? = null,
|
|
||||||
block: (Config) -> Unit
|
|
||||||
) = new(appContext, onCriticalErrorHandler, Config(block))
|
|
||||||
|
|
||||||
@Suppress("UNUSED_PARAMETER")
|
|
||||||
suspend fun new(
|
|
||||||
context: Context,
|
|
||||||
onCriticalErrorHandler: ((Throwable?) -> Boolean)?,
|
|
||||||
config: Config
|
|
||||||
): Initializer {
|
|
||||||
config.validate()
|
|
||||||
|
|
||||||
val loadedCheckpoint = run {
|
|
||||||
val height = config.birthdayHeight
|
|
||||||
?: if (config.defaultToOldestHeight == true) {
|
|
||||||
config.network.saplingActivationHeight
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckpointTool.loadNearest(
|
|
||||||
context,
|
|
||||||
config.network,
|
|
||||||
height
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val rustBackend = initRustBackend(context, config.network, config.alias, loadedCheckpoint.height)
|
|
||||||
|
|
||||||
return Initializer(
|
|
||||||
context.applicationContext,
|
|
||||||
rustBackend,
|
|
||||||
config.network,
|
|
||||||
config.alias,
|
|
||||||
config.lightWalletEndpoint,
|
|
||||||
config.viewingKeys,
|
|
||||||
config.overwriteVks,
|
|
||||||
loadedCheckpoint
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onCriticalError(onCriticalErrorHandler: ((Throwable?) -> Boolean)?, error: Throwable) {
|
|
||||||
twig("********")
|
|
||||||
twig("******** INITIALIZER ERROR: $error")
|
|
||||||
if (error.cause != null) twig("******** caused by ${error.cause}")
|
|
||||||
if (error.cause?.cause != null) twig("******** caused by ${error.cause?.cause}")
|
|
||||||
twig("********")
|
|
||||||
twig(error)
|
|
||||||
|
|
||||||
if (onCriticalErrorHandler == null) {
|
|
||||||
twig(
|
|
||||||
"WARNING: a critical error occurred on the Initializer but no callback is " +
|
|
||||||
"registered to be notified of critical errors! THIS IS PROBABLY A MISTAKE. To " +
|
|
||||||
"respond to these errors (perhaps to update the UI or alert the user) set " +
|
|
||||||
"initializer.onCriticalErrorHandler to a non-null value or use the secondary " +
|
|
||||||
"constructor: Initializer(context, handler) { ... }. Note that the synchronizer " +
|
|
||||||
"and initializer BOTH have error handlers and since the initializer exists " +
|
|
||||||
"before the synchronizer, it needs its error handler set separately."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
onCriticalErrorHandler?.invoke(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun initRustBackend(
|
|
||||||
context: Context,
|
|
||||||
network: ZcashNetwork,
|
|
||||||
alias: String,
|
|
||||||
blockHeight: BlockHeight
|
|
||||||
): RustBackend {
|
|
||||||
val coordinator = DatabaseCoordinator.getInstance(context)
|
|
||||||
|
|
||||||
return RustBackend.init(
|
|
||||||
coordinator.cacheDbFile(network, alias).absolutePath,
|
|
||||||
coordinator.dataDbFile(network, alias).absolutePath,
|
|
||||||
File(context.getCacheDirSuspend(), "params").absolutePath,
|
|
||||||
network,
|
|
||||||
blockHeight
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete the databases associated with this wallet. This removes all compact blocks and
|
|
||||||
* data derived from those blocks. For most wallets, this should not result in a loss of
|
|
||||||
* funds because the seed and spending keys are stored separately. This call just removes
|
|
||||||
* the associated data but not the seed or spending key, themselves, because those are
|
|
||||||
* managed separately by the wallet.
|
|
||||||
*
|
|
||||||
* @param appContext the application context.
|
|
||||||
* @param network the network associated with the data to be erased.
|
|
||||||
* @param alias the alias used to create the local data.
|
|
||||||
*
|
|
||||||
* @return true when one of the associated files was found. False most likely indicates
|
|
||||||
* that the wrong alias was provided.
|
|
||||||
*/
|
|
||||||
override suspend fun erase(
|
|
||||||
appContext: Context,
|
|
||||||
network: ZcashNetwork,
|
|
||||||
alias: String
|
|
||||||
): Boolean = DatabaseCoordinator.getInstance(appContext).deleteDatabases(network, alias)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate that the alias doesn't contain malicious characters by enforcing simple rules which
|
|
||||||
* permit the alias to be used as part of a file name for the preferences and databases. This
|
|
||||||
* enables multiple wallets to exist on one device, which is also helpful for sweeping funds.
|
|
||||||
*
|
|
||||||
* @param alias the alias to validate.
|
|
||||||
*
|
|
||||||
* @throws IllegalArgumentException whenever the alias is not less than 100 characters or
|
|
||||||
* contains something other than alphanumeric characters. Underscores are allowed but aliases
|
|
||||||
* must start with a letter.
|
|
||||||
*/
|
|
||||||
internal fun validateAlias(alias: String) {
|
|
||||||
require(
|
|
||||||
alias.length in ZcashSdk.ALIAS_MIN_LENGTH..ZcashSdk.ALIAS_MAX_LENGTH && alias[0].isLetter() &&
|
|
||||||
alias.all { it.isLetterOrDigit() || it == '_' }
|
|
||||||
) {
|
|
||||||
"ERROR: Invalid alias ($alias). For security, the alias must be shorter than 100 " +
|
|
||||||
"characters and only contain letters, digits or underscores and start with a letter."
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -35,9 +35,11 @@ import cash.z.ecc.android.sdk.ext.ZcashSdk
|
||||||
import cash.z.ecc.android.sdk.internal.block.CompactBlockDbStore
|
import cash.z.ecc.android.sdk.internal.block.CompactBlockDbStore
|
||||||
import cash.z.ecc.android.sdk.internal.block.CompactBlockDownloader
|
import cash.z.ecc.android.sdk.internal.block.CompactBlockDownloader
|
||||||
import cash.z.ecc.android.sdk.internal.block.CompactBlockStore
|
import cash.z.ecc.android.sdk.internal.block.CompactBlockStore
|
||||||
|
import cash.z.ecc.android.sdk.internal.ext.getCacheDirSuspend
|
||||||
import cash.z.ecc.android.sdk.internal.ext.toHexReversed
|
import cash.z.ecc.android.sdk.internal.ext.toHexReversed
|
||||||
import cash.z.ecc.android.sdk.internal.ext.tryNull
|
import cash.z.ecc.android.sdk.internal.ext.tryNull
|
||||||
import cash.z.ecc.android.sdk.internal.isEmpty
|
import cash.z.ecc.android.sdk.internal.isEmpty
|
||||||
|
import cash.z.ecc.android.sdk.internal.model.Checkpoint
|
||||||
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
||||||
import cash.z.ecc.android.sdk.internal.service.LightWalletService
|
import cash.z.ecc.android.sdk.internal.service.LightWalletService
|
||||||
import cash.z.ecc.android.sdk.internal.transaction.OutboundTransactionManager
|
import cash.z.ecc.android.sdk.internal.transaction.OutboundTransactionManager
|
||||||
|
@ -48,17 +50,19 @@ import cash.z.ecc.android.sdk.internal.transaction.TransactionRepository
|
||||||
import cash.z.ecc.android.sdk.internal.transaction.WalletTransactionEncoder
|
import cash.z.ecc.android.sdk.internal.transaction.WalletTransactionEncoder
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
import cash.z.ecc.android.sdk.internal.twigTask
|
import cash.z.ecc.android.sdk.internal.twigTask
|
||||||
|
import cash.z.ecc.android.sdk.jni.RustBackend
|
||||||
import cash.z.ecc.android.sdk.model.BlockHeight
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
||||||
import cash.z.ecc.android.sdk.model.WalletBalance
|
import cash.z.ecc.android.sdk.model.WalletBalance
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
|
||||||
import cash.z.ecc.android.sdk.type.AddressType
|
import cash.z.ecc.android.sdk.type.AddressType
|
||||||
import cash.z.ecc.android.sdk.type.AddressType.Shielded
|
import cash.z.ecc.android.sdk.type.AddressType.Shielded
|
||||||
import cash.z.ecc.android.sdk.type.AddressType.Transparent
|
import cash.z.ecc.android.sdk.type.AddressType.Transparent
|
||||||
import cash.z.ecc.android.sdk.type.AddressType.Unified
|
import cash.z.ecc.android.sdk.type.AddressType.Unified
|
||||||
import cash.z.ecc.android.sdk.type.ConsensusMatchType
|
import cash.z.ecc.android.sdk.type.ConsensusMatchType
|
||||||
|
import cash.z.ecc.android.sdk.type.UnifiedFullViewingKey
|
||||||
import cash.z.wallet.sdk.rpc.Service
|
import cash.z.wallet.sdk.rpc.Service
|
||||||
import io.grpc.ManagedChannel
|
import io.grpc.ManagedChannel
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
|
@ -81,6 +85,7 @@ import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.coroutines.EmptyCoroutineContext
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
|
|
||||||
|
@ -803,37 +808,64 @@ object DefaultSynchronizerFactory {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal suspend fun defaultRustBackend(
|
||||||
|
context: Context,
|
||||||
|
network: ZcashNetwork,
|
||||||
|
alias: String,
|
||||||
|
blockHeight: BlockHeight
|
||||||
|
): RustBackend {
|
||||||
|
val coordinator = DatabaseCoordinator.getInstance(context)
|
||||||
|
|
||||||
|
return RustBackend.init(
|
||||||
|
coordinator.cacheDbFile(network, alias).absolutePath,
|
||||||
|
coordinator.dataDbFile(network, alias).absolutePath,
|
||||||
|
File(context.getCacheDirSuspend(), "params").absolutePath,
|
||||||
|
network,
|
||||||
|
blockHeight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO [#242]: Don't hard code page size. It is a workaround for Uncaught Exception:
|
// TODO [#242]: Don't hard code page size. It is a workaround for Uncaught Exception:
|
||||||
// android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy
|
// android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy
|
||||||
// can touch its views. and is probably related to FlowPagedList
|
// can touch its views. and is probably related to FlowPagedList
|
||||||
// TODO [#242]: https://github.com/zcash/zcash-android-wallet-sdk/issues/242
|
// TODO [#242]: https://github.com/zcash/zcash-android-wallet-sdk/issues/242
|
||||||
private const val DEFAULT_PAGE_SIZE = 1000
|
private const val DEFAULT_PAGE_SIZE = 1000
|
||||||
suspend fun defaultTransactionRepository(initializer: Initializer, seed: ByteArray?): TransactionRepository =
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
|
internal suspend fun defaultTransactionRepository(
|
||||||
|
context: Context,
|
||||||
|
rustBackend: RustBackend,
|
||||||
|
zcashNetwork: ZcashNetwork,
|
||||||
|
checkpoint: Checkpoint,
|
||||||
|
viewingKeys: List<UnifiedFullViewingKey>,
|
||||||
|
seed: ByteArray?
|
||||||
|
): TransactionRepository =
|
||||||
PagedTransactionRepository.new(
|
PagedTransactionRepository.new(
|
||||||
initializer.context,
|
context,
|
||||||
initializer.network,
|
zcashNetwork,
|
||||||
DEFAULT_PAGE_SIZE,
|
DEFAULT_PAGE_SIZE,
|
||||||
initializer.rustBackend,
|
rustBackend,
|
||||||
seed,
|
seed,
|
||||||
initializer.checkpoint,
|
checkpoint,
|
||||||
initializer.viewingKeys,
|
viewingKeys,
|
||||||
initializer.overwriteVks
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
fun defaultBlockStore(initializer: Initializer): CompactBlockStore =
|
internal fun defaultBlockStore(context: Context, rustBackend: RustBackend, zcashNetwork: ZcashNetwork):
|
||||||
|
CompactBlockStore =
|
||||||
CompactBlockDbStore.new(
|
CompactBlockDbStore.new(
|
||||||
initializer.context,
|
context,
|
||||||
initializer.network,
|
zcashNetwork,
|
||||||
initializer.rustBackend.cacheDbFile
|
rustBackend.cacheDbFile
|
||||||
)
|
)
|
||||||
|
|
||||||
fun defaultService(initializer: Initializer): LightWalletService =
|
fun defaultService(context: Context, lightWalletEndpoint: LightWalletEndpoint): LightWalletService =
|
||||||
LightWalletGrpcService.new(initializer.context, initializer.lightWalletEndpoint)
|
LightWalletGrpcService.new(context, lightWalletEndpoint)
|
||||||
|
|
||||||
fun defaultEncoder(
|
internal fun defaultEncoder(
|
||||||
initializer: Initializer,
|
rustBackend: RustBackend,
|
||||||
repository: TransactionRepository
|
repository: TransactionRepository
|
||||||
): TransactionEncoder = WalletTransactionEncoder(initializer.rustBackend, repository)
|
): TransactionEncoder = WalletTransactionEncoder(rustBackend, repository)
|
||||||
|
|
||||||
fun defaultDownloader(
|
fun defaultDownloader(
|
||||||
service: LightWalletService,
|
service: LightWalletService,
|
||||||
|
@ -841,31 +873,33 @@ object DefaultSynchronizerFactory {
|
||||||
): CompactBlockDownloader = CompactBlockDownloader(service, blockStore)
|
): CompactBlockDownloader = CompactBlockDownloader(service, blockStore)
|
||||||
|
|
||||||
suspend fun defaultTxManager(
|
suspend fun defaultTxManager(
|
||||||
initializer: Initializer,
|
context: Context,
|
||||||
|
zcashNetwork: ZcashNetwork,
|
||||||
|
alias: String,
|
||||||
encoder: TransactionEncoder,
|
encoder: TransactionEncoder,
|
||||||
service: LightWalletService
|
service: LightWalletService
|
||||||
): OutboundTransactionManager {
|
): OutboundTransactionManager {
|
||||||
val databaseFile = DatabaseCoordinator.getInstance(initializer.context).pendingTransactionsDbFile(
|
val databaseFile = DatabaseCoordinator.getInstance(context).pendingTransactionsDbFile(
|
||||||
initializer.network,
|
zcashNetwork,
|
||||||
initializer.alias
|
alias
|
||||||
)
|
)
|
||||||
|
|
||||||
return PersistentTransactionManager(
|
return PersistentTransactionManager(
|
||||||
initializer.context,
|
context,
|
||||||
encoder,
|
encoder,
|
||||||
service,
|
service,
|
||||||
databaseFile
|
databaseFile
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun defaultProcessor(
|
internal fun defaultProcessor(
|
||||||
initializer: Initializer,
|
rustBackend: RustBackend,
|
||||||
downloader: CompactBlockDownloader,
|
downloader: CompactBlockDownloader,
|
||||||
repository: TransactionRepository
|
repository: TransactionRepository
|
||||||
): CompactBlockProcessor = CompactBlockProcessor(
|
): CompactBlockProcessor = CompactBlockProcessor(
|
||||||
downloader,
|
downloader,
|
||||||
repository,
|
repository,
|
||||||
initializer.rustBackend,
|
rustBackend,
|
||||||
initializer.rustBackend.birthdayHeight
|
rustBackend.birthdayHeight
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
package cash.z.ecc.android.sdk
|
package cash.z.ecc.android.sdk
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
|
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
|
||||||
|
import cash.z.ecc.android.sdk.db.DatabaseCoordinator
|
||||||
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
||||||
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
|
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
|
||||||
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
||||||
import cash.z.ecc.android.sdk.model.BlockHeight
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
|
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
||||||
import cash.z.ecc.android.sdk.model.WalletBalance
|
import cash.z.ecc.android.sdk.model.WalletBalance
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
|
import cash.z.ecc.android.sdk.tool.CheckpointTool
|
||||||
|
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||||
import cash.z.ecc.android.sdk.type.AddressType
|
import cash.z.ecc.android.sdk.type.AddressType
|
||||||
import cash.z.ecc.android.sdk.type.ConsensusMatchType
|
import cash.z.ecc.android.sdk.type.ConsensusMatchType
|
||||||
import cash.z.wallet.sdk.rpc.Service
|
import cash.z.wallet.sdk.rpc.Service
|
||||||
|
@ -463,29 +469,68 @@ interface Synchronizer {
|
||||||
/**
|
/**
|
||||||
* Primary method that SDK clients will use to construct a synchronizer.
|
* Primary method that SDK clients will use to construct a synchronizer.
|
||||||
*
|
*
|
||||||
* If customized initialization is required (e.g. for dependency injection or testing), see
|
|
||||||
* [DefaultSynchronizerFactory].
|
|
||||||
*
|
|
||||||
* @param initializer the helper that is leveraged for creating all the components that the
|
* @param initializer the helper that is leveraged for creating all the components that the
|
||||||
* Synchronizer requires. It contains all information necessary to build a synchronizer and it is
|
* Synchronizer requires. It contains all information necessary to build a synchronizer and it is
|
||||||
* mainly responsible for initializing the databases associated with this synchronizer and loading
|
* mainly responsible for initializing the databases associated with this synchronizer and loading
|
||||||
* the rust backend.
|
* the rust backend.
|
||||||
* @param seed the wallet's seed phrase. This only needs to be provided if this method returns an
|
* @param seed the wallet's seed phrase. This is required the first time a new wallet is set up. For
|
||||||
* error indicating that the seed phrase is required for a database migration.
|
* subsequent calls, seed is only needed if [InitializerException.SeedRequired] is thrown.
|
||||||
|
* @throws InitializerException.SeedRequired
|
||||||
*/
|
*/
|
||||||
|
/*
|
||||||
|
* If customized initialization is required (e.g. for dependency injection or testing), see
|
||||||
|
* [DefaultSynchronizerFactory].
|
||||||
|
*/
|
||||||
|
@Suppress("LongParameterList")
|
||||||
suspend fun new(
|
suspend fun new(
|
||||||
initializer: Initializer,
|
context: Context,
|
||||||
seed: ByteArray,
|
zcashNetwork: ZcashNetwork,
|
||||||
|
alias: String = "zcash",
|
||||||
|
lightWalletEndpoint: LightWalletEndpoint,
|
||||||
|
seed: ByteArray?,
|
||||||
|
birthday: BlockHeight?
|
||||||
): Synchronizer {
|
): Synchronizer {
|
||||||
val repository = DefaultSynchronizerFactory.defaultTransactionRepository(initializer, seed)
|
val applicationContext = context.applicationContext
|
||||||
val blockStore = DefaultSynchronizerFactory.defaultBlockStore(initializer)
|
|
||||||
val service = DefaultSynchronizerFactory.defaultService(initializer)
|
validateAlias(alias)
|
||||||
val encoder = DefaultSynchronizerFactory.defaultEncoder(initializer, repository)
|
|
||||||
|
val loadedCheckpoint = CheckpointTool.loadNearest(
|
||||||
|
applicationContext,
|
||||||
|
zcashNetwork,
|
||||||
|
birthday ?: zcashNetwork.saplingActivationHeight
|
||||||
|
)
|
||||||
|
|
||||||
|
val rustBackend = DefaultSynchronizerFactory.defaultRustBackend(
|
||||||
|
applicationContext,
|
||||||
|
zcashNetwork,
|
||||||
|
alias,
|
||||||
|
loadedCheckpoint.height
|
||||||
|
)
|
||||||
|
|
||||||
|
val viewingKeys = seed?.let {
|
||||||
|
DerivationTool.deriveUnifiedFullViewingKeys(
|
||||||
|
seed,
|
||||||
|
zcashNetwork,
|
||||||
|
1
|
||||||
|
).toList()
|
||||||
|
} ?: emptyList()
|
||||||
|
|
||||||
|
val repository = DefaultSynchronizerFactory.defaultTransactionRepository(
|
||||||
|
applicationContext,
|
||||||
|
rustBackend,
|
||||||
|
zcashNetwork,
|
||||||
|
loadedCheckpoint,
|
||||||
|
viewingKeys,
|
||||||
|
seed
|
||||||
|
)
|
||||||
|
val blockStore = DefaultSynchronizerFactory.defaultBlockStore(applicationContext, rustBackend, zcashNetwork)
|
||||||
|
val service = DefaultSynchronizerFactory.defaultService(applicationContext, lightWalletEndpoint)
|
||||||
|
val encoder = DefaultSynchronizerFactory.defaultEncoder(rustBackend, repository)
|
||||||
val downloader = DefaultSynchronizerFactory.defaultDownloader(service, blockStore)
|
val downloader = DefaultSynchronizerFactory.defaultDownloader(service, blockStore)
|
||||||
val txManager =
|
val txManager =
|
||||||
DefaultSynchronizerFactory.defaultTxManager(initializer, encoder, service)
|
DefaultSynchronizerFactory.defaultTxManager(applicationContext, zcashNetwork, alias, encoder, service)
|
||||||
val processor =
|
val processor =
|
||||||
DefaultSynchronizerFactory.defaultProcessor(initializer, downloader, repository)
|
DefaultSynchronizerFactory.defaultProcessor(rustBackend, downloader, repository)
|
||||||
|
|
||||||
return SdkSynchronizer(
|
return SdkSynchronizer(
|
||||||
repository,
|
repository,
|
||||||
|
@ -501,8 +546,57 @@ interface Synchronizer {
|
||||||
* This is a blocking call, so it should not be called from the main thread.
|
* This is a blocking call, so it should not be called from the main thread.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun newBlocking(initializer: Initializer, seed: ByteArray): Synchronizer = runBlocking {
|
@Suppress("LongParameterList")
|
||||||
new (initializer, seed)
|
fun newBlocking(
|
||||||
|
context: Context,
|
||||||
|
zcashNetwork: ZcashNetwork,
|
||||||
|
alias: String = "zcash",
|
||||||
|
lightWalletEndpoint: LightWalletEndpoint,
|
||||||
|
seed: ByteArray?,
|
||||||
|
birthday: BlockHeight?
|
||||||
|
): Synchronizer = runBlocking {
|
||||||
|
new(context, zcashNetwork, alias, lightWalletEndpoint, seed, birthday)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the databases associated with this wallet. This removes all compact blocks and
|
||||||
|
* data derived from those blocks. Although most data can be regenerated by setting up a new
|
||||||
|
* Synchronizer instance with the seed, there are two special cases where data is not retained:
|
||||||
|
* 1. Outputs created with a `null` OVK
|
||||||
|
* 2. The UA to which a transaction was sent (recovery from seed will only reveal the receiver, not the full UA)
|
||||||
|
*
|
||||||
|
* @param appContext the application context.
|
||||||
|
* @param network the network associated with the data to be erased.
|
||||||
|
* @param alias the alias used to create the local data.
|
||||||
|
*
|
||||||
|
* @return true when one of the associated files was found. False most likely indicates
|
||||||
|
* that the wrong alias was provided.
|
||||||
|
*/
|
||||||
|
suspend fun erase(
|
||||||
|
appContext: Context,
|
||||||
|
network: ZcashNetwork,
|
||||||
|
alias: String
|
||||||
|
): Boolean = DatabaseCoordinator.getInstance(appContext).deleteDatabases(network, alias)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that the alias doesn't contain malicious characters by enforcing simple rules which
|
||||||
|
* permit the alias to be used as part of a file name for the preferences and databases. This
|
||||||
|
* enables multiple wallets to exist on one device, which is also helpful for sweeping funds.
|
||||||
|
*
|
||||||
|
* @param alias the alias to validate.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException whenever the alias is not less than 100 characters or
|
||||||
|
* contains something other than alphanumeric characters. Underscores are allowed but aliases
|
||||||
|
* must start with a letter.
|
||||||
|
*/
|
||||||
|
private fun validateAlias(alias: String) {
|
||||||
|
require(
|
||||||
|
alias.length in ZcashSdk.ALIAS_MIN_LENGTH..ZcashSdk.ALIAS_MAX_LENGTH && alias[0].isLetter() &&
|
||||||
|
alias.all { it.isLetterOrDigit() || it == '_' }
|
||||||
|
) {
|
||||||
|
"ERROR: Invalid alias ($alias). For security, the alias must be shorter than 100 " +
|
||||||
|
"characters and only contain letters, digits or underscores and start with a letter."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ import cash.z.ecc.android.sdk.db.entity.Sent
|
||||||
import cash.z.ecc.android.sdk.db.entity.TransactionEntity
|
import cash.z.ecc.android.sdk.db.entity.TransactionEntity
|
||||||
import cash.z.ecc.android.sdk.db.entity.Utxo
|
import cash.z.ecc.android.sdk.db.entity.Utxo
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
import cash.z.ecc.android.sdk.type.UnifiedAddressAccount
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Database
|
// Database
|
||||||
|
|
|
@ -3,7 +3,6 @@ package cash.z.ecc.android.sdk.internal.transaction
|
||||||
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
||||||
import cash.z.ecc.android.sdk.db.entity.EncodedTransaction
|
import cash.z.ecc.android.sdk.db.entity.EncodedTransaction
|
||||||
import cash.z.ecc.android.sdk.model.BlockHeight
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
import cash.z.ecc.android.sdk.type.UnifiedAddressAccount
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue