Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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 app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,7 @@ dependencies {
implementation project(':components:chartview')
implementation project(':feature-stacking')
implementation project(':feature-tangem')
implementation project(':feature-trezor')
implementation project(':feature-premium')
implementation project(':feature-logging')
implementation project(':feature-mini-app')
Expand Down
19 changes: 11 additions & 8 deletions app/detekt-baseline.xml

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

<queries>
<package android:name="org.telegram.messenger" />
<package android:name="io.trezor.suite" />
</queries>

<application
Expand Down Expand Up @@ -122,6 +123,18 @@
android:exported="false"
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar" />

<activity
android:name="cash.p.terminal.trezor.ui.TrezorCallbackActivity"
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="pcash" android:host="trezor-result" />
</intent-filter>
</activity>

<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ fun BlockchainType.supports(accountType: AccountType): Boolean {
this == BlockchainType.Monero

is AccountType.HardwareCard,
is AccountType.TrezorDevice,
is AccountType.Mnemonic -> true

is AccountType.HdExtendedKey -> {
Expand Down
20 changes: 20 additions & 0 deletions app/src/main/java/cash/p/terminal/core/adapters/BitcoinAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,25 @@ class BitcoinAdapter(
)
}

is AccountType.TrezorDevice -> {
val trezorSigner = buildTrezorBtcSigner(
accountId = account.id,
blockchainType = wallet.token.blockchainType,
coin = "Bitcoin"
)
return BitcoinKit(
context = App.instance,
extendedKey = requireNotNull(wallet.getHDExtendedKey()),
purpose = derivation.purpose,
walletId = account.id,
syncMode = syncMode,
networkType = NetworkType.MainNet,
confirmationsThreshold = KIT_CONFIRMATIONS_THRESHOLD,
iInputSigner = trezorSigner,
iSchnorrInputSigner = trezorSigner,
)
}

else -> throw UnsupportedAccountException()
}

Expand Down Expand Up @@ -217,6 +236,7 @@ class BitcoinAdapter(
}

is AccountType.HardwareCard,
is AccountType.TrezorDevice,
is AccountType.EvmAddress,
is AccountType.EvmPrivateKey,
is AccountType.MnemonicMonero,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import cash.p.terminal.modules.transactions.FilterTransactionType
import cash.p.terminal.modules.transactions.TransactionLockInfo
import cash.p.terminal.tangem.signer.HardwareWalletEcdaSigner
import cash.p.terminal.tangem.signer.HardwareWalletSchnorrSigner
import cash.p.terminal.trezor.domain.TrezorDeepLinkManager
import cash.p.terminal.trezor.signer.TrezorBtcSigner
import cash.p.terminal.wallet.AdapterState
import cash.p.terminal.wallet.IAdapter
import cash.p.terminal.wallet.IBalanceAdapter
Expand Down Expand Up @@ -698,6 +700,31 @@ abstract class BitcoinBaseAdapter(
hardwarePublicKey = hardwarePublicKey
)
}

@JvmStatic
protected fun buildTrezorBtcSigner(
accountId: String,
blockchainType: BlockchainType,
coin: String
): TrezorBtcSigner {
val hardwarePublicKeyStorage: IHardwarePublicKeyStorage
by inject(IHardwarePublicKeyStorage::class.java)
val trezorDeepLinkManager: TrezorDeepLinkManager
by inject(TrezorDeepLinkManager::class.java)
val hardwarePublicKey = runBlocking {
requireNotNull(
hardwarePublicKeyStorage.getKeyByBlockchain(
accountId = accountId,
blockchainType = blockchainType
)
)
}
return TrezorBtcSigner(
coin = coin,
derivationPath = hardwarePublicKey.derivationPath,
deepLinkManager = trezorDeepLinkManager
)
}
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,24 @@ class BitcoinCashAdapter(
)
}

is AccountType.TrezorDevice -> {
val trezorSigner = buildTrezorBtcSigner(
accountId = account.id,
blockchainType = wallet.token.blockchainType,
coin = "Bcash"
)
return BitcoinCashKit(
context = App.instance,
extendedKey = requireNotNull(wallet.getHDExtendedKey()),
walletId = account.id,
syncMode = syncMode,
networkType = networkType,
confirmationsThreshold = KIT_CONFIRMATIONS_THRESHOLD,
iInputSigner = trezorSigner,
iSchnorrInputSigner = trezorSigner,
)
}

