Updated on 2026-08-14
This commit is contained in:
commit
5fa587d8cc
635 changed files with 18802 additions and 9118 deletions
12
.gitignore
vendored
12
.gitignore
vendored
|
|
@ -3,12 +3,24 @@
|
|||
# Built application files
|
||||
/build
|
||||
/buildSrc
|
||||
**/build/.transforms/**
|
||||
**/build/classes/**
|
||||
**/build/generated/**
|
||||
**/build/intermediates/**
|
||||
**/build/kotlin/**
|
||||
**/build/libs/**
|
||||
**/build/outputs/**
|
||||
**/build/tmp/**
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Google services
|
||||
|
||||
|
||||
# Huawei services
|
||||
app/agconnect-services.json
|
||||
|
||||
app/src/debug/google-services.json
|
||||
app/src/internal/google-services.json
|
||||
app/src/external/google-services.json
|
||||
|
|
|
|||
|
|
@ -11,9 +11,14 @@ plugins {
|
|||
alias(deps.plugins.firebase.crashlytics)
|
||||
alias(deps.plugins.firebase.perf)
|
||||
alias(deps.plugins.ksp)
|
||||
id(deps.plugins.agconnect.get().pluginId)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
agcp {
|
||||
manifest = false
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.wallet"
|
||||
testOptions {
|
||||
|
|
@ -53,12 +58,35 @@ android {
|
|||
keyPassword = keystoreProperties["key_password"] as String
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions += "services"
|
||||
|
||||
productFlavors {
|
||||
create("google") {
|
||||
dimension = "services"
|
||||
buildConfigField("String", "FLAVOR_NAME", "\"google\"")
|
||||
}
|
||||
create("huawei") {
|
||||
dimension = "services"
|
||||
buildConfigField("String", "FLAVOR_NAME", "\"huawei\"")
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
buildConfigField("String", "BUILD_TYPE", "\"debug\"")
|
||||
}
|
||||
release {
|
||||
buildConfigField("String", "BUILD_TYPE", "\"release\"")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
exclude(group = "org.bouncycastle", module = "bcprov-jdk15to18")
|
||||
exclude(group = "com.github.komputing.kethereum")
|
||||
exclude(group = "com.android.tools.build", module = "gradle")
|
||||
|
||||
resolutionStrategy {
|
||||
dependencySubstitution {
|
||||
|
|
@ -121,7 +149,6 @@ dependencies {
|
|||
implementation(projects.domain.quotes)
|
||||
implementation(projects.domain.notifications)
|
||||
implementation(projects.domain.notifications.models)
|
||||
implementation(projects.domain.notifications.toggles)
|
||||
implementation(projects.domain.swap.models)
|
||||
implementation(projects.domain.swap)
|
||||
implementation(projects.domain.walletManager)
|
||||
|
|
@ -243,6 +270,8 @@ dependencies {
|
|||
implementation(projects.features.tangempay.details.impl)
|
||||
implementation(projects.features.tangempay.main.api)
|
||||
implementation(projects.features.tangempay.main.impl)
|
||||
implementation(projects.features.tangempay.onboarding.api)
|
||||
implementation(projects.features.tangempay.onboarding.impl)
|
||||
implementation(projects.features.tokenRecieve.api)
|
||||
implementation(projects.features.tokenRecieve.impl)
|
||||
|
||||
|
|
@ -318,7 +347,6 @@ dependencies {
|
|||
implementation(deps.coil.gif)
|
||||
implementation(deps.coil.svg)
|
||||
implementation(deps.amplitude)
|
||||
implementation(deps.kotsonGson)
|
||||
implementation(deps.spongecastle.core)
|
||||
implementation(deps.lottie)
|
||||
implementation(deps.compose.accompanist.appCompatTheme)
|
||||
|
|
@ -379,4 +407,9 @@ dependencies {
|
|||
// excludes version 9999.0-empty-to-avoid-conflict-with-guava
|
||||
exclude(group = "com.google.guava", module = "listenablefuture")
|
||||
}
|
||||
|
||||
/** Huawei flavor-specific dependencies */
|
||||
"huaweiImplementation"(deps.huawei.push)
|
||||
"huaweiImplementation"(deps.agconnect.agcp)
|
||||
"huaweiImplementation"(deps.agconnect.core)
|
||||
}
|
||||
7
app/proguard-rules.pro
vendored
7
app/proguard-rules.pro
vendored
|
|
@ -7,6 +7,13 @@
|
|||
-keep class com.google.android.gms.internal.** { *; }
|
||||
-keepclasseswithmembers class com.google.firebase.FirebaseException
|
||||
|
||||
# huawei push kit
|
||||
-ignorewarnings
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
-keep class com.huawei.hianalytics.**{*;}
|
||||
-keep class com.huawei.updatesdk.**{*;}
|
||||
-keep class com.huawei.hms.**{*;}
|
||||
|
||||
# hedera sdk
|
||||
-keep class com.hedera.hashgraph.sdk.** { *; }
|
||||
-keep interface com.hedera.hashgraph.sdk.** { *; }
|
||||
|
|
|
|||
|
|
@ -14,9 +14,13 @@ import com.kaspersky.kaspresso.kaspresso.Kaspresso
|
|||
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
|
||||
import com.tangem.common.allure.FailedStepScreenshotInterceptor
|
||||
import com.tangem.common.rules.ApiEnvironmentRule
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.feature.MutableFeatureTogglesManager
|
||||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.domain.promo.PromoRepository
|
||||
import com.tangem.domain.promo.models.PromoId
|
||||
import com.tangem.tap.MainActivity
|
||||
import dagger.hilt.android.testing.HiltAndroidRule
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
|
@ -46,6 +50,12 @@ abstract class BaseTestCase : TestCase(
|
|||
@Inject
|
||||
lateinit var appPreferencesStore: AppPreferencesStore
|
||||
|
||||
@Inject
|
||||
lateinit var featureTogglesManager: FeatureTogglesManager
|
||||
|
||||
@Inject
|
||||
lateinit var promoRepository: PromoRepository
|
||||
|
||||
private val hiltRule = HiltAndroidRule(this)
|
||||
private val apiEnvironmentRule = ApiEnvironmentRule()
|
||||
private val permissionRule = GrantPermissionRule.grant(
|
||||
|
|
@ -86,10 +96,12 @@ abstract class BaseTestCase : TestCase(
|
|||
value = false
|
||||
)
|
||||
}
|
||||
promoRepository.setNeverToShowWalletPromo(PromoId.Sepa)
|
||||
}
|
||||
apiEnvironmentRule.setup(apiConfigsManager)
|
||||
ActivityScenario.launch(MainActivity::class.java)
|
||||
Intents.init()
|
||||
setFeatureToggles()
|
||||
additionalBeforeSection()
|
||||
}.after {
|
||||
additionalAfterSection()
|
||||
|
|
@ -113,4 +125,18 @@ abstract class BaseTestCase : TestCase(
|
|||
{
|
||||
composeTestRule.onRoot(useUnmergedTree = useUnmergedTree).printToLog(tag, maxDepth)
|
||||
}
|
||||
|
||||
|
||||
private fun setFeatureToggles() {
|
||||
runBlocking {
|
||||
with(featureTogglesManager as MutableFeatureTogglesManager) {
|
||||
changeToggle("WALLET_CONNECT_REDESIGN_ENABLED", true)
|
||||
changeToggle("WALLET_BALANCE_FETCHER_ENABLED", true)
|
||||
changeToggle("SEND_VIA_SWAP_ENABLED", true)
|
||||
changeToggle("SWAP_REDESIGN_ENABLED", true)
|
||||
changeToggle("SEND_REDESIGN_ENABLED", true)
|
||||
changeToggle("NEW_ONRAMP_MAIN_ENABLED", true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,5 +3,7 @@ package com.tangem.common.constants
|
|||
object TestConstants {
|
||||
const val TOTAL_BALANCE = "$3,299.18"
|
||||
|
||||
const val RECIPIENT_ADDRESS = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0"
|
||||
|
||||
const val WAIT_UNTIL_TIMEOUT = 20_000L
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
package com.tangem.common.utils
|
||||
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.json.JSONObject
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
fun getWcUri(
|
||||
network: String = "ethereum",
|
||||
baseUrl: String = "[REDACTED_ENV_URL]"
|
||||
): String? {
|
||||
Timber.i("Getting WC URI for network: $network")
|
||||
|
||||
val client = OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS) // Таймаут подключения
|
||||
.readTimeout(60, TimeUnit.SECONDS) // Таймаут чтения ответа
|
||||
.writeTimeout(30, TimeUnit.SECONDS) // Таймаут записи
|
||||
.callTimeout(90, TimeUnit.SECONDS) // Общий таймаут запроса
|
||||
.build()
|
||||
|
||||
val request = Request.Builder()
|
||||
.url("$baseUrl/wc_uri?network=$network")
|
||||
.get()
|
||||
.build()
|
||||
|
||||
return try {
|
||||
client.newCall(request).execute().use { response ->
|
||||
Timber.i("Response code: ${response.code}")
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val body = response.body?.string() ?: ""
|
||||
Timber.i("Response body: $body")
|
||||
|
||||
val jsonObject = JSONObject(body)
|
||||
|
||||
if (jsonObject.getBoolean("success")) {
|
||||
val wcUri = jsonObject.getString("wcUri")
|
||||
Timber.i("Got WC URI successfully: $wcUri")
|
||||
|
||||
wcUri
|
||||
} else {
|
||||
Timber.e("API returned error: ${jsonObject.optString("error", "Unknown")}")
|
||||
null
|
||||
}
|
||||
} else {
|
||||
val errorBody = response.body?.string() ?: "No error body"
|
||||
Timber.e("Request failed: ${response.code}, body: $errorBody")
|
||||
null
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error getting WC URI")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun checkServiceHealth(
|
||||
baseUrl: String = "[REDACTED_ENV_URL]"
|
||||
): String? {
|
||||
Timber.i("Checking service health")
|
||||
|
||||
val client = OkHttpClient()
|
||||
val request = Request.Builder()
|
||||
.url("$baseUrl/health")
|
||||
.get()
|
||||
.build()
|
||||
|
||||
return try {
|
||||
client.newCall(request).execute().use { response ->
|
||||
Timber.i("Response code: ${response.code}")
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val body = response.body?.string() ?: ""
|
||||
Timber.i("Response body: $body")
|
||||
|
||||
if (body.isEmpty()) {
|
||||
Timber.e("Response body is empty")
|
||||
return null
|
||||
}
|
||||
|
||||
val jsonObject = JSONObject(body)
|
||||
val status = jsonObject.optString("status", "")
|
||||
|
||||
if (status.isNotEmpty()) {
|
||||
Timber.i("Got status successfully: $status")
|
||||
status
|
||||
} else {
|
||||
Timber.e("Status field is missing or empty")
|
||||
null
|
||||
}
|
||||
} else {
|
||||
val errorBody = response.body?.string() ?: "No error body"
|
||||
Timber.e("Request failed: ${response.code}, body: $errorBody")
|
||||
null
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error checking health")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package com.tangem.scenarios
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.screens.*
|
||||
import com.tangem.screens.onDisclaimerScreen
|
||||
import com.tangem.screens.onMainScreen
|
||||
import com.tangem.screens.onMarketsTooltipScreen
|
||||
import com.tangem.screens.onStoriesScreen
|
||||
import com.tangem.tap.domain.sdk.mocks.MockContent
|
||||
import com.tangem.tap.domain.sdk.mocks.MockProvider
|
||||
import io.qameta.allure.kotlin.Allure.step
|
||||
|
||||
fun BaseTestCase.openMainScreen(
|
||||
productType: ProductType? = null,
|
||||
mockContent: MockContent? = null,
|
||||
alreadyActivatedDialogIsShown : Boolean = false
|
||||
) {
|
||||
if (productType != null) {
|
||||
MockProvider.setMocks(productType)
|
||||
}
|
||||
if (mockContent != null) {
|
||||
MockProvider.setMocks(mockContent)
|
||||
}
|
||||
step("Click on 'Accept' button") {
|
||||
onDisclaimerScreen { acceptButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Scan' button") {
|
||||
onStoriesScreen { scanButton.clickWithAssertion() }
|
||||
}
|
||||
if (alreadyActivatedDialogIsShown) {
|
||||
step("Click on 'This is my wallet' button") {
|
||||
AlreadyUsedWalletDialogPageObject { thisIsMyWalletButton.click() }
|
||||
}
|
||||
}
|
||||
step("Assert 'Main' screen is displayed") {
|
||||
onMainScreen { screenContainer.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Market Tooltip' screen") {
|
||||
onMarketsTooltipScreen { contentContainer.clickWithAssertion() }
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.synchronizeAddresses(balance: String) {
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = '$balance'") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.openDeviceSettingsScreen() {
|
||||
step("Open wallet details") {
|
||||
onTopBar { moreButton.clickWithAssertion() }
|
||||
}
|
||||
step("Open 'Wallet settings' screen") {
|
||||
onDetailsScreen { walletNameButton.performClick() }
|
||||
}
|
||||
step("Click on 'Device settings' button") {
|
||||
onWalletSettingsScreen { deviceSettingsButton.clickWithAssertion() }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.scenarios
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.Intent.*
|
||||
import android.net.Uri
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import io.github.kakaocup.kakao.intent.KIntent
|
||||
|
||||
fun openAppByDeepLink(deepLinkUri: String?) {
|
||||
val deeplinkScheme = "tangem://wc?uri="
|
||||
val context = ApplicationProvider.getApplicationContext<android.content.Context>()
|
||||
val intent = Intent(ACTION_VIEW, Uri.parse(deeplinkScheme + deepLinkUri)).apply {
|
||||
addFlags(FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
fun checkSendEMailIntentCalled() {
|
||||
val expectedIntent = KIntent {
|
||||
hasAction(ACTION_CHOOSER)
|
||||
hasExtra(EXTRA_TITLE, "Send mail...")
|
||||
hasExtraWithKey(EXTRA_INTENT)
|
||||
}
|
||||
expectedIntent.intended()
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.scenarios
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.screens.onDeviceSettingsScreen
|
||||
import io.qameta.allure.kotlin.Allure.step
|
||||
|
||||
fun BaseTestCase.openResetCardScreen(withBackup: Boolean = false) {
|
||||
step("Click on 'Scan card or ring' button") {
|
||||
onDeviceSettingsScreen { scanCardOrRingButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Reset to Factory Settings' button title is displayed") {
|
||||
onDeviceSettingsScreen { resetToFactorySettingsButtonTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Reset to Factory Settings' button subtitle is displayed") {
|
||||
onDeviceSettingsScreen { resetToFactorySettingsButtonSubtitle(withBackup).assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Reset to Factory Settings' button") {
|
||||
onDeviceSettingsScreen { resetToFactorySettingsButtonTitle.performClick() }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package com.tangem.scenarios
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.screens.AlreadyUsedWalletDialogPageObject
|
||||
import com.tangem.screens.ScanWarningDialogPageObject
|
||||
import com.tangem.screens.onFailedTransactionDialog
|
||||
import io.qameta.allure.kotlin.Allure.step
|
||||
|
||||
fun BaseTestCase.checkFailedTransactionDialog() {
|
||||
step("Assert failed transaction dialog title is displayed") {
|
||||
onFailedTransactionDialog { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert failed transaction dialog text is displayed") {
|
||||
onFailedTransactionDialog { text.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Cancel' button is displayed") {
|
||||
onFailedTransactionDialog { cancelButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Support' button is displayed") {
|
||||
onFailedTransactionDialog { supportButton.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
|
||||
fun checkScanWarningDialog() {
|
||||
step("Assert 'Scan warning' dialog title is displayed") {
|
||||
ScanWarningDialogPageObject { warningTitle.isDisplayed() }
|
||||
}
|
||||
step("Assert warning dialog message is displayed") {
|
||||
ScanWarningDialogPageObject { warningMessage.isDisplayed() }
|
||||
}
|
||||
step("Assert 'Cancel' button is displayed") {
|
||||
ScanWarningDialogPageObject { cancelButton.isDisplayed() }
|
||||
}
|
||||
step("Assert 'How to scan' button is displayed") {
|
||||
ScanWarningDialogPageObject { howToScanButton.isDisplayed() }
|
||||
}
|
||||
step("Assert 'Request support' button is displayed") {
|
||||
ScanWarningDialogPageObject { requestSupportButton.isDisplayed() }
|
||||
}
|
||||
}
|
||||
|
||||
fun checkAlreadyUsedWalletDialog() {
|
||||
step("Assert 'Already used Wallet' dialog title is displayed") {
|
||||
AlreadyUsedWalletDialogPageObject { title.isDisplayed() }
|
||||
}
|
||||
step("Assert dialog message is displayed") {
|
||||
AlreadyUsedWalletDialogPageObject { message.isDisplayed() }
|
||||
}
|
||||
step("Assert 'This is my wallet' button is displayed") {
|
||||
AlreadyUsedWalletDialogPageObject { message.isDisplayed() }
|
||||
}
|
||||
step("Assert 'Cancel' button is displayed") {
|
||||
AlreadyUsedWalletDialogPageObject { cancelButton.isDisplayed() }
|
||||
}
|
||||
step("Assert 'Request support' button is displayed") {
|
||||
AlreadyUsedWalletDialogPageObject { requestSupportButton.isDisplayed() }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
package com.tangem.scenarios
|
||||
|
||||
import androidx.compose.ui.test.junit4.ComposeTestRule
|
||||
import com.kaspersky.kaspresso.testcases.api.scenario.Scenario
|
||||
import com.kaspersky.kaspresso.testcases.core.testcontext.TestContext
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.screens.*
|
||||
import com.tangem.tap.domain.sdk.mocks.MockProvider
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
|
||||
class OpenMainScreenScenario(
|
||||
private val testRule: ComposeTestRule,
|
||||
private val productType: ProductType? = null,
|
||||
) : Scenario() {
|
||||
override val steps: TestContext<Unit>.() -> Unit = {
|
||||
if (productType != null) {
|
||||
MockProvider.setMocks(productType)
|
||||
}
|
||||
ComposeScreen.onComposeScreen<DisclaimerPageObject>(testRule) {
|
||||
step("Click on \"Accept\" button") {
|
||||
acceptButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
ComposeScreen.onComposeScreen<StoriesPageObject>(testRule) {
|
||||
step("Click on \"Scan\" button") {
|
||||
scanButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
ComposeScreen.onComposeScreen<MainScreenPageObject>(testRule) {
|
||||
step("Make sure wallet screen is visible") {
|
||||
assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
ComposeScreen.onComposeScreen<MarketsTooltipPageObject>(testRule) {
|
||||
step("Close Markets tooltip"){
|
||||
contentContainer.performClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package com.tangem.scenarios
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.screens.onResetCardScreen
|
||||
import io.qameta.allure.kotlin.Allure.step
|
||||
|
||||
fun BaseTestCase.checkResetCardScreen(withBackup: Boolean = false) {
|
||||
step("Assert title is displayed") {
|
||||
onResetCardScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Attention' image is displayed") {
|
||||
onResetCardScreen { attentionImage.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Attention' subtitle is displayed") {
|
||||
onResetCardScreen { subtitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert description is displayed") {
|
||||
onResetCardScreen { description.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Lost wallet' checkbox is displayed") {
|
||||
onResetCardScreen { lostWalletAccessCheckBox.assertIsDisplayed() }
|
||||
}
|
||||
if (withBackup) {
|
||||
step("Assert 'Lost password restore' checkbox is displayed") {
|
||||
onResetCardScreen { lostPasswordRestoreCheckBox.assertIsDisplayed() }
|
||||
}
|
||||
} else {
|
||||
step("Assert 'Lost password restore' checkbox doesn't exist") {
|
||||
onResetCardScreen { lostPasswordRestoreCheckBox.assertDoesNotExist() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.checkCheckBoxLogic(withBackup: Boolean = false) {
|
||||
if (withBackup) {
|
||||
step("Click on 'Lost wallet' checkbox") {
|
||||
onResetCardScreen { lostWalletAccessCheckBox.performClick() }
|
||||
}
|
||||
step("Assert 'Lost wallet' checkbox is enabled") {
|
||||
onResetCardScreen { lostWalletAccessCheckBox.assertIsEnabled() }
|
||||
}
|
||||
step("Assert 'Reset the card' button is disabled") {
|
||||
onResetCardScreen { resetCardButton.assertIsNotEnabled() }
|
||||
}
|
||||
step("Click on 'Lost password restore' checkbox") {
|
||||
onResetCardScreen { lostPasswordRestoreCheckBox.performClick() }
|
||||
}
|
||||
step("Assert 'Lost password restore' checkbox is enabled") {
|
||||
onResetCardScreen { lostPasswordRestoreCheckBox.assertIsEnabled() }
|
||||
}
|
||||
step("Assert 'Reset the card' button is enabled") {
|
||||
onResetCardScreen { resetCardButton.assertIsEnabled() }
|
||||
}
|
||||
step("Click on 'Lost password restore' checkbox") {
|
||||
onResetCardScreen { lostPasswordRestoreCheckBox.performClick() }
|
||||
}
|
||||
step("Assert 'Reset the card' button is disabled") {
|
||||
onResetCardScreen { resetCardButton.assertIsNotEnabled() }
|
||||
}
|
||||
step("Click on 'Lost wallet' checkbox") {
|
||||
onResetCardScreen { lostWalletAccessCheckBox.performClick() }
|
||||
}
|
||||
step("Click on 'Lost password restore' checkbox") {
|
||||
onResetCardScreen { lostPasswordRestoreCheckBox.performClick() }
|
||||
}
|
||||
step("Assert 'Reset the card' button is disabled") {
|
||||
onResetCardScreen { resetCardButton.assertIsNotEnabled() }
|
||||
}
|
||||
} else {
|
||||
step("Click on 'Lost wallet' checkbox") {
|
||||
onResetCardScreen { lostWalletAccessCheckBox.performClick() }
|
||||
}
|
||||
step("Assert 'Lost wallet' checkbox is enabled") {
|
||||
onResetCardScreen { lostWalletAccessCheckBox.assertIsEnabled() }
|
||||
}
|
||||
step("Assert 'Reset the card' button is enabled") {
|
||||
onResetCardScreen { resetCardButton.assertIsEnabled() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
package com.tangem.scenarios
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.screens.onWalletConnectBottomSheet
|
||||
import com.tangem.screens.onWalletConnectDetailsBottomSheet
|
||||
import com.tangem.screens.onWalletConnectScreen
|
||||
import io.qameta.allure.kotlin.Allure.step
|
||||
|
||||
fun BaseTestCase.checkWalletConnectBottomSheet() {
|
||||
step("Assert 'Wallet Connect' bottom sheet title is displayed") {
|
||||
onWalletConnectBottomSheet { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Wallet Connect' bottom sheet app icon is displayed") {
|
||||
onWalletConnectBottomSheet { appIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Wallet Connect' bottom sheet app name is displayed") {
|
||||
onWalletConnectBottomSheet { appName.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Wallet Connect' bottom sheet approve icon is displayed") {
|
||||
onWalletConnectBottomSheet { approveIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Wallet Connect' bottom sheet app URL is displayed") {
|
||||
onWalletConnectBottomSheet { appUrl.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Wallet Connect' bottom sheet connection request icon is displayed") {
|
||||
onWalletConnectBottomSheet { connectionRequestIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Wallet Connect' bottom sheet connection request text is displayed") {
|
||||
onWalletConnectBottomSheet { connectionRequestText.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Wallet Connect' bottom sheet connection request chevron is displayed") {
|
||||
onWalletConnectBottomSheet { connectionRequestChevron.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Wallet Connect' bottom sheet wallet icon is displayed") {
|
||||
onWalletConnectBottomSheet { walletIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Wallet Connect' bottom sheet wallet title is displayed") {
|
||||
onWalletConnectBottomSheet { walletNameTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Wallet Connect' bottom sheet wallet name is displayed") {
|
||||
onWalletConnectBottomSheet { walletName.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Wallet Connect' bottom sheet networks icon is displayed") {
|
||||
onWalletConnectBottomSheet { networksIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Wallet Connect' bottom sheet networks title is displayed") {
|
||||
onWalletConnectBottomSheet { networksTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Wallet Connect' bottom sheet right networks icons is displayed") {
|
||||
onWalletConnectBottomSheet { networksIcons.assertIsDisplayed() }
|
||||
}
|
||||
step("'Wallet Connect' bottom sheet networks selector icon is displayed") {
|
||||
onWalletConnectBottomSheet { networksSelectorIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Wallet Connect' bottom sheet 'Cancel' button is displayed") {
|
||||
onWalletConnectBottomSheet { cancelButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Wallet Connect' bottom sheet 'Connect' button is displayed") {
|
||||
onWalletConnectBottomSheet { connectButton.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.checkWalletConnectScreen() {
|
||||
step("Assert 'Wallet Connect' title is displayed") {
|
||||
onWalletConnectScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'More' button is displayed") {
|
||||
onWalletConnectScreen { moreButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert wallet name is displayed") {
|
||||
onWalletConnectScreen { walletName.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert app icon is displayed") {
|
||||
onWalletConnectScreen { appIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert app name is displayed") {
|
||||
onWalletConnectScreen { appName.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert approve icon is displayed") {
|
||||
onWalletConnectScreen { approveIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert app URL is displayed") {
|
||||
onWalletConnectScreen { appUrl.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'New Connection' button is displayed") {
|
||||
onWalletConnectScreen { newConnectionButton.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.checkWalletConnectDetailsBottomSheet(dAppName: String) {
|
||||
step("Assert connection details title is displayed") {
|
||||
onWalletConnectDetailsBottomSheet { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert date is displayed") {
|
||||
onWalletConnectDetailsBottomSheet { date.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Close' button is displayed") {
|
||||
onWalletConnectDetailsBottomSheet { closeButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert app icon is displayed") {
|
||||
onWalletConnectDetailsBottomSheet { appIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert app name is displayed") {
|
||||
onWalletConnectDetailsBottomSheet { appName.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert approve icon is displayed") {
|
||||
onWalletConnectDetailsBottomSheet { approveIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert app URL is displayed") {
|
||||
onWalletConnectDetailsBottomSheet { appUrl.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert wallet icon is displayed") {
|
||||
onWalletConnectDetailsBottomSheet { walletIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert wallet title is displayed") {
|
||||
onWalletConnectDetailsBottomSheet { walletTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert wallet name is displayed") {
|
||||
onWalletConnectDetailsBottomSheet { walletName.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Connected networks' title is displayed") {
|
||||
onWalletConnectDetailsBottomSheet { connectedNetworksTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert connected network item is displayed") {
|
||||
onWalletConnectDetailsBottomSheet { connectedNetworkItem.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert connected network icon is displayed") {
|
||||
onWalletConnectDetailsBottomSheet { connectedNetworkIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert connected dApp name: '$dAppName'") {
|
||||
onWalletConnectDetailsBottomSheet { connectedNetworkName.assertTextContains(dAppName) }
|
||||
}
|
||||
step("Assert connected network symbol is displayed") {
|
||||
onWalletConnectDetailsBottomSheet { connectedNetworkSymbol.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Disconnect button' is displayed") {
|
||||
onWalletConnectDetailsBottomSheet { disconnectButton.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import com.kaspersky.kaspresso.screens.KScreen
|
||||
import com.tangem.wallet.R
|
||||
import io.github.kakaocup.kakao.text.KTextView
|
||||
import io.github.kakaocup.kakao.text.KButton
|
||||
|
||||
object AlreadyUsedWalletDialogPageObject : KScreen<AlreadyUsedWalletDialogPageObject>() {
|
||||
|
||||
override val layoutId: Int? = null
|
||||
override val viewClass: Class<*>? = null
|
||||
|
||||
val title = KTextView {
|
||||
withText(R.string.security_alert_title)
|
||||
}
|
||||
|
||||
val message = KTextView {
|
||||
withText(R.string.wallet_been_activated_message)
|
||||
}
|
||||
|
||||
val cancelButton = KButton {
|
||||
withText(R.string.common_cancel)
|
||||
}
|
||||
|
||||
val requestSupportButton = KButton {
|
||||
withText(R.string.alert_button_request_support)
|
||||
}
|
||||
|
||||
val thisIsMyWalletButton = KButton {
|
||||
withText(R.string.this_is_my_wallet_title)
|
||||
}
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ class BuyTokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProv
|
|||
}
|
||||
|
||||
val errorNotificationText: KNode = child {
|
||||
hasTestTag(NotificationTestTags.TEXT)
|
||||
hasTestTag(NotificationTestTags.MESSAGE)
|
||||
hasText(getResourceString(R.string.common_unknown_error))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsMatcher
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.utils.LazyListItemNode
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.DeviceSettingsScreenTestTags
|
||||
import com.tangem.core.ui.utils.LazyListItemPositionSemantics
|
||||
import com.tangem.wallet.R
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
|
||||
class DeviceSettingsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<DeviceSettingsPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
private val lazyList = KLazyListNode(
|
||||
semanticsProvider = semanticsProvider,
|
||||
viewBuilderAction = { hasTestTag(DeviceSettingsScreenTestTags.LAZY_LIST) },
|
||||
itemTypeBuilder = { itemType(::LazyListItemNode) },
|
||||
positionMatcher = { position ->
|
||||
SemanticsMatcher.expectValue(
|
||||
LazyListItemPositionSemantics,
|
||||
position
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
val imageBlock: KNode = child {
|
||||
hasTestTag(DeviceSettingsScreenTestTags.IMAGE_BLOCK)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val resetToFactorySettingsButtonTitle: KNode = child {
|
||||
hasTestTag(DeviceSettingsScreenTestTags.ITEM_TITLE)
|
||||
hasText(getResourceString(R.string.card_settings_reset_card_to_factory))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val scanCardOrRingButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
hasText(getResourceString(R.string.scan_card_settings_button))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
fun resetToFactorySettingsButtonSubtitle(withBackup: Boolean = false): KNode = child {
|
||||
hasTestTag(DeviceSettingsScreenTestTags.ITEM_SUBTITLE)
|
||||
useUnmergedTree = true
|
||||
if (withBackup) {
|
||||
hasText(getResourceString(R.string.reset_card_with_backup_to_factory_message))
|
||||
} else {
|
||||
hasText(getResourceString(R.string.reset_card_without_backup_to_factory_message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onDeviceSettingsScreen(function: DeviceSettingsPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -4,7 +4,7 @@ import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
|||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.DialogTestTags
|
||||
import com.tangem.core.ui.test.BaseDialogTestTags
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
|
|
@ -14,7 +14,7 @@ class DialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
|||
ComposeScreen<DialogPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val dialogContainer: KNode = child {
|
||||
hasTestTag(DialogTestTags.DIALOG_CONTAINER)
|
||||
hasTestTag(BaseDialogTestTags.CONTAINER)
|
||||
}
|
||||
|
||||
val cancelButton: KNode = child {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import com.kaspersky.kaspresso.screens.KScreen
|
||||
import com.tangem.core.ui.R
|
||||
import io.github.kakaocup.kakao.text.KButton
|
||||
import io.github.kakaocup.kakao.text.KTextView
|
||||
|
||||
object FailedCardVerificationDialogPageObject : KScreen<FailedCardVerificationDialogPageObject>() {
|
||||
|
||||
override val layoutId: Int? = null
|
||||
override val viewClass: Class<*>? = null
|
||||
|
||||
val title = KTextView {
|
||||
withId(R.id.alertTitle)
|
||||
}
|
||||
|
||||
val message = KTextView {
|
||||
withId(R.id.message)
|
||||
}
|
||||
|
||||
val cancelButton = KButton {
|
||||
withText(R.string.common_cancel)
|
||||
}
|
||||
|
||||
val understandButton = KButton {
|
||||
withText(R.string.common_understand)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.BaseDialogTestTags
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import com.tangem.common.ui.R as CommonUIR
|
||||
|
||||
class FailedTransactionDialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<FailedTransactionDialogPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val dialogContainer: KNode = child {
|
||||
hasTestTag(BaseDialogTestTags.CONTAINER)
|
||||
}
|
||||
|
||||
val title: KNode = child {
|
||||
hasTestTag(BaseDialogTestTags.TITLE)
|
||||
hasText(getResourceString(CommonUIR.string.send_alert_transaction_failed_title))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val text: KNode = child {
|
||||
hasTestTag(BaseDialogTestTags.TEXT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val cancelButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.common_cancel))
|
||||
}
|
||||
|
||||
val supportButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.common_support))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onFailedTransactionDialog(function: FailedTransactionDialogPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -8,6 +8,7 @@ import com.tangem.common.extensions.hasLazyListItemPosition
|
|||
import com.tangem.common.utils.LazyListItemNode
|
||||
import com.tangem.core.ui.test.TokenElementsTestTags
|
||||
import com.tangem.core.ui.test.MainScreenTestTags
|
||||
import com.tangem.core.ui.test.NotificationTestTags
|
||||
import com.tangem.core.ui.utils.LazyListItemPositionSemantics
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
|
|
@ -35,7 +36,11 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
|
|||
}
|
||||
)
|
||||
|
||||
val synchronizeAddressesButton: KNode = child {
|
||||
val screenContainer: KNode = child {
|
||||
hasTestTag(MainScreenTestTags.SCREEN_CONTAINER)
|
||||
}
|
||||
|
||||
val synchronizeAddressesButton: KNode = lazyList.child {
|
||||
hasText(getResourceString(R.string.common_generate_addresses))
|
||||
}
|
||||
|
||||
|
|
@ -44,6 +49,29 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
|
|||
hasText(getResourceString(R.string.common_buy))
|
||||
}
|
||||
|
||||
val notificationContainer: KNode = child {
|
||||
hasTestTag(NotificationTestTags.CONTAINER)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val devCardNotificationIcon: KNode = child {
|
||||
hasAnySibling(withText(getResourceString(R.string.warning_developer_card_title)))
|
||||
hasTestTag(NotificationTestTags.ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val devCardNotificationTitle: KNode = child {
|
||||
hasTestTag(NotificationTestTags.TITLE)
|
||||
hasText(getResourceString(R.string.warning_developer_card_title))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val devCardNotificationMessage: KNode = child {
|
||||
hasTestTag(NotificationTestTags.MESSAGE)
|
||||
hasText(getResourceString(R.string.warning_developer_card_message))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Find token list item with title and address
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.ResetCardScreenTestTags
|
||||
import com.tangem.wallet.R
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import androidx.compose.ui.test.hasTestTag as withTestTag
|
||||
import androidx.compose.ui.test.hasText as withText
|
||||
|
||||
class ResetCardPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<ResetCardPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val title: KNode = child {
|
||||
hasTestTag(ResetCardScreenTestTags.TITLE)
|
||||
hasText(getResourceString(R.string.card_settings_reset_card_to_factory))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val attentionImage: KNode = child {
|
||||
hasTestTag(ResetCardScreenTestTags.ATTENTION_IMAGE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val subtitle: KNode = child {
|
||||
hasTestTag(ResetCardScreenTestTags.SUBTITLE)
|
||||
hasText(getResourceString(R.string.common_attention))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val description: KNode = child {
|
||||
hasTestTag(ResetCardScreenTestTags.DESCRIPTION)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val lostWalletAccessCheckBox: KNode = child {
|
||||
hasTestTag(ResetCardScreenTestTags.CHECKBOX)
|
||||
hasAnySibling(
|
||||
withTestTag(ResetCardScreenTestTags.CHECKBOX_TEXT) and
|
||||
withText(getResourceString(R.string.reset_card_to_factory_condition_1))
|
||||
)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val lostPasswordRestoreCheckBox: KNode = child {
|
||||
hasTestTag(ResetCardScreenTestTags.CHECKBOX)
|
||||
hasAnySibling(
|
||||
withTestTag(ResetCardScreenTestTags.CHECKBOX_TEXT) and
|
||||
withText(getResourceString(R.string.reset_card_to_factory_condition_2))
|
||||
)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val resetCardButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.reset_card_to_factory_button_title))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onResetCardScreen(function: ResetCardPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import com.kaspersky.kaspresso.screens.KScreen
|
||||
import com.tangem.wallet.R
|
||||
import io.github.kakaocup.kakao.text.KTextView
|
||||
import io.github.kakaocup.kakao.text.KButton
|
||||
|
||||
object ScanWarningDialogPageObject : KScreen<ScanWarningDialogPageObject>() {
|
||||
|
||||
override val layoutId: Int? = null
|
||||
override val viewClass: Class<*>? = null
|
||||
|
||||
val warningTitle = KTextView {
|
||||
withText(R.string.common_warning)
|
||||
}
|
||||
|
||||
val warningMessage = KTextView {
|
||||
withText(R.string.alert_troubleshooting_scan_card_title)
|
||||
}
|
||||
|
||||
val tryAgainButton = KButton {
|
||||
withId(R.id.try_again_button)
|
||||
}
|
||||
|
||||
val howToScanButton = KButton {
|
||||
withId(R.id.how_to_scan_button)
|
||||
}
|
||||
|
||||
val requestSupportButton = KButton {
|
||||
withId(R.id.request_support_button)
|
||||
}
|
||||
|
||||
val cancelButton = KButton {
|
||||
withId(R.id.cancel_button)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.SendAddressScreenTestTags
|
||||
import com.tangem.features.send.v2.impl.R
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
|
||||
class SendAddressPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<SendAddressPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val addressTextField: KNode = child {
|
||||
hasTestTag(SendAddressScreenTestTags.ADDRESS_TEXT_FIELD)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val nextButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
hasText(getResourceString(R.string.common_next))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onSendAddressScreen(function: SendAddressPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.TopAppBarTestTags
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
|
||||
class SendConfirmPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<SendConfirmPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val title: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val sendButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
hasText(getResourceString(R.string.common_send))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onSendConfirmScreen(function: SendConfirmPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -3,7 +3,7 @@ package com.tangem.screens
|
|||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.StakingSendScreenTestTags
|
||||
import com.tangem.core.ui.test.SendScreenTestTags
|
||||
import com.tangem.core.ui.test.TopAppBarTestTags
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
|
|
@ -12,11 +12,11 @@ import io.github.kakaocup.kakao.common.utilities.getResourceString
|
|||
import com.tangem.features.send.v2.impl.R as SendR
|
||||
import androidx.compose.ui.test.hasTestTag as withTestTag
|
||||
|
||||
class StakingSendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<StakingSendPageObject>(semanticsProvider = semanticsProvider) {
|
||||
class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<SendPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val screenContainer: KNode = child {
|
||||
hasTestTag(StakingSendScreenTestTags.SCREEN_CONTAINER)
|
||||
hasTestTag(SendScreenTestTags.SCREEN_CONTAINER)
|
||||
}
|
||||
|
||||
val title: KNode = child {
|
||||
|
|
@ -25,44 +25,44 @@ class StakingSendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider
|
|||
}
|
||||
|
||||
val amountContainerTitle: KNode = child {
|
||||
hasTestTag(StakingSendScreenTestTags.AMOUNT_CONTAINER_TITLE)
|
||||
hasTestTag(SendScreenTestTags.AMOUNT_CONTAINER_TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val amountContainerText: KNode = child {
|
||||
hasTestTag(StakingSendScreenTestTags.AMOUNT_CONTAINER_TEXT)
|
||||
hasTestTag(SendScreenTestTags.AMOUNT_CONTAINER_TEXT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val amountInputTextField: KNode = child {
|
||||
hasTestTag(StakingSendScreenTestTags.INPUT_TEXT_FIELD)
|
||||
hasTestTag(SendScreenTestTags.INPUT_TEXT_FIELD)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val secondaryAmount: KNode = child {
|
||||
hasTestTag(StakingSendScreenTestTags.SECONDARY_AMOUNT)
|
||||
hasTestTag(SendScreenTestTags.SECONDARY_AMOUNT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val currencyButton: KNode = child {
|
||||
hasTestTag(StakingSendScreenTestTags.CURRENCY_BUTTON)
|
||||
hasAnyChild(withTestTag(StakingSendScreenTestTags.CURRENCY_ICON))
|
||||
hasTestTag(SendScreenTestTags.CURRENCY_BUTTON)
|
||||
hasAnyChild(withTestTag(SendScreenTestTags.CURRENCY_ICON))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val fiatButton: KNode = child {
|
||||
hasTestTag(StakingSendScreenTestTags.CURRENCY_BUTTON)
|
||||
hasAnyChild(withTestTag(StakingSendScreenTestTags.FIAT_ICON))
|
||||
hasTestTag(SendScreenTestTags.CURRENCY_BUTTON)
|
||||
hasAnyChild(withTestTag(SendScreenTestTags.FIAT_ICON))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val maxButton: KNode = child {
|
||||
hasTestTag(StakingSendScreenTestTags.MAX_BUTTON)
|
||||
hasTestTag(SendScreenTestTags.MAX_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val previousButton: KNode = child {
|
||||
hasTestTag(StakingSendScreenTestTags.PREVIOUS_BUTTON)
|
||||
hasTestTag(SendScreenTestTags.PREVIOUS_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
|
|
@ -74,5 +74,5 @@ class StakingSendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider
|
|||
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onStakingSendScreen(function: StakingSendPageObject.() -> Unit) =
|
||||
internal fun BaseTestCase.onSendScreen(function: SendPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.screens
|
|||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.test.BaseAmountBlockTestTags
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.StakingSendDetailsScreenTestTags
|
||||
import com.tangem.core.ui.test.TopAppBarTestTags
|
||||
|
|
@ -11,8 +12,8 @@ import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onCompose
|
|||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
|
||||
class StakingSendDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<StakingSendDetailsPageObject>(semanticsProvider = semanticsProvider) {
|
||||
class StakingConfirmPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<StakingConfirmPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val title: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.TITLE)
|
||||
|
|
@ -20,12 +21,12 @@ class StakingSendDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsP
|
|||
}
|
||||
|
||||
val primaryAmount: KNode = child {
|
||||
hasTestTag(StakingSendDetailsScreenTestTags.PRIMARY_AMOUNT)
|
||||
hasTestTag(BaseAmountBlockTestTags.PRIMARY_AMOUNT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val secondaryAmount: KNode = child {
|
||||
hasTestTag(StakingSendDetailsScreenTestTags.SECONDARY_AMOUNT)
|
||||
hasTestTag(BaseAmountBlockTestTags.SECONDARY_AMOUNT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
|
|
@ -47,5 +48,5 @@ class StakingSendDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsP
|
|||
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onStakingSendDetailsScreen(function: StakingSendDetailsPageObject.() -> Unit) =
|
||||
internal fun BaseTestCase.onStakingConfirmScreen(function: StakingConfirmPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -57,7 +57,7 @@ class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
|
|||
}
|
||||
|
||||
val errorNotificationText: KNode = child {
|
||||
hasTestTag(NotificationTestTags.TEXT)
|
||||
hasTestTag(NotificationTestTags.MESSAGE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -111,6 +111,12 @@ class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvide
|
|||
hasText(getResourceString(R.string.common_buy))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
val sendButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||
hasTestTag(TokenDetailsScreenTestTags.ACTION_BUTTON)
|
||||
hasText(getResourceString(R.string.common_send))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onTokenDetailsScreen(function: TokenDetailsPageObject.() -> Unit) =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.WalletConnectBottomSheetTestTags
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import com.tangem.features.walletconnect.impl.R as WalletConnectImplR
|
||||
|
||||
class WalletConnectBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<WalletConnectBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val title: KNode = child {
|
||||
hasText(getResourceString(WalletConnectImplR.string.wc_wallet_connect))
|
||||
hasTestTag(WalletConnectBottomSheetTestTags.TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val appIcon: KNode = child {
|
||||
hasTestTag(WalletConnectBottomSheetTestTags.APP_ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val appName: KNode = child {
|
||||
hasTestTag(WalletConnectBottomSheetTestTags.APP_NAME)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val approveIcon: KNode = child {
|
||||
hasTestTag(WalletConnectBottomSheetTestTags.APPROVE_ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val appUrl: KNode = child {
|
||||
hasTestTag(WalletConnectBottomSheetTestTags.APP_URL)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val connectionRequestIcon: KNode = child {
|
||||
hasTestTag(WalletConnectBottomSheetTestTags.CONNECTION_REQUEST_ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val connectionRequestText: KNode = child {
|
||||
hasTestTag(WalletConnectBottomSheetTestTags.CONNECTION_REQUEST_TEXT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val connectionRequestChevron: KNode = child {
|
||||
hasTestTag(WalletConnectBottomSheetTestTags.CONNECTION_REQUEST_CHEVRON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val walletIcon: KNode = child {
|
||||
hasTestTag(WalletConnectBottomSheetTestTags.WALLET_ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val walletNameTitle: KNode = child {
|
||||
hasTestTag(WalletConnectBottomSheetTestTags.WALLET_NAME_TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val walletName: KNode = child {
|
||||
hasTestTag(WalletConnectBottomSheetTestTags.WALLET_NAME)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val networksIcon: KNode = child {
|
||||
hasTestTag(WalletConnectBottomSheetTestTags.NETWORKS_ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val networksTitle: KNode = child {
|
||||
hasTestTag(WalletConnectBottomSheetTestTags.NETWORKS_TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val networksIcons: KNode = child {
|
||||
hasTestTag(WalletConnectBottomSheetTestTags.NETWORKS_ICONS)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val networksSelectorIcon: KNode = child {
|
||||
hasTestTag(WalletConnectBottomSheetTestTags.NETWORKS_SELECTOR_ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val cancelButton: KNode = child {
|
||||
hasText(getResourceString(R.string.common_cancel))
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val connectButton: KNode = child {
|
||||
hasText(getResourceString(R.string.wc_common_connect))
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onWalletConnectBottomSheet(function: WalletConnectBottomSheetPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.WalletConnectBottomSheetTestTags
|
||||
import com.tangem.core.ui.test.WalletConnectDetailsBottomSheetTestTags
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import com.tangem.features.walletconnect.impl.R as WalletConnectImplR
|
||||
|
||||
class WalletConnectDetailsBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<WalletConnectDetailsBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val title: KNode = child {
|
||||
hasTestTag(WalletConnectDetailsBottomSheetTestTags.TITLE)
|
||||
hasText(getResourceString(WalletConnectImplR.string.wc_connected_app_title))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val date: KNode = child {
|
||||
hasTestTag(WalletConnectDetailsBottomSheetTestTags.DATE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val closeButton: KNode = child {
|
||||
hasTestTag(WalletConnectDetailsBottomSheetTestTags.CLOSE_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val networksBlockTitle: KNode = child {
|
||||
hasText(getResourceString(WalletConnectImplR.string.wc_connected_networks))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val appIcon: KNode = child {
|
||||
hasTestTag(WalletConnectBottomSheetTestTags.APP_ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val approveIcon: KNode = child {
|
||||
hasTestTag(WalletConnectBottomSheetTestTags.APPROVE_ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val appName: KNode = child {
|
||||
hasTestTag(WalletConnectBottomSheetTestTags.APP_NAME)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val appUrl: KNode = child {
|
||||
hasTestTag(WalletConnectBottomSheetTestTags.APP_URL)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val walletIcon: KNode = child {
|
||||
hasTestTag(WalletConnectDetailsBottomSheetTestTags.WALLET_ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val walletTitle: KNode = child {
|
||||
hasTestTag(WalletConnectDetailsBottomSheetTestTags.WALLET_TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val walletName: KNode = child {
|
||||
hasTestTag(WalletConnectDetailsBottomSheetTestTags.WALLET_NAME)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val connectedNetworksTitle: KNode = child {
|
||||
hasTestTag(WalletConnectDetailsBottomSheetTestTags.NETWORKS_TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val connectedNetworkItem: KNode = child {
|
||||
hasTestTag(WalletConnectDetailsBottomSheetTestTags.NETWORK_ITEM)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val connectedNetworkIcon: KNode = child {
|
||||
hasTestTag(WalletConnectDetailsBottomSheetTestTags.NETWORK_ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val connectedNetworkName: KNode = child {
|
||||
hasTestTag(WalletConnectDetailsBottomSheetTestTags.NETWORK_NAME)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val connectedNetworkSymbol: KNode = child {
|
||||
hasTestTag(WalletConnectDetailsBottomSheetTestTags.NETWORK_SYMBOL)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val disconnectButton: KNode = child {
|
||||
hasText(getResourceString(WalletConnectImplR.string.common_disconnect))
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onWalletConnectDetailsBottomSheet(function: WalletConnectDetailsBottomSheetPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.WalletConnectScreenTestTags
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
|
||||
class WalletConnectPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<WalletConnectPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val title: KNode = child {
|
||||
hasText(getResourceString(R.string.wc_connections))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val moreButton: KNode = child {
|
||||
hasTestTag(WalletConnectScreenTestTags.MORE_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val walletName: KNode = child {
|
||||
hasTestTag(WalletConnectScreenTestTags.WALLET_NAME)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val appIcon: KNode = child {
|
||||
hasTestTag(WalletConnectScreenTestTags.APP_ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val appName: KNode = child {
|
||||
hasTestTag(WalletConnectScreenTestTags.APP_NAME)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val approveIcon: KNode = child {
|
||||
hasTestTag(WalletConnectScreenTestTags.APPROVE_ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val appUrl: KNode = child {
|
||||
hasTestTag(WalletConnectScreenTestTags.APP_URL)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val newConnectionButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
hasText(getResourceString(R.string.wc_new_connection))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onWalletConnectScreen(function: WalletConnectPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -21,7 +21,7 @@ class WalletSettingsPageObject(semanticsProvider: SemanticsNodeInteractionsProvi
|
|||
val linkMoreCardsButton: KNode = walletSettingsItem.child {
|
||||
hasText(getResourceString(R.string.details_row_title_create_backup))
|
||||
}
|
||||
val cardSettingsButton: KNode = walletSettingsItem.child {
|
||||
val deviceSettingsButton: KNode = walletSettingsItem.child {
|
||||
hasText(getResourceString(R.string.card_settings_title))
|
||||
}
|
||||
val referralProgramButton: KNode = walletSettingsItem.child {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
|
|||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
import com.tangem.scenarios.OpenMainScreenScenario
|
||||
import com.tangem.screens.*
|
||||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.scenarios.synchronizeAddresses
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
|
|
@ -36,13 +37,10 @@ class BuyTokenTest : BaseTestCase() {
|
|||
setWireMockScenarioState(scenarioName, "Error")
|
||||
}
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
openMainScreen()
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = $balance") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Click on 'Buy' button") {
|
||||
onMainScreen { buyButton.clickWithAssertion() }
|
||||
|
|
@ -84,13 +82,10 @@ class BuyTokenTest : BaseTestCase() {
|
|||
}
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
openMainScreen()
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = '$balance'") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Click on 'Buy' button") {
|
||||
onMainScreen { buyButton.clickWithAssertion() }
|
||||
|
|
@ -175,13 +170,10 @@ class BuyTokenTest : BaseTestCase() {
|
|||
}
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
openMainScreen()
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = '$balance'") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Click on 'Buy' button") {
|
||||
onMainScreen { buyButton.clickWithAssertion() }
|
||||
|
|
@ -250,13 +242,10 @@ class BuyTokenTest : BaseTestCase() {
|
|||
}
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
openMainScreen()
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = '$balance'") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Click on 'Buy' button") {
|
||||
onMainScreen { buyButton.clickWithAssertion() }
|
||||
|
|
@ -339,13 +328,10 @@ class BuyTokenTest : BaseTestCase() {
|
|||
}
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
openMainScreen()
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = '$balance'") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Click on 'Buy' button") {
|
||||
onMainScreen { buyButton.clickWithAssertion() }
|
||||
|
|
@ -437,13 +423,10 @@ class BuyTokenTest : BaseTestCase() {
|
|||
}
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
openMainScreen()
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = '$balance'") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Click on 'Buy' button") {
|
||||
onMainScreen { buyButton.clickWithAssertion() }
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.tests
|
|||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.scenarios.OpenMainScreenScenario
|
||||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.screens.onDetailsScreen
|
||||
import com.tangem.screens.onReferralProgramScreen
|
||||
import com.tangem.screens.onTopBar
|
||||
|
|
@ -19,7 +19,9 @@ class DetailsTest : BaseTestCase() {
|
|||
@Test
|
||||
fun walletWithoutBackupDetailsTest() =
|
||||
setupHooks().run {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
onTopBar {
|
||||
step("Open wallet details") {
|
||||
moreButton.clickWithAssertion()
|
||||
|
|
@ -53,7 +55,7 @@ class DetailsTest : BaseTestCase() {
|
|||
linkMoreCardsButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'Card Settings' button is visible") {
|
||||
cardSettingsButton.assertIsDisplayed()
|
||||
deviceSettingsButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'Referral program' button is visible") {
|
||||
referralProgramButton.assertIsDisplayed()
|
||||
|
|
@ -67,7 +69,9 @@ class DetailsTest : BaseTestCase() {
|
|||
// @Test
|
||||
fun wallet2DetailsTest() =
|
||||
setupHooks().run {
|
||||
scenario(OpenMainScreenScenario(composeTestRule, ProductType.Wallet2))
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen(productType = ProductType.Wallet2, alreadyActivatedDialogIsShown = true)
|
||||
}
|
||||
onTopBar {
|
||||
step("Open wallet details") {
|
||||
moreButton.clickWithAssertion()
|
||||
|
|
@ -101,7 +105,7 @@ class DetailsTest : BaseTestCase() {
|
|||
linkMoreCardsButton.assertIsNotDisplayed()
|
||||
}
|
||||
step("Assert 'Card Settings' button is visible") {
|
||||
cardSettingsButton.assertIsDisplayed()
|
||||
deviceSettingsButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'Referral program' button is visible") {
|
||||
referralProgramButton.assertIsDisplayed()
|
||||
|
|
@ -115,7 +119,9 @@ class DetailsTest : BaseTestCase() {
|
|||
@Test
|
||||
fun noteDetailsTest() =
|
||||
setupHooks().run {
|
||||
scenario(OpenMainScreenScenario(composeTestRule, ProductType.Note))
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen(ProductType.Note)
|
||||
}
|
||||
onTopBar {
|
||||
step("Open wallet details") {
|
||||
moreButton.clickWithAssertion()
|
||||
|
|
@ -146,7 +152,7 @@ class DetailsTest : BaseTestCase() {
|
|||
}
|
||||
onWalletSettingsScreen {
|
||||
step("Assert 'Card Settings' button is visible") {
|
||||
cardSettingsButton.assertIsDisplayed()
|
||||
deviceSettingsButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert 'Referral program' button does not exist") {
|
||||
referralProgramButton.assertIsNotDisplayed()
|
||||
|
|
@ -162,7 +168,9 @@ class DetailsTest : BaseTestCase() {
|
|||
@Test
|
||||
fun validateReferralProgramScreenTest() =
|
||||
setupHooks().run {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Open wallet details") {
|
||||
onTopBar { moreButton.clickWithAssertion() }
|
||||
}
|
||||
|
|
|
|||
176
app/src/androidTest/kotlin/com/tangem/tests/FeedbackTest.kt
Normal file
176
app/src/androidTest/kotlin/com/tangem/tests/FeedbackTest.kt
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
package com.tangem.tests
|
||||
|
||||
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.RECIPIENT_ADDRESS
|
||||
import com.tangem.common.constants.TestConstants.TOTAL_BALANCE
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.scenarios.*
|
||||
import com.tangem.screens.*
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.sdk.mocks.MockProvider
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.Allure
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class FeedbackTest : BaseTestCase() {
|
||||
|
||||
@AllureId("894")
|
||||
@DisplayName("Send feedback: from details")
|
||||
@Test
|
||||
fun sendFeedbackFromDetailsTest() {
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
device.uiDevice.pressBack()
|
||||
}
|
||||
).run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Click 'More' button on TopBar") {
|
||||
onTopBar { moreButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click 'Contact support' button") {
|
||||
onDetailsScreen { contactSupportButton.clickWithAssertion() }
|
||||
}
|
||||
step("Check 'Contact support' intent is called") {
|
||||
checkSendEMailIntentCalled()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("893")
|
||||
@DisplayName("Send feedback: failed transaction")
|
||||
@Test
|
||||
fun sendFeedbackFromFailedTransactionTest() {
|
||||
val balance = TOTAL_BALANCE
|
||||
val tokenName = "Polygon"
|
||||
val recipientAddress = RECIPIENT_ADDRESS
|
||||
val sendAmount = "1"
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
device.uiDevice.pressBack()
|
||||
}
|
||||
).run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Click on token with name: '$tokenName'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
|
||||
}
|
||||
step("Click 'Send' button") {
|
||||
onTokenDetailsScreen { sendButton.performClick() }
|
||||
}
|
||||
step("Type '$sendAmount' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(sendAmount)
|
||||
}
|
||||
}
|
||||
step("Assert input text field has value: '$sendAmount'") {
|
||||
onSendScreen { amountInputTextField.assertTextContains(value = sendAmount, substring = true) }
|
||||
}
|
||||
step("Click 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Enter address") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(recipientAddress) }
|
||||
}
|
||||
step("Click 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click 'Send' button") {
|
||||
onSendConfirmScreen { sendButton.clickWithAssertion() }
|
||||
}
|
||||
step("Check 'Failed transaction' dialog") {
|
||||
checkFailedTransactionDialog()
|
||||
}
|
||||
step("Click on 'Support' button") {
|
||||
onFailedTransactionDialog { supportButton.performClick() }
|
||||
}
|
||||
step("Check 'Contact support' intent is called") {
|
||||
checkSendEMailIntentCalled()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("3985")
|
||||
@DisplayName("Send feedback: from 'Warning' dialog after card scan")
|
||||
@Test
|
||||
fun sendFeedbackFromScanScreenTest() {
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
device.uiDevice.pressBack()
|
||||
MockProvider.resetEmulateError()
|
||||
}
|
||||
).run {
|
||||
Allure.step("Click on 'Accept' button") {
|
||||
onDisclaimerScreen { acceptButton.clickWithAssertion() }
|
||||
}
|
||||
step("Set scanning error") {
|
||||
MockProvider.setEmulateError(TangemSdkError.TagLost())
|
||||
}
|
||||
step("Click on 'Scan' button") {
|
||||
onStoriesScreen { scanButton.performClick() }
|
||||
}
|
||||
step("Force show 'Scan warning' dialog"){
|
||||
runOnUiThread {
|
||||
val scanFailsState = StateDialog.ScanFailsDialog(source = StateDialog.ScanFailsSource.MAIN)
|
||||
store.dispatch(GlobalAction.ShowDialog(scanFailsState))
|
||||
}
|
||||
}
|
||||
step("Check 'Scan warning' dialog") {
|
||||
checkScanWarningDialog()
|
||||
}
|
||||
step("Click on 'Request support' button") {
|
||||
ScanWarningDialogPageObject { requestSupportButton.click() }
|
||||
}
|
||||
step("Check 'Contact support' intent is called") {
|
||||
checkSendEMailIntentCalled()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("3986")
|
||||
@DisplayName("Send feedback: from scan already used wallet alert dialog")
|
||||
@Ignore("TODO On CI Already used wallet doesn't displayed")
|
||||
@Test
|
||||
fun sendFeedbackAfterScanAlreadyUsedWalletTest() {
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
device.uiDevice.pressBack()
|
||||
}
|
||||
).run {
|
||||
step("Set mocks for Wallet2") {
|
||||
MockProvider.setMocks(ProductType.Wallet2)
|
||||
}
|
||||
step("Click on 'Accept' button") {
|
||||
onDisclaimerScreen { acceptButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Scan' button") {
|
||||
onStoriesScreen { scanButton.clickWithAssertion() }
|
||||
}
|
||||
step("Check 'Already used Wallet' dialog") {
|
||||
checkAlreadyUsedWalletDialog()
|
||||
}
|
||||
step("Click on 'Request support' button") {
|
||||
AlreadyUsedWalletDialogPageObject { requestSupportButton.click() }
|
||||
}
|
||||
step("Check 'Contact support' intent is called") {
|
||||
checkSendEMailIntentCalled()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,9 @@ package com.tangem.tests
|
|||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.TOTAL_BALANCE
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.scenarios.OpenMainScreenScenario
|
||||
import com.tangem.screens.*
|
||||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.scenarios.synchronizeAddresses
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
|
|
@ -21,13 +22,10 @@ class HideTokenTest : BaseTestCase() {
|
|||
val balance = TOTAL_BALANCE
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
openMainScreen()
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button" ) {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = $balance") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.tests
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.scenarios.OpenMainScreenScenario
|
||||
import com.tangem.scenarios.openMainScreen
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import org.junit.Test
|
||||
|
||||
|
|
@ -11,7 +11,9 @@ class MainScreenTest : BaseTestCase() {
|
|||
@Test
|
||||
fun goToMain() {
|
||||
setupHooks().run {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@ import com.tangem.common.BaseTestCase
|
|||
import com.tangem.common.constants.TestConstants.TOTAL_BALANCE
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.extensions.swipeUp
|
||||
import com.tangem.scenarios.OpenMainScreenScenario
|
||||
import com.tangem.screens.onMainScreen
|
||||
import com.tangem.screens.onOrganizeTokensScreen
|
||||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.scenarios.synchronizeAddresses
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
|
|
@ -23,8 +24,9 @@ class OrganizeTokensTest : BaseTestCase() {
|
|||
setupHooks().run {
|
||||
val tokenTitle = "Ethereum"
|
||||
val tokenNetwork = "Ethereum network"
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
openMainScreen()
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button" ) {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
|
|
@ -90,14 +92,12 @@ class OrganizeTokensTest : BaseTestCase() {
|
|||
val ethereumTitle = "Ethereum"
|
||||
val bitcoinTitle = "Bitcoin"
|
||||
val balance = TOTAL_BALANCE
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
openMainScreen()
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button" ) {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = '$balance'") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Check positions of tokens on 'Main Screen'") {
|
||||
onMainScreen {
|
||||
|
|
@ -129,8 +129,9 @@ class OrganizeTokensTest : BaseTestCase() {
|
|||
setupHooks().run {
|
||||
val ethereumTitle = "Ethereum"
|
||||
val bitcoinTitle = "Bitcoin"
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
openMainScreen()
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button" ) {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
|
|
@ -178,14 +179,12 @@ class OrganizeTokensTest : BaseTestCase() {
|
|||
val polygonTitle = "Polygon"
|
||||
val polExMaticTitle = "POL (ex-MATIC)"
|
||||
val balance = TOTAL_BALANCE
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
openMainScreen()
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button" ) {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = '$balance'") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Check positions of tokens on 'Main Screen'") {
|
||||
onMainScreen {
|
||||
|
|
|
|||
85
app/src/androidTest/kotlin/com/tangem/tests/ResetCardTest.kt
Normal file
85
app/src/androidTest/kotlin/com/tangem/tests/ResetCardTest.kt
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package com.tangem.tests
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.scenarios.*
|
||||
import com.tangem.tap.domain.sdk.mocks.content.BackupWalletMockContent
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class ResetCardTest : BaseTestCase() {
|
||||
|
||||
@AllureId("3988")
|
||||
@DisplayName("Reset card: reset Wallet 1.0 card without backup")
|
||||
@Test
|
||||
fun resetWallet1CardWithoutBackupTest() {
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Open 'Device settings' screen") {
|
||||
openDeviceSettingsScreen()
|
||||
}
|
||||
step("Open 'Reset card' screen") {
|
||||
openResetCardScreen()
|
||||
}
|
||||
step("Check 'Reset card' screen") {
|
||||
checkResetCardScreen()
|
||||
}
|
||||
step("Check checkbox logic") {
|
||||
checkCheckBoxLogic()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("3987")
|
||||
@DisplayName("Reset card: reset Wallet 1.0 card with backup")
|
||||
@Test
|
||||
fun resetWallet1CardWithBackupTest() {
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen(mockContent = BackupWalletMockContent)
|
||||
}
|
||||
step("Open 'Device settings' screen") {
|
||||
openDeviceSettingsScreen()
|
||||
}
|
||||
step("Open 'Reset card' screen") {
|
||||
openResetCardScreen(withBackup = true)
|
||||
}
|
||||
step("Check 'Reset card' screen") {
|
||||
checkResetCardScreen(withBackup = true)
|
||||
}
|
||||
step("Check checkbox logic") {
|
||||
checkCheckBoxLogic(withBackup = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("3974")
|
||||
@DisplayName("Reset card: reset Wallet 2.0 card with backup")
|
||||
@Ignore("toDo [REDACTED_TASK_KEY]: On CI Already used wallet dialog doesn't displayed")
|
||||
@Test
|
||||
fun resetWallet2CardWithBackupTest() {
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen(productType = ProductType.Wallet2, alreadyActivatedDialogIsShown = true)
|
||||
}
|
||||
step("Open 'Device settings' screen") {
|
||||
openDeviceSettingsScreen()
|
||||
}
|
||||
step("Open 'Reset card' screen") {
|
||||
openResetCardScreen(withBackup = true)
|
||||
}
|
||||
step("Check 'Reset card' screen") {
|
||||
checkResetCardScreen(withBackup = true)
|
||||
}
|
||||
step("Check checkbox logic") {
|
||||
checkCheckBoxLogic(withBackup = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,8 +5,9 @@ import com.tangem.common.constants.TestConstants.TOTAL_BALANCE
|
|||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
import com.tangem.scenarios.OpenMainScreenScenario
|
||||
import com.tangem.screens.*
|
||||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.scenarios.synchronizeAddresses
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
|
|
@ -20,7 +21,7 @@ class StakingTest : BaseTestCase() {
|
|||
@Test
|
||||
fun validateStakingBlockTest() {
|
||||
val tokenTitle = "POL (ex-MATIC)"
|
||||
val balance = TOTAL_BALANCE
|
||||
val balance = "$3,299.37"
|
||||
val scenarioName = "staking_eth_pol_balances_android"
|
||||
val scenarioState = "Staked"
|
||||
|
||||
|
|
@ -35,13 +36,13 @@ class StakingTest : BaseTestCase() {
|
|||
}
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
openMainScreen()
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Assert wallet balance = $balance") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
step("Assert 'Organize tokens' button is displayed") {
|
||||
onMainScreen { organizeTokensButton().assertIsDisplayed() }
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
|
||||
|
|
@ -75,7 +76,7 @@ class StakingTest : BaseTestCase() {
|
|||
@Test
|
||||
fun validateStakingMoreScreensTest() {
|
||||
val tokenTitle = "POL (ex-MATIC)"
|
||||
val balance = TOTAL_BALANCE
|
||||
val balance = "$3,299.37"
|
||||
val scenarioName = "staking_eth_pol_balances_android"
|
||||
val scenarioState = "Staked"
|
||||
val stakingAmount = "1"
|
||||
|
|
@ -91,13 +92,13 @@ class StakingTest : BaseTestCase() {
|
|||
}
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
openMainScreen()
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Assert wallet balance = $balance") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
step("Assert 'Organize tokens' button is displayed") {
|
||||
onMainScreen { organizeTokensButton().assertIsDisplayed() }
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
|
||||
|
|
@ -151,73 +152,73 @@ class StakingTest : BaseTestCase() {
|
|||
onStakingDetailsScreen { stakeMoreButton.performClick() }
|
||||
}
|
||||
step("Assert 'Send' screen is displayed") {
|
||||
onStakingSendScreen { screenContainer.assertIsDisplayed() }
|
||||
onSendScreen { screenContainer.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Send' screen title is displayed") {
|
||||
onStakingSendScreen { title.assertIsDisplayed() }
|
||||
onSendScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert amount container title is displayed") {
|
||||
onStakingSendScreen { amountContainerTitle.assertIsDisplayed() }
|
||||
onSendScreen { amountContainerTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert amount container text is displayed") {
|
||||
onStakingSendScreen { amountContainerText.assertIsDisplayed() }
|
||||
onSendScreen { amountContainerText.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert input text field is displayed") {
|
||||
onStakingSendScreen { amountInputTextField.assertIsDisplayed() }
|
||||
onSendScreen { amountInputTextField.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert secondary amount is displayed") {
|
||||
onStakingSendScreen { secondaryAmount.assertIsDisplayed() }
|
||||
onSendScreen { secondaryAmount.assertIsDisplayed() }
|
||||
}
|
||||
step("Type '$stakingAmount' in input text field") {
|
||||
onStakingSendScreen {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(stakingAmount)
|
||||
}
|
||||
}
|
||||
step("Assert input text field has value: '$stakingAmount'") {
|
||||
onStakingSendScreen { amountInputTextField.assertTextContains(value = stakingAmount, substring = true) }
|
||||
onSendScreen { amountInputTextField.assertTextContains(value = stakingAmount, substring = true) }
|
||||
}
|
||||
step("Assert currency button is displayed") {
|
||||
onStakingSendScreen { currencyButton.assertIsDisplayed() }
|
||||
onSendScreen { currencyButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert fiat button is displayed") {
|
||||
onStakingSendScreen { fiatButton.assertIsDisplayed() }
|
||||
onSendScreen { fiatButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert currency button is displayed") {
|
||||
onStakingSendScreen { currencyButton.assertIsDisplayed() }
|
||||
onSendScreen { currencyButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert fiat button is displayed") {
|
||||
onStakingSendScreen { fiatButton.assertIsDisplayed() }
|
||||
onSendScreen { fiatButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Max' button is displayed") {
|
||||
onStakingSendScreen { maxButton.assertIsDisplayed() }
|
||||
onSendScreen { maxButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert previous button is displayed") {
|
||||
onStakingSendScreen { previousButton.assertIsDisplayed() }
|
||||
onSendScreen { previousButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Next' button is displayed") {
|
||||
onStakingSendScreen { nextButton.assertIsDisplayed() }
|
||||
onSendScreen { nextButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onStakingSendScreen { nextButton.performClick() }
|
||||
onSendScreen { nextButton.performClick() }
|
||||
}
|
||||
step("Assert 'Send details' screen title is displayed") {
|
||||
onStakingSendDetailsScreen { title.assertIsDisplayed() }
|
||||
onStakingConfirmScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert primary amount is displayed") {
|
||||
onStakingSendDetailsScreen { primaryAmount.assertIsDisplayed() }
|
||||
onStakingConfirmScreen { primaryAmount.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert secondary amount is displayed") {
|
||||
onStakingSendDetailsScreen { secondaryAmount.assertIsDisplayed() }
|
||||
onStakingConfirmScreen { secondaryAmount.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Validator' block is displayed") {
|
||||
onStakingSendDetailsScreen { validatorBlock.assertIsDisplayed() }
|
||||
onStakingConfirmScreen { validatorBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Network Fee' block is displayed") {
|
||||
onStakingSendDetailsScreen { networkFeeBlock.assertIsDisplayed() }
|
||||
onStakingConfirmScreen { networkFeeBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Stake' button is displayed") {
|
||||
onStakingSendDetailsScreen { stakeButton.assertIsDisplayed() }
|
||||
onStakingConfirmScreen { stakeButton.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -243,13 +244,13 @@ class StakingTest : BaseTestCase() {
|
|||
}
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
openMainScreen()
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Assert wallet balance = $balance") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
step("Assert 'Organize tokens' button is displayed") {
|
||||
onMainScreen { organizeTokensButton().assertIsDisplayed() }
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
|
||||
|
|
@ -306,73 +307,73 @@ class StakingTest : BaseTestCase() {
|
|||
onStakingDetailsScreen { stakeButton.performClick() }
|
||||
}
|
||||
step("Assert 'Send' screen is displayed") {
|
||||
onStakingSendScreen { screenContainer.assertIsDisplayed() }
|
||||
onSendScreen { screenContainer.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Send' screen title is displayed") {
|
||||
onStakingSendScreen { title.assertIsDisplayed() }
|
||||
onSendScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert amount container title is displayed") {
|
||||
onStakingSendScreen { amountContainerTitle.assertIsDisplayed() }
|
||||
onSendScreen { amountContainerTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert amount container text is displayed") {
|
||||
onStakingSendScreen { amountContainerText.assertIsDisplayed() }
|
||||
onSendScreen { amountContainerText.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert input text field is displayed") {
|
||||
onStakingSendScreen { amountInputTextField.assertIsDisplayed() }
|
||||
onSendScreen { amountInputTextField.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert secondary amount is displayed") {
|
||||
onStakingSendScreen { secondaryAmount.assertIsDisplayed() }
|
||||
onSendScreen { secondaryAmount.assertIsDisplayed() }
|
||||
}
|
||||
step("Type '$stakingAmount' in input text field") {
|
||||
onStakingSendScreen {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(stakingAmount)
|
||||
}
|
||||
}
|
||||
step("Assert input text field has value: '$stakingAmount'") {
|
||||
onStakingSendScreen { amountInputTextField.assertTextContains(value = stakingAmount, substring = true) }
|
||||
onSendScreen { amountInputTextField.assertTextContains(value = stakingAmount, substring = true) }
|
||||
}
|
||||
step("Assert currency button is displayed") {
|
||||
onStakingSendScreen { currencyButton.assertIsDisplayed() }
|
||||
onSendScreen { currencyButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert fiat button is displayed") {
|
||||
onStakingSendScreen { fiatButton.assertIsDisplayed() }
|
||||
onSendScreen { fiatButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert currency button is displayed") {
|
||||
onStakingSendScreen { currencyButton.assertIsDisplayed() }
|
||||
onSendScreen { currencyButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert fiat button is displayed") {
|
||||
onStakingSendScreen { fiatButton.assertIsDisplayed() }
|
||||
onSendScreen { fiatButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Max' button is displayed") {
|
||||
onStakingSendScreen { maxButton.assertIsDisplayed() }
|
||||
onSendScreen { maxButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert previous button is displayed") {
|
||||
onStakingSendScreen { previousButton.assertIsDisplayed() }
|
||||
onSendScreen { previousButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Next' button is displayed") {
|
||||
onStakingSendScreen { nextButton.assertIsDisplayed() }
|
||||
onSendScreen { nextButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onStakingSendScreen { nextButton.performClick() }
|
||||
onSendScreen { nextButton.performClick() }
|
||||
}
|
||||
step("Assert 'Send details' screen title is displayed") {
|
||||
onStakingSendDetailsScreen { title.assertIsDisplayed() }
|
||||
onStakingConfirmScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert primary amount is displayed") {
|
||||
onStakingSendDetailsScreen { primaryAmount.assertIsDisplayed() }
|
||||
onStakingConfirmScreen { primaryAmount.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert secondary amount is displayed") {
|
||||
onStakingSendDetailsScreen { secondaryAmount.assertIsDisplayed() }
|
||||
onStakingConfirmScreen { secondaryAmount.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Validator' block is displayed") {
|
||||
onStakingSendDetailsScreen { validatorBlock.assertIsDisplayed() }
|
||||
onStakingConfirmScreen { validatorBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Network Fee' block is displayed") {
|
||||
onStakingSendDetailsScreen { networkFeeBlock.assertIsDisplayed() }
|
||||
onStakingConfirmScreen { networkFeeBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Stake' button is displayed") {
|
||||
onStakingSendDetailsScreen { stakeButton.assertIsDisplayed() }
|
||||
onStakingConfirmScreen { stakeButton.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
|
|||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.ApiEnvironment
|
||||
import com.tangem.scenarios.OpenMainScreenScenario
|
||||
import com.tangem.screens.*
|
||||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.scenarios.synchronizeAddresses
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
|
|
@ -32,13 +33,10 @@ class SwapTokenTest : BaseTestCase() {
|
|||
val balance = TOTAL_BALANCE
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
openMainScreen()
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = $balance") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
|
||||
|
|
@ -111,13 +109,10 @@ class SwapTokenTest : BaseTestCase() {
|
|||
val balance = TOTAL_BALANCE
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
openMainScreen()
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = $balance") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
|
||||
|
|
@ -149,7 +144,7 @@ class SwapTokenTest : BaseTestCase() {
|
|||
@ApiEnv(
|
||||
ApiEnvConfig(ApiConfig.ID.Express, ApiEnvironment.PROD)
|
||||
)
|
||||
@AllureId("3546")
|
||||
@AllureId("3547")
|
||||
@DisplayName("Swap: change network fee")
|
||||
@Test
|
||||
fun changeNetworkFeeTest() {
|
||||
|
|
@ -159,13 +154,10 @@ class SwapTokenTest : BaseTestCase() {
|
|||
val balance = TOTAL_BALANCE
|
||||
|
||||
step("Open 'Main Screen'") {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
openMainScreen()
|
||||
}
|
||||
step("Click on 'Synchronize addresses' button") {
|
||||
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert wallet balance = $balance") {
|
||||
onMainScreen { walletBalance().assertTextContains(balance) }
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
|
||||
|
|
|
|||
180
app/src/androidTest/kotlin/com/tangem/tests/WalletConnectTest.kt
Normal file
180
app/src/androidTest/kotlin/com/tangem/tests/WalletConnectTest.kt
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
package com.tangem.tests
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.TOTAL_BALANCE
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.extensions.swipeUp
|
||||
import com.tangem.common.utils.getWcUri
|
||||
import com.tangem.screens.*
|
||||
import com.tangem.scenarios.*
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class WalletConnectTest : BaseTestCase() {
|
||||
|
||||
@AllureId("3833")
|
||||
@DisplayName("WC (React App): open session from deeplink on main screen")
|
||||
@Ignore("TODO [REDACTED_JIRA] React app deeplink doesn't work")
|
||||
@Test
|
||||
fun openWalletConnectSessionOnMainScreen() {
|
||||
val balance = TOTAL_BALANCE
|
||||
val dAppName = "React App"
|
||||
val deepLinkUri = getWcUri()
|
||||
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Create WC session buy deeplink") {
|
||||
openAppByDeepLink(deepLinkUri)
|
||||
}
|
||||
step("Check 'Wallet Connect' bottom sheet") {
|
||||
checkWalletConnectBottomSheet()
|
||||
}
|
||||
step("Click on 'Connect' button") {
|
||||
onWalletConnectBottomSheet { connectButton.performClick() }
|
||||
}
|
||||
step("Click 'More' button on TopBar") {
|
||||
onTopBar { moreButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Wallet Connect' button") {
|
||||
onDetailsScreen { walletConnectButton.clickWithAssertion() }
|
||||
}
|
||||
step("Check 'Wallet Connect' screen") {
|
||||
checkWalletConnectScreen()
|
||||
}
|
||||
step("Click on app icon") {
|
||||
onWalletConnectScreen { appIcon.performClick() }
|
||||
}
|
||||
step("Check 'Wallet Connect' details bottom sheet") {
|
||||
checkWalletConnectDetailsBottomSheet(dAppName)
|
||||
}
|
||||
step("Click on 'Disconnect button' is displayed") {
|
||||
onWalletConnectDetailsBottomSheet { disconnectButton.performClick() }
|
||||
}
|
||||
step("Assert connection is not displayed") {
|
||||
onWalletConnectScreen { appName.assertIsNotDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("3834")
|
||||
@DisplayName("WC (React App): open session from deeplink not on main screen")
|
||||
@Ignore("TODO [REDACTED_JIRA] React app deeplink doesn't work")
|
||||
@Test
|
||||
fun openWalletConnectSessionNotOnMainScreen() {
|
||||
val balance = TOTAL_BALANCE
|
||||
val dAppName = "React App"
|
||||
val deepLinkUri = getWcUri()
|
||||
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Click on 'Buy' button") {
|
||||
onMainScreen { buyButton.clickWithAssertion() }
|
||||
}
|
||||
step("Create WC session buy deeplink") {
|
||||
openAppByDeepLink(deepLinkUri)
|
||||
}
|
||||
step("Check 'Wallet Connect' bottom sheet") {
|
||||
checkWalletConnectBottomSheet()
|
||||
}
|
||||
step("Click on 'Connect' button") {
|
||||
onWalletConnectBottomSheet { connectButton.performClick() }
|
||||
}
|
||||
step("Click 'More' button on TopBar") {
|
||||
onTopBar { moreButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Wallet Connect' button") {
|
||||
onDetailsScreen { walletConnectButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Wallet Connect' bottom sheet is displayed") {
|
||||
onWalletConnectBottomSheet { connectButton.clickWithAssertion() }
|
||||
}
|
||||
step("Check 'Wallet Connect' screen") {
|
||||
checkWalletConnectScreen()
|
||||
}
|
||||
step("Click on app icon") {
|
||||
onWalletConnectScreen { appIcon.performClick() }
|
||||
}
|
||||
step("Check 'Wallet Connect' details bottom sheet") {
|
||||
checkWalletConnectDetailsBottomSheet(dAppName)
|
||||
}
|
||||
step("Click on 'Disconnect button' is displayed") {
|
||||
onWalletConnectDetailsBottomSheet { disconnectButton.performClick() }
|
||||
}
|
||||
step("Assert connection is not displayed") {
|
||||
onWalletConnectScreen { appName.assertIsNotDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("886")
|
||||
@DisplayName("WC (React App): open session from deeplink ")
|
||||
@Ignore("TODO [REDACTED_JIRA] React app deeplink doesn't work")
|
||||
@Test
|
||||
fun openWalletConnectSession() {
|
||||
val balance = TOTAL_BALANCE
|
||||
val dAppName = "React App"
|
||||
val deepLinkUri = getWcUri()
|
||||
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Open recent apps") {
|
||||
device.uiDevice.pressRecentApps()
|
||||
}
|
||||
step("Stop app by swipe") {
|
||||
swipeUp(startHeightRatio = 0.8f)
|
||||
}
|
||||
step("Create WC session buy deeplink") {
|
||||
openAppByDeepLink(deepLinkUri)
|
||||
}
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Check 'Wallet Connect' bottom sheet") {
|
||||
checkWalletConnectBottomSheet()
|
||||
}
|
||||
step("Click on 'Connect' button") {
|
||||
onWalletConnectBottomSheet { connectButton.performClick() }
|
||||
}
|
||||
step("Click 'More' button on TopBar") {
|
||||
onTopBar { moreButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Wallet Connect' button") {
|
||||
onDetailsScreen { walletConnectButton.clickWithAssertion() }
|
||||
}
|
||||
step("Check 'Wallet Connect' screen") {
|
||||
checkWalletConnectScreen()
|
||||
}
|
||||
step("Click on app icon") {
|
||||
onWalletConnectScreen { appIcon.performClick() }
|
||||
}
|
||||
step("Check 'Wallet Connect' details bottom sheet") {
|
||||
checkWalletConnectDetailsBottomSheet(dAppName)
|
||||
}
|
||||
step("Click on 'Disconnect button' is displayed") {
|
||||
onWalletConnectDetailsBottomSheet { disconnectButton.performClick() }
|
||||
}
|
||||
step("Assert connection is not displayed") {
|
||||
onWalletConnectScreen { appName.assertIsNotDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
54
app/src/androidTest/kotlin/com/tangem/tests/WarningTest.kt
Normal file
54
app/src/androidTest/kotlin/com/tangem/tests/WarningTest.kt
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.tests
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.screens.onMainScreen
|
||||
import com.tangem.tap.domain.sdk.mocks.content.DevWalletMockContent
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class WarningTest : BaseTestCase() {
|
||||
|
||||
@AllureId("898")
|
||||
@DisplayName("Warnings: check 'Dev card' warning")
|
||||
@Test
|
||||
fun devCardWarningTest() {
|
||||
setupHooks().run {
|
||||
step("Open 'Main' screen") {
|
||||
openMainScreen(mockContent = DevWalletMockContent)
|
||||
}
|
||||
step("Assert 'Dev card' notification title is displayed") {
|
||||
onMainScreen { devCardNotificationTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Dev card' notification message is displayed") {
|
||||
onMainScreen { devCardNotificationMessage.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Dev card' notification icon is displayed") {
|
||||
onMainScreen { devCardNotificationIcon.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("3991")
|
||||
@DisplayName("Warnings: check 'Dev card' warning is not displayed for release card")
|
||||
@Test
|
||||
fun releaseCardWarningTest() {
|
||||
setupHooks().run {
|
||||
step("Open 'Main' screen") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Assert 'Dev card' notification title is not displayed") {
|
||||
onMainScreen { devCardNotificationTitle.assertIsNotDisplayed() }
|
||||
}
|
||||
step("Assert 'Dev card' notification message is not displayed") {
|
||||
onMainScreen { devCardNotificationMessage.assertIsNotDisplayed() }
|
||||
}
|
||||
step("Assert 'Dev card' notification icon is not displayed") {
|
||||
onMainScreen { devCardNotificationIcon.assertIsNotDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.data
|
||||
package com.tangem.tap
|
||||
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import com.tangem.utils.notifications.PushNotificationsTokenProvider
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.tap.di.data
|
||||
package com.tangem.tap.di
|
||||
|
||||
import com.tangem.tap.data.FirebasePushNotificationsTokenProvider
|
||||
import com.tangem.tap.FirebasePushNotificationsTokenProvider
|
||||
import com.tangem.utils.notifications.PushNotificationsTokenProvider
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
|
|
@ -10,7 +10,7 @@ import javax.inject.Singleton
|
|||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface PushNotificationsModule {
|
||||
internal interface GooglePushModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
17
app/src/huawei/AndroidManifest.xml
Normal file
17
app/src/huawei/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application>
|
||||
<service
|
||||
android:name="com.tangem.tap.HuaweiPushService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.huawei.push.action.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<meta-data
|
||||
android:name="push_kit_auto_init_enabled"
|
||||
android:value="true" />
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.tangem.tap
|
||||
|
||||
import android.content.Context
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import com.huawei.agconnect.AGConnectOptionsBuilder
|
||||
import com.huawei.hms.aaid.HmsInstanceId
|
||||
import com.huawei.hms.common.ApiException
|
||||
import com.tangem.google.GoogleServicesHelper
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.notifications.PushNotificationsTokenProvider
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.tasks.await
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class HuaweiPushNotificationsTokenProvider @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val coroutineDispatcherProvider: CoroutineDispatcherProvider,
|
||||
) : PushNotificationsTokenProvider {
|
||||
|
||||
override suspend fun getToken(): String {
|
||||
val isGoogleServicesAvailable = GoogleServicesHelper.checkGoogleServicesAvailability(context)
|
||||
return if (isGoogleServicesAvailable) {
|
||||
try {
|
||||
FirebaseMessaging.getInstance().token.await()
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex)
|
||||
""
|
||||
}
|
||||
} else {
|
||||
withContext(coroutineDispatcherProvider.io) {
|
||||
try {
|
||||
val appId = AGConnectOptionsBuilder().build(context).getString(APP_ID_KEY)
|
||||
val token = HmsInstanceId.getInstance(context).getToken(appId, TOKEN_REQUEST_MODE)
|
||||
Timber.i("Requested token from HuaweiService: $token")
|
||||
token
|
||||
} catch (e: ApiException) {
|
||||
Timber.i("Fetching token from HuaweiService failed cause: ${e.message}")
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val APP_ID_KEY = "client/app_id"
|
||||
private const val TOKEN_REQUEST_MODE = "HCM"
|
||||
}
|
||||
}
|
||||
47
app/src/huawei/java/com/tangem/tap/HuaweiPushService.kt
Normal file
47
app/src/huawei/java/com/tangem/tap/HuaweiPushService.kt
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package com.tangem.tap
|
||||
|
||||
import android.os.Bundle
|
||||
import com.huawei.hms.push.HmsMessageService
|
||||
import com.huawei.hms.push.RemoteMessage
|
||||
import com.tangem.google.GoogleServicesHelper
|
||||
import com.tangem.tap.common.pushes.PushNotificationDelegate
|
||||
import timber.log.Timber
|
||||
|
||||
class HuaweiPushService : HmsMessageService() {
|
||||
|
||||
private val pushNotificationDelegate: PushNotificationDelegate by lazy {
|
||||
PushNotificationDelegate(applicationContext)
|
||||
}
|
||||
|
||||
override fun onNewToken(token: String?, bundle: Bundle?) {
|
||||
super.onNewToken(token, bundle)
|
||||
Timber.i("HuaweiPushService: On new token from HuaweiService: $token")
|
||||
}
|
||||
|
||||
override fun onTokenError(e: Exception?, bundle: Bundle?) {
|
||||
super.onTokenError(e, bundle)
|
||||
Timber.i("HuaweiPushService: Fetching token from HuaweiService failed cause: ${e?.message}")
|
||||
}
|
||||
|
||||
override fun onMessageReceived(message: RemoteMessage?) {
|
||||
super.onMessageReceived(message)
|
||||
val isGoogleServicesAvailable = GoogleServicesHelper.checkGoogleServicesAvailability(this)
|
||||
if (isGoogleServicesAvailable) return
|
||||
val notification = message?.notification ?: return
|
||||
val channelId = notification.channelId ?: TANGEM_CHANNEL_ID
|
||||
|
||||
pushNotificationDelegate.showNotification(
|
||||
dataMap = message.dataOfMap,
|
||||
title = notification.title,
|
||||
body = notification.body,
|
||||
channelId = channelId,
|
||||
priority = message.urgency,
|
||||
imageUrl = notification.imageUrl,
|
||||
vibratePattern = notification.vibrateConfig,
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val TANGEM_CHANNEL_ID = "Tangem General" // General channel for notifications
|
||||
}
|
||||
}
|
||||
18
app/src/huawei/java/com/tangem/tap/di/HuaweiPushModule.kt
Normal file
18
app/src/huawei/java/com/tangem/tap/di/HuaweiPushModule.kt
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.tap.di
|
||||
|
||||
import com.tangem.tap.HuaweiPushNotificationsTokenProvider
|
||||
import com.tangem.utils.notifications.PushNotificationsTokenProvider
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface HuaweiPushModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindPushNotificationsTokenProvider(impl: HuaweiPushNotificationsTokenProvider): PushNotificationsTokenProvider
|
||||
}
|
||||
|
|
@ -264,6 +264,16 @@
|
|||
android:host="promo"
|
||||
android:scheme="tangem" />
|
||||
</intent-filter>
|
||||
<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:host="onboard-visa"
|
||||
android:scheme="tangem" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Disable android.startup completely. Used for Worker according doc -->
|
||||
|
|
|
|||
|
|
@ -1,160 +1,165 @@
|
|||
{
|
||||
"coins" : [
|
||||
"coins": [
|
||||
{
|
||||
"id" : "matic-token",
|
||||
"symbol" : "MATIC",
|
||||
"name" : "Polygon",
|
||||
"networks" : [
|
||||
"id": "matic-token",
|
||||
"symbol": "MATIC",
|
||||
"name": "Polygon",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0x0000000000000000000000000000000000001010",
|
||||
"decimalCount" : 18
|
||||
"networkId": "polygon-pos/test",
|
||||
"contractAddress": "0x0000000000000000000000000000000000001010",
|
||||
"decimalCount": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "kaspa",
|
||||
"symbol" : "KAS",
|
||||
"name" : "Kaspa",
|
||||
"networks" : [
|
||||
"id": "kaspa",
|
||||
"symbol": "KAS",
|
||||
"name": "Kaspa",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "kaspa/test"
|
||||
"networkId": "kaspa/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Dai Stablecoin-DAI",
|
||||
"symbol" : "DAI",
|
||||
"name" : "Dai Stablecoin",
|
||||
"networks" : [
|
||||
"id": "Dai Stablecoin-DAI",
|
||||
"symbol": "DAI",
|
||||
"name": "Dai Stablecoin",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0xcB1e72786A6eb3b44C2a2429e317c8a2462CFeb1",
|
||||
"decimalCount" : 18
|
||||
"networkId": "polygon-pos/test",
|
||||
"contractAddress": "0xcB1e72786A6eb3b44C2a2429e317c8a2462CFeb1",
|
||||
"decimalCount": 18
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0xec5dcb5dbf4b114c9d0f65bccab49ec54f6a0867",
|
||||
"decimalCount" : 18
|
||||
"networkId": "binance-smart-chain/test",
|
||||
"contractAddress": "0xec5dcb5dbf4b114c9d0f65bccab49ec54f6a0867",
|
||||
"decimalCount": 18
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0x8a9424745056eb399fd19a0ec26a14316684e274",
|
||||
"decimalCount" : 18
|
||||
"networkId": "binance-smart-chain/test",
|
||||
"contractAddress": "0x8a9424745056eb399fd19a0ec26a14316684e274",
|
||||
"decimalCount": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Dummy ERC20-DERC20",
|
||||
"symbol" : "DERC20",
|
||||
"name" : "Dummy ERC20",
|
||||
"networks" : [
|
||||
"id": "Dummy ERC20-DERC20",
|
||||
"symbol": "DERC20",
|
||||
"name": "Dummy ERC20",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0xfe4F5145f6e09952a5ba9e956ED0C25e3Fa4c7F1",
|
||||
"decimalCount" : 18
|
||||
"networkId": "polygon-pos/test",
|
||||
"contractAddress": "0xfe4F5145f6e09952a5ba9e956ED0C25e3Fa4c7F1",
|
||||
"decimalCount": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Ether-ETH",
|
||||
"symbol" : "ETH",
|
||||
"name" : "Ether",
|
||||
"networks" : [
|
||||
"id": "Ether-ETH",
|
||||
"symbol": "ETH",
|
||||
"name": "Ether",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0x714550C2C1Ea08688607D86ed8EeF4f5E4F22323",
|
||||
"decimalCount" : 18
|
||||
"networkId": "polygon-pos/test",
|
||||
"contractAddress": "0x714550C2C1Ea08688607D86ed8EeF4f5E4F22323",
|
||||
"decimalCount": 18
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0xd66c6b4f0be8ce5b39d52e0fd1344c389929b378",
|
||||
"decimalCount" : 18
|
||||
"networkId": "binance-smart-chain/test",
|
||||
"contractAddress": "0xd66c6b4f0be8ce5b39d52e0fd1344c389929b378",
|
||||
"decimalCount": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Test Token-TST",
|
||||
"symbol" : "TST",
|
||||
"name" : "Test Token",
|
||||
"networks" : [
|
||||
"id": "Test Token-TST",
|
||||
"symbol": "TST",
|
||||
"name": "Test Token",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0x2d7882bedcbfddce29ba99965dd3cdf7fcb10a1e",
|
||||
"decimalCount" : 18
|
||||
"networkId": "polygon-pos/test",
|
||||
"contractAddress": "0x2d7882bedcbfddce29ba99965dd3cdf7fcb10a1e",
|
||||
"decimalCount": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "tether",
|
||||
"symbol" : "USDT",
|
||||
"name" : "Tether USD",
|
||||
"networks" : [
|
||||
"id": "tether",
|
||||
"symbol": "USDT",
|
||||
"name": "Tether USD",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0x3813e82e6f7098b9583FC0F33a962D02018B6803",
|
||||
"decimalCount" : 6
|
||||
"networkId": "ethereum/test",
|
||||
"contractAddress": "0xaA8E23Fb1079EA71e0a56F48a2aA51851D8433D0",
|
||||
"decimalCount": 18
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0x337610d27c682e347c9cd60bd4b3b107c9d34ddd",
|
||||
"decimalCount" : 18
|
||||
"networkId": "polygon-pos/test",
|
||||
"contractAddress": "0x3813e82e6f7098b9583FC0F33a962D02018B6803",
|
||||
"decimalCount": 6
|
||||
},
|
||||
{
|
||||
"networkId" : "tron/test",
|
||||
"contractAddress" : "TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj",
|
||||
"decimalCount" : 6
|
||||
"networkId": "binance-smart-chain/test",
|
||||
"contractAddress": "0x337610d27c682e347c9cd60bd4b3b107c9d34ddd",
|
||||
"decimalCount": 18
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0x7ef95a0fee0dd31b22626fa2e10ee6a223f8a684",
|
||||
"decimalCount" : 18
|
||||
"networkId": "tron/test",
|
||||
"contractAddress": "TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj",
|
||||
"decimalCount": 6
|
||||
},
|
||||
{
|
||||
"networkId": "binance-smart-chain/test",
|
||||
"contractAddress": "0x7ef95a0fee0dd31b22626fa2e10ee6a223f8a684",
|
||||
"decimalCount": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "just-gov",
|
||||
"symbol" : "JST",
|
||||
"name" : "JUST GOV",
|
||||
"networks" : [
|
||||
"id": "just-gov",
|
||||
"symbol": "JST",
|
||||
"name": "JUST GOV",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "tron/test",
|
||||
"contractAddress" : "TF17BgPaZYbz8oxbjhriubPDsA7ArKoLX3",
|
||||
"decimalCount" : 18
|
||||
"networkId": "tron/test",
|
||||
"contractAddress": "TF17BgPaZYbz8oxbjhriubPDsA7ArKoLX3",
|
||||
"decimalCount": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Wrapped Ether-WETH",
|
||||
"symbol" : "WETH",
|
||||
"name" : "Wrapped Ether",
|
||||
"networks" : [
|
||||
"id": "Wrapped Ether-WETH",
|
||||
"symbol": "WETH",
|
||||
"name": "Wrapped Ether",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0xA6FA4fB5f76172d178d61B04b0ecd319C5d1C0aa",
|
||||
"decimalCount" : 18
|
||||
"networkId": "polygon-pos/test",
|
||||
"contractAddress": "0xA6FA4fB5f76172d178d61B04b0ecd319C5d1C0aa",
|
||||
"decimalCount": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Wrapped Matic-WMATIC",
|
||||
"symbol" : "WMATIC",
|
||||
"name" : "Wrapped Matic",
|
||||
"networks" : [
|
||||
"id": "Wrapped Matic-WMATIC",
|
||||
"symbol": "WMATIC",
|
||||
"name": "Wrapped Matic",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0xd0A1E359811322d97991E03f863a0C30C2cF029C",
|
||||
"decimalCount" : 18
|
||||
"networkId": "polygon-pos/test",
|
||||
"contractAddress": "0xd0A1E359811322d97991E03f863a0C30C2cF029C",
|
||||
"decimalCount": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "solana",
|
||||
"symbol" : "SOL",
|
||||
"name" : "Solana",
|
||||
"networks" : [
|
||||
"id": "solana",
|
||||
"symbol": "SOL",
|
||||
"name": "Solana",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "solana/test"
|
||||
"networkId": "solana/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -162,263 +167,262 @@
|
|||
"id": "the-open-network",
|
||||
"symbol": "TON",
|
||||
"name": "Toncoin",
|
||||
"networks":
|
||||
[
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "the-open-network/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Tangem Coin A-TCA",
|
||||
"symbol" : "TCA",
|
||||
"name" : "Tangem Coin A",
|
||||
"networks" : [
|
||||
"id": "Tangem Coin A-TCA",
|
||||
"symbol": "TCA",
|
||||
"name": "Tangem Coin A",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "solana/test",
|
||||
"contractAddress" : "22PTNbX31Zuztd6nD82fC8nQdT2hUfWv9XWXKuDkrFqR",
|
||||
"decimalCount" : 9
|
||||
"networkId": "solana/test",
|
||||
"contractAddress": "22PTNbX31Zuztd6nD82fC8nQdT2hUfWv9XWXKuDkrFqR",
|
||||
"decimalCount": 9
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Tangem Coin B-TCB",
|
||||
"symbol" : "TCB",
|
||||
"name" : "Tangem Coin B",
|
||||
"networks" : [
|
||||
"id": "Tangem Coin B-TCB",
|
||||
"symbol": "TCB",
|
||||
"name": "Tangem Coin B",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "solana/test",
|
||||
"contractAddress" : "HmSghNPg6KCk711YJA92aPejt8auyFkvmaED6jbHfUs4",
|
||||
"decimalCount" : 9
|
||||
"networkId": "solana/test",
|
||||
"contractAddress": "HmSghNPg6KCk711YJA92aPejt8auyFkvmaED6jbHfUs4",
|
||||
"decimalCount": 9
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "binancecoin",
|
||||
"symbol" : "BNB",
|
||||
"name" : "Binance",
|
||||
"networks" : [
|
||||
"id": "binancecoin",
|
||||
"symbol": "BNB",
|
||||
"name": "Binance",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "binancecoin/test"
|
||||
"networkId": "binancecoin/test"
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test"
|
||||
"networkId": "binance-smart-chain/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Hemster - 452-HEM",
|
||||
"symbol" : "HEM",
|
||||
"name" : "Hemster - 452",
|
||||
"networks" : [
|
||||
"id": "Hemster - 452-HEM",
|
||||
"symbol": "HEM",
|
||||
"name": "Hemster - 452",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "binancecoin/test",
|
||||
"contractAddress" : "HEM-452",
|
||||
"decimalCount" : 8
|
||||
"networkId": "binancecoin/test",
|
||||
"contractAddress": "HEM-452",
|
||||
"decimalCount": 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Binance-Peg BTCB Token-BTCB",
|
||||
"symbol" : "BTCB",
|
||||
"name" : "Binance-Peg BTCB Token",
|
||||
"networks" : [
|
||||
"id": "Binance-Peg BTCB Token-BTCB",
|
||||
"symbol": "BTCB",
|
||||
"name": "Binance-Peg BTCB Token",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0x6ce8da28e2f864420840cf74474eff5fd80e65b8",
|
||||
"decimalCount" : 18
|
||||
"networkId": "binance-smart-chain/test",
|
||||
"contractAddress": "0x6ce8da28e2f864420840cf74474eff5fd80e65b8",
|
||||
"decimalCount": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Binance-Peg BUSD Token-BUSD",
|
||||
"symbol" : "BUSD",
|
||||
"name" : "Binance-Peg BUSD Token",
|
||||
"networks" : [
|
||||
"id": "Binance-Peg BUSD Token-BUSD",
|
||||
"symbol": "BUSD",
|
||||
"name": "Binance-Peg BUSD Token",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0xed24fc36d5ee211ea25a80239fb8c4cfd80f12ee",
|
||||
"decimalCount" : 18
|
||||
"networkId": "binance-smart-chain/test",
|
||||
"contractAddress": "0xed24fc36d5ee211ea25a80239fb8c4cfd80f12ee",
|
||||
"decimalCount": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Binance-Peg USDC Token-USDC",
|
||||
"symbol" : "USDC",
|
||||
"name" : "Binance-Peg USDC Token",
|
||||
"networks" : [
|
||||
"id": "Binance-Peg USDC Token-USDC",
|
||||
"symbol": "USDC",
|
||||
"name": "Binance-Peg USDC Token",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0x64544969ed7ebf5f083679233325356ebe738930",
|
||||
"decimalCount" : 18
|
||||
"networkId": "binance-smart-chain/test",
|
||||
"contractAddress": "0x64544969ed7ebf5f083679233325356ebe738930",
|
||||
"decimalCount": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Binance-Peg XRP-XRP",
|
||||
"symbol" : "XRP",
|
||||
"name" : "Binance-Peg XRP",
|
||||
"networks" : [
|
||||
"id": "Binance-Peg XRP-XRP",
|
||||
"symbol": "XRP",
|
||||
"name": "Binance-Peg XRP",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0xa83575490d7df4e2f47b7d38ef351a2722ca45b9",
|
||||
"decimalCount" : 18
|
||||
"networkId": "binance-smart-chain/test",
|
||||
"contractAddress": "0xa83575490d7df4e2f47b7d38ef351a2722ca45b9",
|
||||
"decimalCount": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "ethereum",
|
||||
"symbol" : "ETH",
|
||||
"name" : "Ethereum",
|
||||
"networks" : [
|
||||
"id": "ethereum",
|
||||
"symbol": "ETH",
|
||||
"name": "Ethereum",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "ethereum/test"
|
||||
"networkId": "ethereum/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "ethereum-classic",
|
||||
"symbol" : "ETC",
|
||||
"name" : "Ethereum Classic",
|
||||
"networks" : [
|
||||
"id": "ethereum-classic",
|
||||
"symbol": "ETC",
|
||||
"name": "Ethereum Classic",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "ethereum-classic/test"
|
||||
"networkId": "ethereum-classic/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Weenus-WEENUS",
|
||||
"symbol" : "WEENUS",
|
||||
"name" : "Weenus",
|
||||
"networks" : [
|
||||
"id": "Weenus-WEENUS",
|
||||
"symbol": "WEENUS",
|
||||
"name": "Weenus",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "ethereum/test",
|
||||
"contractAddress" : "0xaFF4481D10270F50f203E0763e2597776068CBc5",
|
||||
"decimalCount" : 18
|
||||
"networkId": "ethereum/test",
|
||||
"contractAddress": "0xaFF4481D10270F50f203E0763e2597776068CBc5",
|
||||
"decimalCount": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Xeenus-XEENUS",
|
||||
"symbol" : "XEENUS",
|
||||
"name" : "Xeenus",
|
||||
"networks" : [
|
||||
"id": "Xeenus-XEENUS",
|
||||
"symbol": "XEENUS",
|
||||
"name": "Xeenus",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "ethereum/test",
|
||||
"contractAddress" : "0x022E292b44B5a146F2e8ee36Ff44D3dd863C915c",
|
||||
"decimalCount" : 18
|
||||
"networkId": "ethereum/test",
|
||||
"contractAddress": "0x022E292b44B5a146F2e8ee36Ff44D3dd863C915c",
|
||||
"decimalCount": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Yeenus-YEENUS",
|
||||
"symbol" : "YEENUS",
|
||||
"name" : "Yeenus",
|
||||
"networks" : [
|
||||
"id": "Yeenus-YEENUS",
|
||||
"symbol": "YEENUS",
|
||||
"name": "Yeenus",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "ethereum/test",
|
||||
"contractAddress" : "0xc6fDe3FD2Cc2b173aEC24cc3f267cb3Cd78a26B7",
|
||||
"decimalCount" : 8
|
||||
"networkId": "ethereum/test",
|
||||
"contractAddress": "0xc6fDe3FD2Cc2b173aEC24cc3f267cb3Cd78a26B7",
|
||||
"decimalCount": 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Zeenus-ZEENUS",
|
||||
"symbol" : "ZEENUS",
|
||||
"name" : "Zeenus",
|
||||
"networks" : [
|
||||
"id": "Zeenus-ZEENUS",
|
||||
"symbol": "ZEENUS",
|
||||
"name": "Zeenus",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "ethereum/test",
|
||||
"contractAddress" : "0x1f9061B953bBa0E36BF50F21876132DcF276fC6e",
|
||||
"decimalCount" : 0
|
||||
"networkId": "ethereum/test",
|
||||
"contractAddress": "0x1f9061B953bBa0E36BF50F21876132DcF276fC6e",
|
||||
"decimalCount": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "avalanche-2",
|
||||
"symbol" : "AVAX",
|
||||
"name" : "Avalanche",
|
||||
"networks" : [
|
||||
"id": "avalanche-2",
|
||||
"symbol": "AVAX",
|
||||
"name": "Avalanche",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "avalanche/test"
|
||||
"networkId": "avalanche/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "The Fuji stablecoin-FUJISTABLE",
|
||||
"symbol" : "FUJISTABLE",
|
||||
"name" : "The Fuji stablecoin",
|
||||
"networks" : [
|
||||
"id": "The Fuji stablecoin-FUJISTABLE",
|
||||
"symbol": "FUJISTABLE",
|
||||
"name": "The Fuji stablecoin",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "avalanche/test",
|
||||
"contractAddress" : "0x2058ec2791dD28b6f67DB836ddf87534F4Bbdf22",
|
||||
"decimalCount" : 6
|
||||
"networkId": "avalanche/test",
|
||||
"contractAddress": "0x2058ec2791dD28b6f67DB836ddf87534F4Bbdf22",
|
||||
"decimalCount": 6
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "To the Moon-FUJIMOON",
|
||||
"symbol" : "FUJIMOON",
|
||||
"name" : "To the Moon",
|
||||
"networks" : [
|
||||
"id": "To the Moon-FUJIMOON",
|
||||
"symbol": "FUJIMOON",
|
||||
"name": "To the Moon",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "avalanche/test",
|
||||
"contractAddress" : "0x97132C109c6816525F7f338DCb7435E1412A7668",
|
||||
"decimalCount" : 9
|
||||
"networkId": "avalanche/test",
|
||||
"contractAddress": "0x97132C109c6816525F7f338DCb7435E1412A7668",
|
||||
"decimalCount": 9
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "fantom",
|
||||
"symbol" : "FTM",
|
||||
"name" : "Fantom",
|
||||
"networks" : [
|
||||
"id": "fantom",
|
||||
"symbol": "FTM",
|
||||
"name": "Fantom",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "fantom/test"
|
||||
"networkId": "fantom/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Fantom USD-FUSD",
|
||||
"symbol" : "FUSD",
|
||||
"name" : "Fantom USD",
|
||||
"networks" : [
|
||||
"id": "Fantom USD-FUSD",
|
||||
"symbol": "FUSD",
|
||||
"name": "Fantom USD",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "fantom/test",
|
||||
"contractAddress" : "0x91ea991bd52EE3C40EdA2509701d905e1Ee54074",
|
||||
"decimalCount" : 18
|
||||
"networkId": "fantom/test",
|
||||
"contractAddress": "0x91ea991bd52EE3C40EdA2509701d905e1Ee54074",
|
||||
"decimalCount": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Wrapped Fantom-WFTM",
|
||||
"symbol" : "WFTM",
|
||||
"name" : "Wrapped Fantom",
|
||||
"networks" : [
|
||||
"id": "Wrapped Fantom-WFTM",
|
||||
"symbol": "WFTM",
|
||||
"name": "Wrapped Fantom",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "fantom/test",
|
||||
"contractAddress" : "0xf1277d1Ed8AD466beddF92ef448A132661956621",
|
||||
"decimalCount" : 18
|
||||
"networkId": "fantom/test",
|
||||
"contractAddress": "0xf1277d1Ed8AD466beddF92ef448A132661956621",
|
||||
"decimalCount": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "bitcoin",
|
||||
"symbol" : "BTC",
|
||||
"name" : "Bitcoin",
|
||||
"networks" : [
|
||||
"id": "bitcoin",
|
||||
"symbol": "BTC",
|
||||
"name": "Bitcoin",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "bitcoin/test"
|
||||
"networkId": "bitcoin/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "vechain",
|
||||
"symbol" : "VET",
|
||||
"name" : "VeChain",
|
||||
"networks" : [
|
||||
"id": "vechain",
|
||||
"symbol": "VET",
|
||||
"name": "VeChain",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "vechain/test"
|
||||
"networkId": "vechain/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -435,34 +439,34 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id" : "tron",
|
||||
"symbol" : "TRX",
|
||||
"name" : "Tron",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "tron/test"
|
||||
}
|
||||
]
|
||||
"id": "tron",
|
||||
"symbol": "TRX",
|
||||
"name": "Tron",
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "tron/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "algorand",
|
||||
"symbol" : "ALGO",
|
||||
"name" : "Algorand",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "algorand/test"
|
||||
}
|
||||
]
|
||||
"id": "algorand",
|
||||
"symbol": "ALGO",
|
||||
"name": "Algorand",
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "algorand/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "arbitrum-one",
|
||||
"symbol" : "ETH",
|
||||
"name" : "Arbitrum",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "arbitrum-one/test"
|
||||
}
|
||||
]
|
||||
"id": "arbitrum-one",
|
||||
"symbol": "ETH",
|
||||
"name": "Arbitrum",
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "arbitrum-one/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "stellar",
|
||||
|
|
@ -485,12 +489,12 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id" : "polkadot",
|
||||
"symbol" : "DOT",
|
||||
"name" : "Polkadot",
|
||||
"networks" : [
|
||||
"id": "polkadot",
|
||||
"symbol": "DOT",
|
||||
"name": "Polkadot",
|
||||
"networks": [
|
||||
{
|
||||
"networkId" : "polkadot/test"
|
||||
"networkId": "polkadot/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -509,8 +513,7 @@
|
|||
"id": "kava",
|
||||
"symbol": "KAVA",
|
||||
"name": "Kava EVM",
|
||||
"networks":
|
||||
[
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "kava/test"
|
||||
}
|
||||
|
|
@ -520,8 +523,7 @@
|
|||
"id": "telos",
|
||||
"symbol": "TLOS",
|
||||
"name": "Telos EVM",
|
||||
"networks":
|
||||
[
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "telos/test"
|
||||
}
|
||||
|
|
@ -531,8 +533,7 @@
|
|||
"id": "ravencoin",
|
||||
"symbol": "RVN",
|
||||
"name": "Ravencoin",
|
||||
"networks":
|
||||
[
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "ravencoin/test"
|
||||
}
|
||||
|
|
@ -542,8 +543,7 @@
|
|||
"id": "cosmos",
|
||||
"symbol": "ATOM",
|
||||
"name": "Cosmos Hub",
|
||||
"networks":
|
||||
[
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "cosmos/test"
|
||||
}
|
||||
|
|
@ -798,6 +798,30 @@
|
|||
"networkId": "alephium/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "chainlink",
|
||||
"name": "Chainlink",
|
||||
"symbol": "LINK",
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "ethereum/test",
|
||||
"contractAddress": "0xf8Fb3713D459D7C1018BD0A49D19b4C44290EBE5",
|
||||
"decimalCount": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gho",
|
||||
"name": "GHO",
|
||||
"symbol": "GHO",
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "ethereum/test",
|
||||
"contractAddress": "0xc4bF5CbDaBE595361438F8c6a187bDc330539c60",
|
||||
"decimalCount": 18
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,12 +29,13 @@ import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
|||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.feedback.GetCardInfoUseCase
|
||||
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.onboarding.repository.OnboardingRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
|
|
@ -50,7 +51,6 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
|||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository as WalletConnect2Repository
|
||||
|
||||
@EntryPoint
|
||||
@InstallIn(SingletonComponent::class)
|
||||
|
|
@ -71,8 +71,6 @@ interface ApplicationEntryPoint {
|
|||
|
||||
fun getCardScanningFeatureToggles(): CardScanningFeatureToggles
|
||||
|
||||
fun getWalletConnect2Repository(): WalletConnect2Repository
|
||||
|
||||
fun getScanCardProcessor(): ScanCardProcessor
|
||||
|
||||
fun getAppCurrencyRepository(): AppCurrencyRepository
|
||||
|
|
@ -107,7 +105,7 @@ interface ApplicationEntryPoint {
|
|||
|
||||
fun getSendFeedbackEmailUseCase(): SendFeedbackEmailUseCase
|
||||
|
||||
fun getGetCardInfoUseCase(): GetCardInfoUseCase
|
||||
fun getWalletMetaInfoUseCase(): GetWalletMetaInfoUseCase
|
||||
|
||||
fun getUrlOpener(): UrlOpener
|
||||
|
||||
|
|
@ -151,4 +149,6 @@ interface ApplicationEntryPoint {
|
|||
fun getTangemHotSdk(): TangemHotSdk
|
||||
|
||||
fun getHotWalletFeatureToggles(): HotWalletFeatureToggles
|
||||
|
||||
fun getWcInitializeUseCase(): WcInitializeUseCase
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import com.tangem.domain.settings.repositories.SettingsRepository
|
|||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.legacy.asLockable
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.wallets.usecase.ClearAllHotWalletContextualUnlockUseCase
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.tap.LockTimerWorker.Companion.TAG
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
|
|
@ -22,6 +23,7 @@ import timber.log.Timber
|
|||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.time.Duration
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class LockUserWalletsTimer(
|
||||
private val context: Context,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
|
|
@ -30,6 +32,7 @@ internal class LockUserWalletsTimer(
|
|||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val clearAllHotWalletContextualUnlockUseCase: ClearAllHotWalletContextualUnlockUseCase,
|
||||
) : LifecycleOwner by context as LifecycleOwner,
|
||||
DefaultLifecycleObserver {
|
||||
|
||||
|
|
@ -55,6 +58,9 @@ internal class LockUserWalletsTimer(
|
|||
)
|
||||
|
||||
if (shouldOpenWelcomeScreenOnResume) {
|
||||
if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
clearAllHotWalletContextualUnlockUseCase.invoke()
|
||||
}
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) }
|
||||
settingsRepository.setShouldOpenWelcomeScreenOnResume(value = false)
|
||||
}
|
||||
|
|
@ -120,6 +126,7 @@ internal class LockUserWalletsTimer(
|
|||
start()
|
||||
}
|
||||
.onRight {
|
||||
clearAllHotWalletContextualUnlockUseCase.invoke()
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import com.tangem.domain.apptheme.model.AppThemeMode
|
|||
import com.tangem.domain.card.ScanCardUseCase
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.settings.SetGooglePayAvailabilityUseCase
|
||||
import com.tangem.domain.settings.SetGoogleServicesAvailabilityUseCase
|
||||
|
|
@ -49,12 +50,11 @@ import com.tangem.domain.staking.SendUnsubmittedHashesUseCase
|
|||
import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase
|
||||
import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.wallets.usecase.ClearAllHotWalletContextualUnlockUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.features.tangempay.TangemPayFeatureToggles
|
||||
import com.tangem.features.tester.api.TesterMenuLauncher
|
||||
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
|
||||
import com.tangem.google.GoogleServicesHelper
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.sdk.api.BackupServiceHolder
|
||||
|
|
@ -64,11 +64,9 @@ import com.tangem.tap.common.DialogManager
|
|||
import com.tangem.tap.common.OnActivityResultCallback
|
||||
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
import com.tangem.tap.features.intentHandler.IntentProcessor
|
||||
import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler
|
||||
import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler
|
||||
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
|
||||
import com.tangem.tap.features.main.MainViewModel
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphAction
|
||||
import com.tangem.tap.routing.component.RoutingComponent
|
||||
|
|
@ -116,9 +114,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
@Inject
|
||||
lateinit var scanCardUseCase: ScanCardUseCase
|
||||
|
||||
@Inject
|
||||
lateinit var walletConnectInteractor: WalletConnectInteractor
|
||||
|
||||
@Inject
|
||||
lateinit var settingsRepository: SettingsRepository
|
||||
|
||||
|
|
@ -171,9 +166,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
@Inject
|
||||
internal lateinit var deeplinkFactory: DeepLinkFactory
|
||||
|
||||
@Inject
|
||||
internal lateinit var walletConnectFeatureToggles: WalletConnectFeatureToggles
|
||||
|
||||
@Inject
|
||||
internal lateinit var urlOpener: UrlOpener
|
||||
|
||||
|
|
@ -183,9 +175,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
@Inject
|
||||
internal lateinit var intentProcessor: IntentProcessor
|
||||
|
||||
@Inject
|
||||
internal lateinit var walletConnectLinkIntentHandler: WalletConnectLinkIntentHandler
|
||||
|
||||
@Inject
|
||||
internal lateinit var onPushClickedIntentHandler: OnPushClickedIntentHandler
|
||||
|
||||
|
|
@ -198,10 +187,13 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
@Inject
|
||||
internal lateinit var hotWalletFeatureToggles: HotWalletFeatureToggles
|
||||
|
||||
@Inject
|
||||
internal lateinit var clearAllHotWalletContextualUnlockUseCase: ClearAllHotWalletContextualUnlockUseCase
|
||||
|
||||
@Inject
|
||||
internal lateinit var tangemPayFeatureToggles: TangemPayFeatureToggles
|
||||
|
||||
internal val viewModel: MainViewModel by viewModels()
|
||||
private val viewModel: MainViewModel by viewModels()
|
||||
|
||||
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
|
||||
|
||||
|
|
@ -286,6 +278,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
coroutineScope = mainScope,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
hotWalletFeatureToggles = hotWalletFeatureToggles,
|
||||
clearAllHotWalletContextualUnlockUseCase = clearAllHotWalletContextualUnlockUseCase,
|
||||
)
|
||||
|
||||
initIntentHandlers()
|
||||
|
|
@ -293,7 +286,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
store.dispatch(
|
||||
DaggerGraphAction.SetActivityDependencies(
|
||||
scanCardUseCase = scanCardUseCase,
|
||||
walletConnectInteractor = walletConnectInteractor,
|
||||
cardSdkConfigRepository = cardSdkConfigRepository,
|
||||
),
|
||||
)
|
||||
|
|
@ -369,10 +361,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
|
||||
private fun initIntentHandlers() {
|
||||
intentProcessor.addHandler(onPushClickedIntentHandler)
|
||||
|
||||
if (!walletConnectFeatureToggles.isRedesignedWalletConnectEnabled) {
|
||||
intentProcessor.addHandler(walletConnectLinkIntentHandler)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAppTheme(appThemeMode: AppThemeMode) {
|
||||
|
|
@ -525,8 +513,11 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
|
||||
// Workaround to navigate to TangemPayDetails screen. Will be deleted in next PRs
|
||||
if (tangemPayFeatureToggles.isTangemPayEnabled) {
|
||||
store.dispatchNavigationAction {
|
||||
replaceAll(AppRoute.TangemPayDetails)
|
||||
lifecycleScope.launch {
|
||||
val selectedUserWalledId = userWalletsListRepository.selectedUserWalletSync()?.walletId
|
||||
store.dispatchNavigationAction {
|
||||
replaceAll(AppRoute.TangemPayDetails(requireNotNull(selectedUserWalledId)))
|
||||
}
|
||||
}
|
||||
} else if (userWalletsListManager.isLockable && userWalletsListManager.hasUserWallets) {
|
||||
store.dispatchNavigationAction {
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
|||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.domain.feedback.GetCardInfoUseCase
|
||||
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
||||
|
|
@ -75,9 +75,12 @@ import com.tangem.tap.proxy.redux.DaggerGraphState
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import dagger.hilt.EntryPoints
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.rekotlin.Store
|
||||
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository as WalletConnect2Repository
|
||||
import timber.log.Timber
|
||||
|
||||
lateinit var store: Store<AppState>
|
||||
|
||||
|
|
@ -111,9 +114,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
private val cardScanningFeatureToggles: CardScanningFeatureToggles
|
||||
get() = entryPoint.getCardScanningFeatureToggles()
|
||||
|
||||
private val walletConnect2Repository: WalletConnect2Repository
|
||||
get() = entryPoint.getWalletConnect2Repository()
|
||||
|
||||
private val scanCardProcessor: ScanCardProcessor
|
||||
get() = entryPoint.getScanCardProcessor()
|
||||
|
||||
|
|
@ -165,8 +165,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase
|
||||
get() = entryPoint.getSendFeedbackEmailUseCase()
|
||||
|
||||
private val getCardInfoUseCase: GetCardInfoUseCase
|
||||
get() = entryPoint.getGetCardInfoUseCase()
|
||||
private val getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase
|
||||
get() = entryPoint.getWalletMetaInfoUseCase()
|
||||
|
||||
private val urlOpener
|
||||
get() = entryPoint.getUrlOpener()
|
||||
|
|
@ -236,6 +236,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
private val hotWalletFeatureToggles
|
||||
get() = entryPoint.getHotWalletFeatureToggles()
|
||||
|
||||
private val wcInitializeUseCase
|
||||
get() = entryPoint.getWcInitializeUseCase()
|
||||
|
||||
// endregion
|
||||
|
||||
private val appScope = MainScope()
|
||||
|
|
@ -289,15 +292,16 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
|
||||
tangemAppLoggerInitializer.initialize()
|
||||
|
||||
Timber.i("APP STARTED")
|
||||
if (BuildConfig.TESTER_MENU_ENABLED) {
|
||||
Timber.i(featureTogglesManager.toString())
|
||||
Timber.i(excludedBlockchainsManager.toString())
|
||||
}
|
||||
|
||||
foregroundActivityObserver = ForegroundActivityObserver()
|
||||
registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks)
|
||||
|
||||
// We need to initialize the toggles and excludedBlockchainsManager before the MainActivity starts using them.
|
||||
runBlocking {
|
||||
awaitAll(
|
||||
async { featureTogglesManager.init() },
|
||||
async { excludedBlockchainsManager.init() },
|
||||
)
|
||||
initWithConfigDependency(environmentConfig = environmentConfigStorage.initialize())
|
||||
}
|
||||
|
||||
|
|
@ -329,7 +333,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
)
|
||||
|
||||
appStateHolder.mainStore = store
|
||||
walletConnect2Repository.init(
|
||||
|
||||
wcInitializeUseCase.init(
|
||||
projectId = environmentConfigStorage.getConfigSync().walletConnectProjectId,
|
||||
)
|
||||
}
|
||||
|
|
@ -342,7 +347,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
daggerGraphState = DaggerGraphState(
|
||||
networkConnectionManager = networkConnectionManager,
|
||||
cardScanningFeatureToggles = cardScanningFeatureToggles,
|
||||
walletConnectRepository = walletConnect2Repository,
|
||||
scanCardProcessor = scanCardProcessor,
|
||||
appCurrencyRepository = appCurrencyRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
|
|
@ -357,7 +361,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
settingsRepository = settingsRepository,
|
||||
blockchainSDKFactory = blockchainSDKFactory,
|
||||
sendFeedbackEmailUseCase = sendFeedbackEmailUseCase,
|
||||
getCardInfoUseCase = getCardInfoUseCase,
|
||||
getWalletMetaInfoUseCase = getWalletMetaInfoUseCase,
|
||||
issuersConfigStorage = issuersConfigStorage,
|
||||
urlOpener = urlOpener,
|
||||
shareManager = shareManager,
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@ import android.content.Context
|
|||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.global.GlobalState
|
||||
import com.tangem.tap.common.ui.*
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.*
|
||||
import com.tangem.tap.common.ui.ScanFailsDialog
|
||||
import com.tangem.tap.common.ui.SimpleAlertDialog
|
||||
import com.tangem.tap.common.ui.SimpleCancelableAlertDialog
|
||||
import com.tangem.tap.common.ui.SimpleOkDialog
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.ConfirmDiscardingBackupDialog
|
||||
|
|
@ -59,88 +60,6 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
context = context,
|
||||
)
|
||||
is OnboardingDialog.WalletActivationError -> WalletActivationErrorDialog.create(context, state.dialog)
|
||||
is WalletConnectDialog.UnsupportedCard ->
|
||||
SimpleAlertDialog.create(
|
||||
titleRes = R.string.wallet_connect_title,
|
||||
messageRes = R.string.wallet_connect_scanner_error_not_valid_card,
|
||||
context = context,
|
||||
)
|
||||
is WalletConnectDialog.AddNetwork -> {
|
||||
val message = context.getString(
|
||||
R.string.wallet_connect_error_missing_blockchains,
|
||||
) + state.dialog.networks.joinToString()
|
||||
SimpleAlertDialog.create(
|
||||
titleRes = R.string.wallet_connect_title,
|
||||
message = message,
|
||||
context = context,
|
||||
)
|
||||
}
|
||||
is WalletConnectDialog.OpeningSessionRejected -> {
|
||||
SimpleAlertDialog.create(
|
||||
titleRes = R.string.wallet_connect_title,
|
||||
messageRes = R.string.wallet_connect_same_wcuri,
|
||||
context = context,
|
||||
)
|
||||
}
|
||||
is WalletConnectDialog.SessionTimeout -> {
|
||||
SimpleAlertDialog.create(
|
||||
titleRes = R.string.wallet_connect_title,
|
||||
messageRes = R.string.wallet_connect_error_timeout,
|
||||
context = context,
|
||||
)
|
||||
}
|
||||
is WalletConnectDialog.RequestTransaction -> TransactionDialog.create(state.dialog.data, context)
|
||||
is WalletConnectDialog.PersonalSign -> PersonalSignDialog.create(state.dialog.data, context)
|
||||
is WalletConnectDialog.BnbTransactionDialog ->
|
||||
BnbTransactionDialog.create(
|
||||
preparedData = state.dialog.data,
|
||||
context = context,
|
||||
)
|
||||
is WalletConnectDialog.UnsupportedNetwork -> {
|
||||
val warning = if (state.dialog.networks.isNullOrEmpty()) {
|
||||
context.getString(R.string.wallet_connect_scanner_error_unsupported_network)
|
||||
} else {
|
||||
context.getString(R.string.wallet_connect_error_unsupported_blockchains) +
|
||||
state.dialog.networks.joinToString()
|
||||
}
|
||||
SimpleAlertDialog.create(
|
||||
titleRes = R.string.wallet_connect_title,
|
||||
message = warning,
|
||||
context = context,
|
||||
)
|
||||
}
|
||||
is WalletConnectDialog.UnsupportedDapp -> SimpleAlertDialog.create(
|
||||
titleRes = R.string.wallet_connect_title,
|
||||
messageRes = R.string.wallet_connect_error_unsupported_dapp,
|
||||
context = context,
|
||||
)
|
||||
is WalletConnectDialog.SessionProposalDialog -> {
|
||||
SessionProposalDialog.create(
|
||||
sessionProposal = state.dialog.sessionProposal,
|
||||
networks = state.dialog.networks,
|
||||
context = context,
|
||||
onApprove = state.dialog.onApprove,
|
||||
onReject = state.dialog.onReject,
|
||||
)
|
||||
}
|
||||
is WalletConnectDialog.SignTransactionDialog -> SignTransactionDialog.create(
|
||||
preparedData = state.dialog.data,
|
||||
context = context,
|
||||
)
|
||||
is WalletConnectDialog.SignTransactionsDialog -> SignTransactionsDialog.create(
|
||||
preparedData = state.dialog.data,
|
||||
context = context,
|
||||
)
|
||||
is WalletConnectDialog.PairConnectErrorDialog -> SimpleAlertDialog.create(
|
||||
titleRes = R.string.wallet_connect_title,
|
||||
message = state.dialog.error.message,
|
||||
context = context,
|
||||
)
|
||||
is WalletConnectDialog.UnsupportedWcVersion -> SimpleAlertDialog.create(
|
||||
titleRes = R.string.common_error,
|
||||
messageRes = R.string.unsupported_wc_version,
|
||||
context = context,
|
||||
)
|
||||
is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(
|
||||
context = context,
|
||||
scanResponse = state.dialog.scanResponse,
|
||||
|
|
|
|||
|
|
@ -1,120 +0,0 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.OpenSession.SourceType
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal sealed class WalletConnect(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Wallet Connect", event, params) {
|
||||
|
||||
class ScreenOpened : WalletConnect(event = "WC Screen Opened")
|
||||
class NewSessionInitiated(source: SourceType) : WalletConnect(
|
||||
event = "Session Initiated",
|
||||
params = mapOf(
|
||||
AnalyticsParam.SOURCE to when (source) {
|
||||
SourceType.QR -> "QR"
|
||||
SourceType.DEEPLINK -> "DeepLink"
|
||||
SourceType.CLIPBOARD -> "Clipboard"
|
||||
SourceType.ETC -> "etc"
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
data object SessionFailed : WalletConnect(
|
||||
event = "Session Failed",
|
||||
)
|
||||
|
||||
class DAppConnectionRequested(
|
||||
blockchainNames: List<String>,
|
||||
) : WalletConnect(
|
||||
event = "dApp Connection Requested",
|
||||
params = mapOf(
|
||||
AnalyticsParam.NETWORKS to blockchainNames.joinToString(","),
|
||||
),
|
||||
)
|
||||
|
||||
class DAppConnected(dAppName: String, dAppUrl: String, blockchainNames: List<String>) : WalletConnect(
|
||||
event = "dApp Connected",
|
||||
params = mapOf(
|
||||
AnalyticsParam.DAPP_NAME to dAppName,
|
||||
AnalyticsParam.DAPP_URL to dAppUrl,
|
||||
AnalyticsParam.BLOCKCHAIN to blockchainNames.joinToString(","),
|
||||
),
|
||||
)
|
||||
|
||||
class DAppConnectionFailed(dAppName: String, dAppUrl: String, blockchainNames: List<String>) : WalletConnect(
|
||||
event = "dApp Connection Failed",
|
||||
params = mapOf(
|
||||
AnalyticsParam.DAPP_NAME to dAppName,
|
||||
AnalyticsParam.DAPP_URL to dAppUrl,
|
||||
AnalyticsParam.BLOCKCHAIN to blockchainNames.joinToString(","),
|
||||
),
|
||||
)
|
||||
|
||||
class SessionDisconnected(dAppName: String, dAppUrl: String) : WalletConnect(
|
||||
event = "dApp Disconnected",
|
||||
params = mapOf(
|
||||
AnalyticsParam.DAPP_NAME to dAppName,
|
||||
AnalyticsParam.DAPP_URL to dAppUrl,
|
||||
),
|
||||
)
|
||||
|
||||
class SignatureRequestHandled(
|
||||
params: RequestHandledParams,
|
||||
) : WalletConnect(
|
||||
event = "Signature Request Handled",
|
||||
params = params.toParamsMap(),
|
||||
)
|
||||
|
||||
class SignatureRequestReceived(
|
||||
params: RequestHandledParams,
|
||||
) : WalletConnect(
|
||||
event = "Signature Request Received",
|
||||
params = params.toParamsMap(),
|
||||
)
|
||||
|
||||
class SignatureRequestFailed(
|
||||
params: RequestHandledParams,
|
||||
) : WalletConnect(
|
||||
event = "Signature Request Failed",
|
||||
params = params.toParamsMap(),
|
||||
)
|
||||
|
||||
data class RequestHandledParams(
|
||||
val dAppName: String,
|
||||
val dAppUrl: String,
|
||||
val methodName: String,
|
||||
val blockchain: String,
|
||||
val errorCode: String? = null,
|
||||
val errorDescription: String? = null,
|
||||
) {
|
||||
fun toParamsMap(): Map<String, String> {
|
||||
val validation = if (errorCode == null) Validation.SUCCESS.param else Validation.FAIL.param
|
||||
val code = errorCode ?: SUCCESS_CODE
|
||||
return buildMap {
|
||||
put(AnalyticsParam.DAPP_NAME, dAppName)
|
||||
put(AnalyticsParam.DAPP_URL, dAppUrl)
|
||||
put(AnalyticsParam.METHOD_NAME, methodName)
|
||||
put(AnalyticsParam.BLOCKCHAIN, blockchain)
|
||||
put(AnalyticsParam.VALIDATION, validation)
|
||||
put(AnalyticsParam.ERROR_CODE, code)
|
||||
if (errorDescription != null) {
|
||||
put(AnalyticsParam.ERROR_DESCRIPTION, errorDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class Validation(val param: String) {
|
||||
SUCCESS("Success"),
|
||||
FAIL("Fail"),
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val SUCCESS_CODE = "0"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.tap.common.buildconfig
|
||||
|
||||
import com.tangem.utils.buildConfig.AppConfigurationProvider
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class AppConfigurationProviderImpl @Inject constructor() : AppConfigurationProvider {
|
||||
|
||||
override fun isDebug(): Boolean = BuildConfig.BUILD_TYPE == "debug"
|
||||
override fun isHuawei(): Boolean = BuildConfig.FLAVOR_NAME == "huawei"
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
package com.tangem.tap.common.pushes
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import coil.executeBlocking
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.tap.MainActivity
|
||||
import com.tangem.tap.common.images.createCoilImageLoader
|
||||
import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler
|
||||
import com.tangem.wallet.R
|
||||
|
||||
class PushNotificationDelegate(private val context: Context) {
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
fun showNotification(
|
||||
dataMap: Map<String, String>,
|
||||
title: String?,
|
||||
body: String?,
|
||||
channelId: String,
|
||||
priority: Int,
|
||||
imageUrl: Uri? = null,
|
||||
vibratePattern: LongArray?,
|
||||
) {
|
||||
val intent = Intent(context, MainActivity::class.java).apply {
|
||||
dataMap.forEach { (key, value) ->
|
||||
putExtra(key, value)
|
||||
}
|
||||
putExtra(OnPushClickedIntentHandler.OPENED_FROM_GCM_PUSH, true)
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
}
|
||||
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
/* context = */ context,
|
||||
/* requestCode = */ PUSH_NOTIFICATION_REQUEST_CODE,
|
||||
/* intent = */ intent,
|
||||
/* flags = */ PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE,
|
||||
)
|
||||
|
||||
val notificationBuilder = NotificationCompat.Builder(context, channelId)
|
||||
.setSmallIcon(R.drawable.ic_tangem_24)
|
||||
.setContentTitle(title)
|
||||
.setContentText(body)
|
||||
.setPriority(priority)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setVibrate(vibratePattern)
|
||||
.apply {
|
||||
imageUrl?.let { uri ->
|
||||
val bitmap = getBitmapImageFromUrl(uri)
|
||||
setStyle(
|
||||
NotificationCompat
|
||||
.BigPictureStyle()
|
||||
.bigPicture(bitmap),
|
||||
).setLargeIcon(bitmap)
|
||||
}
|
||||
}
|
||||
|
||||
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val notificationChannel = NotificationChannel(
|
||||
channelId,
|
||||
ContextCompat.getString(context, R.string.tangem_app_name),
|
||||
NotificationManager.IMPORTANCE_HIGH,
|
||||
)
|
||||
notificationManager.createNotificationChannel(notificationChannel)
|
||||
}
|
||||
|
||||
// Generating unique notification id
|
||||
val uniqueId = (System.currentTimeMillis() % Integer.MAX_VALUE).toInt()
|
||||
|
||||
notificationManager.notify(
|
||||
/* id = */ uniqueId,
|
||||
/* notification = */ notificationBuilder.build(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getBitmapImageFromUrl(url: Uri): Bitmap? {
|
||||
return createCoilImageLoader(
|
||||
context,
|
||||
logEnabled = LogConfig.imageLoader,
|
||||
).executeBlocking(
|
||||
ImageRequest.Builder(context)
|
||||
.data(url)
|
||||
.build(),
|
||||
).drawable?.toBitmap()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val PUSH_NOTIFICATION_REQUEST_CODE = 123
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +1,17 @@
|
|||
package com.tangem.tap.common.pushes
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import coil.executeBlocking
|
||||
import coil.request.ImageRequest
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.tap.MainActivity
|
||||
import com.tangem.tap.common.images.createCoilImageLoader
|
||||
import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler
|
||||
import com.tangem.wallet.R
|
||||
import timber.log.Timber
|
||||
|
||||
@SuppressLint("MissingFirebaseInstanceTokenRefresh")
|
||||
internal class TangemPushNotificationService : FirebaseMessagingService() {
|
||||
|
||||
private val pushNotificationDelegate: PushNotificationDelegate by lazy {
|
||||
PushNotificationDelegate(applicationContext)
|
||||
}
|
||||
|
||||
override fun onNewToken(token: String) {
|
||||
super.onNewToken(token)
|
||||
Timber.d("New FCM token received: $token")
|
||||
|
|
@ -36,73 +23,18 @@ internal class TangemPushNotificationService : FirebaseMessagingService() {
|
|||
val notification = message.notification ?: return
|
||||
val channelId = notification.channelId ?: TANGEM_CHANNEL_ID
|
||||
|
||||
val intent = Intent(applicationContext, MainActivity::class.java).apply {
|
||||
message.data.forEach {
|
||||
putExtra(it.key, it.value)
|
||||
}
|
||||
putExtra(OnPushClickedIntentHandler.OPENED_FROM_GCM_PUSH, true)
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
}
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
/* context = */ this,
|
||||
/* requestCode = */ PUSH_NOTIFICATION_REQUEST_CODE,
|
||||
/* intent = */ intent,
|
||||
/* flags = */ PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE,
|
||||
pushNotificationDelegate.showNotification(
|
||||
dataMap = message.data,
|
||||
title = notification.title,
|
||||
body = notification.body,
|
||||
channelId = channelId,
|
||||
priority = message.priority,
|
||||
imageUrl = notification.imageUrl,
|
||||
vibratePattern = notification.vibrateTimings,
|
||||
)
|
||||
|
||||
val notificationBuilder =
|
||||
NotificationCompat.Builder(applicationContext, channelId)
|
||||
.setSmallIcon(R.drawable.ic_tangem_24)
|
||||
.setContentTitle(notification.title)
|
||||
.setContentText(notification.body)
|
||||
.setPriority(message.priority)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setVibrate(notification.vibrateTimings)
|
||||
.apply {
|
||||
notification.imageUrl?.let { uri ->
|
||||
val bitmap = getBitmapImageFromUrl(uri)
|
||||
setStyle(
|
||||
NotificationCompat
|
||||
.BigPictureStyle()
|
||||
.bigPicture(bitmap),
|
||||
).setLargeIcon(bitmap)
|
||||
}
|
||||
}
|
||||
|
||||
val notificationManager = applicationContext.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val notificationChannel = NotificationChannel(
|
||||
channelId,
|
||||
ContextCompat.getString(applicationContext, R.string.tangem_app_name),
|
||||
NotificationManager.IMPORTANCE_HIGH,
|
||||
)
|
||||
notificationManager.createNotificationChannel(notificationChannel)
|
||||
}
|
||||
|
||||
// Generating unique notification id
|
||||
val uniqueId = (System.currentTimeMillis() % Integer.MAX_VALUE).toInt()
|
||||
|
||||
notificationManager.notify(
|
||||
/* id = */ uniqueId,
|
||||
/* notification = */ notificationBuilder.build(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getBitmapImageFromUrl(url: Uri): Bitmap? {
|
||||
return createCoilImageLoader(
|
||||
applicationContext,
|
||||
logEnabled = LogConfig.imageLoader,
|
||||
).executeBlocking(
|
||||
ImageRequest.Builder(applicationContext)
|
||||
.data(url)
|
||||
.build(),
|
||||
).drawable?.toBitmap()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val TANGEM_CHANNEL_ID = "Tangem General" // General channel for notifications
|
||||
const val PUSH_NOTIFICATION_REQUEST_CODE = 123
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package com.tangem.tap.common.redux
|
|||
|
||||
import com.tangem.tap.common.redux.global.globalReducer
|
||||
import com.tangem.tap.features.details.redux.DetailsReducer
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectReducer
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeReducer
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphReducer
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -14,7 +13,6 @@ fun appReducer(action: Action, state: AppState?): AppState {
|
|||
return AppState(
|
||||
globalState = globalReducer(action, state),
|
||||
detailsState = DetailsReducer.reduce(action, state),
|
||||
walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState),
|
||||
welcomeState = WelcomeReducer.reduce(action, state),
|
||||
daggerGraphState = DaggerGraphReducer.reduce(action, state),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import com.tangem.tap.common.redux.global.GlobalState
|
|||
import com.tangem.tap.common.redux.legacy.LegacyMiddleware
|
||||
import com.tangem.tap.features.details.redux.DetailsMiddleware
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectMiddleware
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupMiddleware
|
||||
import com.tangem.tap.features.wallet.redux.middlewares.TradeCryptoMiddleware
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeMiddleware
|
||||
|
|
@ -19,7 +17,6 @@ import org.rekotlin.StateType
|
|||
data class AppState(
|
||||
val globalState: GlobalState = GlobalState(),
|
||||
val detailsState: DetailsState = DetailsState(),
|
||||
val walletConnectState: WalletConnectState = WalletConnectState(),
|
||||
val welcomeState: WelcomeState = WelcomeState(),
|
||||
val daggerGraphState: DaggerGraphState = DaggerGraphState(),
|
||||
) : StateType {
|
||||
|
|
@ -30,7 +27,6 @@ data class AppState(
|
|||
logMiddleware,
|
||||
GlobalMiddleware.handler,
|
||||
DetailsMiddleware().detailsMiddleware,
|
||||
WalletConnectMiddleware().walletConnectMiddleware,
|
||||
BackupMiddleware().backupMiddleware,
|
||||
WelcomeMiddleware().middleware,
|
||||
LockUserWalletsTimerMiddleware().middleware,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.tap.data
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.datasource.local.visa.TangemPayStorage
|
||||
import com.tangem.sdk.storage.AndroidSecureStorageV2
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
private const val DEFAULT_KEY = "tangem_pay_default_key"
|
||||
|
||||
@Singleton
|
||||
internal class DefaultTangemPayStorage @Inject constructor(
|
||||
@ApplicationContext applicationContext: Context,
|
||||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
) : TangemPayStorage {
|
||||
|
||||
private val secureStorage by lazy {
|
||||
AndroidSecureStorageV2(
|
||||
appContext = applicationContext,
|
||||
useStrongBox = false,
|
||||
name = "tangem_pay_storage",
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun store(authHeader: String) = withContext(dispatcherProvider.io) {
|
||||
secureStorage.store(authHeader.encodeToByteArray(throwOnInvalidSequence = true), DEFAULT_KEY)
|
||||
}
|
||||
|
||||
override suspend fun get(): String? = withContext(dispatcherProvider.io) {
|
||||
secureStorage.get(DEFAULT_KEY)?.decodeToString(throwOnInvalidSequence = true)
|
||||
}
|
||||
|
||||
override suspend fun clear() = withContext(dispatcherProvider.io) {
|
||||
secureStorage.delete(DEFAULT_KEY)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.tap.di
|
||||
|
||||
import com.tangem.tap.common.buildconfig.AppConfigurationProviderImpl
|
||||
import com.tangem.utils.buildConfig.AppConfigurationProvider
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface AppConfigurationModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindAppConfigurationProvider(impl: AppConfigurationProviderImpl): AppConfigurationProvider
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
|
|||
import com.tangem.tap.features.intentHandler.IntentProcessor
|
||||
import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler
|
||||
import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler
|
||||
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -19,10 +18,6 @@ internal object IntentHandlingModule {
|
|||
@Singleton
|
||||
fun provideBackgroundScanIntentHandler(): BackgroundScanIntentHandler = BackgroundScanIntentHandler()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideWalletConnectLinkIntentHandler(): WalletConnectLinkIntentHandler = WalletConnectLinkIntentHandler()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnPushClickedIntentHandler(analyticsEventHandler: AnalyticsEventHandler): OnPushClickedIntentHandler =
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.tap.di.data
|
||||
|
||||
import com.tangem.datasource.local.visa.TangemPayStorage
|
||||
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
|
||||
import com.tangem.datasource.local.visa.VisaOTPStorage
|
||||
import com.tangem.tap.data.DefaultTangemPayStorage
|
||||
import com.tangem.tap.data.DefaultVisaAuthTokenStorage
|
||||
import com.tangem.tap.data.DefaultVisaOTPStorage
|
||||
import dagger.Binds
|
||||
|
|
@ -21,4 +23,8 @@ internal interface VisaStorageModule {
|
|||
@Binds
|
||||
@Singleton
|
||||
fun bindVisaOTPStorage(impl: DefaultVisaOTPStorage): VisaOTPStorage
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindTangemPayStorage(impl: DefaultTangemPayStorage): TangemPayStorage
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.domain.feedback.GetCardInfoUseCase
|
||||
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
||||
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.repository.FeedbackRepository
|
||||
|
|
@ -18,8 +18,8 @@ internal object FeedbackDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetCardInfoUseCase(feedbackRepository: FeedbackRepository): GetCardInfoUseCase {
|
||||
return GetCardInfoUseCase(feedbackRepository = feedbackRepository)
|
||||
fun provideGetCardInfoUseCase(feedbackRepository: FeedbackRepository): GetWalletMetaInfoUseCase {
|
||||
return GetWalletMetaInfoUseCase(feedbackRepository = feedbackRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import com.tangem.domain.nft.repository.NFTRepository
|
|||
import com.tangem.domain.quotes.single.SingleQuoteStatusFetcher
|
||||
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -33,29 +32,21 @@ internal object NFTDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun providesFetchNFTCollectionsUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
nftRepository: NFTRepository,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): FetchNFTCollectionsUseCase = FetchNFTCollectionsUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
nftRepository = nftRepository,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesRefreshAllNFTUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
nftRepository: NFTRepository,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): RefreshAllNFTUseCase = RefreshAllNFTUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
nftRepository = nftRepository,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
|
@ -127,16 +118,12 @@ internal object NFTDomainModule {
|
|||
fun provideDisableWalletNFTUseCase(
|
||||
walletsRepository: WalletsRepository,
|
||||
nftRepository: NFTRepository,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): DisableWalletNFTUseCase {
|
||||
return DisableWalletNFTUseCase(
|
||||
walletsRepository = walletsRepository,
|
||||
nftRepository = nftRepository,
|
||||
currenciesRepository = currenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.domain.notifications.*
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.domain.notifications.repository.PushNotificationsRepository
|
||||
import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles
|
||||
import com.tangem.tap.domain.notifications.DefaultNotificationsFeatureToggles
|
||||
import com.tangem.utils.notifications.PushNotificationsTokenProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -79,12 +76,6 @@ internal object NotificationsDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNotificationsFeatureToggles(featureTogglesManager: FeatureTogglesManager): NotificationsFeatureToggles {
|
||||
return DefaultNotificationsFeatureToggles(featureTogglesManager = featureTogglesManager)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetNetworksAvailableForNotifications(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.onramp.repositories.LegacyTopUpRepository
|
||||
import com.tangem.domain.onramp.*
|
||||
import com.tangem.domain.onramp.repositories.HotCryptoRepository
|
||||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
||||
import com.tangem.domain.onramp.repositories.*
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -120,6 +116,12 @@ internal object OnrampDomainModule {
|
|||
return OnrampSaveTransactionUseCase(onrampTransactionRepository, onrampErrorResolver)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnrampSepaAvailableUseCase(onrampRepository: OnrampRepository): OnrampSepaAvailableUseCase {
|
||||
return OnrampSepaAvailableUseCase(onrampRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnrampUpdateTransactionStatusUseCase(
|
||||
|
|
@ -231,4 +233,34 @@ internal object OnrampDomainModule {
|
|||
fun provideGetLegacyTopUpUrlUseCase(legacyTopUpRepository: LegacyTopUpRepository): GetLegacyTopUpUrlUseCase {
|
||||
return GetLegacyTopUpUrlUseCase(legacyTopUpRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetOnrampAllOffersUseCase(
|
||||
onrampRepository: OnrampRepository,
|
||||
onrampErrorResolver: OnrampErrorResolver,
|
||||
settingsRepository: SettingsRepository,
|
||||
): GetOnrampAllOffersUseCase {
|
||||
return GetOnrampAllOffersUseCase(
|
||||
onrampRepository = onrampRepository,
|
||||
errorResolver = onrampErrorResolver,
|
||||
settingsRepository = settingsRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetOnrampOffersUseCase(
|
||||
onrampRepository: OnrampRepository,
|
||||
onrampErrorResolver: OnrampErrorResolver,
|
||||
onrampTransactionRepository: OnrampTransactionRepository,
|
||||
settingsRepository: SettingsRepository,
|
||||
): GetOnrampOffersUseCase {
|
||||
return GetOnrampOffersUseCase(
|
||||
onrampRepository = onrampRepository,
|
||||
errorResolver = onrampErrorResolver,
|
||||
onrampTransactionRepository = onrampTransactionRepository,
|
||||
settingsRepository = settingsRepository,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -54,8 +54,8 @@ internal object SettingsDomainModule {
|
|||
@Singleton
|
||||
fun providesShouldShowSaveWalletScreenUseCase(
|
||||
settingsRepository: SettingsRepository,
|
||||
): ShouldShowSaveWalletScreenUseCase {
|
||||
return ShouldShowSaveWalletScreenUseCase(settingsRepository = settingsRepository)
|
||||
): ShouldShowAskBiometryUseCase {
|
||||
return ShouldShowAskBiometryUseCase(settingsRepository = settingsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -138,10 +138,8 @@ internal object SettingsDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSetSaveWalletScreenShownUseCase(
|
||||
settingsRepository: SettingsRepository,
|
||||
): SetSaveWalletScreenShownUseCase {
|
||||
return SetSaveWalletScreenShownUseCase(settingsRepository = settingsRepository)
|
||||
fun provideSetSaveWalletScreenShownUseCase(settingsRepository: SettingsRepository): SetAskBiometryShownUseCase {
|
||||
return SetAskBiometryShownUseCase(settingsRepository = settingsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ internal object TokensDomainModule {
|
|||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
stakingIdFactory: StakingIdFactory,
|
||||
): AddCryptoCurrenciesUseCase {
|
||||
return AddCryptoCurrenciesUseCase(
|
||||
|
|
@ -56,7 +55,6 @@ internal object TokensDomainModule {
|
|||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
)
|
||||
}
|
||||
|
|
@ -111,13 +109,11 @@ internal object TokensDomainModule {
|
|||
currenciesRepository: CurrenciesRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): RemoveCurrencyUseCase {
|
||||
return RemoveCurrencyUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -156,7 +152,6 @@ internal object TokensDomainModule {
|
|||
dispatchers: CoroutineDispatcherProvider,
|
||||
baseCurrencyStatusOperations: BaseCurrencyStatusOperations,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): GetCurrencyWarningsUseCase {
|
||||
return GetCurrencyWarningsUseCase(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
|
|
@ -165,7 +160,6 @@ internal object TokensDomainModule {
|
|||
currencyChecksRepository = currencyChecksRepository,
|
||||
currencyStatusOperations = baseCurrencyStatusOperations,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -177,7 +171,6 @@ internal object TokensDomainModule {
|
|||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
stakingIdFactory: StakingIdFactory,
|
||||
): FetchCurrencyStatusUseCase {
|
||||
return FetchCurrencyStatusUseCase(
|
||||
|
|
@ -186,7 +179,6 @@ internal object TokensDomainModule {
|
|||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
)
|
||||
}
|
||||
|
|
@ -214,9 +206,8 @@ internal object TokensDomainModule {
|
|||
fun provideGetCryptoCurrencyUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): GetCryptoCurrencyUseCase {
|
||||
return GetCryptoCurrencyUseCase(currenciesRepository, multiWalletCryptoCurrenciesSupplier, tokensFeatureToggles)
|
||||
return GetCryptoCurrencyUseCase(currenciesRepository, multiWalletCryptoCurrenciesSupplier)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -238,13 +229,11 @@ internal object TokensDomainModule {
|
|||
fun provideApplyTokenListSortingUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): ApplyTokenListSortingUseCase {
|
||||
return ApplyTokenListSortingUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
|
@ -304,14 +293,10 @@ internal object TokensDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideIsCryptoCurrencyCoinCouldHideUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): IsCryptoCurrencyCoinCouldHideUseCase {
|
||||
return IsCryptoCurrencyCoinCouldHideUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -330,13 +315,11 @@ internal object TokensDomainModule {
|
|||
fun provideGetBalanceNotEnoughForFeeWarningUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): GetBalanceNotEnoughForFeeWarningUseCase {
|
||||
return GetBalanceNotEnoughForFeeWarningUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
|
@ -377,16 +360,12 @@ internal object TokensDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideRefreshMultiCurrencyWalletQuotesUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): RefreshMultiCurrencyWalletQuotesUseCase {
|
||||
return RefreshMultiCurrencyWalletQuotesUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -402,18 +381,13 @@ internal object TokensDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideBaseCurrencyStatusOperations(
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
|
||||
singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
|
||||
multiYieldBalanceSupplier: MultiYieldBalanceSupplier,
|
||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
stakingIdFactory: StakingIdFactory,
|
||||
): BaseCurrencyStatusOperations {
|
||||
|
|
@ -422,15 +396,10 @@ internal object TokensDomainModule {
|
|||
quotesRepository = quotesRepository,
|
||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||
singleQuoteStatusSupplier = singleQuoteStatusSupplier,
|
||||
singleYieldBalanceSupplier = singleYieldBalanceSupplier,
|
||||
multiYieldBalanceSupplier = multiYieldBalanceSupplier,
|
||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ import com.tangem.domain.demo.models.DemoConfig
|
|||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.transaction.FeeRepository
|
||||
import com.tangem.domain.transaction.TransactionRepository
|
||||
import com.tangem.domain.transaction.WalletAddressServiceRepository
|
||||
|
|
@ -63,18 +61,14 @@ internal object TransactionDomainModule {
|
|||
fun provideAssociateAssetUseCase(
|
||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): AssociateAssetUseCase {
|
||||
return AssociateAssetUseCase(
|
||||
cardSdkConfigRepository = cardSdkConfigRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
currenciesRepository = currenciesRepository,
|
||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.transaction.WalletAddressServiceRepository
|
||||
|
|
@ -9,8 +10,8 @@ import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase
|
|||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.delegate.DefaultUserWalletsSyncDelegate
|
||||
import com.tangem.domain.wallets.delegate.UserWalletsSyncDelegate
|
||||
import com.tangem.domain.wallets.hot.HotWalletAccessor
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.*
|
||||
|
|
@ -25,7 +26,7 @@ import dagger.hilt.InstallIn
|
|||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
@Suppress("TooManyFunctions", "LargeClass")
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object WalletsDomainModule {
|
||||
|
|
@ -352,4 +353,72 @@ internal object WalletsDomainModule {
|
|||
walletsRepository = walletsRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesIsUpgradeWalletNotificationEnabledUseCase(
|
||||
walletsRepository: WalletsRepository,
|
||||
): IsUpgradeWalletNotificationEnabledUseCase {
|
||||
return IsUpgradeWalletNotificationEnabledUseCase(
|
||||
walletsRepository = walletsRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesDismissUpgradeWalletNotificationUseCase(
|
||||
walletsRepository: WalletsRepository,
|
||||
): DismissUpgradeWalletNotificationUseCase {
|
||||
return DismissUpgradeWalletNotificationUseCase(
|
||||
walletsRepository = walletsRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesUnlockHotWalletContextualUseCase(
|
||||
hotWalletAccessor: HotWalletAccessor,
|
||||
): UnlockHotWalletContextualUseCase {
|
||||
return UnlockHotWalletContextualUseCase(
|
||||
hotWalletAccessor = hotWalletAccessor,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetHotWalletContextualUnlockUseCase(
|
||||
hotWalletAccessor: HotWalletAccessor,
|
||||
): GetHotWalletContextualUnlockUseCase {
|
||||
return GetHotWalletContextualUnlockUseCase(
|
||||
hotWalletAccessor = hotWalletAccessor,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesClearHotWalletContextualUnlockUseCase(
|
||||
hotWalletAccessor: HotWalletAccessor,
|
||||
): ClearHotWalletContextualUnlockUseCase {
|
||||
return ClearHotWalletContextualUnlockUseCase(
|
||||
hotWalletAccessor = hotWalletAccessor,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesClearAllHotWalletContextualUnlockUseCase(
|
||||
hotWalletAccessor: HotWalletAccessor,
|
||||
): ClearAllHotWalletContextualUnlockUseCase {
|
||||
return ClearAllHotWalletContextualUnlockUseCase(
|
||||
hotWalletAccessor = hotWalletAccessor,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesExportSeedPhraseUseCase(hotWalletAccessor: HotWalletAccessor): ExportSeedPhraseUseCase {
|
||||
return ExportSeedPhraseUseCase(
|
||||
hotWalletAccessor = hotWalletAccessor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.tap.di.hot
|
||||
|
||||
import com.tangem.data.wallets.hot.DefaultHotWalletAccessor
|
||||
import com.tangem.domain.wallets.hot.HotWalletAccessor
|
||||
import com.tangem.hot.sdk.TangemHotSdk
|
||||
import com.tangem.tap.features.hot.TangemHotSDKProxy
|
||||
import dagger.Binds
|
||||
|
|
@ -15,4 +17,8 @@ internal interface TangemHotSdkModule {
|
|||
@Binds
|
||||
@Singleton
|
||||
fun bindTangemHotSdk(proxy: TangemHotSDKProxy): TangemHotSdk
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindHotWalletAccessor(default: DefaultHotWalletAccessor): HotWalletAccessor
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package com.tangem.tap.domain.notifications
|
||||
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles
|
||||
|
||||
internal class DefaultNotificationsFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : NotificationsFeatureToggles {
|
||||
override val isNotificationsEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled("PUSH_NOTIFICATIONS_ENABLED")
|
||||
}
|
||||
|
|
@ -265,7 +265,7 @@ internal class LegacyScanProcessor @Inject constructor(
|
|||
onOk = { mainScope.launch { onSuccess() } },
|
||||
onSupportClick = {
|
||||
val cardInfo =
|
||||
store.inject(DaggerGraphState::getCardInfoUseCase).invoke(scanResponse).getOrNull()
|
||||
store.inject(DaggerGraphState::getWalletMetaInfoUseCase).invoke(scanResponse).getOrNull()
|
||||
?: error("CardInfo must be not null")
|
||||
|
||||
scope.launch {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,270 @@
|
|||
package com.tangem.tap.domain.sdk.mocks.content
|
||||
|
||||
import com.tangem.common.SuccessResponse
|
||||
import com.tangem.common.card.*
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.operations.attestation.Attestation
|
||||
import com.tangem.operations.backup.PrimaryCard
|
||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.sdk.api.CreateProductWalletTaskResponse
|
||||
import com.tangem.tap.domain.sdk.mocks.MockContent
|
||||
import java.util.Date
|
||||
|
||||
object BackupWalletMockContent : MockContent {
|
||||
|
||||
private val primaryCard = PrimaryCard(
|
||||
cardId = "AC05000000086747",
|
||||
batchId = "AC05",
|
||||
cardPublicKey = byteArrayOf(2, -120, -3, -32, -122, -127, -120, -104, 59, 72, 76, 114, 94, 75, -37, -55, 55, 99, 66, 123, 85, -87, 80, 106, 105, -116, 87, -83, -12, 70, 108, -68, -39),
|
||||
linkingKey = byteArrayOf(
|
||||
2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121,
|
||||
-57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91,
|
||||
),
|
||||
existingWalletsCount = 3, isHDWalletAllowed = true,
|
||||
issuer = Card.Issuer(
|
||||
name = "TANGEM AG",
|
||||
publicKey = byteArrayOf(3, 86, -25, -61, 55, 99, 41, -33, -82, 115, -120, -33, 22, -107, 103, 3, -122, 16, 60, -110, 72, 106, -121, 100, 79, -87, -27, 18, -55, -49, 78, -110, -2),
|
||||
),
|
||||
manufacturer = Card.Manufacturer(
|
||||
name = "TANGEM",
|
||||
manufactureDate = Date(1743759687),
|
||||
signature = byteArrayOf(),
|
||||
),
|
||||
walletCurves = listOf(EllipticCurve.Secp256k1, EllipticCurve.Ed25519, EllipticCurve.Bls12381G2Aug),
|
||||
firmwareVersion = FirmwareVersion(
|
||||
major = 4,
|
||||
minor = 52,
|
||||
patch = 0,
|
||||
type = FirmwareVersion.FirmwareType.Release,
|
||||
),
|
||||
isKeysImportAllowed = false,
|
||||
certificate = null,
|
||||
)
|
||||
|
||||
override val cardDto = CardDTO(
|
||||
cardId = "AC05000000086747",
|
||||
batchId = "AC05",
|
||||
cardPublicKey = byteArrayOf(2, -120, -3, -32, -122, -127, -120, -104, 59, 72, 76, 114, 94, 75, -37, -55, 55, 99, 66, 123, 85, -87, 80, 106, 105, -116, 87, -83, -12, 70, 108, -68, -39),
|
||||
firmwareVersion = CardDTO.FirmwareVersion(
|
||||
major = 4,
|
||||
minor = 52,
|
||||
patch = 0,
|
||||
type = FirmwareVersion.FirmwareType.Release,
|
||||
),
|
||||
manufacturer = CardDTO.Manufacturer(
|
||||
name = "TANGEM",
|
||||
manufactureDate = Date(1649635200000),
|
||||
signature = byteArrayOf(-20, 98, -101, 94, -23, 73, 122, -21, 74, 76, 79, -55, -102, -62, -30, 44, -38, 118, 75, 121, -36, 118, -62, 60, -38, -63, 33, -14, -98, -69, 112, 22, 48, -43, 47, 65, -61, -56, 38, -94, -45, 44, 95, -22, 6, -31, -40, -25, 42, 38, 94, -99, 71, 98, -33, 27, -102, 30, 52, -7, 51, -27, 84, 66),
|
||||
),
|
||||
issuer = CardDTO.Issuer(
|
||||
name = "TANGEM AG",
|
||||
publicKey = byteArrayOf(3, 86, -25, -61, 55, 99, 41, -33, -82, 115, -120, -33, 22, -107, 103, 3, -122, 16, 60, -110, 72, 106, -121, 100, 79, -87, -27, 18, -55, -49, 78, -110, -2),
|
||||
),
|
||||
settings = CardDTO.Settings(
|
||||
securityDelay = 15000,
|
||||
maxWalletsCount = 20,
|
||||
isSettingAccessCodeAllowed = false,
|
||||
isSettingPasscodeAllowed = false,
|
||||
isResettingUserCodesAllowed = true,
|
||||
isLinkedTerminalEnabled = true,
|
||||
isBackupAllowed = true,
|
||||
supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None),
|
||||
isFilesAllowed = true,
|
||||
isHDWalletAllowed = true,
|
||||
isKeysImportAllowed = false,
|
||||
),
|
||||
userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true),
|
||||
linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None,
|
||||
isAccessCodeSet = false,
|
||||
isPasscodeSet = false,
|
||||
supportedCurves = listOf(
|
||||
EllipticCurve.Secp256k1,
|
||||
EllipticCurve.Ed25519,
|
||||
EllipticCurve.Secp256r1,
|
||||
EllipticCurve.Bls12381G2,
|
||||
EllipticCurve.Bls12381G2Aug,
|
||||
EllipticCurve.Bls12381G2Pop,
|
||||
EllipticCurve.Bip0340,
|
||||
),
|
||||
wallets = listOf(
|
||||
CardDTO.Wallet(
|
||||
publicKey = byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
|
||||
chainCode = byteArrayOf(80, 13, -8, -108, -35, 116, -92, 125, -65, -28, 85, 72, -113, 59, 83, 13, 5, -83, -102, 123, 124, -22, 94, 108, -71, 95, 65, -2, 38, 38, -108, 14),
|
||||
curve = EllipticCurve.Secp256k1,
|
||||
settings = CardWallet.Settings(isPermanent = false),
|
||||
totalSignedHashes = 0,
|
||||
remainingSignatures = null,
|
||||
index = 0,
|
||||
hasBackup = true,
|
||||
derivedKeys = mapOf(
|
||||
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
|
||||
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
|
||||
),
|
||||
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
|
||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||
),
|
||||
DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
),
|
||||
DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
),
|
||||
),
|
||||
extendedPublicKey = ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
|
||||
chainCode = byteArrayOf(80, 13, -8, -108, -35, 116, -92, 125, -65, -28, 85, 72, -113, 59, 83, 13, 5, -83, -102, 123, 124, -22, 94, 108, -71, 95, 65, -2, 38, 38, -108, 14),
|
||||
),
|
||||
isImported = false,
|
||||
),
|
||||
CardDTO.Wallet(
|
||||
publicKey = byteArrayOf(-65, -53, -62, -12, -57, -32, -38, -9, -128, -52, -83, -61, 73, 39, 41, 15, -74, -97, 38, 52, -101, 63, 74, -56, -20, 15, 57, -127, 114, -93, -17, -109),
|
||||
chainCode = byteArrayOf(-81, 15, 125, 28, -115, -22, 87, 81, 87, -123, -25, -74, 86, 2, 1, 110, -115, 65, -110, 63, -64, 83, -93, -97, -104, 123, 12, -26, 94, 27, 84, -6),
|
||||
curve = EllipticCurve.Ed25519,
|
||||
settings = CardWallet.Settings(isPermanent = false),
|
||||
totalSignedHashes = 0,
|
||||
remainingSignatures = null,
|
||||
index = 1,
|
||||
hasBackup = true,
|
||||
derivedKeys = emptyMap(),
|
||||
extendedPublicKey = ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(-65, -53, -62, -12, -57, -32, -38, -9, -128, -52, -83, -61, 73, 39, 41, 15, -74, -97, 38, 52, -101, 63, 74, -56, -20, 15, 57, -127, 114, -93, -17, -109),
|
||||
chainCode = byteArrayOf(-81, 15, 125, 28, -115, -22, 87, 81, 87, -123, -25, -74, 86, 2, 1, 110, -115, 65, -110, 63, -64, 83, -93, -97, -104, 123, 12, -26, 94, 27, 84, -6),
|
||||
),
|
||||
isImported = false,
|
||||
),
|
||||
),
|
||||
attestation = Attestation(
|
||||
cardKeyAttestation = Attestation.Status.Verified,
|
||||
walletKeysAttestation = Attestation.Status.Skipped,
|
||||
firmwareAttestation = Attestation.Status.Skipped,
|
||||
cardUniquenessAttestation = Attestation.Status.Skipped,
|
||||
),
|
||||
backupStatus = CardDTO.BackupStatus.Active(1),
|
||||
)
|
||||
|
||||
override val scanResponse = ScanResponse(
|
||||
card = cardDto,
|
||||
productType = ProductType.Wallet,
|
||||
walletData = null,
|
||||
secondTwinPublicKey = null,
|
||||
derivedKeys = emptyMap(),
|
||||
primaryCard = null,
|
||||
)
|
||||
|
||||
override val derivationTaskResponse = DerivationTaskResponse(
|
||||
entries = mapOf(
|
||||
ByteArrayKey(
|
||||
byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
|
||||
)
|
||||
to
|
||||
ExtendedPublicKeysMap(
|
||||
mapOf(
|
||||
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
|
||||
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
|
||||
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey( // btc
|
||||
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
|
||||
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
|
||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch
|
||||
publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121),
|
||||
chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge
|
||||
publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124),
|
||||
chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
override val extendedPublicKey = ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(3, 2, 95, 53, 40, -87, -60, 11, -8, -47, 41, 37, 100, 15, -69, 1, -122, 127, -20, -81, -32, -20, -24, 5, -28, 113, 106, -90, -59, -30, -27, -110, -110),
|
||||
chainCode = byteArrayOf(-95, -87, -95, -25, 27, 96, -57, -92, -69, -106, -45, 10, 85, 4, -92, -68, 49, -24, -28, -50, -49, -77, -20, 118, -50, -27, 104, -93, 115, -50, -46, -34),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
)
|
||||
|
||||
override val successResponse = SuccessResponse(cardId = "AC05000000086747")
|
||||
|
||||
override val createProductWalletTaskResponse = CreateProductWalletTaskResponse(
|
||||
card = cardDto,
|
||||
derivedKeys = mapOf(
|
||||
ByteArrayKey(
|
||||
byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
|
||||
)
|
||||
to
|
||||
ExtendedPublicKeysMap(
|
||||
mapOf(
|
||||
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
|
||||
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
|
||||
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
|
||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
primaryCard = primaryCard,
|
||||
)
|
||||
|
||||
override val importWalletResponse: CreateProductWalletTaskResponse
|
||||
get() = error("Available only for Wallet 2")
|
||||
|
||||
override val createFirstTwinResponse: CreateWalletResponse
|
||||
get() = error("Available only for Twin")
|
||||
|
||||
override val createSecondTwinResponse: CreateWalletResponse
|
||||
get() = error("Available only for Twin")
|
||||
|
||||
override val finalizeTwinResponse: ScanResponse
|
||||
get() = error("Available only for Twin")
|
||||
}
|
||||
|
|
@ -0,0 +1,270 @@
|
|||
package com.tangem.tap.domain.sdk.mocks.content
|
||||
|
||||
import com.tangem.common.SuccessResponse
|
||||
import com.tangem.common.card.*
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.operations.attestation.Attestation
|
||||
import com.tangem.operations.backup.PrimaryCard
|
||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.sdk.api.CreateProductWalletTaskResponse
|
||||
import com.tangem.tap.domain.sdk.mocks.MockContent
|
||||
import java.util.Date
|
||||
|
||||
object DevWalletMockContent : MockContent {
|
||||
|
||||
private val primaryCard = PrimaryCard(
|
||||
cardId = "AC05000000086747",
|
||||
batchId = "AC05",
|
||||
cardPublicKey = byteArrayOf(2, -120, -3, -32, -122, -127, -120, -104, 59, 72, 76, 114, 94, 75, -37, -55, 55, 99, 66, 123, 85, -87, 80, 106, 105, -116, 87, -83, -12, 70, 108, -68, -39),
|
||||
linkingKey = byteArrayOf(
|
||||
2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121,
|
||||
-57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91,
|
||||
),
|
||||
existingWalletsCount = 3, isHDWalletAllowed = true,
|
||||
issuer = Card.Issuer(
|
||||
name = "TANGEM AG",
|
||||
publicKey = byteArrayOf(3, 86, -25, -61, 55, 99, 41, -33, -82, 115, -120, -33, 22, -107, 103, 3, -122, 16, 60, -110, 72, 106, -121, 100, 79, -87, -27, 18, -55, -49, 78, -110, -2),
|
||||
),
|
||||
manufacturer = Card.Manufacturer(
|
||||
name = "TANGEM",
|
||||
manufactureDate = Date(1743759687),
|
||||
signature = byteArrayOf(),
|
||||
),
|
||||
walletCurves = listOf(EllipticCurve.Secp256k1, EllipticCurve.Ed25519, EllipticCurve.Bls12381G2Aug),
|
||||
firmwareVersion = FirmwareVersion(
|
||||
major = 4,
|
||||
minor = 52,
|
||||
patch = 0,
|
||||
type = FirmwareVersion.FirmwareType.Sdk,
|
||||
),
|
||||
isKeysImportAllowed = false,
|
||||
certificate = null,
|
||||
)
|
||||
|
||||
override val cardDto = CardDTO(
|
||||
cardId = "AC05000000086747",
|
||||
batchId = "AC05",
|
||||
cardPublicKey = byteArrayOf(2, -120, -3, -32, -122, -127, -120, -104, 59, 72, 76, 114, 94, 75, -37, -55, 55, 99, 66, 123, 85, -87, 80, 106, 105, -116, 87, -83, -12, 70, 108, -68, -39),
|
||||
firmwareVersion = CardDTO.FirmwareVersion(
|
||||
major = 4,
|
||||
minor = 52,
|
||||
patch = 0,
|
||||
type = FirmwareVersion.FirmwareType.Sdk,
|
||||
),
|
||||
manufacturer = CardDTO.Manufacturer(
|
||||
name = "TANGEM",
|
||||
manufactureDate = Date(1649635200000),
|
||||
signature = byteArrayOf(-20, 98, -101, 94, -23, 73, 122, -21, 74, 76, 79, -55, -102, -62, -30, 44, -38, 118, 75, 121, -36, 118, -62, 60, -38, -63, 33, -14, -98, -69, 112, 22, 48, -43, 47, 65, -61, -56, 38, -94, -45, 44, 95, -22, 6, -31, -40, -25, 42, 38, 94, -99, 71, 98, -33, 27, -102, 30, 52, -7, 51, -27, 84, 66),
|
||||
),
|
||||
issuer = CardDTO.Issuer(
|
||||
name = "TANGEM AG",
|
||||
publicKey = byteArrayOf(3, 86, -25, -61, 55, 99, 41, -33, -82, 115, -120, -33, 22, -107, 103, 3, -122, 16, 60, -110, 72, 106, -121, 100, 79, -87, -27, 18, -55, -49, 78, -110, -2),
|
||||
),
|
||||
settings = CardDTO.Settings(
|
||||
securityDelay = 15000,
|
||||
maxWalletsCount = 20,
|
||||
isSettingAccessCodeAllowed = false,
|
||||
isSettingPasscodeAllowed = false,
|
||||
isResettingUserCodesAllowed = true,
|
||||
isLinkedTerminalEnabled = true,
|
||||
isBackupAllowed = true,
|
||||
supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None),
|
||||
isFilesAllowed = true,
|
||||
isHDWalletAllowed = true,
|
||||
isKeysImportAllowed = false,
|
||||
),
|
||||
userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true),
|
||||
linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None,
|
||||
isAccessCodeSet = false,
|
||||
isPasscodeSet = false,
|
||||
supportedCurves = listOf(
|
||||
EllipticCurve.Secp256k1,
|
||||
EllipticCurve.Ed25519,
|
||||
EllipticCurve.Secp256r1,
|
||||
EllipticCurve.Bls12381G2,
|
||||
EllipticCurve.Bls12381G2Aug,
|
||||
EllipticCurve.Bls12381G2Pop,
|
||||
EllipticCurve.Bip0340,
|
||||
),
|
||||
wallets = listOf(
|
||||
CardDTO.Wallet(
|
||||
publicKey = byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
|
||||
chainCode = byteArrayOf(80, 13, -8, -108, -35, 116, -92, 125, -65, -28, 85, 72, -113, 59, 83, 13, 5, -83, -102, 123, 124, -22, 94, 108, -71, 95, 65, -2, 38, 38, -108, 14),
|
||||
curve = EllipticCurve.Secp256k1,
|
||||
settings = CardWallet.Settings(isPermanent = false),
|
||||
totalSignedHashes = 0,
|
||||
remainingSignatures = null,
|
||||
index = 0,
|
||||
hasBackup = false,
|
||||
derivedKeys = mapOf(
|
||||
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
|
||||
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
|
||||
),
|
||||
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
|
||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||
),
|
||||
DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
),
|
||||
DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
),
|
||||
),
|
||||
extendedPublicKey = ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
|
||||
chainCode = byteArrayOf(80, 13, -8, -108, -35, 116, -92, 125, -65, -28, 85, 72, -113, 59, 83, 13, 5, -83, -102, 123, 124, -22, 94, 108, -71, 95, 65, -2, 38, 38, -108, 14),
|
||||
),
|
||||
isImported = false,
|
||||
),
|
||||
CardDTO.Wallet(
|
||||
publicKey = byteArrayOf(-65, -53, -62, -12, -57, -32, -38, -9, -128, -52, -83, -61, 73, 39, 41, 15, -74, -97, 38, 52, -101, 63, 74, -56, -20, 15, 57, -127, 114, -93, -17, -109),
|
||||
chainCode = byteArrayOf(-81, 15, 125, 28, -115, -22, 87, 81, 87, -123, -25, -74, 86, 2, 1, 110, -115, 65, -110, 63, -64, 83, -93, -97, -104, 123, 12, -26, 94, 27, 84, -6),
|
||||
curve = EllipticCurve.Ed25519,
|
||||
settings = CardWallet.Settings(isPermanent = false),
|
||||
totalSignedHashes = 0,
|
||||
remainingSignatures = null,
|
||||
index = 1,
|
||||
hasBackup = false,
|
||||
derivedKeys = emptyMap(),
|
||||
extendedPublicKey = ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(-65, -53, -62, -12, -57, -32, -38, -9, -128, -52, -83, -61, 73, 39, 41, 15, -74, -97, 38, 52, -101, 63, 74, -56, -20, 15, 57, -127, 114, -93, -17, -109),
|
||||
chainCode = byteArrayOf(-81, 15, 125, 28, -115, -22, 87, 81, 87, -123, -25, -74, 86, 2, 1, 110, -115, 65, -110, 63, -64, 83, -93, -97, -104, 123, 12, -26, 94, 27, 84, -6),
|
||||
),
|
||||
isImported = false,
|
||||
),
|
||||
),
|
||||
attestation = Attestation(
|
||||
cardKeyAttestation = Attestation.Status.Verified,
|
||||
walletKeysAttestation = Attestation.Status.Skipped,
|
||||
firmwareAttestation = Attestation.Status.Skipped,
|
||||
cardUniquenessAttestation = Attestation.Status.Skipped,
|
||||
),
|
||||
backupStatus = CardDTO.BackupStatus.NoBackup,
|
||||
)
|
||||
|
||||
override val scanResponse = ScanResponse(
|
||||
card = cardDto,
|
||||
productType = ProductType.Wallet,
|
||||
walletData = null,
|
||||
secondTwinPublicKey = null,
|
||||
derivedKeys = emptyMap(),
|
||||
primaryCard = null,
|
||||
)
|
||||
|
||||
override val derivationTaskResponse = DerivationTaskResponse(
|
||||
entries = mapOf(
|
||||
ByteArrayKey(
|
||||
byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
|
||||
)
|
||||
to
|
||||
ExtendedPublicKeysMap(
|
||||
mapOf(
|
||||
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
|
||||
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
|
||||
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey( // btc
|
||||
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
|
||||
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
|
||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch
|
||||
publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121),
|
||||
chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge
|
||||
publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124),
|
||||
chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
override val extendedPublicKey = ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(3, 2, 95, 53, 40, -87, -60, 11, -8, -47, 41, 37, 100, 15, -69, 1, -122, 127, -20, -81, -32, -20, -24, 5, -28, 113, 106, -90, -59, -30, -27, -110, -110),
|
||||
chainCode = byteArrayOf(-95, -87, -95, -25, 27, 96, -57, -92, -69, -106, -45, 10, 85, 4, -92, -68, 49, -24, -28, -50, -49, -77, -20, 118, -50, -27, 104, -93, 115, -50, -46, -34),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
)
|
||||
|
||||
override val successResponse = SuccessResponse(cardId = "AC05000000086747")
|
||||
|
||||
override val createProductWalletTaskResponse = CreateProductWalletTaskResponse(
|
||||
card = cardDto,
|
||||
derivedKeys = mapOf(
|
||||
ByteArrayKey(
|
||||
byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
|
||||
)
|
||||
to
|
||||
ExtendedPublicKeysMap(
|
||||
mapOf(
|
||||
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
|
||||
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
|
||||
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
|
||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
primaryCard = primaryCard,
|
||||
)
|
||||
|
||||
override val importWalletResponse: CreateProductWalletTaskResponse
|
||||
get() = error("Available only for Wallet 2")
|
||||
|
||||
override val createFirstTwinResponse: CreateWalletResponse
|
||||
get() = error("Available only for Twin")
|
||||
|
||||
override val createSecondTwinResponse: CreateWalletResponse
|
||||
get() = error("Available only for Twin")
|
||||
|
||||
override val finalizeTwinResponse: ScanResponse
|
||||
get() = error("Available only for Twin")
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ import com.tangem.domain.models.scan.CardDTO
|
|||
import com.tangem.domain.visa.error.VisaActivationError
|
||||
import com.tangem.domain.visa.model.*
|
||||
import com.tangem.domain.visa.repository.VisaActivationRepository
|
||||
import com.tangem.domain.visa.repository.VisaAuthRepository
|
||||
import com.tangem.domain.visa.datasource.VisaAuthRemoteDataSource
|
||||
import com.tangem.operations.GenerateOTPCommand
|
||||
import com.tangem.operations.attestation.AttestCardKeyCommand
|
||||
import com.tangem.operations.pins.SetUserCodeCommand
|
||||
|
|
@ -46,7 +46,7 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
@Assisted private val coroutineScope: CoroutineScope,
|
||||
private val otpStorage: VisaOTPStorage,
|
||||
private val visaAuthTokenStorage: VisaAuthTokenStorage,
|
||||
private val visaAuthRepository: VisaAuthRepository,
|
||||
private val visaAuthRemoteDataSource: VisaAuthRemoteDataSource,
|
||||
private val visaActivationRepositoryFactory: VisaActivationRepository.Factory,
|
||||
) : CardSessionRunnable<VisaCardActivationResponse> {
|
||||
|
||||
|
|
@ -168,7 +168,7 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
signedChallenge: VisaAuthSignedChallenge,
|
||||
cardWalletAddress: String,
|
||||
): Either<TangemError, VisaDataToSignByCardWallet> = either {
|
||||
val tokens = visaAuthRepository.getAccessTokens(signedChallenge)
|
||||
val tokens = visaAuthRemoteDataSource.getAccessTokens(signedChallenge)
|
||||
.getOrElse { raise(it.tangemError) }
|
||||
|
||||
visaAuthTokenStorage.store(cardId, tokens)
|
||||
|
|
|
|||
|
|
@ -4,9 +4,5 @@ import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
|||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
|
||||
internal class DefaultTokensFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : TokensFeatureToggles {
|
||||
|
||||
override val isWalletBalanceFetcherEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "WALLET_BALANCE_FETCHER_ENABLED")
|
||||
}
|
||||
@Suppress("UnusedPrivateMember") private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : TokensFeatureToggles
|
||||
|
|
@ -11,21 +11,16 @@ import com.tangem.common.map
|
|||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository.LockMethod
|
||||
import com.tangem.domain.core.wallets.error.*
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.wallets.R
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.hot.HotWalletPasswordRequester
|
||||
import com.tangem.domain.core.wallets.error.DeleteWalletError
|
||||
import com.tangem.domain.core.wallets.error.LockWalletsError
|
||||
import com.tangem.domain.core.wallets.error.SaveWalletError
|
||||
import com.tangem.domain.core.wallets.error.SelectWalletError
|
||||
import com.tangem.domain.core.wallets.error.SetLockError
|
||||
import com.tangem.domain.core.wallets.error.UnlockWalletError
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository.LockMethod
|
||||
import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository
|
||||
import com.tangem.domain.wallets.hot.HotWalletPasswordRequester
|
||||
import com.tangem.hot.sdk.model.HotWalletId
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
|
||||
|
|
@ -38,6 +33,8 @@ import com.tangem.utils.ProviderSuspend
|
|||
import com.tangem.utils.extensions.indexOfFirstOrNull
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
internal class DefaultUserWalletsListRepository(
|
||||
|
|
@ -54,32 +51,40 @@ internal class DefaultUserWalletsListRepository(
|
|||
|
||||
override val userWallets = MutableStateFlow<List<UserWallet>?>(null)
|
||||
override val selectedUserWallet = MutableStateFlow<UserWallet?>(null)
|
||||
private val mutex = Mutex()
|
||||
|
||||
override suspend fun load() {
|
||||
if (userWallets.value != null) return
|
||||
mutex.withLock {
|
||||
if (userWallets.value != null) return
|
||||
|
||||
if (savePersistentInformation().not()) {
|
||||
// If we don't save persistent information, we don't need to load user wallets
|
||||
// and we should clear any existing data
|
||||
clearPersistentData()
|
||||
userWallets.value = emptyList()
|
||||
return
|
||||
}
|
||||
|
||||
val unsecuredEncryptionKeys = userWalletEncryptionKeysRepository.getAllUnsecured()
|
||||
|
||||
publicInformationRepository.getAll()
|
||||
.map { it.toUserWallets() }
|
||||
.flatMap { wallets ->
|
||||
sensitiveInformationRepository.getAll(unsecuredEncryptionKeys)
|
||||
.map { wallets.updateWith(it) }
|
||||
}.doOnSuccess {
|
||||
userWallets.value = it
|
||||
if (savePersistentInformation().not()) {
|
||||
// If we don't save persistent information, we don't need to load user wallets
|
||||
// and we should clear any existing data
|
||||
clearPersistentData()
|
||||
updateWallets { emptyList() }
|
||||
return
|
||||
}
|
||||
|
||||
val selectedUserWalletId = selectedUserWalletRepository.get()
|
||||
selectedUserWallet.value = userWallets.value?.firstOrNull { it.walletId == selectedUserWalletId }
|
||||
?: userWallets.value?.firstOrNull()
|
||||
val unsecuredEncryptionKeys = userWalletEncryptionKeysRepository.getAllUnsecured()
|
||||
|
||||
publicInformationRepository.getAll()
|
||||
.map { it.toUserWallets() }
|
||||
.flatMap { wallets ->
|
||||
sensitiveInformationRepository.getAll(unsecuredEncryptionKeys)
|
||||
.map { wallets.updateWith(it) }
|
||||
}
|
||||
.doOnSuccess { loadedWallets ->
|
||||
userWallets.update { toUpdate ->
|
||||
val selectedUserWalletId = selectedUserWalletRepository.get()
|
||||
selectedUserWallet.value = loadedWallets.firstOrNull { it.walletId == selectedUserWalletId }
|
||||
?: loadedWallets.firstOrNull()?.also {
|
||||
selectedUserWalletRepository.set(it.walletId)
|
||||
}
|
||||
|
||||
loadedWallets
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun userWalletsSync(): List<UserWallet> {
|
||||
|
|
@ -118,7 +123,7 @@ internal class DefaultUserWalletsListRepository(
|
|||
}
|
||||
|
||||
// update the userWallets state and add if it doesn't exist
|
||||
userWallets.update { currentWallets ->
|
||||
updateWallets { currentWallets ->
|
||||
val wallets = currentWallets ?: emptyList()
|
||||
if (wallets.any { it.walletId == userWallet.walletId }) {
|
||||
wallets.map { if (it.walletId == userWallet.walletId) userWallet else it }
|
||||
|
|
@ -191,18 +196,17 @@ internal class DefaultUserWalletsListRepository(
|
|||
|
||||
userWalletEncryptionKeysRepository.delete(userWalletIds)
|
||||
|
||||
val userWalletsBeforeDelete = userWallets.value ?: return@either
|
||||
|
||||
userWallets.update { currentWallets ->
|
||||
currentWallets?.filterNot { it.walletId in userWalletIds }
|
||||
}
|
||||
|
||||
selectedUserWallet.update { currentSelected ->
|
||||
if (currentSelected == null) return@update null
|
||||
|
||||
userWallets.value?.findAvailableUserWallet(
|
||||
userWalletsBeforeDelete.indexOfFirstOrNull { it.walletId == currentSelected.walletId } ?: 0,
|
||||
)
|
||||
val updatedWallets = currentWallets?.filter { userWalletIds.contains(it.walletId).not() }
|
||||
selectedUserWallet.update { currentSelected ->
|
||||
if (currentSelected == null) return@update null
|
||||
val newSelected = updatedWallets?.findAvailableUserWallet(
|
||||
currentWallets.indexOfFirstOrNull { it.walletId == currentSelected.walletId } ?: 0,
|
||||
)
|
||||
selectedUserWalletRepository.set(newSelected?.walletId)
|
||||
newSelected
|
||||
}
|
||||
updatedWallets
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -248,7 +252,7 @@ internal class DefaultUserWalletsListRepository(
|
|||
removePasswordAttempts(userWallet)
|
||||
|
||||
sensitiveInformationRepository.getAll(listOf(encryptionKey))
|
||||
.doOnSuccess { sensitiveInfo -> userWallets.update { it?.updateWith(sensitiveInfo) } }
|
||||
.doOnSuccess { sensitiveInfo -> updateWallets { it?.updateWith(sensitiveInfo) } }
|
||||
.doOnFailure { error ->
|
||||
raise(UnlockWalletError.UnableToUnlock)
|
||||
}
|
||||
|
|
@ -266,9 +270,14 @@ internal class DefaultUserWalletsListRepository(
|
|||
raise(UnlockWalletError.ScannedCardWalletNotMatched)
|
||||
}
|
||||
|
||||
saveWithoutLock(userWallet.copy(scanResponse = scanResponse), canOverride = true)
|
||||
.mapLeft { UnlockWalletError.UnableToUnlock }
|
||||
.bind()
|
||||
val encryptionKey = UserWalletEncryptionKey(
|
||||
walletId = userWallet.walletId,
|
||||
encryptionKey = scanResponse.encryptionKey ?: raise(UnlockWalletError.UnableToUnlock),
|
||||
)
|
||||
|
||||
sensitiveInformationRepository.getAll(listOf(encryptionKey))
|
||||
.doOnSuccess { sensitiveInfo -> updateWallets { it?.updateWith(sensitiveInfo) } }
|
||||
.doOnFailure { error -> raise(UnlockWalletError.UnableToUnlock) }
|
||||
}
|
||||
.doOnFailure {
|
||||
raise(UnlockWalletError.UserCancelled)
|
||||
|
|
@ -278,7 +287,8 @@ internal class DefaultUserWalletsListRepository(
|
|||
}
|
||||
|
||||
override suspend fun unlockAllWallets(): Either<UnlockWalletError, Unit> = either {
|
||||
val userWalletIds = userWalletsSync().map { it.walletId }.toSet()
|
||||
val userWallets = userWalletsSync()
|
||||
val userWalletIds = userWallets.map { it.walletId }.toSet()
|
||||
val biometricKeys = runCatching {
|
||||
userWalletEncryptionKeysRepository.getAllBiometric()
|
||||
}.getOrElse {
|
||||
|
|
@ -291,7 +301,7 @@ internal class DefaultUserWalletsListRepository(
|
|||
val unlockedWalletsIds = allKeys.map { it.walletId }
|
||||
|
||||
val unlockedWallets = unlockedWalletsIds.mapNotNull { id ->
|
||||
userWalletsSync().firstOrNull { it.walletId == id }
|
||||
userWallets.firstOrNull { it.walletId == id }
|
||||
}
|
||||
|
||||
// Remove all password attempts for unlocked hot wallets
|
||||
|
|
@ -306,7 +316,7 @@ internal class DefaultUserWalletsListRepository(
|
|||
|
||||
sensitiveInformationRepository.getAll(allKeys)
|
||||
.doOnSuccess { sensitiveInfo ->
|
||||
userWallets.update { it?.updateWith(sensitiveInfo) }
|
||||
updateWallets { userWallets.updateWith(sensitiveInfo) }
|
||||
}
|
||||
.doOnFailure { raise(UnlockWalletError.UnableToUnlock) }
|
||||
}
|
||||
|
|
@ -318,7 +328,7 @@ internal class DefaultUserWalletsListRepository(
|
|||
raise(LockWalletsError.NothingToLock)
|
||||
}
|
||||
|
||||
userWallets.update {
|
||||
updateWallets {
|
||||
it?.map {
|
||||
if (it.walletId !in unsecuredWalletIds) {
|
||||
it.lock()
|
||||
|
|
@ -389,6 +399,17 @@ internal class DefaultUserWalletsListRepository(
|
|||
return tangemSdkManagerProvider.invoke().canUseBiometry && useBiometricAuthentication
|
||||
}
|
||||
|
||||
private fun updateWallets(block: (List<UserWallet>?) -> List<UserWallet>?) {
|
||||
userWallets.update {
|
||||
val updated = block(it)
|
||||
selectedUserWallet.update { currentSelected ->
|
||||
if (currentSelected == null) return@update null
|
||||
updated?.find { it.walletId == currentSelected.walletId }
|
||||
}
|
||||
updated
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the nearest available wallet that can be selected
|
||||
*
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.common.extensions.calculateSha256
|
|||
import com.tangem.domain.common.extensions.calculateHmacSha256
|
||||
import com.tangem.domain.models.MobileWallet
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
||||
internal val UserWallet.encryptionKey: ByteArray?
|
||||
|
|
@ -19,6 +20,9 @@ private fun calculateEncryptionKey(publicKey: ByteArray): ByteArray {
|
|||
return message.calculateHmacSha256(keyHash)
|
||||
}
|
||||
|
||||
val ScanResponse.encryptionKey: ByteArray?
|
||||
get() = findPublicKey(this.card.wallets)?.let { calculateEncryptionKey(it) }
|
||||
|
||||
private fun findPublicKey(wallets: List<CardDTO.Wallet>): ByteArray? {
|
||||
return wallets.firstOrNull()?.publicKey
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import com.tangem.domain.visa.error.VisaApiError
|
|||
import com.tangem.domain.visa.error.VisaCardScanError
|
||||
import com.tangem.domain.visa.model.*
|
||||
import com.tangem.domain.visa.repository.VisaActivationRepository
|
||||
import com.tangem.domain.visa.repository.VisaAuthRepository
|
||||
import com.tangem.domain.visa.datasource.VisaAuthRemoteDataSource
|
||||
import com.tangem.operations.attestation.AttestCardKeyCommand
|
||||
import com.tangem.operations.attestation.AttestCardKeyResponse
|
||||
import com.tangem.operations.attestation.AttestWalletKeyResponse
|
||||
|
|
@ -26,7 +26,7 @@ import javax.inject.Inject
|
|||
import kotlin.coroutines.resume
|
||||
|
||||
internal class VisaCardScanHandler @Inject constructor(
|
||||
private val visaAuthRepository: VisaAuthRepository,
|
||||
private val visaAuthRemoteDataSource: VisaAuthRemoteDataSource,
|
||||
private val visaActivationRepositoryFactory: VisaActivationRepository.Factory,
|
||||
private val visaAuthTokenStorage: VisaAuthTokenStorage,
|
||||
) {
|
||||
|
|
@ -83,7 +83,7 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
|
||||
Timber.i("Requesting challenge for wallet authorization")
|
||||
|
||||
val challengeResponse = visaAuthRepository.getCardWalletAuthChallenge(
|
||||
val challengeResponse = visaAuthRemoteDataSource.getCardWalletAuthChallenge(
|
||||
cardId = card.cardId,
|
||||
// This is the wallet public key, not the address and it's alright, as the API expects it in this format
|
||||
cardWalletAddress = wallet.publicKey.toHexString(),
|
||||
|
|
@ -122,7 +122,7 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
cardWalletAddress: String,
|
||||
signedChallenge: VisaAuthSignedChallenge,
|
||||
): CompletionResult<VisaCardActivationStatus> {
|
||||
val authorizationTokensResponse = visaAuthRepository.getAccessTokens(signedChallenge = signedChallenge)
|
||||
val authorizationTokensResponse = visaAuthRemoteDataSource.getAccessTokens(signedChallenge = signedChallenge)
|
||||
.getOrElse {
|
||||
Timber.i("Failed to get Access token for Wallet public key authorization.")
|
||||
return if (
|
||||
|
|
@ -149,7 +149,7 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
|
||||
Timber.i("Requesting authorization challenge to sign")
|
||||
|
||||
val challengeResponse = visaAuthRepository.getCardAuthChallenge(
|
||||
val challengeResponse = visaAuthRemoteDataSource.getCardAuthChallenge(
|
||||
cardId = card.cardId,
|
||||
cardPublicKey = card.cardPublicKey.toHexString(),
|
||||
).getOrElse {
|
||||
|
|
@ -174,7 +174,7 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
val authorizationTokensResponse = visaAuthRepository.getAccessTokens(
|
||||
val authorizationTokensResponse = visaAuthRemoteDataSource.getAccessTokens(
|
||||
signedChallenge = challengeResponse.toSignedChallenge(
|
||||
signedChallenge = attestCardKeyResponse.cardSignature.toHexString(),
|
||||
salt = attestCardKeyResponse.salt.toHexString(),
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
package com.tangem.tap.domain.walletconnect
|
||||
|
||||
import com.github.salomonbrys.kotson.registerTypeAdapter
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTradeOrder
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTransferOrder
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.tradeOrderSerializer
|
||||
import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData
|
||||
import com.tangem.tap.features.details.redux.walletconnect.TradeData
|
||||
import com.tangem.utils.extensions.stripZeroPlainString
|
||||
import timber.log.Timber
|
||||
|
||||
internal object BnbHelper {
|
||||
|
||||
fun createMessageData(order: WcBinanceTransferOrder): BinanceMessageData.Transfer {
|
||||
val input = order.msgs.first().inputs.first()
|
||||
val output = order.msgs.first().inputs.first()
|
||||
|
||||
val currency =
|
||||
input.coins.firstOrNull()?.denom ?: Blockchain.Binance
|
||||
val amount = input.coins
|
||||
.mapNotNull { if (it.denom == currency) it.amount else null }
|
||||
.sum()
|
||||
.toBigDecimal()
|
||||
.movePointLeft(Blockchain.Binance.decimals())
|
||||
.stripZeroPlainString()
|
||||
|
||||
val gson = GsonBuilder()
|
||||
.registerTypeAdapter(tradeOrderSerializer)
|
||||
.serializeNulls()
|
||||
.create()
|
||||
|
||||
return BinanceMessageData.Transfer(
|
||||
outputAddress = output.address,
|
||||
amount = amount,
|
||||
address = input.address,
|
||||
data = gson.toJson(order).toByteArray().calculateSha256(),
|
||||
)
|
||||
}
|
||||
|
||||
fun createMessageData(order: WcBinanceTradeOrder): BinanceMessageData.Trade {
|
||||
val address = order.msgs.first().sender
|
||||
|
||||
val tradeData = order.msgs.map {
|
||||
val price = it.price.toBigDecimal()
|
||||
.movePointLeft(Blockchain.Binance.decimals())
|
||||
.stripTrailingZeros()
|
||||
val quantity = it.quantity.toBigDecimal()
|
||||
.movePointLeft(Blockchain.Binance.decimals())
|
||||
.stripTrailingZeros()
|
||||
|
||||
val amount = price * quantity
|
||||
val symbol = it.symbol.substringBefore("-")
|
||||
|
||||
TradeData(
|
||||
price = "$price ${Blockchain.Binance.currency}",
|
||||
quantity = "$quantity $symbol",
|
||||
amount = "$amount ${Blockchain.Binance.currency}",
|
||||
symbol = symbol,
|
||||
)
|
||||
}
|
||||
val gson = GsonBuilder()
|
||||
.registerTypeAdapter(tradeOrderSerializer)
|
||||
.serializeNulls()
|
||||
.create()
|
||||
val serialized = gson.toJson(order)
|
||||
Timber.d(serialized)
|
||||
|
||||
return BinanceMessageData.Trade(
|
||||
tradeData = tradeData,
|
||||
address = address,
|
||||
data = serialized.toByteArray().calculateSha256(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package com.tangem.tap.domain.walletconnect
|
||||
|
||||
import com.github.salomonbrys.kotson.fromJson
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonParser
|
||||
|
||||
object EthSignHelper {
|
||||
private val gson: Gson by lazy {
|
||||
GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.serializeNulls()
|
||||
.create()
|
||||
}
|
||||
|
||||
fun tryToParseEthTypedMessageString(message: String): String? {
|
||||
return try {
|
||||
val messageString = message
|
||||
.replace("\\", "")
|
||||
.removePrefix("\"")
|
||||
.removeSuffix("\"")
|
||||
val messageJson = JsonParser().parse(messageString)
|
||||
val filteredMap = gson.fromJson<Map<*, *>>(messageJson)
|
||||
.filterKeys { it == "domain" || it == "message" }
|
||||
|
||||
gson.toJson(filteredMap)
|
||||
} catch (exception: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,513 +0,0 @@
|
|||
package com.tangem.tap.domain.walletconnect
|
||||
|
||||
import com.tangem.Message
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumGasLoader
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.smartcontract.CompiledSmartContractCallData
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.extensions.*
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toDecompressedPublicKey
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.data.walletconnect.network.ethereum.LegacySdkHelper
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.operations.sign.SignHashCommand
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.common.extensions.toFormattedString
|
||||
import com.tangem.tap.domain.walletconnect2.domain.TransactionType
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcEthereumTransaction
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcSignMessage
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTradeOrder
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTransferOrder
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WcEthTransactionType
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WcPersonalSignData
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WcTransactionData
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class WalletConnectSdkHelper {
|
||||
|
||||
private val userWalletsListManager by lazy {
|
||||
store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
suspend fun prepareTransactionData(data: EthTransactionData): WcTransactionData {
|
||||
val transaction = data.transaction
|
||||
val blockchain = requireNotNull(Blockchain.fromNetworkId(data.networkId)) { "Blockchain not found" }
|
||||
val walletManager = requireNotNull(getWalletManager(blockchain, data.rawDerivationPath)) {
|
||||
"WalletManager not found"
|
||||
}
|
||||
|
||||
walletManager.safeUpdate(isDemoCard())
|
||||
val wallet = walletManager.wallet
|
||||
val balance = requireNotNull(wallet.amounts[AmountType.Coin]?.value) {
|
||||
"Coin balance not found"
|
||||
}
|
||||
|
||||
val decimals = wallet.blockchain.decimals()
|
||||
|
||||
val value = (transaction.value ?: "0")
|
||||
.hexToBigDecimal()
|
||||
.movePointLeft(decimals)
|
||||
|
||||
requireNotNull(value) {
|
||||
"Transaction amount is null"
|
||||
}
|
||||
|
||||
// TODO move fee calculation to SDK getFee() [REDACTED_JIRA]
|
||||
val gasLimit = getGasLimitFromTx(value, walletManager, transaction, blockchain)
|
||||
val gasPrice = getGasPrice(walletManager, transaction)
|
||||
|
||||
val feeDecimal = (gasLimit * gasPrice).movePointLeft(decimals)
|
||||
val total = value + feeDecimal
|
||||
val feeAmount = Amount(feeDecimal, wallet.blockchain)
|
||||
|
||||
val destinationAddress = requireNotNull(transaction.to) { "Destination address is null" }
|
||||
|
||||
val fee = if (blockchain.isEvm()) {
|
||||
// TODO [REDACTED_JIRA]
|
||||
// workaround for Mantle, remove after [REDACTED_JIRA]
|
||||
val patchedAmount = if (blockchain == Blockchain.Mantle) {
|
||||
feeAmount.copy(value = feeAmount.value?.multiply(MANTLE_FEE_ESTIMATE_MULTIPLIER))
|
||||
} else {
|
||||
feeAmount
|
||||
}
|
||||
Fee.Ethereum.Legacy(patchedAmount, gasLimit.toBigInteger(), gasPrice.toBigInteger())
|
||||
} else {
|
||||
Fee.Common(feeAmount)
|
||||
}
|
||||
val transactionData = TransactionData.Uncompiled(
|
||||
amount = Amount(value, wallet.blockchain),
|
||||
fee = fee,
|
||||
sourceAddress = transaction.from,
|
||||
destinationAddress = destinationAddress,
|
||||
extras = EthereumTransactionExtras(
|
||||
callData = CompiledSmartContractCallData(transaction.data.removePrefix(HEX_PREFIX).hexToBytes()),
|
||||
gasLimit = gasLimit.toBigInteger(),
|
||||
nonce = transaction.nonce?.hexToBigDecimal()?.toBigInteger(),
|
||||
),
|
||||
)
|
||||
|
||||
val dialogData = TransactionRequestDialogData(
|
||||
dAppName = data.metaName,
|
||||
dAppUrl = data.metaUrl,
|
||||
amount = value.toFormattedString(decimals),
|
||||
feeAmount = feeDecimal.toFormattedString(decimals),
|
||||
totalAmount = total.toFormattedString(decimals),
|
||||
balance = balance.toFormattedString(decimals),
|
||||
isEnoughFundsToSend = balance - total >= BigDecimal.ZERO,
|
||||
topic = data.topic,
|
||||
id = data.id,
|
||||
type = data.type,
|
||||
)
|
||||
|
||||
return WcTransactionData(
|
||||
type = data.type,
|
||||
transaction = transactionData,
|
||||
topic = data.topic,
|
||||
id = data.id,
|
||||
walletManager = walletManager,
|
||||
dialogData = dialogData,
|
||||
)
|
||||
}
|
||||
|
||||
fun isDemoCard(): Boolean {
|
||||
val userWallet = userWalletsListManager.selectedUserWalletSync ?: return false
|
||||
return userWallet is UserWallet.Cold && userWallet.scanResponse.isDemoCard()
|
||||
}
|
||||
|
||||
private suspend fun getWalletManager(blockchain: Blockchain, derivationPath: String?): WalletManager? {
|
||||
val userWallet = userWalletsListManager.selectedUserWalletSync ?: return null
|
||||
val walletManagerFacade = store.inject(DaggerGraphState::walletManagersFacade)
|
||||
return walletManagerFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWallet.walletId,
|
||||
blockchain = blockchain,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun completeTransaction(data: WcTransactionData, cardId: String?): String? {
|
||||
return when (data.type) {
|
||||
WcEthTransactionType.EthSendTransaction -> sendTransaction(data, cardId)
|
||||
WcEthTransactionType.EthSignTransaction -> signTransaction(data, cardId)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getGasPrice(walletManager: WalletManager, transaction: WcEthereumTransaction): BigDecimal {
|
||||
val txGasPrice = transaction.gasPrice?.hexToBigDecimal()
|
||||
if (txGasPrice != null) {
|
||||
return txGasPrice
|
||||
}
|
||||
return when (val result = (walletManager as? EthereumGasLoader)?.getGasPrice()) {
|
||||
is Result.Success -> result.data.toBigDecimal()
|
||||
is Result.Failure -> {
|
||||
(result.error as? Throwable)?.let { Timber.e(it, "getGasPrice failed") }
|
||||
|
||||
error("Unable to get gas price: ${result.error}")
|
||||
}
|
||||
null -> error("Gas price is null")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getGasLimitFromTx(
|
||||
value: BigDecimal,
|
||||
walletManager: WalletManager,
|
||||
transaction: WcEthereumTransaction,
|
||||
blockchain: Blockchain,
|
||||
): BigDecimal {
|
||||
return transaction.gas?.hexToBigDecimal()
|
||||
?: transaction.gasLimit?.hexToBigDecimal()
|
||||
?: getGasLimitFromBlockchain(
|
||||
value = value,
|
||||
walletManager = walletManager,
|
||||
transaction = transaction,
|
||||
).increaseForMantleIfNeeded(blockchain)
|
||||
}
|
||||
|
||||
private suspend fun getGasLimitFromBlockchain(
|
||||
value: BigDecimal,
|
||||
walletManager: WalletManager,
|
||||
transaction: WcEthereumTransaction,
|
||||
): BigDecimal {
|
||||
val gasLimitResult = (walletManager as? EthereumGasLoader)?.getGasLimit(
|
||||
amount = Amount(value, walletManager.wallet.blockchain),
|
||||
destination = transaction.to ?: "",
|
||||
callData = CompiledSmartContractCallData(transaction.data.hexToBytes()),
|
||||
)
|
||||
return when (gasLimitResult) {
|
||||
is Result.Success -> gasLimitResult.data.toBigDecimal().multiply(BigDecimal("1.2"))
|
||||
is Result.Failure -> {
|
||||
(gasLimitResult.error as? Throwable)?.let { Timber.e(it, "getGasLimit failed") }
|
||||
DEFAULT_MAX_GASLIMIT.toBigDecimal() // Set high gasLimit if not provided
|
||||
}
|
||||
else -> DEFAULT_MAX_GASLIMIT.toBigDecimal() // Set high gasLimit if not provided
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Workaround for Mantle. Remove after [REDACTED_JIRA]
|
||||
private fun BigDecimal.increaseForMantleIfNeeded(blockchain: Blockchain): BigDecimal {
|
||||
return if (blockchain == Blockchain.Mantle) {
|
||||
this.multiply(MANTLE_FEE_ESTIMATE_MULTIPLIER)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun sendTransaction(data: WcTransactionData, cardId: String?): String? {
|
||||
val sdk = store.inject(DaggerGraphState::cardSdkConfigRepository).sdk
|
||||
|
||||
val result = (data.walletManager as TransactionSender).send(
|
||||
transactionData = data.transaction,
|
||||
signer = store.inject(DaggerGraphState::transactionSignerFactory).createTransactionSigner(
|
||||
cardId = cardId,
|
||||
sdk = sdk,
|
||||
twinKey = null, // use null here because twin doesn't support WC
|
||||
),
|
||||
)
|
||||
return when (result) {
|
||||
is Result.Success -> {
|
||||
val hash = result.data.hash
|
||||
if (hash.startsWith(HEX_PREFIX)) {
|
||||
hash
|
||||
} else {
|
||||
HEX_PREFIX + hash
|
||||
}
|
||||
}
|
||||
is Result.Failure -> {
|
||||
Timber.e(result.error as BlockchainSdkError)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun signTransaction(data: WcTransactionData, cardId: String?): String? {
|
||||
val dataToSign = EthereumUtils.buildTransactionToSign(
|
||||
transactionData = data.transaction,
|
||||
blockchain = data.walletManager.wallet.blockchain,
|
||||
)
|
||||
|
||||
val command = SignHashCommand(
|
||||
hash = dataToSign.hash,
|
||||
walletPublicKey = data.walletManager.wallet.publicKey.seedKey,
|
||||
derivationPath = data.walletManager.wallet.publicKey.derivationPath,
|
||||
)
|
||||
return when (
|
||||
val result = tangemSdkManager.runTaskAsync(
|
||||
runnable = command,
|
||||
initialMessage = Message(),
|
||||
cardId = cardId,
|
||||
preflightReadFilter = null,
|
||||
)
|
||||
) {
|
||||
is CompletionResult.Success -> {
|
||||
val hash = EthereumUtils.prepareTransactionToSend(
|
||||
signature = result.data.signature,
|
||||
transactionToSign = dataToSign,
|
||||
walletPublicKey = data.walletManager.wallet.publicKey,
|
||||
blockchain = data.walletManager.wallet.blockchain,
|
||||
).toHexString()
|
||||
if (hash.startsWith(HEX_PREFIX)) {
|
||||
hash
|
||||
} else {
|
||||
HEX_PREFIX + hash
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
Timber.e(result.error.customMessage)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun prepareBnbTradeOrder(data: WcBinanceTradeOrder): BinanceMessageData.Trade {
|
||||
return BnbHelper.createMessageData(data)
|
||||
}
|
||||
|
||||
fun prepareBnbTransferOrder(data: WcBinanceTransferOrder): BinanceMessageData.Transfer {
|
||||
return BnbHelper.createMessageData(data)
|
||||
}
|
||||
|
||||
suspend fun signBnbTransaction(
|
||||
data: ByteArray,
|
||||
networkId: String,
|
||||
derivationPath: String?,
|
||||
cardId: String?,
|
||||
): String? {
|
||||
val blockchain = Blockchain.fromNetworkId(networkId) ?: return null
|
||||
val wallet = getWalletManager(blockchain, derivationPath)?.wallet ?: return null
|
||||
|
||||
val command = SignHashCommand(
|
||||
hash = data,
|
||||
walletPublicKey = wallet.publicKey.seedKey,
|
||||
derivationPath = wallet.publicKey.derivationPath,
|
||||
)
|
||||
return when (
|
||||
val result = tangemSdkManager.runTaskAsync(
|
||||
runnable = command,
|
||||
initialMessage = Message(),
|
||||
cardId = cardId,
|
||||
preflightReadFilter = null,
|
||||
)
|
||||
) {
|
||||
is CompletionResult.Success -> {
|
||||
val key = wallet.publicKey.blockchainKey.toDecompressedPublicKey()
|
||||
getBnbResultString(
|
||||
key.toHexString(),
|
||||
result.data.signature.toHexString(),
|
||||
)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
Timber.e(result.error.customMessage)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun prepareDataForPersonalSign(
|
||||
message: WcSignMessage,
|
||||
topic: String,
|
||||
metaName: String,
|
||||
id: Long,
|
||||
): WcPersonalSignData {
|
||||
val messageData = when (message.type) {
|
||||
WcSignMessage.WCSignType.MESSAGE,
|
||||
WcSignMessage.WCSignType.PERSONAL_MESSAGE,
|
||||
-> createMessageData(message)
|
||||
WcSignMessage.WCSignType.TYPED_MESSAGE -> EthereumUtils.makeTypedDataHash(message.data)
|
||||
WcSignMessage.WCSignType.SOLANA_MESSAGE -> message.data.decodeBase58()
|
||||
}
|
||||
|
||||
val messageString = message.data.hexToAscii()
|
||||
?: EthSignHelper.tryToParseEthTypedMessageString(message.data)
|
||||
?: message.data
|
||||
|
||||
val dialogData = PersonalSignDialogData(
|
||||
dAppName = metaName,
|
||||
message = messageString,
|
||||
topic = topic,
|
||||
id = id,
|
||||
)
|
||||
return WcPersonalSignData(
|
||||
hash = requireNotNull(messageData) { "Message data must not be null" },
|
||||
topic = topic,
|
||||
id = id,
|
||||
dialogData = dialogData,
|
||||
type = message.type,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createMessageData(message: WcSignMessage): ByteArray = LegacySdkHelper.createMessageData(message.data)
|
||||
|
||||
private fun String.hexToAscii(): String? = LegacySdkHelper.hexToAscii(hex = this)
|
||||
|
||||
suspend fun signPersonalMessage(
|
||||
hashToSign: ByteArray,
|
||||
networkId: String,
|
||||
type: WcSignMessage.WCSignType,
|
||||
derivationPath: String?,
|
||||
cardId: String?,
|
||||
): String? {
|
||||
val blockchain = Blockchain.fromNetworkId(networkId) ?: return null
|
||||
val wallet = getWalletManager(blockchain, derivationPath)?.wallet ?: return null
|
||||
|
||||
val command = SignHashCommand(
|
||||
hash = hashToSign,
|
||||
walletPublicKey = wallet.publicKey.seedKey,
|
||||
derivationPath = wallet.publicKey.derivationPath,
|
||||
)
|
||||
return when (
|
||||
val result = tangemSdkManager.runTaskAsync(
|
||||
runnable = command,
|
||||
cardId = cardId,
|
||||
preflightReadFilter = null,
|
||||
)
|
||||
) {
|
||||
is CompletionResult.Success -> {
|
||||
val signedHash = result.data.signature
|
||||
|
||||
return when (type) {
|
||||
WcSignMessage.WCSignType.SOLANA_MESSAGE -> getSolanaResultString(signedHash)
|
||||
else -> UnmarshalHelper.unmarshalSignatureExtended(
|
||||
signature = signedHash,
|
||||
hash = hashToSign,
|
||||
publicKey = wallet.publicKey.blockchainKey.toDecompressedPublicKey(),
|
||||
).asRSVLegacyEVM().toHexString().formatHex()
|
||||
.lowercase() // use lowercase because some dapps cant handle UPPERCASE
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
Timber.e(result.error.customMessage)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns result of signing prepared to send in WC request
|
||||
*/
|
||||
suspend fun signTransaction(
|
||||
hashToSign: ByteArray,
|
||||
networkId: String,
|
||||
type: TransactionType,
|
||||
derivationPath: String?,
|
||||
cardId: String?,
|
||||
): String? {
|
||||
val blockchain = Blockchain.fromNetworkId(networkId) ?: return null
|
||||
val wallet = getWalletManager(blockchain, derivationPath)?.wallet ?: return null
|
||||
|
||||
val command = SignHashCommand(
|
||||
hash = hashToSign,
|
||||
walletPublicKey = wallet.publicKey.seedKey,
|
||||
derivationPath = wallet.publicKey.derivationPath,
|
||||
)
|
||||
return when (
|
||||
val result = tangemSdkManager.runTaskAsync(
|
||||
runnable = command,
|
||||
cardId = cardId,
|
||||
preflightReadFilter = null,
|
||||
)
|
||||
) {
|
||||
is CompletionResult.Success -> {
|
||||
val signedHash = result.data.signature
|
||||
|
||||
when (type) {
|
||||
TransactionType.SOLANA_TX -> {
|
||||
getSolanaResultString(signedHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
Timber.e(result.error.customMessage)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns result of signing prepared to send in WC request
|
||||
*/
|
||||
suspend fun signTransactions(
|
||||
hashesToSign: List<ByteArray>,
|
||||
networkId: String,
|
||||
type: TransactionType,
|
||||
derivationPath: String?,
|
||||
cardId: String?,
|
||||
): String? {
|
||||
val blockchain = Blockchain.fromNetworkId(networkId) ?: return null
|
||||
val wallet = getWalletManager(blockchain, derivationPath)?.wallet ?: return null
|
||||
val sdk = store.inject(DaggerGraphState::cardSdkConfigRepository).sdk
|
||||
|
||||
val signer = store.inject(DaggerGraphState::transactionSignerFactory).createTransactionSigner(
|
||||
cardId = cardId,
|
||||
sdk = sdk,
|
||||
twinKey = null,
|
||||
)
|
||||
|
||||
return when (val signingResult = signer.sign(hashesToSign, wallet.publicKey)) {
|
||||
is CompletionResult.Failure -> {
|
||||
Timber.e(signingResult.error.customMessage)
|
||||
null
|
||||
}
|
||||
is CompletionResult.Success -> {
|
||||
when (type) {
|
||||
TransactionType.SOLANA_TX -> {
|
||||
val result = signingResult.data.mapIndexed { index, bytes ->
|
||||
byteArrayOf(1) + bytes + hashesToSign[index]
|
||||
}
|
||||
getSolanaResultTxHashesString(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSolanaResultString(signedHash: ByteArray) = "{ signature: \"${signedHash.encodeBase58()}\" }"
|
||||
|
||||
/**
|
||||
* Build json object
|
||||
* {
|
||||
* "transactions": [
|
||||
* "signed_tx_hash"
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
private fun getSolanaResultTxHashesString(signedHashes: List<ByteArray>): String {
|
||||
val result = JSONObject()
|
||||
val transactions = JSONArray()
|
||||
signedHashes.forEach {
|
||||
transactions.put(it.encodeBase64())
|
||||
}
|
||||
result.put("transactions", transactions)
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
private fun getBnbResultString(publicKey: String, signature: String) =
|
||||
"{\"signature\":\"$signature\",\"publicKey\":\"$publicKey\"}"
|
||||
|
||||
private companion object {
|
||||
const val HEX_PREFIX = "0x"
|
||||
const val DEFAULT_MAX_GASLIMIT = 350000
|
||||
// TODO remove after [REDACTED_JIRA]
|
||||
private val MANTLE_FEE_ESTIMATE_MULTIPLIER = BigDecimal("1.8")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
package com.tangem.tap.domain.walletconnect2.app
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcBlockchainHelper
|
||||
import com.tangem.domain.walletconnect.model.legacy.Account
|
||||
|
||||
internal class TangemWcBlockchainHelper : WcBlockchainHelper {
|
||||
|
||||
private val supportedNonEvmBlockchains = setOf(Blockchain.Solana, Blockchain.SolanaTestnet)
|
||||
|
||||
override fun chainIdToNetworkIdOrNull(chainId: String): String? {
|
||||
val parsedId = chainId.parseId() ?: return null
|
||||
val blockchain = parsedId.chainIdToBlockchain()
|
||||
|
||||
return blockchain?.toNetworkId()
|
||||
}
|
||||
|
||||
override fun chainIdsToBlockchains(chainIds: List<String>): List<Blockchain> {
|
||||
return chainIds.mapNotNull {
|
||||
it.parseId()?.chainIdToBlockchain()
|
||||
}.distinct()
|
||||
}
|
||||
|
||||
override fun chainIdToMissingNetworkNameOrNull(chainId: String): String? {
|
||||
val parsedId = chainId.parseId() ?: return null
|
||||
val blockchain = parsedId.chainIdToBlockchain()
|
||||
|
||||
return blockchain?.fullName
|
||||
?: if (parsedId.first == EVM_NAMESPACE) {
|
||||
chainId
|
||||
} else {
|
||||
parsedId.first.replaceFirstChar(Char::titlecase)
|
||||
}
|
||||
}
|
||||
|
||||
override fun networkIdToChainIdOrNull(networkId: String): List<String> {
|
||||
val blockchain = Blockchain.fromNetworkId(networkId)
|
||||
val namespace = blockchain?.getCaip2Namespace() ?: return emptyList()
|
||||
return blockchain.getCaip2ChainIds().map {
|
||||
"$namespace$CHAIN_SEPARATOR$it"
|
||||
}
|
||||
}
|
||||
|
||||
override fun getNamespaceFromFullChainIdOrNull(chainId: String): String? {
|
||||
val parsed = chainId.split(CHAIN_SEPARATOR)
|
||||
return parsed.firstOrNull()
|
||||
}
|
||||
|
||||
override fun chainIdToFullNameOrNull(chainId: String): String? {
|
||||
val networkId = chainIdToNetworkIdOrNull(chainId) ?: return null
|
||||
return Blockchain.fromNetworkId(networkId)?.fullName
|
||||
}
|
||||
|
||||
override fun chainIdsToAccounts(
|
||||
walletAddress: String,
|
||||
chainIds: List<String>,
|
||||
derivationPath: String?,
|
||||
): List<Account> {
|
||||
return chainIds.map { chainId ->
|
||||
Account(chainId, walletAddress, derivationPath)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Blockchain.getCaip2ChainIds(): List<String> {
|
||||
if (this.isEvm()) return listOfNotNull(this.getChainId()?.toString())
|
||||
|
||||
return when (this) {
|
||||
/*
|
||||
* The WC sample application and documentation use a commented out chain ID. However, in real dApps
|
||||
* uncommented is used.
|
||||
* Docs: https://docs.walletconnect.com/advanced/multichain/chain-list
|
||||
*
|
||||
* */
|
||||
Blockchain.Solana -> listOf("5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")
|
||||
Blockchain.SolanaTestnet -> listOf("z4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z")
|
||||
Blockchain.Polkadot -> listOf("91b171bb158e2d3848fa23a9f1c25182")
|
||||
Blockchain.Tron -> listOf("0x2b6653dc")
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Blockchain.getCaip2Namespace(): String? {
|
||||
return when {
|
||||
this.isEvm() -> EVM_NAMESPACE
|
||||
supportedNonEvmBlockchains.contains(this) -> this.toNetworkId().substringBefore(TESTNET_SEPARATOR)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.parseId(): Pair<String, String>? {
|
||||
val parsed = this.split(CHAIN_SEPARATOR)
|
||||
if (parsed.size != 2) return null
|
||||
|
||||
return parsed[0] to parsed[1]
|
||||
}
|
||||
|
||||
private fun Pair<String, String>.chainIdToBlockchain(): Blockchain? {
|
||||
return when (first) {
|
||||
EVM_NAMESPACE -> {
|
||||
second.toIntOrNull()
|
||||
?.let(Blockchain::fromChainId)
|
||||
}
|
||||
SOLANA_NAMESPACE -> Blockchain.Solana
|
||||
else -> {
|
||||
Blockchain.fromNetworkId(networkId = first)
|
||||
.takeIf(supportedNonEvmBlockchains::contains)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val EVM_NAMESPACE = "eip155"
|
||||
const val SOLANA_NAMESPACE = "solana"
|
||||
const val CHAIN_SEPARATOR = ":"
|
||||
const val TESTNET_SEPARATOR = "/"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
package com.tangem.tap.domain.walletconnect2.app
|
||||
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectEventsHandler
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog
|
||||
import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen
|
||||
import com.tangem.tap.store
|
||||
import timber.log.Timber
|
||||
|
||||
internal class WalletConnectEventsHandlerImpl : WalletConnectEventsHandler {
|
||||
override fun onProposalReceived(proposal: WalletConnectEvents.SessionProposal, networksFormatted: String) {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.SessionProposalDialog(
|
||||
sessionProposal = proposal,
|
||||
networks = networksFormatted,
|
||||
onApprove = { store.dispatchOnMain(WalletConnectAction.ApproveProposal(proposal)) },
|
||||
onReject = { store.dispatchOnMain(WalletConnectAction.RejectProposal) },
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onSessionEstablished() {
|
||||
store.dispatchOnMain(WalletConnectAction.SessionEstablished)
|
||||
}
|
||||
|
||||
override fun onSessionRejected(error: WalletConnectError) {
|
||||
store.dispatchOnMain(WalletConnectAction.SessionRejected(error))
|
||||
}
|
||||
|
||||
override fun onListOfSessionsUpdated(sessions: List<WcSessionForScreen>) {
|
||||
Timber.d("WC2: List of sessions updated. Sessions: $sessions")
|
||||
store.dispatchOnMain(WalletConnectAction.SessionListUpdated(sessions))
|
||||
}
|
||||
|
||||
override fun onSessionRequest(request: WcPreparedRequest) {
|
||||
store.dispatchOnMain(WalletConnectAction.ShowSessionRequest(request))
|
||||
}
|
||||
|
||||
override fun onUnsupportedRequest() {
|
||||
store.dispatchOnMain(WalletConnectAction.RejectUnsupportedRequest)
|
||||
}
|
||||
|
||||
override fun onPairConnectError(error: Throwable) {
|
||||
store.dispatchOnMain(WalletConnectAction.PairConnectErrorAction(error))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,686 +0,0 @@
|
|||
package com.tangem.tap.domain.walletconnect2.data
|
||||
|
||||
import android.app.Application
|
||||
import arrow.core.flatten
|
||||
import com.reown.android.Core
|
||||
import com.reown.android.CoreClient
|
||||
import com.reown.android.relay.ConnectionType
|
||||
import com.reown.walletkit.client.Wallet
|
||||
import com.reown.walletkit.client.WalletKit
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.data.walletconnect.pair.UnsupportedDApps
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.walletconnect.WcPairService
|
||||
import com.tangem.domain.walletconnect.model.WcPairRequest
|
||||
import com.tangem.domain.walletconnect.model.legacy.Account
|
||||
import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase
|
||||
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
|
||||
import com.tangem.tap.common.analytics.events.WalletConnect
|
||||
import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper
|
||||
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcJrpcMethods
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcJrpcRequestsDeserializer
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcRequest
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.*
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.OpenSession.SourceType
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import timber.log.Timber
|
||||
|
||||
internal class DefaultLegacyWalletConnectRepositoryFacade constructor(
|
||||
private val stub: LegacyWalletConnectRepositoryStub,
|
||||
private val legacy: DefaultLegacyWalletConnectRepository,
|
||||
private val walletConnectFeatureToggles: WalletConnectFeatureToggles,
|
||||
private val wcInitializeUseCase: WcInitializeUseCase,
|
||||
private val wcPairService: WcPairService,
|
||||
) : LegacyWalletConnectRepository {
|
||||
private val isNewWc by lazy {
|
||||
walletConnectFeatureToggles.isRedesignedWalletConnectEnabled
|
||||
}
|
||||
override val events: Flow<WalletConnectEvents> by lazy {
|
||||
if (isNewWc) stub.events else legacy.events
|
||||
}
|
||||
override val activeSessions: Flow<List<WalletConnectSession>> by lazy {
|
||||
if (isNewWc) stub.activeSessions else legacy.activeSessions
|
||||
}
|
||||
override val currentSessions: List<WalletConnectSession> by lazy {
|
||||
if (isNewWc) stub.currentSessions else legacy.currentSessions
|
||||
}
|
||||
|
||||
override fun init(projectId: String) {
|
||||
if (isNewWc) {
|
||||
wcInitializeUseCase.init(projectId)
|
||||
} else {
|
||||
legacy.init(projectId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setUserNamespaces(userNamespaces: Map<NetworkNamespace, List<Account>>) {
|
||||
if (isNewWc) stub.setUserNamespaces(userNamespaces) else legacy.setUserNamespaces(userNamespaces)
|
||||
}
|
||||
|
||||
override fun updateSessions() {
|
||||
if (isNewWc) stub.updateSessions() else legacy.updateSessions()
|
||||
}
|
||||
|
||||
override fun pair(userWalletId: UserWalletId, uri: String, source: SourceType) {
|
||||
val src = when (source) {
|
||||
SourceType.QR -> WcPairRequest.Source.QR
|
||||
SourceType.DEEPLINK -> WcPairRequest.Source.DEEPLINK
|
||||
SourceType.CLIPBOARD -> WcPairRequest.Source.CLIPBOARD
|
||||
SourceType.ETC -> WcPairRequest.Source.ETC
|
||||
}
|
||||
if (isNewWc) {
|
||||
wcPairService.pair(WcPairRequest(uri = uri, source = src, userWalletId = userWalletId))
|
||||
} else {
|
||||
legacy.pair(userWalletId = userWalletId, uri = uri, source = source)
|
||||
}
|
||||
}
|
||||
|
||||
override fun disconnect(topic: String) {
|
||||
if (isNewWc) stub.disconnect(topic) else legacy.disconnect(topic)
|
||||
}
|
||||
|
||||
override fun approve(userNamespaces: Map<NetworkNamespace, List<Account>>, blockchainNames: List<String>) {
|
||||
if (isNewWc) stub.approve(userNamespaces, blockchainNames) else legacy.approve(userNamespaces, blockchainNames)
|
||||
}
|
||||
|
||||
override fun reject() {
|
||||
if (isNewWc) stub.reject() else legacy.reject()
|
||||
}
|
||||
|
||||
override fun sendRequest(requestData: RequestData, result: String) {
|
||||
if (isNewWc) stub.sendRequest(requestData, result) else legacy.sendRequest(requestData, result)
|
||||
}
|
||||
|
||||
override fun rejectRequest(requestData: RequestData, error: WalletConnectError) {
|
||||
if (isNewWc) stub.rejectRequest(requestData, error) else legacy.rejectRequest(requestData, error)
|
||||
}
|
||||
|
||||
override fun cancelRequest(topic: String, id: Long, message: String) {
|
||||
if (isNewWc) stub.cancelRequest(topic, id, message) else legacy.cancelRequest(topic, id, message)
|
||||
}
|
||||
}
|
||||
|
||||
internal class LegacyWalletConnectRepositoryStub : LegacyWalletConnectRepository {
|
||||
override val events: Flow<WalletConnectEvents> = emptyFlow()
|
||||
override val activeSessions: Flow<List<WalletConnectSession>> = emptyFlow()
|
||||
override val currentSessions: List<WalletConnectSession> = listOf()
|
||||
|
||||
override fun init(projectId: String) = Unit
|
||||
|
||||
override fun setUserNamespaces(userNamespaces: Map<NetworkNamespace, List<Account>>) = Unit
|
||||
|
||||
override fun updateSessions() = Unit
|
||||
|
||||
override fun pair(userWalletId: UserWalletId, uri: String, source: SourceType) = Unit
|
||||
|
||||
override fun disconnect(topic: String) = Unit
|
||||
|
||||
override fun approve(userNamespaces: Map<NetworkNamespace, List<Account>>, blockchainNames: List<String>) = Unit
|
||||
|
||||
override fun reject() = Unit
|
||||
|
||||
override fun sendRequest(requestData: RequestData, result: String) = Unit
|
||||
|
||||
override fun rejectRequest(requestData: RequestData, error: WalletConnectError) = Unit
|
||||
|
||||
override fun cancelRequest(topic: String, id: Long, message: String) = Unit
|
||||
}
|
||||
|
||||
@Suppress("LargeClass")
|
||||
internal class DefaultLegacyWalletConnectRepository(
|
||||
private val application: Application,
|
||||
private val wcRequestDeserializer: WcJrpcRequestsDeserializer,
|
||||
private val analyticsHandler: AnalyticsEventHandler,
|
||||
) : LegacyWalletConnectRepository {
|
||||
|
||||
private var sessionProposal: Wallet.Model.SessionProposal? = null
|
||||
private var userNamespaces: Map<NetworkNamespace, List<Account>>? = null
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
|
||||
private val _events: MutableSharedFlow<WalletConnectEvents> = MutableSharedFlow()
|
||||
override val events: Flow<WalletConnectEvents> = _events
|
||||
|
||||
private val _activeSessions: MutableSharedFlow<List<WalletConnectSession>> = MutableSharedFlow()
|
||||
override val activeSessions: Flow<List<WalletConnectSession>> = _activeSessions
|
||||
private val blockchainHelper by lazy { TangemWcBlockchainHelper() }
|
||||
|
||||
override var currentSessions: List<WalletConnectSession> = emptyList()
|
||||
private set
|
||||
|
||||
/**
|
||||
* @param projectId Project ID at https://cloud.walletconnect.com/
|
||||
*/
|
||||
override fun init(projectId: String) {
|
||||
val relayUrl = "relay.walletconnect.com"
|
||||
val serverUrl = "wss://$relayUrl?projectId=$projectId"
|
||||
val connectionType = ConnectionType.AUTOMATIC
|
||||
|
||||
val appMetaData = Core.Model.AppMetaData(
|
||||
name = "Tangem",
|
||||
description = "Tangem Wallet",
|
||||
url = "tangem.com",
|
||||
icons = listOf(
|
||||
"https://user-images.githubusercontent.com/24321494/124071202-72a00900-da58-11eb-935a-dcdab21de52b.png",
|
||||
),
|
||||
redirect = "kotlin-wallet-wc:/request", // Custom Redirect URI
|
||||
)
|
||||
|
||||
CoreClient.initialize(
|
||||
relayServerUrl = serverUrl,
|
||||
connectionType = connectionType,
|
||||
application = application,
|
||||
metaData = appMetaData,
|
||||
) { error ->
|
||||
Timber.e("Error while initializing client: $error")
|
||||
scope.launch {
|
||||
_events.emit(
|
||||
WalletConnectEvents.SessionApprovalError(
|
||||
WalletConnectError.ExternalApprovalError(error.throwable.message),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
WalletKit.initialize(
|
||||
Wallet.Params.Init(core = CoreClient),
|
||||
onSuccess = {
|
||||
val walletDelegate = defineWalletDelegate()
|
||||
WalletKit.setWalletDelegate(walletDelegate)
|
||||
},
|
||||
onError = { error ->
|
||||
Timber.e("Error while initializing Web3Wallet: $error")
|
||||
scope.launch {
|
||||
_events.emit(
|
||||
WalletConnectEvents.SessionApprovalError(
|
||||
WalletConnectError.ExternalApprovalError(error.throwable.message),
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun defineWalletDelegate(): WalletKit.WalletDelegate {
|
||||
return object : WalletKit.WalletDelegate {
|
||||
|
||||
@Suppress("LongMethod")
|
||||
override fun onSessionProposal(
|
||||
sessionProposal: Wallet.Model.SessionProposal,
|
||||
verifyContext: Wallet.Model.VerifyContext,
|
||||
) {
|
||||
// Triggered when wallet receives the session proposal sent by a Dapp
|
||||
Timber.i("sessionProposal: $sessionProposal")
|
||||
this@DefaultLegacyWalletConnectRepository.sessionProposal = sessionProposal
|
||||
|
||||
if (sessionProposal.name in UnsupportedDApps.list) {
|
||||
Timber.i("Unsupported DApp")
|
||||
scope.launch {
|
||||
_events.emit(
|
||||
WalletConnectEvents.SessionApprovalError(
|
||||
WalletConnectError.UnsupportedDApp,
|
||||
),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val missingNetworks = findMissingNetworks(
|
||||
namespaces = sessionProposal.requiredNamespaces,
|
||||
userNamespaces = this@DefaultLegacyWalletConnectRepository.userNamespaces ?: emptyMap(),
|
||||
)
|
||||
|
||||
if (missingNetworks.isNotEmpty()) {
|
||||
Timber.i("Not added blockchains: $missingNetworks")
|
||||
scope.launch {
|
||||
_events.emit(
|
||||
WalletConnectEvents.SessionApprovalError(
|
||||
WalletConnectError.ApprovalErrorMissingNetworks(missingNetworks.toList()),
|
||||
),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val optionalMissingNetwork = findMissingNetworks(
|
||||
namespaces = sessionProposal.optionalNamespaces,
|
||||
userNamespaces = this@DefaultLegacyWalletConnectRepository.userNamespaces ?: emptyMap(),
|
||||
)
|
||||
|
||||
val optionalWithoutMissingNetworks = removeMissingNetworks(
|
||||
namespaces = sessionProposal.optionalNamespaces,
|
||||
userNamespaces = this@DefaultLegacyWalletConnectRepository.userNamespaces ?: emptyMap(),
|
||||
)
|
||||
|
||||
// for cases when optionalNamespaces is not empty but we doesn't support none of them
|
||||
if (optionalMissingNetwork.isNotEmpty() &&
|
||||
optionalWithoutMissingNetworks.isEmpty() &&
|
||||
sessionProposal.requiredNamespaces.isEmpty() // if requiredNamespaces is not empty we can connect
|
||||
) {
|
||||
Timber.i("Not added optional blockchains: $optionalMissingNetwork")
|
||||
|
||||
val unsupportedNetworks = sessionProposal.optionalNamespaces.values
|
||||
.flatMap { it.chains ?: emptyList() }
|
||||
.filter { blockchainHelper.chainIdToNetworkIdOrNull(it) == null }
|
||||
|
||||
scope.launch {
|
||||
val error = if (unsupportedNetworks.isNotEmpty()) {
|
||||
WalletConnectEvents.SessionApprovalError(
|
||||
WalletConnectError.ApprovalErrorUnsupportedNetwork(unsupportedNetworks),
|
||||
)
|
||||
} else {
|
||||
WalletConnectEvents.SessionApprovalError(
|
||||
WalletConnectError.ApprovalErrorMissingNetworks(optionalMissingNetwork.toList()),
|
||||
)
|
||||
}
|
||||
_events.emit(error)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val requiredChainIds = sessionProposal.requiredNamespaces.values.flatMap { it.chains ?: emptyList() }
|
||||
val optionalChainIds = optionalWithoutMissingNetworks.toList()
|
||||
val networks = (requiredChainIds + optionalChainIds)
|
||||
.mapNotNull { blockchainHelper.chainIdToFullNameOrNull(it) }
|
||||
.distinct()
|
||||
analyticsHandler.send(WalletConnect.DAppConnectionRequested(networks))
|
||||
|
||||
scope.launch {
|
||||
_events.emit(
|
||||
WalletConnectEvents.SessionProposal(
|
||||
sessionProposal.name,
|
||||
sessionProposal.description,
|
||||
sessionProposal.url,
|
||||
sessionProposal.icons,
|
||||
requiredChainIds,
|
||||
optionalChainIds,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSessionRequest(
|
||||
sessionRequest: Wallet.Model.SessionRequest,
|
||||
verifyContext: Wallet.Model.VerifyContext,
|
||||
) {
|
||||
// Triggered when a Dapp sends SessionRequest to sign a transaction or a message
|
||||
Timber.i("sessionRequest: $sessionRequest")
|
||||
val request = wcRequestDeserializer.deserialize(
|
||||
method = sessionRequest.request.method,
|
||||
params = sessionRequest.request.params,
|
||||
)
|
||||
Timber.i("sessionRequestParsed: $request")
|
||||
|
||||
when (request) {
|
||||
is WcRequest.AddChain -> {
|
||||
// we can send approval automatically, because in WC 2.0 the list of chains is approved when
|
||||
// initial connection is established
|
||||
sendRequest(
|
||||
RequestData(
|
||||
topic = sessionRequest.topic,
|
||||
requestId = sessionRequest.request.id,
|
||||
blockchain = sessionRequest.chainId.toString(),
|
||||
method = WcJrpcMethods.WALLET_ADD_ETHEREUM_CHAIN.code,
|
||||
),
|
||||
result = "",
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
val event = WalletConnect.SignatureRequestReceived(
|
||||
WalletConnect.RequestHandledParams(
|
||||
dAppName = sessionRequest.peerMetaData?.name ?: "",
|
||||
dAppUrl = sessionRequest.peerMetaData?.url ?: "",
|
||||
methodName = sessionRequest.request.method,
|
||||
blockchain = sessionRequest.chainId
|
||||
?.let { blockchainHelper.chainIdToNetworkIdOrNull(it) } ?: "",
|
||||
),
|
||||
)
|
||||
analyticsHandler.send(event)
|
||||
scope.launch {
|
||||
_events.emit(
|
||||
WalletConnectEvents.SessionRequest(
|
||||
request = request,
|
||||
chainId = sessionRequest.chainId,
|
||||
topic = sessionRequest.topic,
|
||||
id = sessionRequest.request.id,
|
||||
metaUrl = sessionRequest.peerMetaData?.url ?: "",
|
||||
metaName = sessionRequest.peerMetaData?.name ?: "",
|
||||
method = sessionRequest.request.method,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSessionDelete(sessionDelete: Wallet.Model.SessionDelete) {
|
||||
// Triggered when the session is deleted by the peer
|
||||
if (sessionDelete is Wallet.Model.SessionDelete.Success) {
|
||||
scope.launch {
|
||||
_events.emit(WalletConnectEvents.SessionDeleted(sessionDelete.topic))
|
||||
updateSessionsInternal().join()
|
||||
}
|
||||
}
|
||||
Timber.i("onSessionDelete: $sessionDelete")
|
||||
}
|
||||
|
||||
override fun onSessionExtend(session: Wallet.Model.Session) {
|
||||
Timber.i("onSessionExtend: $session")
|
||||
}
|
||||
|
||||
override fun onSessionSettleResponse(settleSessionResponse: Wallet.Model.SettledSessionResponse) {
|
||||
// Triggered when wallet receives the session settlement response from Dapp
|
||||
Timber.i("onSessionSettleResponse: $settleSessionResponse")
|
||||
if (settleSessionResponse is Wallet.Model.SettledSessionResponse.Result) {
|
||||
scope.launch {
|
||||
_events.emit(
|
||||
WalletConnectEvents.SessionApprovalSuccess(
|
||||
topic = settleSessionResponse.session.topic,
|
||||
accounts = userNamespaces?.flatMap { it.value } ?: emptyList(),
|
||||
),
|
||||
)
|
||||
updateSessionsInternal().join()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSessionUpdateResponse(sessionUpdateResponse: Wallet.Model.SessionUpdateResponse) {
|
||||
// Triggered when wallet receives the session update response from Dapp
|
||||
Timber.i("onSessionUpdateResponse: $sessionUpdateResponse")
|
||||
updateSessionsInternal()
|
||||
}
|
||||
|
||||
override fun onConnectionStateChange(state: Wallet.Model.ConnectionState) {
|
||||
// Triggered whenever the connection state is changed
|
||||
Timber.i("onConnectionStateChange: $state")
|
||||
if (state.isAvailable) updateSessionsInternal()
|
||||
}
|
||||
|
||||
override fun onError(error: Wallet.Model.Error) {
|
||||
// Triggered whenever there is an issue inside the SDK
|
||||
Timber.i("onError: $error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun setUserNamespaces(userNamespaces: Map<NetworkNamespace, List<Account>>) {
|
||||
this.userNamespaces = userNamespaces
|
||||
}
|
||||
|
||||
override fun pair(userWalletId: UserWalletId, uri: String, source: SourceType) {
|
||||
analyticsHandler.send(WalletConnect.NewSessionInitiated(source = source))
|
||||
WalletKit.pair(
|
||||
params = Wallet.Params.Pair(uri),
|
||||
onSuccess = {
|
||||
Timber.i("Paired successfully: $it")
|
||||
},
|
||||
onError = {
|
||||
Timber.e("Error while pairing: $it")
|
||||
analyticsHandler.send(WalletConnect.SessionFailed)
|
||||
scope.launch {
|
||||
_events.emit(
|
||||
WalletConnectEvents.PairConnectError(it.throwable),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun approve(userNamespaces: Map<NetworkNamespace, List<Account>>, blockchainNames: List<String>) {
|
||||
val sessionProposal: Wallet.Model.SessionProposal = requireNotNull(this.sessionProposal)
|
||||
|
||||
val userChains = userNamespaces.flatMap { namespace ->
|
||||
namespace.value.map { it.chainId to "${it.chainId}:${it.walletAddress}" }
|
||||
}.groupBy { pair -> pair.first }
|
||||
.mapValues { entry -> entry.value.map { pair -> pair.second }.toSet() }
|
||||
|
||||
val preparedRequiredNamespaces = sessionProposal.requiredNamespaces
|
||||
.map { requiredNamespace ->
|
||||
val accountsRequired = requiredNamespace.value.chains
|
||||
?.mapNotNull { chain -> userChains[chain] }
|
||||
?.flatten() ?: emptyList()
|
||||
val optionalNamespace = sessionProposal.optionalNamespaces[requiredNamespace.key]
|
||||
val accountsOptional = optionalNamespace?.chains
|
||||
?.mapNotNull { chain -> userChains[chain] }
|
||||
?.flatten() ?: emptyList()
|
||||
|
||||
val methods = (requiredNamespace.value.methods + (optionalNamespace?.methods ?: emptyList()))
|
||||
.distinct()
|
||||
requiredNamespace.key to Wallet.Model.Namespace.Session(
|
||||
accounts = (accountsRequired + accountsOptional).distinct(),
|
||||
methods = methods,
|
||||
events = requiredNamespace.value.events,
|
||||
)
|
||||
}.toMap()
|
||||
|
||||
val sessionApproval = Wallet.Params.SessionApprove(
|
||||
proposerPublicKey = sessionProposal.proposerPublicKey,
|
||||
namespaces = preparedRequiredNamespaces.ifEmpty {
|
||||
sessionProposal.createPreparedOptionalNamespaces(userChains)
|
||||
}.filterValues { it.accounts.isNotEmpty() && it.chains?.isNotEmpty() == true },
|
||||
)
|
||||
|
||||
Timber.i("Session approval is prepared for sending: $sessionApproval")
|
||||
|
||||
WalletKit.approveSession(
|
||||
params = sessionApproval,
|
||||
onSuccess = {
|
||||
Timber.i("Approved successfully: $it")
|
||||
analyticsHandler.send(
|
||||
WalletConnect.DAppConnected(
|
||||
dAppName = sessionProposal.name,
|
||||
dAppUrl = sessionProposal.url,
|
||||
blockchainNames = blockchainNames,
|
||||
),
|
||||
)
|
||||
},
|
||||
onError = {
|
||||
Timber.e("Error while approving: $it")
|
||||
analyticsHandler.send(
|
||||
WalletConnect.DAppConnectionFailed(
|
||||
dAppName = sessionProposal.name,
|
||||
dAppUrl = sessionProposal.url,
|
||||
blockchainNames = blockchainNames,
|
||||
),
|
||||
)
|
||||
scope.launch {
|
||||
_events.emit(
|
||||
WalletConnectEvents.SessionApprovalError(
|
||||
WalletConnectError.ExternalApprovalError(it.throwable.message),
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun Wallet.Model.SessionProposal.createPreparedOptionalNamespaces(
|
||||
userChains: Map<String, Set<String>>,
|
||||
): Map<String, Wallet.Model.Namespace.Session> {
|
||||
return optionalNamespaces
|
||||
.map { optionalNamespace ->
|
||||
val accountsOptional = optionalNamespace.value.chains
|
||||
?.mapNotNull { chain -> userChains[chain] }
|
||||
?.flatten() ?: emptyList()
|
||||
|
||||
val methods = optionalNamespace.value.methods
|
||||
optionalNamespace.key to Wallet.Model.Namespace.Session(
|
||||
accounts = accountsOptional.distinct(),
|
||||
methods = methods,
|
||||
chains = userChains.keys
|
||||
.filter { it.startsWith(optionalNamespace.key) }
|
||||
.toList(),
|
||||
events = optionalNamespace.value.events,
|
||||
)
|
||||
}
|
||||
.toMap()
|
||||
}
|
||||
|
||||
override fun sendRequest(requestData: RequestData, result: String) {
|
||||
val session = currentSessions.find { it.topic == requestData.topic }
|
||||
// Add Ethereum Chain method is processed without user input, skip logging it
|
||||
if (requestData.method != WcJrpcMethods.WALLET_ADD_ETHEREUM_CHAIN.code) {
|
||||
analyticsHandler.send(
|
||||
WalletConnect.SignatureRequestHandled(
|
||||
WalletConnect.RequestHandledParams(
|
||||
dAppName = session?.name ?: "",
|
||||
dAppUrl = session?.url ?: "",
|
||||
methodName = requestData.method,
|
||||
blockchain = requestData.blockchain,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val params = Wallet.Params.SessionRequestResponse(
|
||||
sessionTopic = requestData.topic,
|
||||
jsonRpcResponse = Wallet.Model.JsonRpcResponse.JsonRpcResult(
|
||||
id = requestData.requestId,
|
||||
result = result,
|
||||
),
|
||||
)
|
||||
Timber.i("Session request response: $params")
|
||||
|
||||
WalletKit.respondSessionRequest(
|
||||
params = params,
|
||||
onSuccess = { response ->
|
||||
Timber.i("Session request responded successfully: $response")
|
||||
},
|
||||
onError = { error ->
|
||||
Timber.e(error.throwable, "Error while responging session request")
|
||||
|
||||
val handledParams = WalletConnect.RequestHandledParams(
|
||||
dAppName = session?.name ?: "",
|
||||
dAppUrl = session?.url ?: "",
|
||||
methodName = requestData.method,
|
||||
blockchain = requestData.blockchain,
|
||||
errorCode = WalletConnectError.ValidationError.error,
|
||||
errorDescription = error.throwable.message,
|
||||
)
|
||||
analyticsHandler.send(WalletConnect.SignatureRequestFailed(handledParams))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun rejectRequest(requestData: RequestData, error: WalletConnectError) {
|
||||
val session = currentSessions.find { it.topic == requestData.topic }
|
||||
|
||||
analyticsHandler.send(
|
||||
WalletConnect.SignatureRequestFailed(
|
||||
WalletConnect.RequestHandledParams(
|
||||
dAppName = session?.name ?: "",
|
||||
dAppUrl = session?.url ?: "",
|
||||
methodName = requestData.method,
|
||||
blockchain = requestData.blockchain,
|
||||
errorCode = error.toString(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
cancelRequest(requestData.topic, requestData.requestId, error.error)
|
||||
}
|
||||
|
||||
override fun cancelRequest(topic: String, id: Long, message: String) {
|
||||
WalletKit.respondSessionRequest(
|
||||
params = Wallet.Params.SessionRequestResponse(
|
||||
sessionTopic = topic,
|
||||
jsonRpcResponse = Wallet.Model.JsonRpcResponse.JsonRpcError(
|
||||
id = id,
|
||||
code = 0,
|
||||
message = message,
|
||||
),
|
||||
),
|
||||
onSuccess = {},
|
||||
onError = {},
|
||||
)
|
||||
}
|
||||
|
||||
override fun reject() {
|
||||
WalletKit.rejectSession(
|
||||
params = Wallet.Params.SessionReject(
|
||||
proposerPublicKey = sessionProposal?.proposerPublicKey ?: "",
|
||||
reason = "",
|
||||
),
|
||||
onSuccess = {
|
||||
Timber.i("Rejected successfully: $it")
|
||||
},
|
||||
onError = {
|
||||
Timber.e("Error while rejecting: $it")
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun disconnect(topic: String) {
|
||||
val session = currentSessions.find { it.topic == topic }
|
||||
WalletKit.disconnectSession(
|
||||
params = Wallet.Params.SessionDisconnect(topic),
|
||||
onSuccess = {
|
||||
analyticsHandler.send(
|
||||
WalletConnect.SessionDisconnected(
|
||||
dAppName = session?.name ?: "",
|
||||
dAppUrl = session?.url ?: "",
|
||||
),
|
||||
)
|
||||
updateSessionsInternal()
|
||||
Timber.i("Disconnected successfully: $it")
|
||||
},
|
||||
onError = {
|
||||
Timber.e("Error while disconnecting: $it")
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fun send(topic: String, id: Long, data: String) {
|
||||
WalletKit.respondSessionRequest(
|
||||
params = Wallet.Params.SessionRequestResponse(
|
||||
sessionTopic = topic,
|
||||
jsonRpcResponse = Wallet.Model.JsonRpcResponse.JsonRpcResult(
|
||||
id = id,
|
||||
result = data,
|
||||
),
|
||||
),
|
||||
onError = {},
|
||||
onSuccess = {},
|
||||
)
|
||||
}
|
||||
|
||||
override fun updateSessions() {
|
||||
updateSessionsInternal()
|
||||
}
|
||||
|
||||
private fun updateSessionsInternal(): Job = scope.launch {
|
||||
val availableSessions = WalletKit.getListOfActiveSessions()
|
||||
.map {
|
||||
WalletConnectSession(
|
||||
topic = it.topic,
|
||||
icon = it.metaData?.icons?.firstOrNull(),
|
||||
name = it.metaData?.name,
|
||||
url = it.metaData?.url,
|
||||
)
|
||||
}
|
||||
Timber.i("Available sessions: $availableSessions")
|
||||
currentSessions = availableSessions
|
||||
_activeSessions.emit(availableSessions)
|
||||
}
|
||||
|
||||
private fun findMissingNetworks(
|
||||
namespaces: Map<String, Wallet.Model.Namespace.Proposal>,
|
||||
userNamespaces: Map<NetworkNamespace, List<Account>>,
|
||||
): Collection<String> {
|
||||
val requiredChains = namespaces.values.flatMap { it.chains ?: emptyList() }
|
||||
val userChains = userNamespaces.flatMap { it.value.map { account -> account.chainId } }
|
||||
return requiredChains.subtract(userChains.toSet())
|
||||
}
|
||||
|
||||
private fun removeMissingNetworks(
|
||||
namespaces: Map<String, Wallet.Model.Namespace.Proposal>,
|
||||
userNamespaces: Map<NetworkNamespace, List<Account>>,
|
||||
): Collection<String> {
|
||||
val wcProvidedChains = namespaces.values.flatMap { it.chains ?: emptyList() }
|
||||
val userChains = userNamespaces.flatMap { it.value.map { account -> account.chainId } }
|
||||
return wcProvidedChains.intersect(userChains.toSet())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
package com.tangem.tap.domain.walletconnect2.data
|
||||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.datasource.files.FileReader
|
||||
import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository
|
||||
import com.tangem.domain.walletconnect.model.legacy.Session
|
||||
import timber.log.Timber
|
||||
|
||||
internal class DefaultWalletConnectSessionsRepository(
|
||||
private val moshi: Moshi,
|
||||
private val fileReader: FileReader,
|
||||
) :
|
||||
WalletConnectSessionsRepository {
|
||||
|
||||
private val sessionsAdapter: JsonAdapter<List<Session>> = moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, Session::class.java),
|
||||
)
|
||||
|
||||
override suspend fun loadSessions(userWallet: String): List<Session> {
|
||||
return try {
|
||||
val fileContent = fileReader.readFile(getFileNameForUserWallet(userWallet))
|
||||
sessionsAdapter.fromJson(fileContent) ?: emptyList()
|
||||
} catch (exception: Exception) {
|
||||
Timber.d(exception)
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun saveSession(userWallet: String, session: Session) {
|
||||
val updatedList = loadSessions(userWallet).plus(session)
|
||||
writeSessionToFile(sessions = updatedList, userWallet = userWallet)
|
||||
}
|
||||
|
||||
override suspend fun removeSession(userWallet: String, topic: String) {
|
||||
val updatedList = loadSessions(userWallet).filterNot { it.topic == topic }
|
||||
writeSessionToFile(sessions = updatedList, userWallet = userWallet)
|
||||
}
|
||||
|
||||
private fun writeSessionToFile(sessions: List<Session>, userWallet: String) {
|
||||
val serialized = sessionsAdapter.toJson(sessions)
|
||||
try {
|
||||
fileReader.rewriteFile(serialized, getFileNameForUserWallet(userWallet))
|
||||
} catch (exception: Exception) {
|
||||
Timber.e(exception)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val FILE_NAME_PREFIX = "wc_2"
|
||||
|
||||
private fun getFileNameForUserWallet(userWallet: String): String {
|
||||
return "$FILE_NAME_PREFIX-${userWallet.uppercase()}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
package com.tangem.tap.domain.walletconnect2.di
|
||||
|
||||
import android.app.Application
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.datasource.di.SdkMoshi
|
||||
import com.tangem.datasource.files.FileReader
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.walletconnect.WcPairService
|
||||
import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository
|
||||
import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper
|
||||
import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper
|
||||
import com.tangem.tap.domain.walletconnect2.app.WalletConnectEventsHandlerImpl
|
||||
import com.tangem.tap.domain.walletconnect2.data.DefaultLegacyWalletConnectRepository
|
||||
import com.tangem.tap.domain.walletconnect2.data.DefaultLegacyWalletConnectRepositoryFacade
|
||||
import com.tangem.tap.domain.walletconnect2.data.DefaultWalletConnectSessionsRepository
|
||||
import com.tangem.tap.domain.walletconnect2.data.LegacyWalletConnectRepositoryStub
|
||||
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcJrpcRequestsDeserializer
|
||||
import com.tangem.tap.domain.walletconnect2.toggles.DefaultWalletConnectFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object WalletConnectInteractorModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideWalletConnectInteractor(
|
||||
wcRepository: LegacyWalletConnectRepository,
|
||||
wcSessionsRepository: WalletConnectSessionsRepository,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
walletConnectFeatureToggles: WalletConnectFeatureToggles,
|
||||
coroutineDispatcherProvider: CoroutineDispatcherProvider,
|
||||
getSelectedWalletUseCase: GetSelectedWalletUseCase,
|
||||
): WalletConnectInteractor {
|
||||
return WalletConnectInteractor(
|
||||
handler = WalletConnectEventsHandlerImpl(),
|
||||
walletConnectRepository = wcRepository,
|
||||
sessionsRepository = wcSessionsRepository,
|
||||
sdkHelper = WalletConnectSdkHelper(),
|
||||
blockchainHelper = TangemWcBlockchainHelper(),
|
||||
currenciesRepository = currenciesRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
getSelectedWalletUseCase = getSelectedWalletUseCase,
|
||||
dispatchers = coroutineDispatcherProvider,
|
||||
walletConnectFeatureToggles = walletConnectFeatureToggles,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object WalletConnectModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideWalletConnectFeatureToggles(featureTogglesManager: FeatureTogglesManager): WalletConnectFeatureToggles {
|
||||
return DefaultWalletConnectFeatureToggles(featureTogglesManager)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideWalletConnectRepository(
|
||||
application: Application,
|
||||
wcRequestDeserializer: WcJrpcRequestsDeserializer,
|
||||
analyticsHandler: AnalyticsEventHandler,
|
||||
walletConnectFeatureToggles: WalletConnectFeatureToggles,
|
||||
wcInitializeUseCase: WcInitializeUseCase,
|
||||
wcPairService: WcPairService,
|
||||
): LegacyWalletConnectRepository {
|
||||
val legacy = DefaultLegacyWalletConnectRepository(
|
||||
application = application,
|
||||
wcRequestDeserializer = wcRequestDeserializer,
|
||||
analyticsHandler = analyticsHandler,
|
||||
)
|
||||
val stub = LegacyWalletConnectRepositoryStub()
|
||||
return DefaultLegacyWalletConnectRepositoryFacade(
|
||||
stub,
|
||||
legacy,
|
||||
walletConnectFeatureToggles,
|
||||
wcInitializeUseCase,
|
||||
wcPairService,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideWalletConnectSessionsRepository(
|
||||
@SdkMoshi moshi: Moshi,
|
||||
fileReader: FileReader,
|
||||
): WalletConnectSessionsRepository {
|
||||
return DefaultWalletConnectSessionsRepository(
|
||||
moshi = moshi,
|
||||
fileReader = fileReader,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.walletconnect.model.legacy.Account
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.*
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.OpenSession.SourceType
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface LegacyWalletConnectRepository {
|
||||
|
||||
val events: Flow<WalletConnectEvents>
|
||||
|
||||
val activeSessions: Flow<List<WalletConnectSession>>
|
||||
|
||||
val currentSessions: List<WalletConnectSession>
|
||||
|
||||
fun init(projectId: String)
|
||||
|
||||
fun setUserNamespaces(userNamespaces: Map<NetworkNamespace, List<Account>>)
|
||||
|
||||
fun updateSessions()
|
||||
|
||||
fun pair(userWalletId: UserWalletId, uri: String, source: SourceType)
|
||||
|
||||
fun disconnect(topic: String)
|
||||
|
||||
fun approve(userNamespaces: Map<NetworkNamespace, List<Account>>, blockchainNames: List<String>)
|
||||
|
||||
fun reject()
|
||||
|
||||
fun sendRequest(requestData: RequestData, result: String)
|
||||
|
||||
fun rejectRequest(requestData: RequestData, error: WalletConnectError)
|
||||
|
||||
fun cancelRequest(topic: String, id: Long, message: String = "")
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
|
||||
import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen
|
||||
|
||||
interface WalletConnectEventsHandler {
|
||||
fun onProposalReceived(proposal: WalletConnectEvents.SessionProposal, networksFormatted: String)
|
||||
|
||||
fun onSessionEstablished()
|
||||
|
||||
fun onSessionRejected(error: WalletConnectError)
|
||||
|
||||
fun onListOfSessionsUpdated(sessions: List<WcSessionForScreen>)
|
||||
|
||||
fun onSessionRequest(request: WcPreparedRequest)
|
||||
|
||||
fun onUnsupportedRequest()
|
||||
|
||||
fun onPairConnectError(error: Throwable)
|
||||
}
|
||||
|
|
@ -1,457 +0,0 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import arrow.core.flatten
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.walletconnect.model.legacy.Account
|
||||
import com.tangem.domain.walletconnect.model.legacy.Session
|
||||
import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.filterNotNull
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.*
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import timber.log.Timber
|
||||
import java.util.Stack
|
||||
|
||||
@Suppress("LargeClass", "LongParameterList")
|
||||
class WalletConnectInteractor(
|
||||
private val handler: WalletConnectEventsHandler,
|
||||
private val walletConnectRepository: LegacyWalletConnectRepository,
|
||||
private val sessionsRepository: WalletConnectSessionsRepository,
|
||||
private val sdkHelper: WalletConnectSdkHelper,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val walletConnectFeatureToggles: WalletConnectFeatureToggles,
|
||||
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
|
||||
val blockchainHelper: WcBlockchainHelper,
|
||||
) {
|
||||
private val isNewWc by lazy { walletConnectFeatureToggles.isRedesignedWalletConnectEnabled }
|
||||
|
||||
private var isWalletConnectReadyForDeepLinks = false
|
||||
|
||||
private val wcScope = CoroutineScope(
|
||||
SupervisorJob() + dispatchers.io + CoroutineExceptionHandler { _, throwable ->
|
||||
Timber.e("CoroutineException: from: LISTENER SCOPE, exception: $throwable")
|
||||
},
|
||||
)
|
||||
|
||||
private val listenerScope = CoroutineScope(
|
||||
SupervisorJob() + dispatchers.io + CoroutineExceptionHandler { _, throwable ->
|
||||
Timber.e("CoroutineException: from: LISTENER SCOPE, exception: $throwable")
|
||||
},
|
||||
)
|
||||
|
||||
/** Stack of deeplinks to handle if user wallet is not selected or cryptocurrency statuses are not available */
|
||||
private val deeplinkStack: Stack<String> = Stack()
|
||||
|
||||
private val events = walletConnectRepository.events
|
||||
private val sessions = walletConnectRepository.activeSessions
|
||||
|
||||
private var userWalletId: String = ""
|
||||
private var cardId: String? = null
|
||||
|
||||
private var currentRequest: WalletConnectEvents.SessionRequest? = null
|
||||
private val sessionRequestConverter = WcSessionRequestConverter(
|
||||
blockchainHelper = blockchainHelper,
|
||||
sessionsRepository = sessionsRepository,
|
||||
sdkHelper = sdkHelper,
|
||||
)
|
||||
|
||||
init {
|
||||
getSelectedWalletUseCase().onRight { userWalletFlow ->
|
||||
userWalletFlow
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.onEach(::initWithWallet)
|
||||
.flowOn(dispatchers.io)
|
||||
.launchIn(wcScope)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initWithWallet(userWallet: UserWallet) {
|
||||
if (isNewWc) return
|
||||
if (userWallet.isMultiCurrency) {
|
||||
Timber.i("WalletConnect: initialize and setup networks for ${userWallet.walletId}")
|
||||
startListeningWc(userWallet.walletId.stringValue, getCardId(userWallet))
|
||||
subscribeOnCurrenciesUpdates(userWallet)
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeOnCurrenciesUpdates(userWallet: UserWallet) {
|
||||
currenciesRepository.getMultiCurrencyWalletCurrenciesUpdates(userWallet.walletId)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.onEach { currencies ->
|
||||
setupUserChains(userWallet, currencies)
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
.launchIn(wcScope)
|
||||
}
|
||||
|
||||
private suspend fun setupUserChains(userWallet: UserWallet, currencies: List<CryptoCurrency>) {
|
||||
val accounts = getAccountsForWc(
|
||||
userWallet = userWallet,
|
||||
networks = currencies.map { it.network }.distinct(),
|
||||
)
|
||||
setUserChains(accounts)
|
||||
handleDeeplinkStack(accounts)
|
||||
}
|
||||
|
||||
private fun startListeningWc(userWalletId: String, cardId: String?) {
|
||||
this.userWalletId = userWalletId
|
||||
this.cardId = cardId
|
||||
listenerScope.coroutineContext.cancelChildren()
|
||||
listenerScope.launch {
|
||||
launch { subscribeToEvents() }
|
||||
launch { subscribeToSessions() }
|
||||
walletConnectRepository.updateSessions()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setUserChains(accounts: List<Account>) {
|
||||
val userNamespaces: Map<NetworkNamespace, List<Account>> = accounts
|
||||
.groupBy { account ->
|
||||
blockchainHelper.getNamespaceFromFullChainIdOrNull(account.chainId)
|
||||
?.let { NetworkNamespace(it) }
|
||||
}.filterNotNull()
|
||||
walletConnectRepository.setUserNamespaces(userNamespaces)
|
||||
}
|
||||
|
||||
private fun handleDeeplinkStack(accounts: List<Account>) {
|
||||
runCatching {
|
||||
if (accounts.isEmpty()) return
|
||||
isWalletConnectReadyForDeepLinks = true
|
||||
if (deeplinkStack.empty()) return
|
||||
val lastDeeplink = deeplinkStack.pop()
|
||||
val action = WalletConnectAction
|
||||
.OpenSession(
|
||||
wcUri = lastDeeplink,
|
||||
source = WalletConnectAction.OpenSession.SourceType.DEEPLINK,
|
||||
userWalletId = UserWalletId(userWalletId),
|
||||
)
|
||||
store.dispatchOnMain(action)
|
||||
}.onFailure {
|
||||
Timber.e("WC deeplink handling failed. $it")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun subscribeToEvents() {
|
||||
events
|
||||
.onEach { wcEvent ->
|
||||
Timber.i("WalletConnect: event: $wcEvent")
|
||||
when (wcEvent) {
|
||||
is WalletConnectEvents.SessionProposal -> {
|
||||
val unsupportedNetworks = wcEvent.requiredChainIds
|
||||
.filter { blockchainHelper.chainIdToNetworkIdOrNull(it) == null }
|
||||
if (unsupportedNetworks.isNotEmpty()) {
|
||||
val error = WalletConnectError.ApprovalErrorUnsupportedNetwork(unsupportedNetworks)
|
||||
handler.onSessionRejected(error)
|
||||
return@onEach
|
||||
}
|
||||
val networksFormatted = (wcEvent.requiredChainIds + wcEvent.optionalChainIds)
|
||||
.mapNotNull { blockchainHelper.chainIdToFullNameOrNull(it) }
|
||||
.distinct()
|
||||
.toString()
|
||||
handler.onProposalReceived(proposal = wcEvent, networksFormatted = networksFormatted)
|
||||
}
|
||||
is WalletConnectEvents.SessionApprovalError -> {
|
||||
val error = when (wcEvent.error) {
|
||||
is WalletConnectError.ApprovalErrorMissingNetworks -> {
|
||||
val missingNetworks = wcEvent.error.missingChains.mapNotNull {
|
||||
blockchainHelper.chainIdToMissingNetworkNameOrNull(it)
|
||||
}
|
||||
|
||||
WalletConnectError.ApprovalErrorAddNetwork(missingNetworks)
|
||||
}
|
||||
else -> wcEvent.error
|
||||
}
|
||||
handler.onSessionRejected(error)
|
||||
}
|
||||
is WalletConnectEvents.SessionApprovalSuccess -> {
|
||||
sessionsRepository.saveSession(
|
||||
userWallet = userWalletId,
|
||||
session = Session(
|
||||
accounts = wcEvent.accounts,
|
||||
topic = wcEvent.topic,
|
||||
),
|
||||
)
|
||||
walletConnectRepository.updateSessions()
|
||||
handler.onSessionEstablished()
|
||||
}
|
||||
is WalletConnectEvents.SessionDeleted -> {
|
||||
sessionsRepository.removeSession(userWalletId, wcEvent.topic)
|
||||
}
|
||||
is WalletConnectEvents.SessionRequest -> {
|
||||
handleRequest(wcEvent)
|
||||
}
|
||||
is WalletConnectEvents.PairConnectError -> {
|
||||
handler.onPairConnectError(wcEvent.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
.collect()
|
||||
}
|
||||
|
||||
private suspend fun subscribeToSessions() {
|
||||
sessions
|
||||
.onEach { listOfSessions ->
|
||||
val relevantTopics = getTopicsForUserWallet(userWalletId, sessionsRepository)
|
||||
val filteredSessions = filterSessionsForUserWallet(listOfSessions, relevantTopics)
|
||||
handler.onListOfSessionsUpdated(filteredSessions)
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
.collect()
|
||||
}
|
||||
|
||||
private fun filterSessionsForUserWallet(
|
||||
availableSessions: List<WalletConnectSession>,
|
||||
relevantTopics: List<String>,
|
||||
): List<WcSessionForScreen> {
|
||||
return availableSessions.filter { relevantTopics.contains(it.topic) }
|
||||
.map {
|
||||
WcSessionForScreen(
|
||||
description = it.name ?: "",
|
||||
sessionId = it.topic,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getTopicsForUserWallet(
|
||||
userWalletId: String,
|
||||
repository: WalletConnectSessionsRepository,
|
||||
): List<String> {
|
||||
return repository.loadSessions(userWalletId).map { it.topic }
|
||||
}
|
||||
|
||||
fun approveSessionProposal(accounts: List<Account>) {
|
||||
if (isNewWc) return
|
||||
Timber.i("Approve session proposal: $accounts")
|
||||
val userNamespaces: Map<NetworkNamespace, List<Account>> = accounts
|
||||
.groupBy { account ->
|
||||
blockchainHelper.getNamespaceFromFullChainIdOrNull(account.chainId)
|
||||
?.let { NetworkNamespace(it) }
|
||||
}.filterNotNull()
|
||||
|
||||
val blockchainNames = blockchainHelper.chainIdsToBlockchains(userNamespaces.values.flatten().map { it.chainId })
|
||||
.map { it.fullName }
|
||||
walletConnectRepository.approve(
|
||||
userNamespaces = userNamespaces,
|
||||
blockchainNames = blockchainNames,
|
||||
)
|
||||
}
|
||||
|
||||
fun rejectSessionProposal() {
|
||||
if (isNewWc) return
|
||||
Timber.i("Reject session proposal")
|
||||
walletConnectRepository.reject()
|
||||
}
|
||||
|
||||
fun disconnectSession(topic: String) {
|
||||
if (isNewWc) return
|
||||
Timber.i("Disconnect session: $topic")
|
||||
walletConnectRepository.disconnect(topic)
|
||||
}
|
||||
|
||||
fun cancelRequest(topic: String, id: Long) {
|
||||
if (isNewWc) return
|
||||
Timber.i("Cancel request: $topic, $id")
|
||||
walletConnectRepository.cancelRequest(topic, id)
|
||||
}
|
||||
|
||||
private suspend fun handleRequest(sessionRequest: WalletConnectEvents.SessionRequest) {
|
||||
val error: WalletConnectError? = when {
|
||||
sessionsRepository.loadSessions(userWalletId).none { it.topic == sessionRequest.topic } -> {
|
||||
WalletConnectError.WrongUserWallet
|
||||
}
|
||||
sessionRequest.request is WcRequest.CustomRequest -> {
|
||||
WalletConnectError.UnsupportedMethod
|
||||
}
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
val networkId = sessionRequest.chainId?.let { blockchainHelper.chainIdToNetworkIdOrNull(it) } ?: ""
|
||||
val requestData = RequestData(
|
||||
topic = sessionRequest.topic,
|
||||
requestId = sessionRequest.id,
|
||||
blockchain = networkId,
|
||||
method = sessionRequest.method,
|
||||
)
|
||||
|
||||
if (error != null) {
|
||||
walletConnectRepository.rejectRequest(requestData, error)
|
||||
return
|
||||
}
|
||||
|
||||
when (sessionRequest.request) {
|
||||
is WcRequest.BnbCancel -> Unit
|
||||
is WcRequest.BnbTxConfirm -> walletConnectRepository.sendRequest(
|
||||
requestData = requestData,
|
||||
result = "",
|
||||
)
|
||||
else -> {
|
||||
currentRequest = sessionRequest
|
||||
|
||||
val data = prepareRequestData(sessionRequest).getOrElse { e ->
|
||||
val wrappedError = e as? WalletConnectError ?: WalletConnectError.UnknownError(
|
||||
message = e.localizedMessage ?: "Unknown error",
|
||||
)
|
||||
|
||||
walletConnectRepository.rejectRequest(requestData, wrappedError)
|
||||
handler.onSessionRejected(wrappedError)
|
||||
return
|
||||
}
|
||||
|
||||
handler.onSessionRequest(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun continueWithRequest(request: WcPreparedRequest) {
|
||||
if (isNewWc) return
|
||||
val currentRequest = this.currentRequest
|
||||
if (currentRequest == null || request.topic != currentRequest.topic) return
|
||||
|
||||
val networkId = blockchainHelper.chainIdToNetworkIdOrNull(currentRequest.chainId.orEmpty()) ?: return
|
||||
val signingResultData = when (request) {
|
||||
is WcPreparedRequest.BnbTransaction -> sdkHelper.signBnbTransaction(
|
||||
data = request.preparedRequestData.data.data,
|
||||
networkId = networkId,
|
||||
derivationPath = request.derivationPath,
|
||||
cardId = cardId,
|
||||
)
|
||||
is WcPreparedRequest.EthTransaction -> sdkHelper.completeTransaction(
|
||||
data = request.preparedRequestData,
|
||||
cardId = cardId,
|
||||
)
|
||||
is WcPreparedRequest.EthSign -> sdkHelper.signPersonalMessage(
|
||||
hashToSign = request.preparedRequestData.hash,
|
||||
networkId = networkId,
|
||||
type = request.preparedRequestData.type,
|
||||
derivationPath = request.derivationPath,
|
||||
cardId = cardId,
|
||||
)
|
||||
is WcPreparedRequest.SolanaSignTransaction -> sdkHelper.signTransaction(
|
||||
hashToSign = request.preparedRequestData.hashToSign,
|
||||
networkId = networkId,
|
||||
type = request.preparedRequestData.type,
|
||||
derivationPath = request.derivationPath,
|
||||
cardId = cardId,
|
||||
)
|
||||
is WcPreparedRequest.SolanaSignMultipleTransactions -> sdkHelper.signTransactions(
|
||||
hashesToSign = request.preparedRequestData.hashesToSign,
|
||||
networkId = networkId,
|
||||
type = request.preparedRequestData.type,
|
||||
derivationPath = request.derivationPath,
|
||||
cardId = cardId,
|
||||
)
|
||||
}
|
||||
|
||||
val requestData = RequestData(
|
||||
topic = request.topic,
|
||||
requestId = request.requestId,
|
||||
blockchain = networkId,
|
||||
method = currentRequest.method,
|
||||
)
|
||||
|
||||
if (signingResultData == null) {
|
||||
walletConnectRepository.rejectRequest(requestData, WalletConnectError.SigningError)
|
||||
} else {
|
||||
walletConnectRepository.sendRequest(
|
||||
requestData = requestData,
|
||||
result = signingResultData,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles Wallet Connect deep links.
|
||||
* If wallet connect is able to handle the deeplink, session is started with deeplink.
|
||||
* Otherwise, deeplink is stored until wallet connect is ready to handle it.
|
||||
*
|
||||
* @param deeplink deeplink to handle
|
||||
*/
|
||||
fun addDeeplink(deeplink: String) {
|
||||
if (isNewWc) return
|
||||
val deeplinkRegex = Regex(WC_PARAM_REGEX)
|
||||
val matched = deeplinkRegex.findAll(deeplink)
|
||||
val sessionTopic = matched.firstOrNull { it.value.contains(WC_TOPIC_QUERY_NAME) }?.groupValues?.lastOrNull()
|
||||
|
||||
val isAlreadyActiveSessionTopic = walletConnectRepository.currentSessions.any { session ->
|
||||
session.topic == sessionTopic
|
||||
}
|
||||
|
||||
if (isAlreadyActiveSessionTopic && sessionTopic != null) {
|
||||
Timber.i("WC already has an active session topic: $deeplink")
|
||||
return
|
||||
}
|
||||
|
||||
if (isWalletConnectReadyForDeepLinks) {
|
||||
val action = WalletConnectAction.OpenSession(
|
||||
wcUri = deeplink,
|
||||
source = WalletConnectAction.OpenSession.SourceType.DEEPLINK,
|
||||
userWalletId = UserWalletId(userWalletId),
|
||||
)
|
||||
store.dispatchOnMain(action)
|
||||
} else {
|
||||
deeplinkStack.push(deeplink)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCardId(userWallet: UserWallet): String? {
|
||||
return if (userWallet is UserWallet.Cold && userWallet.scanResponse.card.backupStatus?.isActive != true) {
|
||||
userWallet.cardId
|
||||
} else { // if wallet has backup, any card from wallet can be used to sign
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getAccountsForWc(userWallet: UserWallet, networks: List<Network>): List<Account> {
|
||||
val walletManagers = networks.mapNotNull {
|
||||
val blockchain = it.toBlockchain()
|
||||
|
||||
walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWallet.walletId,
|
||||
blockchain = blockchain,
|
||||
derivationPath = it.derivationPath.value,
|
||||
)
|
||||
}
|
||||
return walletManagers.flatMap {
|
||||
val wallet = it.wallet
|
||||
val chainIds = blockchainHelper.networkIdToChainIdOrNull(wallet.blockchain.toNetworkId())
|
||||
blockchainHelper.chainIdsToAccounts(
|
||||
walletAddress = wallet.address,
|
||||
chainIds = chainIds,
|
||||
derivationPath = wallet.publicKey.derivationPath?.rawPath,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun prepareRequestData(
|
||||
sessionRequest: WalletConnectEvents.SessionRequest,
|
||||
): Result<WcPreparedRequest> {
|
||||
return sessionRequestConverter.prepareRequest(sessionRequest, userWalletId)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val WC_TOPIC_QUERY_NAME = "sessionTopic"
|
||||
const val WC_PARAM_REGEX = "([a-zA-Z\\d-]+)=([a-zA-Z\\d]+)"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.walletconnect.model.legacy.Account
|
||||
|
||||
interface WcBlockchainHelper {
|
||||
fun chainIdToNetworkIdOrNull(chainId: String): String?
|
||||
|
||||
fun chainIdToMissingNetworkNameOrNull(chainId: String): String?
|
||||
|
||||
fun networkIdToChainIdOrNull(networkId: String): List<String>
|
||||
|
||||
fun getNamespaceFromFullChainIdOrNull(chainId: String): String?
|
||||
|
||||
fun chainIdToFullNameOrNull(chainId: String): String?
|
||||
|
||||
fun chainIdsToAccounts(walletAddress: String, chainIds: List<String>, derivationPath: String?): List<Account>
|
||||
|
||||
fun chainIdsToBlockchains(chainIds: List<String>): List<Blockchain>
|
||||
}
|
||||
|
|
@ -1,297 +0,0 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import com.squareup.moshi.*
|
||||
import com.tangem.datasource.di.SdkMoshi
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceCancelOrder
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTradeOrder
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTransferOrder
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTxConfirmParam
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.solana.SolanaSignMessage
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.solana.SolanaTransactionRequest
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
internal enum class WcJrpcMethods(val code: String) {
|
||||
|
||||
ETH_SIGN("eth_sign"),
|
||||
ETH_PERSONAL_SIGN("personal_sign"),
|
||||
ETH_SIGN_TYPE_DATA("eth_signTypedData"),
|
||||
ETH_SIGN_TYPE_DATA_V4("eth_signTypedData_v4"),
|
||||
ETH_SIGN_TRANSACTION("eth_signTransaction"),
|
||||
ETH_SEND_TRANSACTION("eth_sendTransaction"),
|
||||
|
||||
BNB_SIGN("bnb_sign"),
|
||||
BNB_TRANSACTION_CONFIRM("bnb_tx_confirmation"),
|
||||
SIGN_TRANSACTION("trust_signTransaction"),
|
||||
WALLET_ADD_ETHEREUM_CHAIN("wallet_addEthereumChain"),
|
||||
|
||||
SOLANA_SIGN_TX("solana_signTransaction"),
|
||||
SOLANA_SIGN_MESSAGE("solana_signMessage"),
|
||||
SOLANA_SIGN_ALL_TX("solana_signAllTransactions"),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun fromCode(code: String): WcJrpcMethods? = values().firstOrNull { it.code == code }
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WcSignTransaction(
|
||||
@Json(name = "network")
|
||||
val network: Int,
|
||||
|
||||
@Json(name = "transaction")
|
||||
val transaction: String,
|
||||
) : WcRequestData
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WcSignMessage(
|
||||
@Json(name = "raw")
|
||||
val raw: List<String>,
|
||||
|
||||
@Json(name = "type")
|
||||
val type: WCSignType,
|
||||
) : WcRequestData {
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class WCSignType {
|
||||
MESSAGE, PERSONAL_MESSAGE, TYPED_MESSAGE, SOLANA_MESSAGE,
|
||||
}
|
||||
|
||||
/**
|
||||
* Raw parameters will always be the message and the address. Depending on the WCSignType,
|
||||
* those parameters can be swapped as description below:
|
||||
*
|
||||
* - MESSAGE: `[address, data ]`
|
||||
* - TYPED_MESSAGE: `[address, data]`
|
||||
* - PERSONAL_MESSAGE: `[data, address]`
|
||||
* - SOLANA_MESSAGE: `[publicKey (address), message]`
|
||||
*
|
||||
* reference: https://docs.walletconnect.org/json-rpc/ethereum#eth_signtypeddata
|
||||
*/
|
||||
val data
|
||||
get() = when (type) {
|
||||
WCSignType.PERSONAL_MESSAGE -> raw[0]
|
||||
else -> raw[1]
|
||||
}
|
||||
|
||||
val address
|
||||
get() = when (type) {
|
||||
WCSignType.PERSONAL_MESSAGE -> raw[1]
|
||||
else -> raw[0]
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WcSignMessageData(
|
||||
@Json(name = "address")
|
||||
val address: String,
|
||||
|
||||
@Json(name = "message")
|
||||
val message: String,
|
||||
) : WcRequestData
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WcAddChain(
|
||||
@Json(name = "chainId")
|
||||
val chainId: String,
|
||||
) : WcRequestData
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WcEthereumTransaction(
|
||||
@Json(name = "from")
|
||||
val from: String,
|
||||
|
||||
@Json(name = "to")
|
||||
val to: String?,
|
||||
|
||||
@Json(name = "nonce")
|
||||
val nonce: String?,
|
||||
|
||||
@Json(name = "gasPrice")
|
||||
val gasPrice: String?,
|
||||
|
||||
@Json(name = "maxFeePerGas")
|
||||
val maxFeePerGas: String?,
|
||||
|
||||
@Json(name = "maxPriorityFeePerGas")
|
||||
val maxPriorityFeePerGas: String?,
|
||||
|
||||
@Json(name = "gas")
|
||||
val gas: String?,
|
||||
|
||||
@Json(name = "gasLimit")
|
||||
val gasLimit: String?,
|
||||
|
||||
@Json(name = "value")
|
||||
val value: String?,
|
||||
|
||||
@Json(name = "data")
|
||||
val data: String,
|
||||
) : WcRequestData
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SolanaTransactionsRequest(
|
||||
@Json(name = "transactions")
|
||||
val transactions: List<String>,
|
||||
) : WcRequestData
|
||||
|
||||
interface WcRequestData
|
||||
|
||||
data class WcCustomRequestData(val data: String) : WcRequestData
|
||||
|
||||
sealed class WcRequest(open val data: WcRequestData) {
|
||||
data class EthSign(override val data: WcSignMessage) : WcRequest(data)
|
||||
data class EthSignTransaction(override val data: WcEthereumTransaction) : WcRequest(data)
|
||||
data class EthSendTransaction(override val data: WcEthereumTransaction) : WcRequest(data)
|
||||
data class BnbTrade(override val data: WcBinanceTradeOrder) : WcRequest(data)
|
||||
data class BnbCancel(override val data: WcBinanceCancelOrder) : WcRequest(data)
|
||||
data class BnbTransfer(override val data: WcBinanceTransferOrder) : WcRequest(data)
|
||||
data class BnbTxConfirm(override val data: WcBinanceTxConfirmParam) : WcRequest(data)
|
||||
data class SignTransaction(override val data: WcSignTransaction) : WcRequest(data)
|
||||
data class AddChain(override val data: WcAddChain) : WcRequest(data)
|
||||
data class CustomRequest(override val data: WcCustomRequestData) : WcRequest(data)
|
||||
data class SolanaSignRequest(override val data: SolanaTransactionRequest) : WcRequest(data)
|
||||
data class SolanaSignTransactions(override val data: SolanaTransactionsRequest) : WcRequest(data)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
internal class WcJrpcRequestsDeserializer @Inject constructor(@SdkMoshi private val moshi: Moshi) {
|
||||
|
||||
@Suppress("ComplexMethod", "LongMethod")
|
||||
fun deserialize(method: String, params: String): WcRequest {
|
||||
val customRequest = WcRequest.CustomRequest(WcCustomRequestData(params))
|
||||
val wcMethod: WcJrpcMethods = WcJrpcMethods.fromCode(method) ?: return customRequest
|
||||
|
||||
return when (wcMethod) {
|
||||
WcJrpcMethods.ETH_SIGN_TRANSACTION -> {
|
||||
val deserializedParams = moshi.adapter<List<WcEthereumTransaction>>(
|
||||
Types.newParameterizedType(List::class.java, WcEthereumTransaction::class.java),
|
||||
).fromJsonFirstOrNull(params) ?: return customRequest
|
||||
WcRequest.EthSignTransaction(data = deserializedParams)
|
||||
}
|
||||
WcJrpcMethods.ETH_SEND_TRANSACTION -> {
|
||||
val deserializedParams = moshi.adapter<List<WcEthereumTransaction>>(
|
||||
Types.newParameterizedType(List::class.java, WcEthereumTransaction::class.java),
|
||||
).fromJsonFirstOrNull(params) ?: return customRequest
|
||||
WcRequest.EthSendTransaction(data = deserializedParams)
|
||||
}
|
||||
WcJrpcMethods.ETH_SIGN -> {
|
||||
val deserializedParams = moshi.adapter<List<String>>(
|
||||
Types.newParameterizedType(List::class.java, String::class.java),
|
||||
).fromJsonOrNull(params) ?: return customRequest
|
||||
val data = WcSignMessage(
|
||||
raw = deserializedParams,
|
||||
type = WcSignMessage.WCSignType.MESSAGE,
|
||||
)
|
||||
WcRequest.EthSign(data = data)
|
||||
}
|
||||
WcJrpcMethods.ETH_PERSONAL_SIGN -> {
|
||||
val deserializedParams = moshi.adapter<List<String>>(
|
||||
Types.newParameterizedType(List::class.java, String::class.java),
|
||||
).fromJsonOrNull(params) ?: return customRequest
|
||||
val data = WcSignMessage(
|
||||
raw = deserializedParams,
|
||||
type = WcSignMessage.WCSignType.PERSONAL_MESSAGE,
|
||||
)
|
||||
WcRequest.EthSign(data = data)
|
||||
}
|
||||
WcJrpcMethods.ETH_SIGN_TYPE_DATA, WcJrpcMethods.ETH_SIGN_TYPE_DATA_V4 -> {
|
||||
val deserializedParams = listOf(
|
||||
params.substring(params.indexOf("\"") + 1, params.indexOf("\"", startIndex = 2)),
|
||||
params.substring(params.indexOfFirst { it == '{' }, params.indexOfLast { it == '}' } + 1),
|
||||
)
|
||||
val data = WcSignMessage(
|
||||
deserializedParams,
|
||||
WcSignMessage.WCSignType.TYPED_MESSAGE,
|
||||
)
|
||||
Timber.d("TypedData params: $deserializedParams")
|
||||
WcRequest.EthSign(data)
|
||||
}
|
||||
WcJrpcMethods.BNB_SIGN -> {
|
||||
return deserializeBnb(moshi, params) ?: customRequest
|
||||
}
|
||||
WcJrpcMethods.BNB_TRANSACTION_CONFIRM -> {
|
||||
val deserializedParams = moshi.adapter<List<WcBinanceTxConfirmParam>>(
|
||||
Types.newParameterizedType(List::class.java, WcBinanceTxConfirmParam::class.java),
|
||||
).fromJsonFirstOrNull(params) ?: return customRequest
|
||||
WcRequest.BnbTxConfirm(data = deserializedParams)
|
||||
}
|
||||
WcJrpcMethods.SIGN_TRANSACTION -> {
|
||||
val deserializedParams = moshi.adapter<List<WcSignTransaction>>(
|
||||
Types.newParameterizedType(List::class.java, WcSignTransaction::class.java),
|
||||
).fromJsonFirstOrNull(params) ?: return customRequest
|
||||
|
||||
WcRequest.SignTransaction(data = deserializedParams)
|
||||
}
|
||||
WcJrpcMethods.WALLET_ADD_ETHEREUM_CHAIN -> {
|
||||
val deserializedParams: WcAddChain = moshi.adapter<List<WcAddChain>>(
|
||||
Types.newParameterizedType(List::class.java, WcAddChain::class.java),
|
||||
).fromJsonFirstOrNull(params) ?: return customRequest
|
||||
|
||||
WcRequest.AddChain(data = deserializedParams)
|
||||
}
|
||||
WcJrpcMethods.SOLANA_SIGN_TX -> {
|
||||
val tx = moshi.adapter(SolanaTransactionRequest::class.java)
|
||||
.fromJsonOrNull(params)
|
||||
?: return customRequest
|
||||
|
||||
WcRequest.SolanaSignRequest(data = tx)
|
||||
}
|
||||
WcJrpcMethods.SOLANA_SIGN_MESSAGE -> {
|
||||
val signMessage = moshi.adapter(SolanaSignMessage::class.java)
|
||||
.fromJsonOrNull(params)
|
||||
?: return customRequest
|
||||
val data = WcSignMessage(
|
||||
raw = listOf(signMessage.publicKey, signMessage.message),
|
||||
type = WcSignMessage.WCSignType.SOLANA_MESSAGE,
|
||||
)
|
||||
|
||||
WcRequest.EthSign(data = data)
|
||||
}
|
||||
WcJrpcMethods.SOLANA_SIGN_ALL_TX -> {
|
||||
val transactionsToSign = moshi.adapter(SolanaTransactionsRequest::class.java)
|
||||
.fromJsonOrNull(params) ?: return customRequest
|
||||
WcRequest.SolanaSignTransactions(transactionsToSign)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun deserializeBnb(moshi: Moshi, params: String): WcRequest? {
|
||||
val cancelOrder = moshi.adapter<List<WcBinanceCancelOrder>>(
|
||||
Types.newParameterizedType(List::class.java, WcBinanceCancelOrder::class.java),
|
||||
).fromJsonFirstOrNull(params)
|
||||
if (cancelOrder != null) return WcRequest.BnbCancel(cancelOrder)
|
||||
|
||||
val tradeOrder = moshi.adapter<List<WcBinanceTradeOrder>>(
|
||||
Types.newParameterizedType(List::class.java, WcBinanceTradeOrder::class.java),
|
||||
).fromJsonFirstOrNull(params)
|
||||
if (tradeOrder != null) return WcRequest.BnbTrade(tradeOrder)
|
||||
|
||||
val transferOrder = moshi.adapter<List<WcBinanceTransferOrder>>(
|
||||
Types.newParameterizedType(List::class.java, WcBinanceTransferOrder::class.java),
|
||||
).fromJsonFirstOrNull(params)
|
||||
if (transferOrder != null) return WcRequest.BnbTransfer(transferOrder)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun <T> JsonAdapter<T>.fromJsonOrNull(data: String): T? {
|
||||
return try {
|
||||
this.fromJson(data)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e.message)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> JsonAdapter<List<T>>.fromJsonFirstOrNull(data: String): T? {
|
||||
return try {
|
||||
this.fromJson(data)?.firstOrNull()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e.message)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.BnbData
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WcPersonalSignData
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WcTransactionData
|
||||
|
||||
sealed class WcPreparedRequest(
|
||||
open val preparedRequestData: Any,
|
||||
val topic: String,
|
||||
val requestId: Long,
|
||||
val derivationPath: String?,
|
||||
) {
|
||||
class EthSign(
|
||||
override val preparedRequestData: WcPersonalSignData,
|
||||
topic: String,
|
||||
requestId: Long,
|
||||
derivationPath: String?,
|
||||
) : WcPreparedRequest(preparedRequestData, topic, requestId, derivationPath)
|
||||
|
||||
class EthTransaction(
|
||||
override val preparedRequestData: WcTransactionData,
|
||||
topic: String,
|
||||
requestId: Long,
|
||||
derivationPath: String?,
|
||||
) : WcPreparedRequest(preparedRequestData, topic, requestId, derivationPath)
|
||||
|
||||
class BnbTransaction(
|
||||
override val preparedRequestData: BnbData,
|
||||
topic: String,
|
||||
requestId: Long,
|
||||
derivationPath: String?,
|
||||
) : WcPreparedRequest(preparedRequestData, topic, requestId, derivationPath)
|
||||
|
||||
class SolanaSignTransaction(
|
||||
override val preparedRequestData: GenericTransactionData.SingleHash,
|
||||
topic: String,
|
||||
requestId: Long,
|
||||
derivationPath: String?,
|
||||
) : WcPreparedRequest(preparedRequestData, topic, requestId, derivationPath)
|
||||
|
||||
class SolanaSignMultipleTransactions(
|
||||
override val preparedRequestData: GenericTransactionData.MultipleHashes,
|
||||
topic: String,
|
||||
requestId: Long,
|
||||
derivationPath: String?,
|
||||
) : WcPreparedRequest(preparedRequestData, topic, requestId, derivationPath)
|
||||
}
|
||||
|
||||
sealed interface GenericTransactionData {
|
||||
val dAppName: String
|
||||
val type: TransactionType
|
||||
|
||||
data class SingleHash(
|
||||
val hashToSign: ByteArray,
|
||||
override val dAppName: String,
|
||||
override val type: TransactionType,
|
||||
) : GenericTransactionData
|
||||
|
||||
data class MultipleHashes(
|
||||
val hashesToSign: List<ByteArray>,
|
||||
override val dAppName: String,
|
||||
override val type: TransactionType,
|
||||
) : GenericTransactionData
|
||||
}
|
||||
|
||||
enum class TransactionType {
|
||||
SOLANA_TX,
|
||||
}
|
||||
|
|
@ -1,184 +0,0 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import com.tangem.blockchain.blockchains.solana.SolanaTransactionHelper
|
||||
import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.BnbData
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WcEthTransactionType
|
||||
import okio.ByteString.Companion.decodeBase64
|
||||
|
||||
internal class WcSessionRequestConverter(
|
||||
private val blockchainHelper: WcBlockchainHelper,
|
||||
private val sessionsRepository: WalletConnectSessionsRepository,
|
||||
private val sdkHelper: WalletConnectSdkHelper,
|
||||
) {
|
||||
|
||||
@Suppress("LongMethod")
|
||||
suspend fun prepareRequest(
|
||||
sessionRequest: WalletConnectEvents.SessionRequest,
|
||||
userWalletId: String,
|
||||
): Result<WcPreparedRequest> = runCatching {
|
||||
val networkId = requireNotNull(blockchainHelper.chainIdToNetworkIdOrNull(sessionRequest.chainId.orEmpty())) {
|
||||
"Failed to get network ID for chain ID: ${sessionRequest.chainId}"
|
||||
}
|
||||
val derivationPath = getDerivationPath(
|
||||
sessionsRepository = sessionsRepository,
|
||||
sessionRequest = sessionRequest,
|
||||
userWalletId = userWalletId,
|
||||
walletAddress = getWalletAddress(sessionRequest.request),
|
||||
)
|
||||
|
||||
when (val request = sessionRequest.request) {
|
||||
is WcRequest.EthSendTransaction -> {
|
||||
val data = sdkHelper.prepareTransactionData(
|
||||
EthTransactionData(
|
||||
transaction = request.data,
|
||||
networkId = networkId,
|
||||
rawDerivationPath = derivationPath,
|
||||
id = sessionRequest.id,
|
||||
topic = sessionRequest.topic,
|
||||
type = WcEthTransactionType.EthSendTransaction,
|
||||
metaName = sessionRequest.metaName,
|
||||
metaUrl = sessionRequest.metaUrl,
|
||||
),
|
||||
)
|
||||
|
||||
WcPreparedRequest.EthTransaction(
|
||||
preparedRequestData = data,
|
||||
topic = sessionRequest.topic,
|
||||
requestId = sessionRequest.id,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
}
|
||||
is WcRequest.EthSignTransaction -> {
|
||||
val data = sdkHelper.prepareTransactionData(
|
||||
EthTransactionData(
|
||||
transaction = request.data,
|
||||
networkId = networkId,
|
||||
rawDerivationPath = derivationPath,
|
||||
id = sessionRequest.id,
|
||||
topic = sessionRequest.topic,
|
||||
type = WcEthTransactionType.EthSignTransaction,
|
||||
metaName = sessionRequest.metaName,
|
||||
metaUrl = sessionRequest.metaUrl,
|
||||
),
|
||||
)
|
||||
|
||||
WcPreparedRequest.EthTransaction(
|
||||
preparedRequestData = data,
|
||||
topic = sessionRequest.topic,
|
||||
requestId = sessionRequest.id,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
}
|
||||
is WcRequest.EthSign -> {
|
||||
val data = sdkHelper.prepareDataForPersonalSign(
|
||||
request.data,
|
||||
sessionRequest.topic,
|
||||
sessionRequest.metaName,
|
||||
sessionRequest.id,
|
||||
)
|
||||
WcPreparedRequest.EthSign(
|
||||
preparedRequestData = data,
|
||||
topic = sessionRequest.topic,
|
||||
requestId = sessionRequest.id,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
}
|
||||
is WcRequest.BnbTrade -> {
|
||||
val data = sdkHelper.prepareBnbTradeOrder(request.data)
|
||||
WcPreparedRequest.BnbTransaction(
|
||||
BnbData(
|
||||
data = data,
|
||||
topic = sessionRequest.topic,
|
||||
requestId = sessionRequest.id,
|
||||
dAppName = sessionRequest.metaName,
|
||||
),
|
||||
topic = sessionRequest.topic,
|
||||
requestId = sessionRequest.id,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
}
|
||||
is WcRequest.BnbTransfer -> {
|
||||
val data = sdkHelper.prepareBnbTransferOrder(request.data)
|
||||
WcPreparedRequest.BnbTransaction(
|
||||
BnbData(
|
||||
data = data,
|
||||
topic = sessionRequest.topic,
|
||||
requestId = sessionRequest.id,
|
||||
dAppName = sessionRequest.metaName,
|
||||
),
|
||||
topic = sessionRequest.topic,
|
||||
requestId = sessionRequest.id,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
}
|
||||
is WcRequest.SolanaSignRequest -> {
|
||||
val transaction = request.data.transaction
|
||||
|
||||
WcPreparedRequest.SolanaSignTransaction(
|
||||
preparedRequestData = GenericTransactionData.SingleHash(
|
||||
hashToSign = transaction.prepareSolanaTransaction(),
|
||||
dAppName = sessionRequest.metaName,
|
||||
type = TransactionType.SOLANA_TX,
|
||||
),
|
||||
topic = sessionRequest.topic,
|
||||
requestId = sessionRequest.id,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
}
|
||||
is WcRequest.SolanaSignTransactions -> {
|
||||
val transactions = request.data.transactions
|
||||
WcPreparedRequest.SolanaSignMultipleTransactions(
|
||||
preparedRequestData = GenericTransactionData.MultipleHashes(
|
||||
hashesToSign = transactions.map { it.prepareSolanaTransaction() }, //
|
||||
dAppName = sessionRequest.metaName,
|
||||
type = TransactionType.SOLANA_TX,
|
||||
),
|
||||
topic = sessionRequest.topic,
|
||||
requestId = sessionRequest.id,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
}
|
||||
else -> throw WalletConnectError.UnsupportedMethod
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Input transaction in Base64 string
|
||||
*/
|
||||
private fun String.prepareSolanaTransaction(): ByteArray {
|
||||
val transaction = this.decodeBase64()?.toByteArray() ?: ByteArray(0)
|
||||
return SolanaTransactionHelper.removeSignaturesPlaceholders(transaction)
|
||||
}
|
||||
|
||||
private fun getWalletAddress(request: WcRequest): String? {
|
||||
return when (request) {
|
||||
is WcRequest.BnbTrade -> request.data.accountNumber
|
||||
is WcRequest.BnbTransfer -> request.data.accountNumber
|
||||
is WcRequest.EthSendTransaction -> request.data.from
|
||||
is WcRequest.EthSignTransaction -> request.data.from
|
||||
is WcRequest.EthSign -> request.data.address
|
||||
is WcRequest.SolanaSignRequest -> request.data.feePayer
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getDerivationPath(
|
||||
sessionsRepository: WalletConnectSessionsRepository,
|
||||
sessionRequest: WalletConnectEvents.SessionRequest,
|
||||
userWalletId: String,
|
||||
walletAddress: String?,
|
||||
): String? {
|
||||
return sessionsRepository.loadSessions(userWalletId)
|
||||
.firstOrNull { it.topic == sessionRequest.topic }
|
||||
?.accounts?.firstOrNull {
|
||||
// if walletAddress == null take first account
|
||||
it.chainId == sessionRequest.chainId &&
|
||||
(walletAddress == null || it.walletAddress.lowercase() == walletAddress.lowercase())
|
||||
}?.derivationPath
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain.models
|
||||
|
||||
import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData
|
||||
|
||||
data class BnbData(
|
||||
val data: BinanceMessageData,
|
||||
val topic: String,
|
||||
val requestId: Long,
|
||||
val dAppName: String,
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue