197 lines
7.3 KiB
Kotlin
197 lines
7.3 KiB
Kotlin
package cash.z.ecc.android.sdk.util
|
|
|
|
import androidx.test.platform.app.InstrumentationRegistry
|
|
import cash.z.ecc.android.bip39.Mnemonics
|
|
import cash.z.ecc.android.bip39.toSeed
|
|
import cash.z.ecc.android.sdk.SdkSynchronizer
|
|
import cash.z.ecc.android.sdk.Synchronizer
|
|
import cash.z.ecc.android.sdk.internal.Twig
|
|
import cash.z.ecc.android.sdk.internal.deriveUnifiedSpendingKey
|
|
import cash.z.ecc.android.sdk.internal.jni.RustDerivationTool
|
|
import cash.z.ecc.android.sdk.model.Account
|
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
|
import cash.z.ecc.android.sdk.model.Testnet
|
|
import cash.z.ecc.android.sdk.model.WalletBalance
|
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
|
import co.electriccoin.lightwallet.client.model.LightWalletEndpoint
|
|
import kotlinx.coroutines.CoroutineScope
|
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
import kotlinx.coroutines.SupervisorJob
|
|
import kotlinx.coroutines.delay
|
|
import kotlinx.coroutines.flow.first
|
|
import kotlinx.coroutines.launch
|
|
import kotlinx.coroutines.newFixedThreadPoolContext
|
|
import kotlinx.coroutines.runBlocking
|
|
import java.util.concurrent.TimeoutException
|
|
|
|
/**
|
|
* A simple wallet that connects to testnet for integration testing. The intention is that it is
|
|
* easy to drive and nice to use.
|
|
*/
|
|
@OptIn(DelicateCoroutinesApi::class)
|
|
class TestWallet(
|
|
val seedPhrase: String,
|
|
val alias: String = "TestWallet",
|
|
val network: ZcashNetwork = ZcashNetwork.Testnet,
|
|
val endpoint: LightWalletEndpoint = LightWalletEndpoint.Testnet,
|
|
startHeight: BlockHeight? = null
|
|
) {
|
|
constructor(
|
|
backup: Backups,
|
|
network: ZcashNetwork = ZcashNetwork.Testnet,
|
|
alias: String = "TestWallet"
|
|
) : this(
|
|
backup.seedPhrase,
|
|
network = network,
|
|
startHeight = if (network == ZcashNetwork.Mainnet) backup.mainnetBirthday else backup.testnetBirthday,
|
|
alias = alias
|
|
)
|
|
|
|
val walletScope = CoroutineScope(
|
|
SupervisorJob() + newFixedThreadPoolContext(3, this.javaClass.simpleName)
|
|
)
|
|
|
|
// Although runBlocking isn't great, this usage is OK because this is only used within the
|
|
// automated tests
|
|
|
|
private val account = Account.DEFAULT
|
|
private val context = InstrumentationRegistry.getInstrumentation().context
|
|
private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed()
|
|
private val spendingKey =
|
|
runBlocking { RustDerivationTool.new().deriveUnifiedSpendingKey(seed, network = network, account) }
|
|
val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(
|
|
context,
|
|
network,
|
|
alias,
|
|
lightWalletEndpoint = endpoint,
|
|
seed = seed,
|
|
startHeight
|
|
) as SdkSynchronizer
|
|
|
|
val available get() = synchronizer.saplingBalances.value?.available
|
|
val unifiedAddress =
|
|
runBlocking { synchronizer.getUnifiedAddress(account) }
|
|
val transparentAddress =
|
|
runBlocking { synchronizer.getTransparentAddress(account) }
|
|
val birthdayHeight get() = synchronizer.latestBirthdayHeight
|
|
val networkName get() = synchronizer.network.networkName
|
|
|
|
suspend fun transparentBalance(): WalletBalance {
|
|
synchronizer.refreshUtxos(account, synchronizer.latestBirthdayHeight)
|
|
return synchronizer.getTransparentBalance(transparentAddress)
|
|
}
|
|
|
|
suspend fun sync(timeout: Long = -1): TestWallet {
|
|
val killSwitch = walletScope.launch {
|
|
if (timeout > 0) {
|
|
delay(timeout)
|
|
throw TimeoutException("Failed to sync wallet within ${timeout}ms")
|
|
}
|
|
}
|
|
|
|
// block until synced
|
|
synchronizer.status.first { it == Synchronizer.Status.SYNCED }
|
|
killSwitch.cancel()
|
|
return this
|
|
}
|
|
|
|
suspend fun send(
|
|
address: String = transparentAddress,
|
|
memo: String = "",
|
|
amount: Zatoshi = Zatoshi(500L)
|
|
): TestWallet {
|
|
synchronizer.sendToAddress(spendingKey, amount, address, memo)
|
|
return this
|
|
}
|
|
|
|
suspend fun rewindToHeight(height: BlockHeight): TestWallet {
|
|
synchronizer.rewindToNearestHeight(height, false)
|
|
return this
|
|
}
|
|
|
|
suspend fun shieldFunds(): TestWallet {
|
|
synchronizer.refreshUtxos(Account.DEFAULT, BlockHeight.new(ZcashNetwork.Mainnet, 935000)).let { count ->
|
|
Twig.debug { "FOUND $count new UTXOs" }
|
|
}
|
|
|
|
synchronizer.getTransparentBalance(transparentAddress).let { walletBalance ->
|
|
Twig.debug { "FOUND utxo balance of total: ${walletBalance.total} available: ${walletBalance.available}" }
|
|
|
|
if (walletBalance.available.value > 0L) {
|
|
synchronizer.shieldFunds(spendingKey)
|
|
}
|
|
}
|
|
|
|
return this
|
|
}
|
|
|
|
suspend fun join(timeout: Long? = null): TestWallet {
|
|
// block until stopped
|
|
Twig.debug { "Staying alive until synchronizer is stopped!" }
|
|
if (timeout != null) {
|
|
Twig.debug { "Scheduling a stop in ${timeout}ms" }
|
|
walletScope.launch {
|
|
delay(timeout)
|
|
synchronizer.close()
|
|
}
|
|
}
|
|
synchronizer.status.first { it == Synchronizer.Status.STOPPED }
|
|
Twig.debug { "Stopped!" }
|
|
return this
|
|
}
|
|
|
|
companion object {
|
|
}
|
|
|
|
enum class Backups(val seedPhrase: String, val testnetBirthday: BlockHeight, val mainnetBirthday: BlockHeight) {
|
|
// TODO [#902]: Get the proper birthday values for test wallets
|
|
// TODO [#902]: https://github.com/zcash/zcash-android-wallet-sdk/issues/902
|
|
DEFAULT(
|
|
"column rhythm acoustic gym cost fit keen maze fence seed mail medal shrimp tell relief clip" +
|
|
" cannon foster soldier shallow refuse lunar parrot banana",
|
|
BlockHeight.new(
|
|
ZcashNetwork.Testnet,
|
|
1_355_928
|
|
),
|
|
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
|
|
),
|
|
SAMPLE_WALLET(
|
|
"input frown warm senior anxiety abuse yard prefer churn reject people glimpse govern glory" +
|
|
" crumble swallow verb laptop switch trophy inform friend permit purpose",
|
|
BlockHeight.new(
|
|
ZcashNetwork.Testnet,
|
|
1_330_190
|
|
),
|
|
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
|
|
),
|
|
DEV_WALLET(
|
|
"still champion voice habit trend flight survey between bitter process artefact blind carbon" +
|
|
" truly provide dizzy crush flush breeze blouse charge solid fish spread",
|
|
BlockHeight.new(
|
|
ZcashNetwork.Testnet,
|
|
1_000_000
|
|
),
|
|
BlockHeight.new(ZcashNetwork.Mainnet, 991645)
|
|
),
|
|
ALICE(
|
|
"quantum whisper lion route fury lunar pelican image job client hundred sauce chimney barely" +
|
|
" life cliff spirit admit weekend message recipe trumpet impact kitten",
|
|
BlockHeight.new(
|
|
ZcashNetwork.Testnet,
|
|
1_330_190
|
|
),
|
|
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
|
|
),
|
|
BOB(
|
|
"canvas wine sugar acquire garment spy tongue odor hole cage year habit bullet make label human" +
|
|
" unit option top calm neutral try vocal arena",
|
|
BlockHeight.new(
|
|
ZcashNetwork.Testnet,
|
|
1_330_190
|
|
),
|
|
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
|
|
)
|
|
}
|
|
}
|