else -> throw UnsupportedAccountException()
}

Expand Down Expand Up @@ -208,6 +226,7 @@ class BitcoinCashAdapter(
is AccountType.EvmAddress,
is AccountType.EvmPrivateKey,
is AccountType.HardwareCard,
is AccountType.TrezorDevice,
is AccountType.MnemonicMonero,
is AccountType.SolanaAddress,
is AccountType.TonAddress,
Expand Down
20 changes: 20 additions & 0 deletions app/src/main/java/cash/p/terminal/core/adapters/DashAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,26 @@ class DashAdapter(
)
}

is AccountType.TrezorDevice -> {
val trezorSigner = buildTrezorBtcSigner(
accountId = account.id,
blockchainType = wallet.token.blockchainType,
coin = "Dash"
)
DashKit(
context = App.instance,
extendedKey = requireNotNull(wallet.getHDExtendedKey()),
walletId = account.id,
syncMode = syncMode,
networkType = NetworkType.MainNet,
confirmationsThreshold = KIT_CONFIRMATIONS_THRESHOLD,
initWithEmptySeeds = true,
iInputSigner = trezorSigner,
iSchnorrInputSigner = trezorSigner,
minConnectedPeerSize = MIN_CONNECTED_PEER_TO_SEND_SIZE
)
}

else -> throw UnsupportedAccountException()
}.apply {
setupPeers(masterNodesRepository, userPeers)
Expand Down
18 changes: 18 additions & 0 deletions app/src/main/java/cash/p/terminal/core/adapters/DogecoinAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,24 @@ class DogecoinAdapter(
)
}

is AccountType.TrezorDevice -> {
val trezorSigner = buildTrezorBtcSigner(
accountId = account.id,
blockchainType = wallet.token.blockchainType,
coin = "Dogecoin"
)
return DogecoinKit(
context = App.instance,
extendedKey = requireNotNull(wallet.getHDExtendedKey()),
walletId = account.id,
syncMode = syncMode,
networkType = DogecoinKit.NetworkType.MainNet,
confirmationsThreshold = KIT_CONFIRMATIONS_THRESHOLD,
iInputSigner = trezorSigner,
iSchnorrInputSigner = trezorSigner,
)
}

