Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#1533] Flexa integration #1588

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ jobs:
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS: ${{ secrets.UPLOAD_KEY_ALIAS }}
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS_PASSWORD: ${{ secrets.UPLOAD_KEY_ALIAS_PASSWORD }}
ORG_GRADLE_PROJECT_ZCASH_COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
ORG_GRADLE_PROJECT_ZCASH_FLEXA_KEY: ${{ secrets.FLEXA_PUBLISHABLE_KEY }}
run: |
./gradlew :app:publishToGooglePlay
- name: Collect Artifacts
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ jobs:
# Because Fulladle doesn't allow Test Orchestrator to be enabled/disabled for a specific submodule, it must be enabled for all modules
ORG_GRADLE_PROJECT_IS_USE_TEST_ORCHESTRATOR: true
ORG_GRADLE_PROJECT_ZCASH_COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
ORG_GRADLE_PROJECT_ZCASH_FLEXA_KEY: ${{ secrets.FLEXA_PUBLISHABLE_KEY }}
run: |
./gradlew runFlank
- name: Collect Artifacts
Expand Down Expand Up @@ -355,6 +356,7 @@ jobs:
ORG_GRADLE_PROJECT_ZCASH_EMULATOR_WTF_API_KEY: ${{ secrets.EMULATOR_WTF_API_KEY }}
ORG_GRADLE_PROJECT_IS_ANDROID_INSTRUMENTATION_TEST_COVERAGE_ENABLED: true
ORG_GRADLE_PROJECT_ZCASH_COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
ORG_GRADLE_PROJECT_ZCASH_FLEXA_KEY: ${{ secrets.FLEXA_PUBLISHABLE_KEY }}
run: |
./gradlew testDebugWithEmulatorWtf :ui-integration-test:testZcashmainnetDebugWithEmulatorWtf
- name: Collect Artifacts
Expand Down Expand Up @@ -403,6 +405,7 @@ jobs:
ORG_GRADLE_PROJECT_ZCASH_EMULATOR_WTF_API_KEY: ${{ secrets.EMULATOR_WTF_API_KEY }}
ORG_GRADLE_PROJECT_IS_ANDROID_INSTRUMENTATION_TEST_COVERAGE_ENABLED: false
ORG_GRADLE_PROJECT_ZCASH_COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
ORG_GRADLE_PROJECT_ZCASH_FLEXA_KEY: ${{ secrets.FLEXA_PUBLISHABLE_KEY }}
run: |
./gradlew :app:testZcashmainnetDebugWithEmulatorWtf :ui-screenshot-test:testZcashmainnetDebugWithEmulatorWtf
- name: Collect Artifacts
Expand Down Expand Up @@ -462,6 +465,7 @@ jobs:
ORG_GRADLE_PROJECT_ZCASH_SUPPORT_EMAIL_ADDRESS: ${{ vars.SUPPORT_EMAIL_ADDRESS }}
ORG_GRADLE_PROJECT_IS_CRASH_ON_STRICT_MODE_VIOLATION: true
ORG_GRADLE_PROJECT_ZCASH_COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
ORG_GRADLE_PROJECT_ZCASH_FLEXA_KEY: ${{ secrets.FLEXA_PUBLISHABLE_KEY }}
run: |
./gradlew :app:assembleDebug
- name: Authenticate to Google Cloud for Firebase Test Lab
Expand Down Expand Up @@ -533,6 +537,7 @@ jobs:
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS: androiddebugkey
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS_PASSWORD: android
ORG_GRADLE_PROJECT_ZCASH_COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
ORG_GRADLE_PROJECT_ZCASH_FLEXA_KEY: ${{ secrets.FLEXA_PUBLISHABLE_KEY }}
run: |
./gradlew :app:assembleDebug :app:bundleRelease :app:packageZcashmainnetReleaseUniversalApk
- name: Collect Artifacts
Expand Down Expand Up @@ -598,6 +603,7 @@ jobs:
GOOGLE_CLOUD_PROJECT: ${{ vars.FIREBASE_TEST_LAB_PROJECT }}
ORG_GRADLE_PROJECT_ZCASH_FIREBASE_TEST_LAB_API_KEY_PATH: ${{ steps.auth_test_lab.outputs.credentials_file_path }}
ORG_GRADLE_PROJECT_ZCASH_COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
ORG_GRADLE_PROJECT_ZCASH_FLEXA_KEY: ${{ secrets.FLEXA_PUBLISHABLE_KEY }}
run: |
unzip ${BINARIES_ZIP_PATH}
./gradlew :app:runFlankSanityConfigRelease
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import co.electriccoin.zcash.di.viewModelModule
import co.electriccoin.zcash.preference.StandardPreferenceProvider
import co.electriccoin.zcash.spackle.StrictModeCompat
import co.electriccoin.zcash.spackle.Twig
import co.electriccoin.zcash.ui.common.repository.FlexaRepository
import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
Expand All @@ -19,6 +20,7 @@ import org.koin.core.context.startKoin
@Suppress("unused")
class ZcashApplication : CoroutineApplication() {
private val standardPreferenceProvider by inject<StandardPreferenceProvider>()
private val flexaRepository by inject<FlexaRepository>()

override fun onCreate() {
super.onCreate()
Expand All @@ -42,6 +44,8 @@ class ZcashApplication : CoroutineApplication() {
// Since analytics will need disk IO internally, we want this to be registered after strict
// mode is configured to ensure none of that IO happens on the main thread
configureAnalytics()

flexaRepository.init()
}

private fun configureLogging() {
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ tasks {
"ZCASH_GOOGLE_PLAY_DEPLOY_TRACK" to "internal",
"ZCASH_GOOGLE_PLAY_DEPLOY_STATUS" to "draft",

"ZCASH_FLEXA_KEY" to "",
"ZCASH_COINBASE_APP_ID" to "",
"SDK_INCLUDED_BUILD_PATH" to "",
"BIP_39_INCLUDED_BUILD_PATH" to ""
Expand Down
5 changes: 4 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ IS_SCREEN_ROTATION_ENABLED=false
# set it up.
ZCASH_COINBASE_APP_ID=

# Set the flexa publishable key to setup local integration. Replaced by CI action.
ZCASH_FLEXA_KEY=

# Set keystore details to enable build signing. Typically these
# are overridden via ~/.gradle/gradle.properties to allow secure injection.
# Debug keystore is useful if using Google Maps or Firebase, which require API keys to be linked
Expand Down Expand Up @@ -189,7 +192,7 @@ ANDROIDX_TEST_SERVICE_VERSION=1.4.2
ANDROIDX_UI_AUTOMATOR_VERSION=2.3.0
ANDROIDX_WORK_MANAGER_VERSION=2.9.0
ANDROIDX_BROWSER_VERSION=1.8.0
CORE_LIBRARY_DESUGARING_VERSION=2.0.4
CORE_LIBRARY_DESUGARING_VERSION=2.1.2
FIREBASE_BOM_VERSION_MATCHER=33.1.1
GOOGLE_AUTH_LIB_JAVA_VERSION=1.18.0
JACOCO_VERSION=0.8.12
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package co.electriccoin.zcash.preference

import android.content.Context
import co.electriccoin.zcash.preference.api.PreferenceProvider

class InstallationPreferenceProvider(private val context: Context) : PreferenceHolder() {
override suspend fun create(): PreferenceProvider {
return AndroidPreferenceProvider.newStandard(context, "co.electriccoin.zcash.installation")
}
}
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ dependencyResolutionManagement {
}
}
}
mavenLocal()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this change, please?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flexa is not on maven, I am instead building their sdk locally lol

}

@Suppress("MaxLineLength")
Expand Down
13 changes: 12 additions & 1 deletion ui-lib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ android {
"src/main/res/ui/export_data",
"src/main/res/ui/home",
"src/main/res/ui/choose_server",
"src/main/res/ui/integrations",
"src/main/res/ui/new_wallet_recovery",
"src/main/res/ui/onboarding",
"src/main/res/ui/receive",
Expand Down Expand Up @@ -78,6 +79,14 @@ androidComponents {
comment = "Whether is the SecureScreen sensitive data protection enabled"
)
)
variant.buildConfigFields.put(
"ZCASH_FLEXA_KEY",
BuildConfigField(
type = "String",
value = "\"${project.property("ZCASH_FLEXA_KEY")?.toString().orEmpty()}\"",
comment = "Publishable key of the Flexa integration"
)
)
variant.buildConfigFields.put(
"ZCASH_COINBASE_APP_ID",
BuildConfigField(
Expand Down Expand Up @@ -126,6 +135,9 @@ dependencies {
implementation(libs.zcash.bip39)
implementation(libs.zxing)

api("com.flexa:core:1.0.3")
api("com.flexa:spend:1.0.3")

implementation(projects.buildInfoLib)
implementation(projects.configurationApiLib)
implementation(projects.crashAndroidLib)
Expand Down Expand Up @@ -158,4 +170,3 @@ dependencies {
}
}
}

2 changes: 2 additions & 0 deletions ui-lib/src/main/java/co/electriccoin/zcash/di/CoreModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import cash.z.ecc.android.sdk.WalletCoordinator
import co.electriccoin.zcash.configuration.AndroidConfigurationFactory
import co.electriccoin.zcash.global.newInstance
import co.electriccoin.zcash.preference.EncryptedPreferenceProvider
import co.electriccoin.zcash.preference.InstallationPreferenceProvider
import co.electriccoin.zcash.preference.StandardPreferenceProvider
import co.electriccoin.zcash.preference.model.entry.PreferenceKey
import co.electriccoin.zcash.ui.preference.PersistableWalletPreferenceDefault
Expand All @@ -31,6 +32,7 @@ val coreModule =

singleOf(::StandardPreferenceProvider)
singleOf(::EncryptedPreferenceProvider)
singleOf(::InstallationPreferenceProvider)

single { BiometricManager.from(get()) }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package co.electriccoin.zcash.di

import co.electriccoin.zcash.ui.common.provider.FlexaAccountIdProvider
import co.electriccoin.zcash.ui.common.provider.GetDefaultServersProvider
import co.electriccoin.zcash.ui.common.provider.GetVersionInfoProvider
import co.electriccoin.zcash.ui.common.provider.GetZcashCurrencyProvider
Expand All @@ -11,4 +12,5 @@ val providerModule =
factoryOf(::GetDefaultServersProvider)
factoryOf(::GetVersionInfoProvider)
factoryOf(::GetZcashCurrencyProvider)
factoryOf(::FlexaAccountIdProvider)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@ package co.electriccoin.zcash.di

import co.electriccoin.zcash.ui.common.repository.AddressBookRepository
import co.electriccoin.zcash.ui.common.repository.AddressBookRepositoryImpl
import co.electriccoin.zcash.ui.common.repository.BalanceRepository
import co.electriccoin.zcash.ui.common.repository.BalanceRepositoryImpl
import co.electriccoin.zcash.ui.common.repository.ConfigurationRepository
import co.electriccoin.zcash.ui.common.repository.ConfigurationRepositoryImpl
import co.electriccoin.zcash.ui.common.repository.ExchangeRateRepository
import co.electriccoin.zcash.ui.common.repository.ExchangeRateRepositoryImpl
import co.electriccoin.zcash.ui.common.repository.FlexaRepository
import co.electriccoin.zcash.ui.common.repository.FlexaRepositoryImpl
import co.electriccoin.zcash.ui.common.repository.WalletRepository
import co.electriccoin.zcash.ui.common.repository.WalletRepositoryImpl
import org.koin.core.module.dsl.singleOf
Expand All @@ -14,5 +20,8 @@ val repositoryModule =
module {
singleOf(::WalletRepositoryImpl) bind WalletRepository::class
singleOf(::ConfigurationRepositoryImpl) bind ConfigurationRepository::class
singleOf(::ExchangeRateRepositoryImpl) bind ExchangeRateRepository::class
singleOf(::FlexaRepositoryImpl) bind FlexaRepository::class
singleOf(::BalanceRepositoryImpl) bind BalanceRepository::class
singleOf(::AddressBookRepositoryImpl) bind AddressBookRepository::class
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import co.electriccoin.zcash.ui.common.usecase.GetPersistableWalletUseCase
import co.electriccoin.zcash.ui.common.usecase.GetSelectedEndpointUseCase
import co.electriccoin.zcash.ui.common.usecase.GetSynchronizerUseCase
import co.electriccoin.zcash.ui.common.usecase.GetTransparentAddressUseCase
import co.electriccoin.zcash.ui.common.usecase.IsCoinbaseAvailableUseCase
import co.electriccoin.zcash.ui.common.usecase.IsFlexaAvailableUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveAddressBookContactsUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveConfigurationUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveFastestServersUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveSelectedEndpointUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveSynchronizerUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveWalletStateUseCase
import co.electriccoin.zcash.ui.common.usecase.PersistEndpointUseCase
import co.electriccoin.zcash.ui.common.usecase.RefreshFastestServersUseCase
import co.electriccoin.zcash.ui.common.usecase.RescanBlockchainUseCase
Expand Down Expand Up @@ -43,4 +46,7 @@ val useCaseModule =
singleOf(::UpdateContactUseCase)
singleOf(::DeleteContactUseCase)
singleOf(::GetContactUseCase)
singleOf(::IsFlexaAvailableUseCase)
singleOf(::ObserveWalletStateUseCase)
singleOf(::IsCoinbaseAvailableUseCase)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import co.electriccoin.zcash.ui.screen.advancedsettings.viewmodel.AdvancedSettin
import co.electriccoin.zcash.ui.screen.chooseserver.ChooseServerViewModel
import co.electriccoin.zcash.ui.screen.contact.viewmodel.AddContactViewModel
import co.electriccoin.zcash.ui.screen.contact.viewmodel.UpdateContactViewModel
import co.electriccoin.zcash.ui.screen.integrations.viewmodel.IntegrationsViewModel
import co.electriccoin.zcash.ui.screen.onboarding.viewmodel.OnboardingViewModel
import co.electriccoin.zcash.ui.screen.restore.viewmodel.RestoreViewModel
import co.electriccoin.zcash.ui.screen.restoresuccess.viewmodel.RestoreSuccessViewModel
Expand Down Expand Up @@ -45,4 +46,5 @@ val viewModelModule =
viewModelOf(::AddressBookViewModel)
viewModelOf(::AddContactViewModel)
viewModelOf(::UpdateContactViewModel)
viewModelOf(::IntegrationsViewModel)
}
6 changes: 6 additions & 0 deletions ui-lib/src/main/java/co/electriccoin/zcash/ui/Navigation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import co.electriccoin.zcash.ui.NavigationTargets.DELETE_WALLET
import co.electriccoin.zcash.ui.NavigationTargets.EXCHANGE_RATE_OPT_IN
import co.electriccoin.zcash.ui.NavigationTargets.EXPORT_PRIVATE_DATA
import co.electriccoin.zcash.ui.NavigationTargets.HOME
import co.electriccoin.zcash.ui.NavigationTargets.INTEGRATIONS
import co.electriccoin.zcash.ui.NavigationTargets.NOT_ENOUGH_SPACE
import co.electriccoin.zcash.ui.NavigationTargets.SCAN
import co.electriccoin.zcash.ui.NavigationTargets.SEED_RECOVERY
Expand Down Expand Up @@ -67,6 +68,7 @@ import co.electriccoin.zcash.ui.screen.exchangerate.optin.AndroidExchangeRateOpt
import co.electriccoin.zcash.ui.screen.exchangerate.settings.AndroidSettingsExchangeRateOptIn
import co.electriccoin.zcash.ui.screen.exportdata.WrapExportPrivateData
import co.electriccoin.zcash.ui.screen.home.WrapHome
import co.electriccoin.zcash.ui.screen.integrations.WrapIntegrations
import co.electriccoin.zcash.ui.screen.scan.WrapScanValidator
import co.electriccoin.zcash.ui.screen.seedrecovery.WrapSeedRecovery
import co.electriccoin.zcash.ui.screen.send.ext.toSerializableAddress
Expand Down Expand Up @@ -219,6 +221,9 @@ internal fun MainActivity.Navigation() {
composable(WHATS_NEW) {
WrapWhatsNew()
}
composable(INTEGRATIONS) {
WrapIntegrations()
}
composable(EXCHANGE_RATE_OPT_IN) {
AndroidExchangeRateOptIn()
}
Expand Down Expand Up @@ -474,6 +479,7 @@ object NavigationTargets {
const val ADDRESS_BOOK = "address_book"
const val ADD_NEW_CONTACT = "add_new_contact"
const val UPDATE_CONTACT = "update_contact"
const val INTEGRATIONS = "integrations"
}

object NavigationArgs {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package co.electriccoin.zcash.ui.common.provider

import co.electriccoin.zcash.preference.InstallationPreferenceProvider
import co.electriccoin.zcash.preference.model.entry.PreferenceKey
import co.electriccoin.zcash.preference.model.entry.StringPreferenceDefault
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.security.MessageDigest
import java.util.UUID

class FlexaAccountIdProvider(
private val installationPreferenceProvider: InstallationPreferenceProvider
) {
private val preference = StringPreferenceDefault(PreferenceKey("FLEXA_ACCOUNT_ID"), "")

suspend operator fun invoke(): String =
withContext(Dispatchers.IO) {
val existing = preference.getValue(installationPreferenceProvider())

existing.ifEmpty {
val new = UUID.randomUUID().toString().toSha256()
preference.putValue(installationPreferenceProvider(), new)
new
}
}

private fun String.toSha256() =
MessageDigest.getInstance("SHA-256")
.digest(toByteArray())
.fold("") { str, value -> str + "%02x".format(value) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package co.electriccoin.zcash.ui.common.repository

import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT
import co.electriccoin.zcash.ui.common.compose.BalanceState
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
import co.electriccoin.zcash.ui.common.model.hasChangePending
import co.electriccoin.zcash.ui.common.model.hasValuePending
import co.electriccoin.zcash.ui.common.model.spendableBalance
import co.electriccoin.zcash.ui.common.model.totalBalance
import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.WhileSubscribed
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.stateIn

interface BalanceRepository {
/**
* A flow of the wallet balances state used for the UI layer. It's computed from [WalletSnapshot]'s properties
* and provides the result [BalanceState] UI state.
*/
val state: StateFlow<BalanceState>
}

class BalanceRepositoryImpl(
walletRepository: WalletRepository,
exchangeRateRepository: ExchangeRateRepository
) : BalanceRepository {
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())

override val state: StateFlow<BalanceState> =
combine(
walletRepository.walletSnapshot.filterNotNull(),
exchangeRateRepository.state,
) { snapshot, exchangeRateUsd ->
when {
// Show the loader only under these conditions:
// - Available balance is currently zero AND total balance is non-zero
// - And wallet has some ChangePending or ValuePending in progress
(
snapshot.spendableBalance().value == 0L &&
snapshot.totalBalance().value > 0L &&
(snapshot.hasChangePending() || snapshot.hasValuePending())
) -> {
BalanceState.Loading(
totalBalance = snapshot.totalBalance(),
exchangeRate = exchangeRateUsd
)
}

else -> {
BalanceState.Available(
totalBalance = snapshot.totalBalance(),
spendableBalance = snapshot.spendableBalance(),
exchangeRate = exchangeRateUsd
)
}
}
}.stateIn(
scope,
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
BalanceState.None(ExchangeRateState.OptedOut)
)
}
Loading
Loading