else -> throw UnsupportedAccountException()
}
}
Expand Down
19 changes: 19 additions & 0 deletions app/src/main/java/cash/p/terminal/core/adapters/LitecoinAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,24 @@ class LitecoinAdapter(
iSchnorrInputSigner = hardwareWalletSchnorrSigner
)
}
is AccountType.TrezorDevice -> {
val trezorSigner = buildTrezorBtcSigner(
accountId = account.id,
blockchainType = wallet.token.blockchainType,
coin = "Litecoin"
)
return LitecoinKit(
context = App.instance,
extendedKey = requireNotNull(wallet.getHDExtendedKey()),
purpose = derivation.purpose,
walletId = account.id,
syncMode = syncMode,
networkType = NetworkType.MainNet,
confirmationsThreshold = KIT_CONFIRMATIONS_THRESHOLD,
iInputSigner = trezorSigner,
iSchnorrInputSigner = trezorSigner,
)
}
else -> throw UnsupportedAccountException()
}
}
Expand Down Expand Up @@ -198,6 +216,7 @@ class LitecoinAdapter(
is AccountType.EvmAddress,
is AccountType.EvmPrivateKey,
is AccountType.HardwareCard,
is AccountType.TrezorDevice,
is AccountType.MnemonicMonero,
is AccountType.SolanaAddress,
is AccountType.TonAddress,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.reactive.asFlow
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.koin.java.KoinJavaComponent.inject
Expand Down Expand Up @@ -131,6 +132,7 @@ class ZcashAdapter(
private val accountType =
(wallet.account.type as? AccountType.Mnemonic)
?: (wallet.account.type as? AccountType.ZCashUfvKey)
?: (wallet.account.type as? AccountType.TrezorDevice)
?: throw UnsupportedAccountException()

private val seed = (accountType as? AccountType.Mnemonic)?.seed ?: ByteArray(0)
Expand All @@ -146,7 +148,8 @@ class ZcashAdapter(
private val scope = CoroutineScope(Dispatchers.IO)

private var balanceCheckJob: Job? = null
private val balanceCheckMutex = kotlinx.coroutines.sync.Mutex()
private val balanceCheckMutex = Mutex()
private var importUfvkError : Throwable? = null

init {
Timber.i("ZcashAdapter type $addressSpecTyped")
Expand All @@ -159,7 +162,7 @@ class ZcashAdapter(
}

init {
val walletInitMode = if (existingWallet || isWatchOnlyAccount()) {
val walletInitMode = if (existingWallet || requiresUfvkImport()) {
WalletInitMode.ExistingWallet
} else when (wallet.account.origin) {
AccountOrigin.Created -> WalletInitMode.NewWallet
Expand All @@ -184,7 +187,7 @@ class ZcashAdapter(
accountBirthday = it
}

val setup = if (!isWatchOnlyAccount()) {
val setup = if (!requiresUfvkImport()) {
AccountCreateSetup(
seed = FirstClassByteArray(seed),
accountName = wallet.account.name,
Expand All @@ -209,11 +212,7 @@ class ZcashAdapter(
isExchangeRateEnabled = false
)

if (isWatchOnlyAccount()) {
runBlocking {
importWatchAccount()
}
}
runBlocking { importWatchAccountIfNeeded() }

zcashAccount = runBlocking { tryOrNull { getFirstAccount() } }
receiveAddress = runBlocking { getReceiveAddressOrEmpty() }
Expand Down Expand Up @@ -241,14 +240,23 @@ class ZcashAdapter(
}
}

private fun isWatchOnlyAccount(): Boolean {
private fun requiresUfvkImport(): Boolean {
return wallet.account.type is AccountType.ZCashUfvKey
|| wallet.account.type is AccountType.TrezorDevice
}

private fun getWatchOnlyUfvk(): String? {
return when (wallet.account.type) {
is AccountType.ZCashUfvKey -> (wallet.account.type as AccountType.ZCashUfvKey).key
is AccountType.TrezorDevice -> wallet.hardwarePublicKey?.key?.value
else -> null
}
}

private suspend fun importWatchAccount() {
private suspend fun importWatchAccountIfNeeded() {
if (!requiresUfvkImport()) return
val key = getWatchOnlyUfvk() ?: return
try {
val key = (wallet.account.type as? AccountType.ZCashUfvKey)?.key
?: return
(synchronizer as Synchronizer).importAccountByUfvk(
AccountImportSetup(
accountName = wallet.account.name,
Expand All @@ -257,8 +265,9 @@ class ZcashAdapter(
ufvk = UnifiedFullViewingKey(key)
)
)
} catch (ex: Exception) {
Timber.e(ex, "Failed to import watch-only ZCash account")
} catch (e: Exception) {
Timber.e(e, "Failed to import watch-only ZCash account")
importUfvkError = e
}
}

Expand Down Expand Up @@ -345,7 +354,7 @@ class ZcashAdapter(
accountBirthday = it
}
try {
val setup = if (!isWatchOnlyAccount()) {
val setup = if (!requiresUfvkImport()) {
AccountCreateSetup(
seed = FirstClassByteArray(seed),
accountName = wallet.account.name,
Expand All @@ -369,9 +378,7 @@ class ZcashAdapter(
isExchangeRateEnabled = false
)

if (isWatchOnlyAccount()) {
importWatchAccount()
}
importWatchAccountIfNeeded()

} catch (ex: Exception) {
// To prevent crash with synchronizer creation in some situations
Expand Down Expand Up @@ -407,6 +414,10 @@ class ZcashAdapter(
}

override fun start() {
importUfvkError?.let {
syncState = AdapterState.NotSynced(it)
return
}
startJob?.cancel()
startJob = scope.launch {
try {
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/cash/p/terminal/core/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import cash.p.terminal.featureStacking.di.featureStackingModule
import cash.p.terminal.network.di.networkModule
import cash.p.terminal.premium.di.featurePremiumModule
import cash.p.terminal.tangem.di.featureTangemModule
import cash.p.terminal.trezor.di.featureTrezorModule
import cash.p.terminal.wallet.di.walletFeatureModule
import org.koin.dsl.module

Expand All @@ -23,6 +24,7 @@ val appModule = module {
walletFeatureModule,
featureStackingModule,
featureTangemModule,
featureTrezorModule,
networkModule,
swapProvidersModule,
contractValidatorModule,
Expand Down
Loading
Loading