Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-21 00:28:36 +03:00
commit 2a36bb65b5
262 changed files with 6989 additions and 2102 deletions

View file

@ -119,6 +119,7 @@ dependencies {
implementation(projects.domain.card) implementation(projects.domain.card)
implementation(projects.domain.demo) implementation(projects.domain.demo)
implementation(projects.domain.demo.models) implementation(projects.domain.demo.models)
implementation(projects.domain.express)
implementation(projects.domain.wallets) implementation(projects.domain.wallets)
implementation(projects.domain.wallets.models) implementation(projects.domain.wallets.models)
implementation(projects.domain.settings) implementation(projects.domain.settings)

View file

@ -5,7 +5,10 @@ object TestConstants {
const val RECIPIENT_ADDRESS = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0" const val RECIPIENT_ADDRESS = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0"
const val BITCOIN_ADDRESS = "bc1qtg9aa6jcpqtvun0pe0uct7sxm8nq2nsxfmfxm3" const val BITCOIN_ADDRESS = "bc1qtg9aa6jcpqtvun0pe0uct7sxm8nq2nsxfmfxm3"
const val CARDANO_ADDRESS = "addr1q8f9499e58k4hhfd9vhawprxt3xd94x7rmlyp33ee4xkatakcl2zgkrg0p6ceqkndtkw4cumfe9enhdph8yhuswn785srksm9p" const val CARDANO_ADDRESS =
"addr1q8f9499e58k4hhfd9vhawprxt3xd94x7rmlyp33ee4xkatakcl2zgkrg0p6ceqkndtkw4cumfe9enhdph8yhuswn785srksm9p"
const val SOLANA_RECIPIENT_ADDRESS = "5fcy9woa8Di1QHcce65CsV3XKrxdB2pD4HJx5xx82ipM"
const val POLKADOT_RECIPIENT_ADDRESS = "143TfgFYAFfM86LRzt4UcFNU3KosxCndBCVz2U5HCxpLidKZ"
const val WAIT_UNTIL_TIMEOUT = 20_000L const val WAIT_UNTIL_TIMEOUT = 20_000L
const val WAIT_UNTIL_TIMEOUT_LONG = 30_000L const val WAIT_UNTIL_TIMEOUT_LONG = 30_000L

View file

@ -20,12 +20,12 @@ fun BaseTestCase.swipeVertical(
) )
} }
fun BaseTestCase.pullToRefresh() { fun BaseTestCase.pullToRefresh(steps: Int = 1000) {
swipeVertical( swipeVertical(
direction = SwipeDirection.DOWN, direction = SwipeDirection.DOWN,
startHeightRatio = 0.2f, startHeightRatio = 0.2f,
endHeightRatio = 0.8f, endHeightRatio = 0.8f,
steps = 1000 steps = steps
) )
} }

View file

@ -115,4 +115,28 @@ fun BaseTestCase.checkMultiCurrencyMainScreen(
step("Assert 'Organize tokens' button is displayed") { step("Assert 'Organize tokens' button is displayed") {
onMainScreen { organizeTokensButton().assertIsDisplayed() } onMainScreen { organizeTokensButton().assertIsDisplayed() }
} }
}
fun BaseTestCase.assertActionButtonsForMultiCurrencyWallet(isEnabled: Boolean = true) {
if (isEnabled) {
step("Assert 'Buy' button is enabled") {
onMainScreen { buyButton.assertIsEnabled() }
}
step("Assert 'Swap' button is enabled") {
onMainScreen { swapButton.assertIsEnabled() }
}
step("Assert 'Sell' button is enabled") {
onMainScreen { sellButton.assertIsEnabled() }
}
} else {
step("Assert 'Buy' button is not enabled") {
onMainScreen { buyButton.assertIsNotEnabled() }
}
step("Assert 'Swap' button is not enabled") {
onMainScreen { swapButton.assertIsNotEnabled() }
}
step("Assert 'Sell' button is not enabled") {
onMainScreen { sellButton.assertIsNotEnabled() }
}
}
} }

View file

@ -8,6 +8,7 @@ import com.tangem.screens.AlreadyUsedWalletDialogPageObject.requestSupportButton
import com.tangem.screens.AlreadyUsedWalletDialogPageObject.thisIsMyWalletButton import com.tangem.screens.AlreadyUsedWalletDialogPageObject.thisIsMyWalletButton
import com.tangem.screens.AlreadyUsedWalletDialogPageObject.title import com.tangem.screens.AlreadyUsedWalletDialogPageObject.title
import com.tangem.screens.ScanWarningDialogPageObject import com.tangem.screens.ScanWarningDialogPageObject
import com.tangem.screens.onActionIsUnavailableDialog
import com.tangem.screens.onFailedTransactionDialog import com.tangem.screens.onFailedTransactionDialog
import io.qameta.allure.kotlin.Allure.step import io.qameta.allure.kotlin.Allure.step
@ -63,4 +64,16 @@ fun checkAlreadyUsedWalletDialog() {
step("Assert 'Request support' button is displayed") { step("Assert 'Request support' button is displayed") {
AlreadyUsedWalletDialogPageObject { requestSupportButton.isDisplayed() } AlreadyUsedWalletDialogPageObject { requestSupportButton.isDisplayed() }
} }
}
fun BaseTestCase.checkActionIsUnavailableDialog() {
step("Assert 'Action is unavailable' dialog title is displayed") {
onActionIsUnavailableDialog { title.assertIsDisplayed() }
}
step("Assert 'Action is unavailable' dialog text is displayed") {
onActionIsUnavailableDialog { text.assertIsDisplayed() }
}
step("Assert 'Action is unavailable' dialog 'Ok' button is displayed") {
onActionIsUnavailableDialog { okButton.assertIsDisplayed() }
}
} }

View file

@ -0,0 +1,57 @@
package com.tangem.scenarios
import com.tangem.common.BaseTestCase
import com.tangem.screens.onSendConfirmScreen
import io.github.kakaocup.compose.node.element.KNode
import io.qameta.allure.kotlin.Allure.step
fun BaseTestCase.checkSendWarning(
titleResId: Int,
messageResId: Int,
amount: String,
isDisplayed: Boolean = true,
) {
val assertDisplay = if (isDisplayed) "displayed" else "not displayed"
step("Assert 'Send confirm screen' is displayed") {
onSendConfirmScreen {
title.assertIsDisplayed()
}
}
step("Assert warning title is $assertDisplay") {
onSendConfirmScreen {
warningTitle(titleResId).assertVisibility(isDisplayed)
}
}
step("Assert warning icon is $assertDisplay") {
onSendConfirmScreen {
sendWarningIcon(messageResId, amount).assertVisibility(isDisplayed)
}
}
step("Assert warning message is $assertDisplay") {
onSendConfirmScreen {
sendWarningMessage(messageResId, amount).assertVisibility(isDisplayed)
}
}
if (isDisplayed)
step("Assert 'Send' button is disabled") {
onSendConfirmScreen {
sendButton.assertIsNotEnabled()
}
}
else
step("Assert 'Send' button is enabled") {
onSendConfirmScreen {
sendButton.assertIsEnabled()
}
}
}
private fun KNode.assertVisibility(shouldBeDisplayed: Boolean) {
if (shouldBeDisplayed) {
assertIsDisplayed()
} else {
assertIsNotDisplayed()
}
}

View file

@ -0,0 +1,40 @@
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 ActionIsUnavailableDialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<ActionIsUnavailableDialogPageObject>(semanticsProvider = semanticsProvider) {
val dialogContainer: KNode = child {
hasTestTag(BaseDialogTestTags.CONTAINER)
}
val title: KNode = child {
hasTestTag(BaseDialogTestTags.TITLE)
hasText(getResourceString(CommonUIR.string.action_buttons_something_wrong_alert_title))
useUnmergedTree = true
}
val text: KNode = child {
hasTestTag(BaseDialogTestTags.TEXT)
hasText(getResourceString(CommonUIR.string.action_buttons_something_wrong_alert_message))
useUnmergedTree = true
}
val okButton: KNode = child {
hasTestTag(BaseButtonTestTags.BUTTON)
hasText(getResourceString(R.string.common_ok))
}
}
internal fun BaseTestCase.onActionIsUnavailableDialog(function: ActionIsUnavailableDialogPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -17,6 +17,14 @@ class DialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
hasTestTag(BaseDialogTestTags.CONTAINER) hasTestTag(BaseDialogTestTags.CONTAINER)
} }
val title: KNode = child {
hasTestTag(BaseDialogTestTags.TITLE)
}
val text: KNode = child {
hasTestTag(BaseDialogTestTags.TEXT)
}
val cancelButton: KNode = child { val cancelButton: KNode = child {
hasTestTag(BaseButtonTestTags.BUTTON) hasTestTag(BaseButtonTestTags.BUTTON)
hasText(getResourceString(R.string.common_cancel)) hasText(getResourceString(R.string.common_cancel))

View file

@ -0,0 +1,24 @@
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.*
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 SellPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<SellPageObject>(semanticsProvider = semanticsProvider) {
val title: KNode = child {
hasTestTag(TopAppBarTestTags.TITLE)
hasText(getResourceString(R.string.common_sell))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onSellScreen(function: SellPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -2,17 +2,13 @@ package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase import com.tangem.common.BaseTestCase
import com.tangem.core.ui.R import com.tangem.core.ui.test.*
import com.tangem.core.ui.test.BaseButtonTestTags import com.tangem.wallet.R
import com.tangem.core.ui.test.NotificationTestTags
import com.tangem.core.ui.test.SendConfirmScreenTestTags
import com.tangem.core.ui.test.TopAppBarTestTags
import io.github.kakaocup.compose.node.element.ComposeScreen 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.ComposeScreen.Companion.onComposeScreen
import io.github.kakaocup.compose.node.element.KNode import io.github.kakaocup.compose.node.element.KNode
import io.github.kakaocup.kakao.common.utilities.getResourceString import io.github.kakaocup.kakao.common.utilities.getResourceString
import androidx.compose.ui.test.hasText as withText import androidx.compose.ui.test.hasText as withText
import com.tangem.common.ui.R as CommonUiR
class SendConfirmPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : class SendConfirmPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<SendConfirmPageObject>(semanticsProvider = semanticsProvider) { ComposeScreen<SendConfirmPageObject>(semanticsProvider = semanticsProvider) {
@ -23,15 +19,25 @@ class SendConfirmPageObject(semanticsProvider: SemanticsNodeInteractionsProvider
} }
val sendButton: KNode = child { val sendButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT) hasTestTag(BaseButtonTestTags.BUTTON)
hasText(getResourceString(R.string.common_send)) hasAnyDescendant(withText(getResourceString(R.string.common_send)))
useUnmergedTree = true useUnmergedTree = true
} }
val primaryAmount: KNode = child {
hasTestTag(BaseAmountBlockTestTags.PRIMARY_AMOUNT)
useUnmergedTree = true
}
val minimumSendAmountErrorTitle: KNode = child { fun leaveDepositButton(amount: String): KNode = child {
hasTestTag(BaseButtonTestTags.BUTTON)
hasAnyDescendant(withText(getResourceString(R.string.send_notification_leave_button, amount)))
useUnmergedTree = true
}
fun warningTitle(titleResId: Int): KNode = child {
hasTestTag(NotificationTestTags.TITLE) hasTestTag(NotificationTestTags.TITLE)
hasText(getResourceString(CommonUiR.string.send_notification_invalid_amount_title)) hasText(getResourceString(titleResId))
useUnmergedTree = true useUnmergedTree = true
} }
@ -40,34 +46,35 @@ class SendConfirmPageObject(semanticsProvider: SemanticsNodeInteractionsProvider
useUnmergedTree = true useUnmergedTree = true
} }
fun minimumSendAmountErrorIcon(amount: String): KNode = child { fun sendWarningIcon(messageResId: Int, amount: String): KNode = child {
hasTestTag(NotificationTestTags.ICON)
hasAnySibling( hasAnySibling(
withText( withText(
getResourceString( getResourceString(
CommonUiR.string.send_notification_invalid_minimum_amount_text, messageResId,
amount, amount,
amount, amount,
) )
) )
) )
hasTestTag(NotificationTestTags.ICON)
useUnmergedTree = true useUnmergedTree = true
} }
fun minimumSendAmountErrorMessage( fun sendWarningMessage(
messageResId: Int,
amount: String, amount: String,
): KNode = child { ): KNode = child {
hasTestTag(NotificationTestTags.MESSAGE) hasTestTag(NotificationTestTags.MESSAGE)
hasText( hasText(
getResourceString( getResourceString(
CommonUiR.string.send_notification_invalid_minimum_amount_text, messageResId,
amount, amount,
amount, amount,
) )
) )
useUnmergedTree = true useUnmergedTree = true
} }
} }
internal fun BaseTestCase.onSendConfirmScreen(function: SendConfirmPageObject.() -> Unit) = internal fun BaseTestCase.onSendConfirmScreen(function: SendConfirmPageObject.() -> Unit) =

View file

@ -64,6 +64,12 @@ class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
useUnmergedTree = true useUnmergedTree = true
} }
val continueButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(SendR.string.common_continue))
useUnmergedTree = true
}
} }
internal fun BaseTestCase.onSendScreen(function: SendPageObject.() -> Unit) = internal fun BaseTestCase.onSendScreen(function: SendPageObject.() -> Unit) =

View file

@ -4,11 +4,16 @@ import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.CARDANO_ADDRESS import com.tangem.common.constants.TestConstants.CARDANO_ADDRESS
import com.tangem.common.extensions.clickWithAssertion import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.extensions.pullToRefresh import com.tangem.common.extensions.pullToRefresh
import com.tangem.common.ui.R
import com.tangem.common.utils.resetWireMockScenarioState import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.scenarios.checkSendWarning
import com.tangem.scenarios.openMainScreen import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.synchronizeAddresses import com.tangem.scenarios.synchronizeAddresses
import com.tangem.screens.* import com.tangem.screens.onMainScreen
import com.tangem.screens.onSendAddressScreen
import com.tangem.screens.onSendScreen
import com.tangem.screens.onTokenDetailsScreen
import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName import io.qameta.allure.kotlin.junit4.DisplayName
@ -29,6 +34,9 @@ class BlockchainTest : BaseTestCase() {
val scenarioName = "user_tokens_api" val scenarioName = "user_tokens_api"
val scenarioState = "Cardano" val scenarioState = "Cardano"
val invalidAmountTitleResId = R.string.send_notification_invalid_amount_title
val invalidAmountMessageResId = R.string.send_notification_invalid_minimum_amount_text
setupHooks( setupHooks(
additionalAfterSection = { additionalAfterSection = {
resetWireMockScenarioState(scenarioName) resetWireMockScenarioState(scenarioName)
@ -64,14 +72,12 @@ class BlockchainTest : BaseTestCase() {
step("Click on 'Next' button") { step("Click on 'Next' button") {
onSendAddressScreen { nextButton.clickWithAssertion() } onSendAddressScreen { nextButton.clickWithAssertion() }
} }
step("Assert 'Invalid amount' error title is displayed") { step("Assert 'Invalid amount warning' is displayed") {
onSendConfirmScreen { minimumSendAmountErrorTitle.assertIsDisplayed() } checkSendWarning(
} titleResId = invalidAmountTitleResId,
step("Assert 'Invalid amount' error icon is displayed") { messageResId = invalidAmountMessageResId,
onSendConfirmScreen { minimumSendAmountErrorIcon(minAmount).assertIsDisplayed() } amount = minAmount
} )
step("Assert 'Invalid amount' error message is displayed") {
onSendConfirmScreen { minimumSendAmountErrorMessage(minAmount).assertIsDisplayed() }
} }
step("Press system 'Back' button") { step("Press system 'Back' button") {
device.uiDevice.pressBack() device.uiDevice.pressBack()
@ -97,14 +103,13 @@ class BlockchainTest : BaseTestCase() {
step("Click on 'Next' button") { step("Click on 'Next' button") {
onSendAddressScreen { nextButton.clickWithAssertion() } onSendAddressScreen { nextButton.clickWithAssertion() }
} }
step("Assert 'Invalid amount' error title is not displayed") { step("Assert 'Invalid amount warning' is not displayed") {
onSendConfirmScreen { minimumSendAmountErrorTitle.assertIsNotDisplayed() } checkSendWarning(
} titleResId = invalidAmountTitleResId,
step("Assert 'Invalid amount' error icon is not displayed") { messageResId = invalidAmountMessageResId,
onSendConfirmScreen { minimumSendAmountErrorIcon(minAmount).assertIsNotDisplayed() } amount = minAmount,
} isDisplayed = false
step("Assert 'Invalid amount' error message is not displayed") { )
onSendConfirmScreen { minimumSendAmountErrorMessage(minAmount).assertIsNotDisplayed() }
} }
} }
} }
@ -136,10 +141,16 @@ class BlockchainTest : BaseTestCase() {
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState) setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
} }
step("Set WireMock scenario: '$rippleAccountInfoScenarioName' to state: '$rippleAccountInfoErrorState'") { step("Set WireMock scenario: '$rippleAccountInfoScenarioName' to state: '$rippleAccountInfoErrorState'") {
setWireMockScenarioState(scenarioName = rippleAccountInfoScenarioName, state = rippleAccountInfoErrorState) setWireMockScenarioState(
scenarioName = rippleAccountInfoScenarioName,
state = rippleAccountInfoErrorState
)
} }
step("Set WireMock scenario: '$rippleAccountLinesScenarioName' to state: '$rippleAccountLinesErrorState'") { step("Set WireMock scenario: '$rippleAccountLinesScenarioName' to state: '$rippleAccountLinesErrorState'") {
setWireMockScenarioState(scenarioName = rippleAccountLinesScenarioName, state = rippleAccountLinesErrorState) setWireMockScenarioState(
scenarioName = rippleAccountLinesScenarioName,
state = rippleAccountLinesErrorState
)
} }
step("Open 'Main Screen'") { step("Open 'Main Screen'") {
openMainScreen() openMainScreen()
@ -169,10 +180,16 @@ class BlockchainTest : BaseTestCase() {
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState) setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
} }
step("Set WireMock scenario: '$rippleAccountInfoScenarioName' to state: '$rippleAccountInfoStartedState'") { step("Set WireMock scenario: '$rippleAccountInfoScenarioName' to state: '$rippleAccountInfoStartedState'") {
setWireMockScenarioState(scenarioName = rippleAccountInfoScenarioName, state = rippleAccountInfoStartedState) setWireMockScenarioState(
scenarioName = rippleAccountInfoScenarioName,
state = rippleAccountInfoStartedState
)
} }
step("Set WireMock scenario: '$rippleAccountLinesScenarioName' to state: '$rippleAccountLinesStartedState'") { step("Set WireMock scenario: '$rippleAccountLinesScenarioName' to state: '$rippleAccountLinesStartedState'") {
setWireMockScenarioState(scenarioName = rippleAccountLinesScenarioName, state = rippleAccountLinesStartedState) setWireMockScenarioState(
scenarioName = rippleAccountLinesScenarioName,
state = rippleAccountLinesStartedState
)
} }
step("Pull to refresh") { step("Pull to refresh") {
pullToRefresh() pullToRefresh()

View file

@ -5,11 +5,18 @@ import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.BITCOIN_ADDRESS import com.tangem.common.constants.TestConstants.BITCOIN_ADDRESS
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
import com.tangem.common.extensions.clickWithAssertion import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.extensions.*
import com.tangem.common.utils.assertClipboardTextEquals import com.tangem.common.utils.assertClipboardTextEquals
import com.tangem.common.utils.clearClipboard import com.tangem.common.utils.clearClipboard
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.scenarios.assertActionButtonsForMultiCurrencyWallet
import com.tangem.scenarios.checkActionIsUnavailableDialog
import com.tangem.scenarios.openMainScreen import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.synchronizeAddresses import com.tangem.scenarios.synchronizeAddresses
import com.tangem.screens.* import com.tangem.screens.*
import com.tangem.tap.domain.sdk.mocks.MockContent
import com.tangem.tap.domain.sdk.mocks.content.TwinsMockContent
import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName import io.qameta.allure.kotlin.junit4.DisplayName
@ -391,4 +398,258 @@ class MainScreenActionButtonsTest : BaseTestCase() {
} }
} }
} }
@AllureId("895")
@DisplayName("Action buttons: check blockchain information by click on 'Buy' button")
@Test
fun checkClickOnBuyButtonOnMainTest() {
val cardType: MockContent = TwinsMockContent
val cardName = "Twin"
val tokenTitle = "Bitcoin"
val tokenSymbol = "BTC"
setupHooks().run {
step("Open 'Main Screen' on '$cardName' card") {
openMainScreen(mockContent = cardType, isTwinsCard = true)
}
step("Assert 'Buy' button is displayed") {
onMainScreen { buyButton.assertIsDisplayed() }
}
step("Click on 'Buy' button") {
onMainScreen { buyButton.performClick() }
}
step("Click on 'Confirm' button in 'Dialog'") {
waitForIdle()
onDialog { confirmButton.clickWithAssertion() }
}
step("Assert top app bar title contains '$tokenTitle'") {
onBuyTokenDetailsScreen { topBarTitle.assertTextContains("Buy $tokenTitle") }
}
step("Assert fiat currency text field is displayed") {
onBuyTokenDetailsScreen { fiatAmountTextField.assertIsDisplayed() }
}
step("Assert fiat currency icon is displayed") {
onBuyTokenDetailsScreen { fiatCurrencyIcon.assertIsDisplayed() }
}
step("Assert token amount field is displayed") {
onBuyTokenDetailsScreen { tokenAmountField.assertTextContains(tokenSymbol, substring = true) }
}
step("Assert 'Continue' button") {
onBuyTokenDetailsScreen { continueButton.assertIsDisplayed() }
}
}
}
@AllureId("4395")
@DisplayName("Action buttons (main screen): click on buttons with success response")
@Test
fun clickOnActionButtonsWithSuccessResponseTest() {
val tokenTitle = "Ethereum"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Assert 'Buy' button is displayed") {
onMainScreen { buyButton.assertIsDisplayed() }
}
step("Click on 'Buy' button") {
onMainScreen { buyButton.performClick() }
}
step("Assert 'Buy' screen title is displayed") {
onBuyTokenScreen { topAppBarTitle.assertIsDisplayed() }
}
step("Assert token with title: '$tokenTitle' is displayed") {
onBuyTokenScreen { tokenWithTitleAndFiatAmount(tokenTitle).assertIsDisplayed() }
}
step("Press 'Back' button") {
device.uiDevice.pressBack()
}
step("Assert 'Swap' button is displayed") {
onMainScreen { swapButton.assertIsDisplayed() }
}
step("Click on 'Swap' button") {
onMainScreen { swapButton.performClick() }
}
step("Click on close button on stories screen") {
onSwapStoriesScreen { closeButton.performClick() }
}
step("Assert 'Swap' token screen title is displayed") {
onSwapTokenScreen { title.assertIsDisplayed() }
}
step("Press 'Back' button") {
device.uiDevice.pressBack()
}
step("Assert 'Sell' button is displayed") {
onMainScreen { sellButton.assertIsDisplayed() }
}
step("Click on 'Sell' button") {
onMainScreen { sellButton.performClick() }
}
step("Assert 'Sell' token screen title is displayed") {
onSellScreen { title.assertIsDisplayed() }
}
}
}
@AllureId("4396")
@DisplayName("Action buttons (main screen): click on buttons without data")
@Test
fun clickOnActionButtonsWithoutDataTest() {
setupHooks(
additionalAfterSection = {
enableWiFi()
enableMobileData()
}
).run {
step("Turn off internet") {
disableWiFi()
disableMobileData()
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Assert 'Buy' button is displayed") {
onMainScreen { buyButton.assertIsDisplayed() }
}
step("Click on 'Buy' button") {
onMainScreen { buyButton.performClick() }
}
step("Check 'Action is unavailable' dialog") {
checkActionIsUnavailableDialog()
}
step("Click on 'Ok' button") {
onDialog { okButton.performClick() }
}
step("Assert 'Swap' button is displayed") {
onMainScreen { swapButton.assertIsDisplayed() }
}
step("Click on 'Swap' button") {
onMainScreen { swapButton.performClick() }
}
step("Check 'Action is unavailable' dialog") {
checkActionIsUnavailableDialog()
}
step("Click on 'Ok' button") {
onDialog { okButton.performClick() }
}
step("Assert 'Sell' button is displayed") {
onMainScreen { sellButton.assertIsDisplayed() }
}
step("Click on 'Sell' button") {
onMainScreen { sellButton.performClick() }
}
step("Check 'Action is unavailable' dialog") {
checkActionIsUnavailableDialog()
}
step("Click on 'Ok' button") {
onDialog { okButton.performClick() }
}
}
}
@AllureId("4398")
@DisplayName("Action buttons (main screen): click on buttons with error response")
@Test
fun clickOnActionButtonsWithErrorResponseTest() {
val scenarioName = "express_api_assets"
val scenarioState = "Error"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(scenarioName)
}
).run {
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
setWireMockScenarioState(scenarioName, scenarioState)
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Assert 'Buy' button is displayed") {
onMainScreen { buyButton.assertIsDisplayed() }
}
step("Click on 'Buy' button") {
onMainScreen { buyButton.performClick() }
}
step("Check 'Action is unavailable' dialog") {
checkActionIsUnavailableDialog()
}
step("Click on 'Ok' button") {
onDialog { okButton.performClick() }
}
step("Assert 'Swap' button is displayed") {
onMainScreen { swapButton.assertIsDisplayed() }
}
step("Click on 'Swap' button") {
onMainScreen { swapButton.performClick() }
}
step("Check 'Action is unavailable' dialog") {
checkActionIsUnavailableDialog()
}
step("Click on 'Ok' button") {
onDialog { okButton.performClick() }
}
step("Assert 'Sell' button is displayed") {
onMainScreen { sellButton.assertIsDisplayed() }
}
step("Click on 'Sell' button") {
onMainScreen { sellButton.performClick() }
}
// ToDo("[REDACTED_JIRA] - add ability to use MoonPay mocks")
// step("Check 'Action is unavailable' dialog") {
// checkActionIsUnavailableDialog()
// }
// step("Click on 'Ok' button") {
// onDialog { okButton.performClick() }
// }
}
}
@AllureId("3642")
@DisplayName("Action buttons (main screen): check buttons state")
@Test
fun checkButtonsStateTest() {
val scenarioName = "user_tokens_api"
val scenarioState = "EmptyTokensList"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(scenarioName)
}
).run {
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState)
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Assert action buttons is not enabled") {
assertActionButtonsForMultiCurrencyWallet(isEnabled = false)
}
step("Reset Wiremock scenario: '$scenarioName'") {
resetWireMockScenarioState(scenarioName)
}
step("Perform pull to refresh") {
pullToRefresh(steps = 10)
waitForIdle()
}
step("Assert action buttons is enabled") {
assertActionButtonsForMultiCurrencyWallet(isEnabled = true)
}
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState)
}
step("Perform pull to refresh") {
pullToRefresh(steps = 10)
waitForIdle()
}
step("Assert action buttons is not enabled") {
assertActionButtonsForMultiCurrencyWallet(isEnabled = false)
}
}
}
} }

View file

@ -0,0 +1,249 @@
package com.tangem.tests.send.warnings
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.SOLANA_RECIPIENT_ADDRESS
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.scenarios.checkSendWarning
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.synchronizeAddresses
import com.tangem.screens.onMainScreen
import com.tangem.screens.onSendAddressScreen
import com.tangem.screens.onSendScreen
import com.tangem.screens.onTokenDetailsScreen
import com.tangem.wallet.R
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 SolanaWarningsTest : BaseTestCase() {
private val tokenName = "Solana"
private val amountToLeaveLessThanRent = "0.0016941"
private val amountToLeaveGreaterThanRent = "0.0000941"
private val amountToLeaveRentOnly = "0.00168934"
private val userTokensScenarioName = "user_tokens_api"
private val userTokensScenarioState = "Solana"
private val quotesScenarioName = "quotes_api"
private val quotesScenarioState = "Solana"
private val rentAmount = "0.000890880"
private val invalidAmountTitleResId = R.string.send_notification_invalid_amount_title
private val invalidAmountMessageResId = R.string.send_notification_invalid_amount_rent_fee
@AllureId("564")
@DisplayName("Warnings: warning is displayed, if after send balance is less than rent amount (SOLANA)")
@Test
fun warningIsDisplayedWhenLeaveLessThanRent() {
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(userTokensScenarioName)
resetWireMockScenarioState(quotesScenarioName)
}
).run {
step("Set WireMock scenario: '$userTokensScenarioName' to state: '$userTokensScenarioState'") {
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
}
step("Set WireMock scenario: '$quotesScenarioName' to state: '$quotesScenarioState'") {
setWireMockScenarioState(scenarioName = quotesScenarioName, state = quotesScenarioState)
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
}
step("Click on 'Send' button") {
onTokenDetailsScreen { sendButton.performClick() }
}
step("Type '$amountToLeaveLessThanRent' in input text field") {
onSendScreen {
amountInputTextField.performClick()
amountInputTextField.performTextReplacement(amountToLeaveLessThanRent)
}
}
step("Click on 'Next' button") {
onSendScreen { nextButton.clickWithAssertion() }
}
step("Type address in input text field") {
onSendAddressScreen { addressTextField.performTextReplacement(SOLANA_RECIPIENT_ADDRESS) }
}
step("Click on 'Next' button") {
onSendAddressScreen { nextButton.clickWithAssertion() }
}
step("Assert 'Invalid amount warning' is displayed") {
checkSendWarning(
titleResId = invalidAmountTitleResId,
messageResId = invalidAmountMessageResId,
amount = rentAmount
)
}
}
}
@AllureId("567")
@DisplayName("Warnings: warning is not displayed, if after send balance is greater than rent amount (SOLANA)")
@Test
fun warningIsNotDisplayedWhenLeaveGreaterThanRent() {
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(userTokensScenarioName)
resetWireMockScenarioState(quotesScenarioName)
}
).run {
step("Set WireMock scenario: '$userTokensScenarioName' to state: '$userTokensScenarioState'") {
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
}
step("Set WireMock scenario: '$quotesScenarioName' to state: '$quotesScenarioState'") {
setWireMockScenarioState(scenarioName = quotesScenarioName, state = quotesScenarioState)
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
}
step("Click on 'Send' button") {
onTokenDetailsScreen { sendButton.performClick() }
}
step("Type '$amountToLeaveGreaterThanRent' in input text field") {
onSendScreen {
amountInputTextField.performClick()
amountInputTextField.performTextReplacement(amountToLeaveGreaterThanRent)
}
}
step("Click on 'Next' button") {
onSendScreen { nextButton.clickWithAssertion() }
}
step("Type address in input text field") {
onSendAddressScreen { addressTextField.performTextReplacement(SOLANA_RECIPIENT_ADDRESS) }
}
step("Click on 'Next' button") {
onSendAddressScreen { nextButton.clickWithAssertion() }
}
step("Assert 'Invalid amount warning' is not displayed") {
checkSendWarning(
titleResId = invalidAmountTitleResId,
messageResId = invalidAmountMessageResId,
amount = rentAmount,
isDisplayed = false
)
}
}
}
@AllureId("566")
@DisplayName("Warnings: warning is not displayed, if after send balance is equal to rent amount (SOLANA)")
@Test
fun warningIsNotDisplayedWhenLeaveOnlyRent() {
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(userTokensScenarioName)
resetWireMockScenarioState(quotesScenarioName)
}
).run {
step("Set WireMock scenario: '$userTokensScenarioName' to state: '$userTokensScenarioState'") {
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
}
step("Set WireMock scenario: '$quotesScenarioName' to state: '$quotesScenarioState'") {
setWireMockScenarioState(scenarioName = quotesScenarioName, state = quotesScenarioState)
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
}
step("Click on 'Send' button") {
onTokenDetailsScreen { sendButton.performClick() }
}
step("Type '$amountToLeaveRentOnly' in input text field") {
onSendScreen {
amountInputTextField.performClick()
amountInputTextField.performTextReplacement(amountToLeaveRentOnly)
}
}
step("Click on 'Next' button") {
onSendScreen { nextButton.clickWithAssertion() }
}
step("Type address in input text field") {
onSendAddressScreen { addressTextField.performTextReplacement(SOLANA_RECIPIENT_ADDRESS) }
}
step("Click on 'Next' button") {
onSendAddressScreen { nextButton.clickWithAssertion() }
}
step("Assert 'Invalid amount warning' is not displayed") {
checkSendWarning(
titleResId = invalidAmountTitleResId,
messageResId = invalidAmountMessageResId,
amount = rentAmount,
isDisplayed = false
)
}
}
}
@AllureId("565")
@DisplayName("Warnings: warning is not displayed, if after send balance is zero (SOLANA)")
@Test
fun warningIsNotDisplayedWhenLeaveZeroSol() {
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(userTokensScenarioName)
resetWireMockScenarioState(quotesScenarioName)
}
).run {
step("Set WireMock scenario: '$userTokensScenarioName' to state: '$userTokensScenarioState'") {
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
}
step("Set WireMock scenario: '$quotesScenarioName' to state: '$quotesScenarioState'") {
setWireMockScenarioState(scenarioName = quotesScenarioName, state = quotesScenarioState)
}
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
}
step("Click on 'Send' button") {
onTokenDetailsScreen { sendButton.performClick() }
}
step("Type max amount in input text field") {
onSendScreen {
maxButton.performClick()
}
}
step("Click on 'Next' button") {
onSendScreen { nextButton.clickWithAssertion() }
}
step("Type address in input text field") {
onSendAddressScreen { addressTextField.performTextReplacement(SOLANA_RECIPIENT_ADDRESS) }
}
step("Click on 'Next' button") {
onSendAddressScreen { nextButton.clickWithAssertion() }
}
step("Assert 'Invalid amount warning' is not displayed") {
checkSendWarning(
titleResId = invalidAmountTitleResId,
messageResId = invalidAmountMessageResId,
amount = rentAmount,
isDisplayed = false
)
}
}
}
}

View file

@ -3,10 +3,13 @@ package com.tangem.tap.common.analytics
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.utils.AnalyticsContextProxy import com.tangem.core.analytics.utils.AnalyticsContextProxy
import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.tap.common.extensions.addContext import com.tangem.tap.common.extensions.addContext
import com.tangem.tap.common.extensions.addHotWalletContext
import com.tangem.tap.common.extensions.eraseContext import com.tangem.tap.common.extensions.eraseContext
import com.tangem.tap.common.extensions.removeContext import com.tangem.tap.common.extensions.removeContext
import com.tangem.tap.common.extensions.setContext import com.tangem.tap.common.extensions.setContext
import com.tangem.tap.common.extensions.setHotWalletContext
/** /**
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
@ -17,6 +20,14 @@ internal class DefaultAnalyticsContextProxy : AnalyticsContextProxy {
Analytics.setContext(scanResponse) Analytics.setContext(scanResponse)
} }
override fun addContext(userWallet: UserWallet) {
Analytics.addContext(userWallet)
}
override fun setHotWalletContext() {
Analytics.setHotWalletContext()
}
override fun eraseContext() { override fun eraseContext() {
Analytics.eraseContext() Analytics.eraseContext()
} }
@ -25,6 +36,10 @@ internal class DefaultAnalyticsContextProxy : AnalyticsContextProxy {
Analytics.addContext(scanResponse) Analytics.addContext(scanResponse)
} }
override fun addHotWalletContext() {
Analytics.addHotWalletContext()
}
override fun removeContext() { override fun removeContext() {
Analytics.removeContext() Analytics.removeContext()
} }

View file

@ -6,6 +6,7 @@ class BlockchainApiExceptionEvent(
selectedHost: String, selectedHost: String,
exceptionHost: String, exceptionHost: String,
error: String, error: String,
blockchain: String,
) : AnalyticsEvent( ) : AnalyticsEvent(
category = "BlockchainSdk", category = "BlockchainSdk",
event = "Exception", event = "Exception",
@ -13,5 +14,6 @@ class BlockchainApiExceptionEvent(
AnalyticsParam.BLOCKCHAIN_SELECTED_HOST to selectedHost, AnalyticsParam.BLOCKCHAIN_SELECTED_HOST to selectedHost,
AnalyticsParam.BLOCKCHAIN_EXCEPTION_HOST to exceptionHost, AnalyticsParam.BLOCKCHAIN_EXCEPTION_HOST to exceptionHost,
AnalyticsParam.ERROR_DESCRIPTION to error, AnalyticsParam.ERROR_DESCRIPTION to error,
AnalyticsParam.BLOCKCHAIN to blockchain,
), ),
) )

View file

@ -1,6 +1,8 @@
package com.tangem.tap.common.analytics.handlers package com.tangem.tap.common.analytics.handlers
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.ExceptionHandlerOutput import com.tangem.blockchain.common.ExceptionHandlerOutput
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.core.analytics.api.AnalyticsErrorHandler import com.tangem.core.analytics.api.AnalyticsErrorHandler
import com.tangem.tap.common.analytics.events.BlockchainApiExceptionEvent import com.tangem.tap.common.analytics.events.BlockchainApiExceptionEvent
import javax.inject.Inject import javax.inject.Inject
@ -8,12 +10,13 @@ import javax.inject.Inject
class BlockchainExceptionHandler @Inject constructor( class BlockchainExceptionHandler @Inject constructor(
private val analyticsErrorHandler: AnalyticsErrorHandler, private val analyticsErrorHandler: AnalyticsErrorHandler,
) : ExceptionHandlerOutput { ) : ExceptionHandlerOutput {
override fun handleApiSwitch(currentHost: String, nextHost: String, message: String) { override fun handleApiSwitch(currentHost: String, nextHost: String, message: String, blockchain: Blockchain) {
analyticsErrorHandler.sendErrorEvent( analyticsErrorHandler.sendErrorEvent(
BlockchainApiExceptionEvent( BlockchainApiExceptionEvent(
selectedHost = nextHost, selectedHost = nextHost,
exceptionHost = currentHost, exceptionHost = currentHost,
error = message, error = message,
blockchain = blockchain.toNetworkId(),
), ),
) )
} }

View file

@ -0,0 +1,22 @@
package com.tangem.tap.common.analytics.paramsInterceptor
import com.tangem.core.analytics.api.ParamsInterceptor
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
class HotWalletContextInterceptor(
val parent: ParamsInterceptor? = null,
) : ParamsInterceptor {
override fun id(): String = HotWalletContextInterceptor.id()
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = true
override fun intercept(params: MutableMap<String, String>) {
params[AnalyticsParam.PRODUCT_TYPE] = "Mobile Wallet"
}
companion object {
fun id(): String = HotWalletContextInterceptor::class.java.simpleName
}
}

View file

@ -9,7 +9,7 @@ import com.tangem.domain.models.scan.ScanResponse
*/ */
class LinkedCardContextInterceptor( class LinkedCardContextInterceptor(
scanResponse: ScanResponse, scanResponse: ScanResponse,
val parent: LinkedCardContextInterceptor? = null, val parent: ParamsInterceptor? = null,
) : ParamsInterceptor { ) : ParamsInterceptor {
private val contextInterceptor = CardContextInterceptor(scanResponse) private val contextInterceptor = CardContextInterceptor(scanResponse)

View file

@ -4,6 +4,7 @@ import com.tangem.core.analytics.Analytics
import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.tap.common.analytics.paramsInterceptor.HotWalletContextInterceptor
import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterceptor import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterceptor
/** /**
@ -24,19 +25,46 @@ fun Analytics.setContext(scanResponse: ScanResponse) {
fun Analytics.setContext(userWallet: UserWallet) { fun Analytics.setContext(userWallet: UserWallet) {
setUserId(userWallet.walletId.stringValue) setUserId(userWallet.walletId.stringValue)
// TODO add product type for hot ([REDACTED_TASK_KEY] [Hot Wallet] Analytics)
if (userWallet is UserWallet.Cold) { when (userWallet) {
addParamsInterceptor(LinkedCardContextInterceptor(userWallet.scanResponse)) is UserWallet.Cold -> {
removeParamsInterceptor(HotWalletContextInterceptor.id())
addParamsInterceptor(LinkedCardContextInterceptor(userWallet.scanResponse))
}
is UserWallet.Hot -> {
removeParamsInterceptor(LinkedCardContextInterceptor.id())
addParamsInterceptor(HotWalletContextInterceptor())
}
} }
} }
fun Analytics.setHotWalletContext() {
addParamsInterceptor(HotWalletContextInterceptor())
}
/** /**
* Erases the context * Erases the context
*/ */
fun Analytics.eraseContext() { fun Analytics.eraseContext() {
clearUserId() clearUserId()
removeParamsInterceptor(LinkedCardContextInterceptor.id()) removeParamsInterceptor(LinkedCardContextInterceptor.id())
removeParamsInterceptor(HotWalletContextInterceptor.id())
}
/**
* Adds a new context and keeps a previous context as the parent of the new one
*/
fun Analytics.addContext(userWallet: UserWallet) {
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id())
?: removeParamsInterceptor(HotWalletContextInterceptor.id())
val newContext = when (userWallet) {
is UserWallet.Cold -> LinkedCardContextInterceptor(userWallet.scanResponse, parent = currentContext)
is UserWallet.Hot -> HotWalletContextInterceptor(parent = currentContext)
}
setUserId(userWalletId = userWallet.walletId.stringValue)
addParamsInterceptor(newContext)
} }
/** /**
@ -48,18 +76,32 @@ fun Analytics.addContext(scanResponse: ScanResponse) {
setUserId(userWalletId.stringValue) setUserId(userWalletId.stringValue)
} }
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id())
?: removeParamsInterceptor(HotWalletContextInterceptor.id())
val newContext = LinkedCardContextInterceptor(scanResponse, parent = currentContext) val newContext = LinkedCardContextInterceptor(scanResponse, parent = currentContext)
addParamsInterceptor(newContext) addParamsInterceptor(newContext)
} }
fun Analytics.addHotWalletContext() {
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor
val newContext = HotWalletContextInterceptor(currentContext)
addParamsInterceptor(newContext)
}
/** /**
* Removes the current context and restores the previous one if it was present. * Removes the current context and restores the previous one if it was present.
*/ */
fun Analytics.removeContext() { fun Analytics.removeContext() {
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id())
val previousContext = currentContext?.parent ?: return ?: removeParamsInterceptor(HotWalletContextInterceptor.id())
val previousContext = when (currentContext) {
is LinkedCardContextInterceptor -> currentContext.parent
is HotWalletContextInterceptor -> currentContext.parent
else -> null
} ?: return
addParamsInterceptor(previousContext) addParamsInterceptor(previousContext)
} }

View file

@ -1,10 +1,10 @@
package com.tangem.tap.di package com.tangem.tap.di
import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader
import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.express.ExpressServiceFetcher
import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase
import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrenciesRepository
@ -46,14 +46,14 @@ internal object ActivityModule {
@Singleton @Singleton
fun provideDefaultRampManager( fun provideDefaultRampManager(
appStateHolder: AppStateHolder, appStateHolder: AppStateHolder,
expressServiceLoader: ExpressServiceLoader, expressServiceFetcher: ExpressServiceFetcher,
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
excludedBlockchains: ExcludedBlockchains, excludedBlockchains: ExcludedBlockchains,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): RampStateManager { ): RampStateManager {
return DefaultRampManager( return DefaultRampManager(
sellService = Provider { requireNotNull(appStateHolder.sellService) }, sellService = Provider { requireNotNull(appStateHolder.sellService) },
expressServiceLoader = expressServiceLoader, expressServiceFetcher = expressServiceFetcher,
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
dispatchers = dispatchers, dispatchers = dispatchers,
excludedBlockchains = excludedBlockchains, excludedBlockchains = excludedBlockchains,

View file

@ -179,20 +179,6 @@ internal object OnrampDomainModule {
) )
} }
@Provides
@Singleton
fun provideGetOnrampV2QuotesUseCase(
settingsRepository: SettingsRepository,
onrampRepository: OnrampRepository,
onrampErrorResolver: OnrampErrorResolver,
): GetOnrampV2QuotesUseCase {
return GetOnrampV2QuotesUseCase(
settingsRepository = settingsRepository,
repository = onrampRepository,
errorResolver = onrampErrorResolver,
)
}
@Provides @Provides
@Singleton @Singleton
fun provideGetOnrampProviderWithQuoteUseCase( fun provideGetOnrampProviderWithQuoteUseCase(

View file

@ -20,11 +20,7 @@ import com.tangem.domain.staking.single.SingleYieldBalanceSupplier
import com.tangem.domain.tokens.* import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.*
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository
import com.tangem.domain.tokens.repository.TokenReceiveWarningsViewedRepository
import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles
@ -60,24 +56,6 @@ internal object TokensDomainModule {
) )
} }
@Provides
@Singleton
fun provideFetchTokenListUseCase(
currenciesRepository: CurrenciesRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
stakingIdFactory: StakingIdFactory,
): FetchTokenListUseCase {
return FetchTokenListUseCase(
currenciesRepository = currenciesRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
stakingIdFactory = stakingIdFactory,
)
}
@Provides @Provides
@Singleton @Singleton
fun provideFetchPendingTransactionsUseCase( fun provideFetchPendingTransactionsUseCase(
@ -184,24 +162,6 @@ internal object TokensDomainModule {
) )
} }
@Provides
@Singleton
fun provideFetchCardTokenListUseCase(
currenciesRepository: CurrenciesRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
stakingIdFactory: StakingIdFactory,
): FetchCardTokenListUseCase {
return FetchCardTokenListUseCase(
currenciesRepository = currenciesRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
stakingIdFactory = stakingIdFactory,
)
}
@Provides @Provides
@Singleton @Singleton
fun provideGetCryptoCurrencyUseCase( fun provideGetCryptoCurrencyUseCase(

View file

@ -109,7 +109,7 @@ object BackupWalletMockContent : MockContent {
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), 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( 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), 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),
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), 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( DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
@ -185,7 +185,7 @@ object BackupWalletMockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),
@ -244,7 +244,7 @@ object BackupWalletMockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),

View file

@ -109,7 +109,7 @@ object DevWalletMockContent : MockContent {
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), 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( 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), 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),
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), 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( DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
@ -185,7 +185,7 @@ object DevWalletMockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),
@ -244,7 +244,7 @@ object DevWalletMockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),

View file

@ -105,7 +105,7 @@ object Firmware412MockContent : MockContent {
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), 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( 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), 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),
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), 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( DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
@ -181,7 +181,7 @@ object Firmware412MockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),
@ -240,7 +240,7 @@ object Firmware412MockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),

View file

@ -109,7 +109,7 @@ object RingMockContent : MockContent {
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), 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( 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), 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),
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), 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( DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
@ -185,7 +185,7 @@ object RingMockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),
@ -244,7 +244,7 @@ object RingMockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),

View file

@ -109,7 +109,7 @@ object ShibaMockContent : MockContent {
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), 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( 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), 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),
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), 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( DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
@ -185,7 +185,7 @@ object ShibaMockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),
@ -244,7 +244,7 @@ object ShibaMockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),

View file

@ -109,7 +109,7 @@ object ShibaNoBackupMockContent : MockContent {
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), 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( 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), 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),
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), 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( DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
@ -185,7 +185,7 @@ object ShibaNoBackupMockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),
@ -244,7 +244,7 @@ object ShibaNoBackupMockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),

View file

@ -138,7 +138,7 @@ object ShibaNoBackupNoWalletsMockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),
@ -197,7 +197,7 @@ object ShibaNoBackupNoWalletsMockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),

View file

@ -229,7 +229,7 @@ object Wallet2MockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),
@ -281,7 +281,7 @@ object Wallet2MockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),

View file

@ -229,7 +229,7 @@ object Wallet2NoBackupMockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),
@ -281,7 +281,7 @@ object Wallet2NoBackupMockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),

View file

@ -141,7 +141,7 @@ object Wallet2NoBackupNoWalletsMockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),
@ -193,7 +193,7 @@ object Wallet2NoBackupNoWalletsMockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),

View file

@ -229,7 +229,7 @@ object Wallet2WithSeedPhraseMockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),
@ -281,7 +281,7 @@ object Wallet2WithSeedPhraseMockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),

View file

@ -110,7 +110,7 @@ object WalletMockContent : MockContent {
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), 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( 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), 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),
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), 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( DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
@ -125,10 +125,18 @@ object WalletMockContent : MockContent {
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), 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), 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/1852'/1815'/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'/144'/0'/0/0") to ExtendedPublicKey( DerivationPath("m/44'/144'/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), 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), 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'/501'/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( 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), 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),
@ -194,7 +202,7 @@ object WalletMockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),
@ -235,6 +243,13 @@ object WalletMockContent : MockContent {
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/148'/0'") to ExtendedPublicKey( // XLM
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,
),
), ),
), ),
ByteArrayKey( ByteArrayKey(
@ -244,14 +259,21 @@ object WalletMockContent : MockContent {
ExtendedPublicKeysMap( ExtendedPublicKeysMap(
mapOf( mapOf(
DerivationPath("m/1852'/1815'/0'/0/0") to ExtendedPublicKey( // cardano DerivationPath("m/1852'/1815'/0'/0/0") to ExtendedPublicKey( // cardano
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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/1852'/1815'/0'/2/0") to ExtendedPublicKey( // cardano extended DerivationPath("m/1852'/1815'/0'/2/0") to ExtendedPublicKey( // cardano extended
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), 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),
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'/501'/0'") to ExtendedPublicKey( // Solana
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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),
@ -290,14 +312,14 @@ object WalletMockContent : MockContent {
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth 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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // xrp DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // xrp
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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),
@ -312,19 +334,26 @@ object WalletMockContent : MockContent {
ExtendedPublicKeysMap( ExtendedPublicKeysMap(
mapOf( mapOf(
DerivationPath("m/1852'/1815'/0'/0/0") to ExtendedPublicKey( // cardano DerivationPath("m/1852'/1815'/0'/0/0") to ExtendedPublicKey( // cardano
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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/1852'/1815'/0'/2/0") to ExtendedPublicKey( // cardano extended DerivationPath("m/1852'/1815'/0'/2/0") to ExtendedPublicKey( // cardano extended
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), 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),
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), 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, depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0, childNumber = 0,
), ),
DerivationPath("m/44'/501'/0'") to ExtendedPublicKey( // Solana
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,
),
), ),
), ),
), ),

View file

@ -6,12 +6,11 @@ import arrow.core.raise.either
import arrow.core.raise.ensure import arrow.core.raise.ensure
import arrow.core.right import arrow.core.right
import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE
import com.tangem.datasource.api.express.models.response.Asset
import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader
import com.tangem.domain.core.lce.Lce import com.tangem.domain.core.lce.Lce
import com.tangem.domain.exchange.ExpressAvailabilityState import com.tangem.domain.exchange.ExpressAvailabilityState
import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.express.ExpressServiceFetcher
import com.tangem.domain.express.models.ExpressAsset
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
@ -26,10 +25,9 @@ import com.tangem.utils.isNullOrZero
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
@Suppress("LongParameterList")
internal class DefaultRampManager( internal class DefaultRampManager(
private val sellService: Provider<ExchangeService>, private val sellService: Provider<ExchangeService>,
private val expressServiceLoader: ExpressServiceLoader, private val expressServiceFetcher: ExpressServiceFetcher,
private val currenciesRepository: CurrenciesRepository, private val currenciesRepository: CurrenciesRepository,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
excludedBlockchains: ExcludedBlockchains, excludedBlockchains: ExcludedBlockchains,
@ -107,7 +105,7 @@ internal class DefaultRampManager(
} }
override fun getExpressInitializationStatus(userWalletId: UserWalletId): Flow<ExchangeServiceInitializationStatus> { override fun getExpressInitializationStatus(userWalletId: UserWalletId): Flow<ExchangeServiceInitializationStatus> {
return expressServiceLoader.getInitializationStatus(userWalletId) return expressServiceFetcher.getInitializationStatus(userWalletId)
} }
override suspend fun getSendUnavailabilityReason( override suspend fun getSendUnavailabilityReason(
@ -151,14 +149,14 @@ internal class DefaultRampManager(
userWalletId: UserWalletId, userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency, cryptoCurrency: CryptoCurrency,
): ExpressAvailabilityState { ): ExpressAvailabilityState {
val asset = expressServiceLoader.getInitializationStatus(userWalletId).firstOrNull() val asset = expressServiceFetcher.getInitializationStatus(userWalletId).firstOrNull()
?: return ExpressAvailabilityState.Loading ?: return ExpressAvailabilityState.Loading
return when (asset) { return when (asset) {
is Lce.Error -> ExpressAvailabilityState.Error is Lce.Error -> ExpressAvailabilityState.Error
is Lce.Loading -> ExpressAvailabilityState.Loading is Lce.Loading -> ExpressAvailabilityState.Loading
is Lce.Content -> { is Lce.Content -> {
val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(it) } val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(assetId = it.id) }
foundAsset?.exchangeAvailable?.toSwapAvailabilityState() foundAsset?.isExchangeAvailable?.toSwapAvailabilityState()
?: ExpressAvailabilityState.AssetNotFound ?: ExpressAvailabilityState.AssetNotFound
} }
} }
@ -168,15 +166,15 @@ internal class DefaultRampManager(
userWalletId: UserWalletId, userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency, cryptoCurrency: CryptoCurrency,
): ExpressAvailabilityState { ): ExpressAvailabilityState {
val asset = expressServiceLoader.getInitializationStatus(userWalletId).firstOrNull() val asset = expressServiceFetcher.getInitializationStatus(userWalletId).firstOrNull()
?: return ExpressAvailabilityState.Loading ?: return ExpressAvailabilityState.Loading
return when (asset) { return when (asset) {
is Lce.Error -> ExpressAvailabilityState.Error is Lce.Error -> ExpressAvailabilityState.Error
is Lce.Loading -> ExpressAvailabilityState.Loading is Lce.Loading -> ExpressAvailabilityState.Loading
is Lce.Content -> { is Lce.Content -> {
val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(it) } val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(assetId = it.id) }
foundAsset?.onrampAvailable?.toOnrampAvailabilityState() foundAsset?.isOnrampAvailable?.toOnrampAvailabilityState()
?: ExpressAvailabilityState.AssetNotFound ?: ExpressAvailabilityState.AssetNotFound
} }
} }
@ -211,8 +209,13 @@ internal class DefaultRampManager(
} }
} }
private fun CryptoCurrency.findAssetPredicate(asset: Asset): Boolean { private fun CryptoCurrency.findAssetPredicate(assetId: ExpressAsset.ID): Boolean {
val contractAddress = (this as? CryptoCurrency.Token)?.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE val currencyAssedId = ExpressAsset.ID(
return asset.network == network.backendId && asset.contractAddress.equals(contractAddress, ignoreCase = true) networkId = this.network.backendId,
contractAddress = (this as? CryptoCurrency.Token)?.contractAddress,
)
return assetId.networkId == currencyAssedId.networkId &&
assetId.contractAddress.equals(currencyAssedId.contractAddress, ignoreCase = true)
} }
} }

View file

@ -160,6 +160,6 @@ internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency?
Pepecoin, PepecoinTestnet -> null Pepecoin, PepecoinTestnet -> null
Hyperliquid, HyperliquidTestnet -> null Hyperliquid, HyperliquidTestnet -> null
Quai, QuaiTestnet -> null Quai, QuaiTestnet -> null
// Linea, LineaTestnet -> null Linea, LineaTestnet -> null
// ArbitrumNova -> null ArbitrumNova -> null
} }

View file

@ -36,9 +36,10 @@ import com.tangem.features.send.v2.api.SendComponent
import com.tangem.features.send.v2.api.SendEntryPointComponent import com.tangem.features.send.v2.api.SendEntryPointComponent
import com.tangem.features.staking.api.StakingComponent import com.tangem.features.staking.api.StakingComponent
import com.tangem.features.swap.SwapComponent import com.tangem.features.swap.SwapComponent
import com.tangem.features.tangempay.components.TangemPayDetailsComponent import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent import com.tangem.features.tangempay.components.TangemPayOnboardingComponent
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.* import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.ContinueOnboarding
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.Deeplink
import com.tangem.features.tokendetails.TokenDetailsComponent import com.tangem.features.tokendetails.TokenDetailsComponent
import com.tangem.features.wallet.WalletEntryComponent import com.tangem.features.wallet.WalletEntryComponent
import com.tangem.features.walletconnect.components.WalletConnectEntryComponent import com.tangem.features.walletconnect.components.WalletConnectEntryComponent
@ -100,6 +101,7 @@ internal class ChildFactory @Inject constructor(
private val chooseManagedTokensComponentFactory: ChooseManagedTokensComponent.Factory, private val chooseManagedTokensComponentFactory: ChooseManagedTokensComponent.Factory,
private val createWalletSelectionComponentFactory: CreateWalletSelectionComponent.Factory, private val createWalletSelectionComponentFactory: CreateWalletSelectionComponent.Factory,
private val createWalletStartComponentFactory: CreateWalletStartComponent.Factory, private val createWalletStartComponentFactory: CreateWalletStartComponent.Factory,
private val createHardwareWalletComponentFactory: CreateHardwareWalletComponent.Factory,
private val createMobileWalletComponentFactory: CreateMobileWalletComponent.Factory, private val createMobileWalletComponentFactory: CreateMobileWalletComponent.Factory,
private val upgradeWalletComponentFactory: UpgradeWalletComponent.Factory, private val upgradeWalletComponentFactory: UpgradeWalletComponent.Factory,
private val addExistingWalletComponentFactory: AddExistingWalletComponent.Factory, private val addExistingWalletComponentFactory: AddExistingWalletComponent.Factory,
@ -107,8 +109,9 @@ internal class ChildFactory @Inject constructor(
private val createWalletBackupComponentFactory: CreateWalletBackupComponent.Factory, private val createWalletBackupComponentFactory: CreateWalletBackupComponent.Factory,
private val updateAccessCodeComponentFactory: UpdateAccessCodeComponent.Factory, private val updateAccessCodeComponentFactory: UpdateAccessCodeComponent.Factory,
private val viewPhraseComponentFactory: ViewPhraseComponent.Factory, private val viewPhraseComponentFactory: ViewPhraseComponent.Factory,
private val forgetWalletComponentFactory: ForgetWalletComponent.Factory,
private val sendEntryPointComponentFactory: SendEntryPointComponent.Factory, private val sendEntryPointComponentFactory: SendEntryPointComponent.Factory,
private val tangemPayDetailsComponentFactory: TangemPayDetailsComponent.Factory, private val tangemPayDetailsContainerComponentFactory: TangemPayDetailsContainerComponent.Factory,
private val tangemPayOnboardingComponentFactory: TangemPayOnboardingComponent.Factory, private val tangemPayOnboardingComponentFactory: TangemPayOnboardingComponent.Factory,
private val kycComponentFactory: KycComponent.Factory, private val kycComponentFactory: KycComponent.Factory,
private val yieldSupplyPromoComponentFactory: YieldSupplyPromoComponent.Factory, private val yieldSupplyPromoComponentFactory: YieldSupplyPromoComponent.Factory,
@ -138,7 +141,6 @@ internal class ChildFactory @Inject constructor(
is AppRoute.ManageTokens -> { is AppRoute.ManageTokens -> {
val source = when (route.source) { val source = when (route.source) {
AppRoute.ManageTokens.Source.SETTINGS -> ManageTokensSource.SETTINGS AppRoute.ManageTokens.Source.SETTINGS -> ManageTokensSource.SETTINGS
AppRoute.ManageTokens.Source.ONBOARDING -> ManageTokensSource.ONBOARDING
AppRoute.ManageTokens.Source.STORIES -> ManageTokensSource.STORIES AppRoute.ManageTokens.Source.STORIES -> ManageTokensSource.STORIES
} }
@ -293,7 +295,7 @@ internal class ChildFactory @Inject constructor(
context = context, context = context,
params = StakingComponent.Params( params = StakingComponent.Params(
userWalletId = route.userWalletId, userWalletId = route.userWalletId,
cryptoCurrencyId = route.cryptoCurrencyId, cryptoCurrency = route.cryptoCurrency,
yieldId = route.yieldId, yieldId = route.yieldId,
), ),
componentFactory = stakingComponentFactory, componentFactory = stakingComponentFactory,
@ -496,6 +498,13 @@ internal class ChildFactory @Inject constructor(
componentFactory = createWalletSelectionComponentFactory, componentFactory = createWalletSelectionComponentFactory,
) )
} }
is AppRoute.CreateHardwareWallet -> {
createComponentChild(
context = context,
params = Unit,
componentFactory = createHardwareWalletComponentFactory,
)
}
is AppRoute.CreateMobileWallet -> { is AppRoute.CreateMobileWallet -> {
createComponentChild( createComponentChild(
context = context, context = context,
@ -555,6 +564,15 @@ internal class ChildFactory @Inject constructor(
componentFactory = viewPhraseComponentFactory, componentFactory = viewPhraseComponentFactory,
) )
} }
is AppRoute.ForgetWallet -> {
createComponentChild(
context = context,
params = ForgetWalletComponent.Params(
userWalletId = route.userWalletId,
),
componentFactory = forgetWalletComponentFactory,
)
}
is AppRoute.SendEntryPoint -> { is AppRoute.SendEntryPoint -> {
createComponentChild( createComponentChild(
context = context, context = context,
@ -604,8 +622,11 @@ internal class ChildFactory @Inject constructor(
is AppRoute.TangemPayDetails -> { is AppRoute.TangemPayDetails -> {
createComponentChild( createComponentChild(
context = context, context = context,
params = TangemPayDetailsComponent.Params(config = route.config), params = TangemPayDetailsContainerComponent.Params(
componentFactory = tangemPayDetailsComponentFactory, userWalletId = route.userWalletId,
config = route.config,
),
componentFactory = tangemPayDetailsContainerComponentFactory,
) )
} }
is AppRoute.TangemPayOnboarding -> { is AppRoute.TangemPayOnboarding -> {

View file

@ -126,9 +126,12 @@ sealed class AppRoute(val path: String) : Route {
val portfolioId: PortfolioId? = null, val portfolioId: PortfolioId? = null,
) : AppRoute(path = "${source.name.lowercase()}/manage_tokens/${portfolioId?.stringValue}") { ) : AppRoute(path = "${source.name.lowercase()}/manage_tokens/${portfolioId?.stringValue}") {
/**
* Source of launching the screen.
* ManageTokens screen launched from Onboarding by another route. See `OnboardingRoute.ManageTokens`.
*/
enum class Source { enum class Source {
STORIES, STORIES,
ONBOARDING,
SETTINGS, SETTINGS,
} }
} }
@ -192,9 +195,9 @@ sealed class AppRoute(val path: String) : Route {
@Serializable @Serializable
data class Staking( data class Staking(
val userWalletId: UserWalletId, val userWalletId: UserWalletId,
val cryptoCurrencyId: CryptoCurrency.ID, val cryptoCurrency: CryptoCurrency,
val yieldId: String, val yieldId: String,
) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrencyId.value}/$yieldId") ) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrency.id.value}/$yieldId")
@Serializable @Serializable
data class PushNotification( data class PushNotification(
@ -322,6 +325,9 @@ sealed class AppRoute(val path: String) : Route {
} }
} }
@Serializable
object CreateHardwareWallet : AppRoute(path = "/create_hardware_wallet")
@Serializable @Serializable
object CreateMobileWallet : AppRoute(path = "/create_mobile_wallet") object CreateMobileWallet : AppRoute(path = "/create_mobile_wallet")
@ -353,6 +359,11 @@ sealed class AppRoute(val path: String) : Route {
val userWalletId: UserWalletId, val userWalletId: UserWalletId,
) : AppRoute(path = "/view_seed_phrase/${userWalletId.stringValue}") ) : AppRoute(path = "/view_seed_phrase/${userWalletId.stringValue}")
@Serializable
data class ForgetWallet(
val userWalletId: UserWalletId,
) : AppRoute(path = "/forget_wallet/${userWalletId.stringValue}")
@Serializable @Serializable
data class SendEntryPoint( data class SendEntryPoint(
val userWalletId: UserWalletId, val userWalletId: UserWalletId,
@ -383,8 +394,9 @@ sealed class AppRoute(val path: String) : Route {
@Serializable @Serializable
data class TangemPayDetails( data class TangemPayDetails(
val userWalletId: UserWalletId,
val config: TangemPayDetailsConfig, val config: TangemPayDetailsConfig,
) : AppRoute(path = "/tangem_pay_details") ) : AppRoute(path = "/tangem_pay_details/${userWalletId.stringValue}")
@Serializable @Serializable
data class TangemPayOnboarding( data class TangemPayOnboarding(

View file

@ -3,6 +3,7 @@ package com.tangem.common.ui.account
import com.tangem.common.ui.R import com.tangem.common.ui.R
import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState
import com.tangem.core.ui.components.token.state.TokenItemState.Subtitle2State
import com.tangem.core.ui.extensions.pluralReference import com.tangem.core.ui.extensions.pluralReference
import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.fiat import com.tangem.core.ui.format.bigdecimal.fiat
@ -56,8 +57,8 @@ class AccountCryptoPortfolioItemStateConverter(
) )
} }
private fun Account.CryptoPortfolio.mapToLoadingState(): TokenItemState.Loading { private fun Account.CryptoPortfolio.mapToLoadingState(): TokenItemState.Content {
return TokenItemState.Loading( return TokenItemState.Content(
id = account.accountId.value, id = account.accountId.value,
iconState = AccountIconItemStateConverter.convert(account), iconState = AccountIconItemStateConverter.convert(account),
titleState = TokenItemState.TitleState.Content( titleState = TokenItemState.TitleState.Content(
@ -71,6 +72,10 @@ class AccountCryptoPortfolioItemStateConverter(
), ),
isAvailable = false, isAvailable = false,
), ),
fiatAmountState = FiatAmountState.Loading,
subtitle2State = Subtitle2State.Loading,
onItemLongClick = null,
onItemClick = onItemClick?.let { onItemClick -> { onItemClick(account) } },
) )
} }

View file

@ -6,6 +6,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -29,6 +30,7 @@ fun AccountTitle(
accountTitleUM: AccountTitleUM, accountTitleUM: AccountTitleUM,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
textStyle: TextStyle = TangemTheme.typography.subtitle2, textStyle: TextStyle = TangemTheme.typography.subtitle2,
textColor: Color = TangemTheme.colors.text.tertiary,
) { ) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@ -40,19 +42,20 @@ fun AccountTitle(
Text( Text(
text = accountTitleUM.prefixText.resolveReference(), text = accountTitleUM.prefixText.resolveReference(),
style = textStyle, style = textStyle,
color = TangemTheme.colors.text.tertiary, color = textColor,
) )
AccountLabel( AccountLabel(
name = accountTitleUM.name, name = accountTitleUM.name,
icon = accountTitleUM.icon, icon = accountTitleUM.icon,
iconSize = AccountIconSize.ExtraSmall, iconSize = AccountIconSize.ExtraSmall,
nameStyle = textStyle, nameStyle = textStyle,
nameColor = textColor,
) )
} }
is AccountTitleUM.Text -> Text( is AccountTitleUM.Text -> Text(
text = accountTitleUM.title.resolveReference(), text = accountTitleUM.title.resolveReference(),
style = textStyle, style = textStyle,
color = TangemTheme.colors.text.tertiary, color = textColor,
modifier = Modifier.testTag(SendScreenTestTags.AMOUNT_CONTAINER_TITLE), modifier = Modifier.testTag(SendScreenTestTags.AMOUNT_CONTAINER_TITLE),
) )
} }

View file

@ -1,6 +1,7 @@
package com.tangem.core.analytics.utils package com.tangem.core.analytics.utils
import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWallet
/** /**
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
@ -9,9 +10,15 @@ interface AnalyticsContextProxy {
fun setContext(scanResponse: ScanResponse) fun setContext(scanResponse: ScanResponse)
fun addContext(userWallet: UserWallet)
fun setHotWalletContext()
fun eraseContext() fun eraseContext()
fun addContext(scanResponse: ScanResponse) fun addContext(scanResponse: ScanResponse)
fun addHotWalletContext()
fun removeContext() fun removeContext()
} }

View file

@ -30,9 +30,5 @@
{ {
"name": "zklink", "name": "zklink",
"version": "undefined" "version": "undefined"
},
{
"name": "scroll",
"version": "undefined"
} }
] ]

View file

@ -1,5 +0,0 @@
package com.tangem.datasource.api.express.models
object TangemExpressValues {
const val EMPTY_CONTRACT_ADDRESS_VALUE = "0"
}

View file

@ -20,4 +20,18 @@ data class GetWalletAccountsResponse(
@Json(name = "sort") val sort: SortType, @Json(name = "sort") val sort: SortType,
@Json(name = "totalAccounts") val totalAccounts: Int, @Json(name = "totalAccounts") val totalAccounts: Int,
) )
}
/** Flattens the tokens from all wallet accounts into a single list */
fun GetWalletAccountsResponse.flattenTokens(): List<UserTokensResponse.Token> {
return accounts.flatMap { it.tokens.orEmpty() }
}
/** Converts the [GetWalletAccountsResponse] into a [UserTokensResponse] */
fun GetWalletAccountsResponse.toUserTokensResponse(): UserTokensResponse {
return UserTokensResponse(
group = wallet.group,
sort = wallet.sort,
tokens = flattenTokens(),
)
} }

View file

@ -1,18 +0,0 @@
package com.tangem.datasource.di.exchangeservice
import com.tangem.datasource.exchangeservice.swap.DefaultExpressServiceLoader
import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader
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 ExchangeServiceLoaderModule {
@Binds
@Singleton
fun bindExpressServiceLoader(defaultExpressServiceLoader: DefaultExpressServiceLoader): ExpressServiceLoader
}

View file

@ -1,22 +0,0 @@
package com.tangem.datasource.exchangeservice.swap
import com.tangem.datasource.api.express.models.request.LeastTokenInfo
import com.tangem.datasource.api.express.models.response.Asset
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import kotlinx.coroutines.flow.Flow
/**
* Express service loader
*
[REDACTED_AUTHOR]
*/
interface ExpressServiceLoader {
/** Update service using [userWallet] and [userTokens] */
suspend fun update(userWallet: UserWallet, userTokens: List<LeastTokenInfo>)
/** Get initialization status by [userWalletId] */
fun getInitializationStatus(userWalletId: UserWalletId): Flow<Lce<Throwable, List<Asset>>>
}

View file

@ -0,0 +1,79 @@
package com.tangem.core.ui.components.feature
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@Composable
fun FeatureBlock(title: String, description: String, iconRes: Int, modifier: Modifier = Modifier) {
Row(
modifier = modifier,
) {
Icon(
modifier = Modifier
.padding(horizontal = 12.dp),
painter = painterResource(iconRes),
contentDescription = null,
tint = TangemTheme.colors.icon.primary1,
)
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp),
) {
Text(
text = title,
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
Text(
modifier = Modifier
.padding(top = 4.dp),
text = description,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
)
}
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PreviewFeatureBlock() {
TangemThemePreview {
Column(
modifier = Modifier
.background(TangemTheme.colors.background.primary)
.padding(16.dp),
) {
FeatureBlock(
title = stringResourceSafe(R.string.backup_info_save_title),
description = stringResourceSafe(R.string.backup_info_save_description, "12"),
iconRes = R.drawable.ic_lock_24,
)
Spacer(modifier = Modifier.height(24.dp))
FeatureBlock(
title = stringResourceSafe(R.string.backup_info_keep_title),
description = stringResourceSafe(R.string.backup_info_keep_description),
iconRes = R.drawable.ic_settings_24,
)
}
}
}

View file

@ -21,7 +21,7 @@ import com.tangem.core.ui.utils.getGreyScaleColorFilter
* @param onImageError composable to show if image loading failed * @param onImageError composable to show if image loading failed
*/ */
@Composable @Composable
internal fun InputRowAsyncImage( fun InputRowAsyncImage(
imageUrl: String, imageUrl: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
isGrayscale: Boolean = false, isGrayscale: Boolean = false,

View file

@ -53,7 +53,7 @@ fun SelectorRowItem(
val textStyle = if (isSelected && showSelectedAppearance) { val textStyle = if (isSelected && showSelectedAppearance) {
TangemTheme.typography.subtitle2 TangemTheme.typography.subtitle2
} else { } else {
TangemTheme.typography.body2 TangemTheme.typography.body1
} }
Box( Box(
modifier = modifier modifier = modifier

View file

@ -0,0 +1,33 @@
package com.tangem.core.ui.extensions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
/**
* Utility class for keeping themed color reference from app theme.
*
* It necessary to use [Immutable] annotation for runtime stability.
*
* @property value color provider from theme
*/
@Immutable
data class ColorReference(val value: @Composable () -> Color)
/**
* Creates a [ColorReference] using a themed color from the app theme with a lambda.
*
* @param value The color provider from theme.
* @return A [ColorReference] representing the themed color.
*/
fun themedColor(value: @Composable () -> Color): ColorReference {
return ColorReference(value)
}
/**
* Resolves [ColorReference] to [Color]
*/
@Composable
fun ColorReference.resolveReference(): Color {
return value()
}

View file

@ -1,6 +1,6 @@
package com.tangem.core.ui.test package com.tangem.core.ui.test
object BaseAmountBlockTestTags { object BaseAmountBlockTestTags {
const val PRIMARY_AMOUNT = "STAKING_SEND_DETAILS_SCREEN_PRIMARY_AMOUNT" const val PRIMARY_AMOUNT = "SEND_DETAILS_SCREEN_PRIMARY_AMOUNT"
const val SECONDARY_AMOUNT = "TAKING_SEND_DETAILS_SCREEN_SECONDARY_AMOUNT" const val SECONDARY_AMOUNT = "SEND_DETAILS_SCREEN_SECONDARY_AMOUNT"
} }

View file

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="72dp"
android:height="72dp"
android:viewportWidth="72"
android:viewportHeight="72">
<path
android:pathData="M69.781,45.692L69.79,45.709C71.126,48.06 72,50.879 72,53.877C72,57.927 70.612,62.075 67.529,65.265C64.394,68.508 60.145,70.096 55.73,70.096H16.27C11.855,70.096 7.606,68.508 4.471,65.265C1.388,62.075 0,57.927 0,53.877C0,51.094 0.713,48.238 2.266,45.611L21.966,11.287C25.02,5.881 30.514,3.086 36,3.086C41.553,3.086 46.97,5.934 50.018,11.26C50.02,11.263 50.022,11.266 50.023,11.269L69.781,45.692ZM41.932,15.893L61.688,50.313C62.324,51.433 62.68,52.681 62.68,53.877C62.68,57.772 60.058,60.777 55.73,60.777H16.27C11.942,60.777 9.32,57.772 9.32,53.877C9.32,52.681 9.625,51.459 10.312,50.313L30.068,15.893C31.367,13.577 33.658,12.406 36,12.406C38.342,12.406 40.608,13.577 41.932,15.893Z"
android:strokeAlpha="0.12"
android:fillColor="#FF3333"
android:fillType="evenOdd"
android:fillAlpha="0.12"/>
<path
android:pathData="M36,9.771C39.182,9.771 42.273,11.382 44.047,14.484L44.049,14.486L63.49,48.36C64.308,49.799 64.8,51.456 64.8,53.126C64.8,55.617 63.955,57.976 62.262,59.728C60.554,61.494 58.159,62.462 55.414,62.462H16.586C13.842,62.462 11.446,61.494 9.738,59.728C8.045,57.976 7.2,55.617 7.2,53.126C7.2,51.514 7.613,49.864 8.526,48.331L27.948,14.493C29.705,11.368 32.836,9.771 36,9.771ZM36,12.318C33.695,12.318 31.441,13.471 30.163,15.751L10.725,49.618C10.048,50.745 9.747,51.949 9.747,53.126C9.747,56.958 12.328,59.914 16.586,59.914H55.414C59.673,59.914 62.253,56.958 62.253,53.126C62.253,51.949 61.902,50.72 61.275,49.618L41.837,15.751C40.534,13.471 38.305,12.318 36,12.318ZM34.984,45.657C35.942,45.657 36.718,46.433 36.718,47.391C36.718,48.348 35.942,49.124 34.984,49.124C34.027,49.124 33.251,48.348 33.251,47.391C33.251,46.433 34.027,45.657 34.984,45.657ZM34.984,25.845C35.805,25.845 36.471,26.51 36.471,27.331V42.189C36.471,43.01 35.805,43.676 34.984,43.676C34.164,43.676 33.499,43.01 33.499,42.189V27.331C33.499,26.511 34.164,25.845 34.984,25.845Z"
android:fillColor="#FF3333"/>
</vector>

View file

@ -27,6 +27,7 @@ dependencies {
api(projects.domain.common) api(projects.domain.common)
api(projects.domain.models) api(projects.domain.models)
api(projects.domain.tokens) api(projects.domain.tokens)
api(projects.domain.wallets)
// endregion // endregion
// region Project - Data // region Project - Data

View file

@ -0,0 +1,18 @@
package com.tangem.data.account.di
import com.tangem.data.account.producer.DefaultSingleAccountProducer
import com.tangem.domain.account.producer.SingleAccountProducer
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 SingleAccountProducerFactoryModule {
@Binds
@Singleton
fun bindSingleAccountProducerFactory(impl: DefaultSingleAccountProducer.Factory): SingleAccountProducer.Factory
}

View file

@ -0,0 +1,23 @@
package com.tangem.data.account.di
import com.tangem.domain.account.producer.SingleAccountProducer
import com.tangem.domain.account.supplier.SingleAccountSupplier
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 SingleAccountSupplierModule {
@Provides
@Singleton
fun provideSingleAccountSupplier(factory: SingleAccountProducer.Factory): SingleAccountSupplier {
return object : SingleAccountSupplier(
factory = factory,
keyCreator = { "single_account_${it.accountId.value}" },
) {}
}
}

View file

@ -4,7 +4,6 @@ import com.tangem.data.account.store.AccountsResponseStore
import com.tangem.data.account.store.AccountsResponseStoreFactory import com.tangem.data.account.store.AccountsResponseStoreFactory
import com.tangem.data.account.utils.DefaultWalletAccountsResponseFactory import com.tangem.data.account.utils.DefaultWalletAccountsResponseFactory
import com.tangem.data.account.utils.assignTokens import com.tangem.data.account.utils.assignTokens
import com.tangem.data.account.utils.toUserTokensResponse
import com.tangem.data.common.account.WalletAccountsFetcher import com.tangem.data.common.account.WalletAccountsFetcher
import com.tangem.data.common.account.WalletAccountsSaver import com.tangem.data.common.account.WalletAccountsSaver
import com.tangem.data.common.api.safeApiCall import com.tangem.data.common.api.safeApiCall
@ -19,6 +18,7 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
import com.tangem.datasource.api.tangemTech.models.account.SaveWalletAccountsResponse import com.tangem.datasource.api.tangemTech.models.account.SaveWalletAccountsResponse
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
import com.tangem.datasource.api.tangemTech.models.account.toUserTokensResponse
import com.tangem.datasource.utils.getSyncOrNull import com.tangem.datasource.utils.getSyncOrNull
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -51,18 +51,25 @@ internal class DefaultWalletAccountsFetcher @Inject constructor(
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
) : WalletAccountsFetcher, WalletAccountsSaver { ) : WalletAccountsFetcher, WalletAccountsSaver {
override suspend fun fetch(userWalletId: UserWalletId) { override suspend fun fetch(userWalletId: UserWalletId): GetWalletAccountsResponse {
val savedAccountsResponse = getAccountsResponseStore(userWalletId = userWalletId).getSyncOrNull() val savedAccountsResponse = getAccountsResponseStore(userWalletId = userWalletId).getSyncOrNull()
val accountsResponse = fetchWalletAccounts(userWalletId, savedAccountsResponse) val accountsResponse = fetchWalletAccounts(userWalletId, savedAccountsResponse)
?: return
if (accountsResponse.accounts.isEmpty()) { return when {
initializeAccounts(userWalletId, accountsResponse) accountsResponse.accounts.isEmpty() -> {
} else if (accountsResponse.unassignedTokens.isNotEmpty()) { initializeAccounts(userWalletId, accountsResponse)
assignTokens(userWalletId, accountsResponse) }
accountsResponse.unassignedTokens.isNotEmpty() -> {
assignTokens(userWalletId, accountsResponse)
}
else -> accountsResponse
} }
} }
override suspend fun getSaved(userWalletId: UserWalletId): GetWalletAccountsResponse? {
return getAccountsResponseStore(userWalletId = userWalletId).getSyncOrNull()
}
override suspend fun store(userWalletId: UserWalletId, response: GetWalletAccountsResponse) { override suspend fun store(userWalletId: UserWalletId, response: GetWalletAccountsResponse) {
val store = getAccountsResponseStore(userWalletId = userWalletId) val store = getAccountsResponseStore(userWalletId = userWalletId)
@ -115,7 +122,7 @@ internal class DefaultWalletAccountsFetcher @Inject constructor(
private suspend fun fetchWalletAccounts( private suspend fun fetchWalletAccounts(
userWalletId: UserWalletId, userWalletId: UserWalletId,
savedAccountsResponse: GetWalletAccountsResponse?, savedAccountsResponse: GetWalletAccountsResponse?,
): GetWalletAccountsResponse? { ): GetWalletAccountsResponse {
return safeApiCall( return safeApiCall(
call = { call = {
val apiResponse = withContext(dispatchers.io) { val apiResponse = withContext(dispatchers.io) {
@ -145,7 +152,10 @@ internal class DefaultWalletAccountsFetcher @Inject constructor(
) )
} }
private suspend fun initializeAccounts(userWalletId: UserWalletId, accountsResponse: GetWalletAccountsResponse) { private suspend fun initializeAccounts(
userWalletId: UserWalletId,
accountsResponse: GetWalletAccountsResponse,
): GetWalletAccountsResponse {
val response = defaultWalletAccountsResponseFactory.create( val response = defaultWalletAccountsResponseFactory.create(
userWalletId = userWalletId, userWalletId = userWalletId,
userTokensResponse = UserTokensResponse( userTokensResponse = UserTokensResponse(
@ -156,14 +166,17 @@ internal class DefaultWalletAccountsFetcher @Inject constructor(
) )
userTokensSaver.push(userWalletId = userWalletId, response = response.toUserTokensResponse()) userTokensSaver.push(userWalletId = userWalletId, response = response.toUserTokensResponse())
val syncedResponse = push(userWalletId = userWalletId, accounts = response.accounts) val syncedResponse = push(userWalletId = userWalletId, accounts = response.accounts) ?: response
if (syncedResponse != null) { store(userWalletId = userWalletId, response = syncedResponse)
store(userWalletId = userWalletId, response = syncedResponse)
} return syncedResponse
} }
private suspend fun assignTokens(userWalletId: UserWalletId, accountsResponse: GetWalletAccountsResponse) { private suspend fun assignTokens(
userWalletId: UserWalletId,
accountsResponse: GetWalletAccountsResponse,
): GetWalletAccountsResponse {
val accountsResponseWithTokens = accountsResponse.assignTokens(userWalletId) val accountsResponseWithTokens = accountsResponse.assignTokens(userWalletId)
store(userWalletId = userWalletId, response = accountsResponseWithTokens) store(userWalletId = userWalletId, response = accountsResponseWithTokens)
@ -172,6 +185,8 @@ internal class DefaultWalletAccountsFetcher @Inject constructor(
userWalletId = userWalletId, userWalletId = userWalletId,
response = accountsResponseWithTokens.toUserTokensResponse(), response = accountsResponseWithTokens.toUserTokensResponse(),
) )
return accountsResponseWithTokens
} }
private suspend fun getETag(userWalletId: UserWalletId): String? { private suspend fun getETag(userWalletId: UserWalletId): String? {

View file

@ -1,7 +1,6 @@
package com.tangem.data.account.fetcher package com.tangem.data.account.fetcher
import com.tangem.data.account.utils.DefaultWalletAccountsResponseFactory import com.tangem.data.account.utils.DefaultWalletAccountsResponseFactory
import com.tangem.data.account.utils.toUserTokensResponse
import com.tangem.data.common.currency.UserTokensResponseAccountIdEnricher import com.tangem.data.common.currency.UserTokensResponseAccountIdEnricher
import com.tangem.data.common.currency.UserTokensSaver import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.ApiResponseError
@ -10,6 +9,7 @@ import com.tangem.datasource.api.common.response.isNetworkError
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
import com.tangem.datasource.api.tangemTech.models.account.toUserTokensResponse
import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import timber.log.Timber import timber.log.Timber
@ -49,11 +49,13 @@ internal class FetchWalletAccountsErrorHandler @Inject constructor(
savedAccountsResponse: GetWalletAccountsResponse?, savedAccountsResponse: GetWalletAccountsResponse?,
pushWalletAccounts: suspend (UserWalletId, List<WalletAccountDTO>) -> GetWalletAccountsResponse?, pushWalletAccounts: suspend (UserWalletId, List<WalletAccountDTO>) -> GetWalletAccountsResponse?,
storeWalletAccounts: suspend (UserWalletId, GetWalletAccountsResponse) -> Unit, storeWalletAccounts: suspend (UserWalletId, GetWalletAccountsResponse) -> Unit,
): GetWalletAccountsResponse? { ): GetWalletAccountsResponse {
val isResponseUpToDate = error.isNetworkError(code = Code.NOT_MODIFIED) val isResponseUpToDate = error.isNetworkError(code = Code.NOT_MODIFIED)
if (isResponseUpToDate) { if (isResponseUpToDate) {
Timber.e("ETag is up to date, no need to update accounts for wallet: $userWalletId") Timber.e("ETag is up to date, no need to update accounts for wallet: $userWalletId")
return savedAccountsResponse return requireNotNull(savedAccountsResponse) {
"Saved accounts response is null for wallet: $userWalletId"
}
} }
var response = savedAccountsResponse ?: createDefaultResponse(userWalletId) var response = savedAccountsResponse ?: createDefaultResponse(userWalletId)

View file

@ -3,8 +3,8 @@ package com.tangem.data.account.producer
import arrow.core.Option import arrow.core.Option
import arrow.core.some import arrow.core.some
import com.tangem.data.account.store.AccountsResponseStoreFactory import com.tangem.data.account.store.AccountsResponseStoreFactory
import com.tangem.data.account.utils.toUserTokensResponse
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
import com.tangem.datasource.api.tangemTech.models.account.toUserTokensResponse
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.isMultiCurrency import com.tangem.domain.models.wallet.isMultiCurrency
@ -54,6 +54,7 @@ internal class AccountListCryptoCurrenciesProducer @AssistedInject constructor(
).toSet() ).toSet()
} }
.onEmpty { emit(emptySet()) } .onEmpty { emit(emptySet()) }
.distinctUntilChanged()
.flowOn(dispatchers.default) .flowOn(dispatchers.default)
} }

View file

@ -0,0 +1,55 @@
package com.tangem.data.account.producer
import arrow.core.Option
import arrow.core.none
import com.tangem.domain.account.producer.SingleAccountListProducer
import com.tangem.domain.account.producer.SingleAccountProducer
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.models.account.Account
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.mapNotNull
/**
* Default implementation of [SingleAccountProducer] that produces a flow of [Account.CryptoPortfolio]
* for a single account identified by [SingleAccountProducer.Params.accountId].
*
* It uses [SingleAccountListSupplier] to get the list of accounts and filters it to find the
* specific account.
*
* @property params Parameters containing the account ID for which the portfolio is produced.
* @property singleAccountListSupplier Supplier to get the list of accounts.
* @property dispatchers Coroutine dispatcher provider for managing threading.
*/
internal class DefaultSingleAccountProducer @AssistedInject constructor(
@Assisted val params: SingleAccountProducer.Params,
private val singleAccountListSupplier: SingleAccountListSupplier,
private val dispatchers: CoroutineDispatcherProvider,
) : SingleAccountProducer {
override val fallback: Option<Account.CryptoPortfolio>
get() = none()
override fun produce(): Flow<Account.CryptoPortfolio> {
return singleAccountListSupplier(
params = SingleAccountListProducer.Params(userWalletId = params.accountId.userWalletId),
)
.mapNotNull { accountList ->
accountList.accounts.firstOrNull {
it is Account.CryptoPortfolio && params.accountId == it.accountId
} as? Account.CryptoPortfolio
}
.distinctUntilChanged()
.flowOn(dispatchers.default)
}
@AssistedFactory
interface Factory : SingleAccountProducer.Factory {
override fun create(params: SingleAccountProducer.Params): DefaultSingleAccountProducer
}
}

View file

@ -9,13 +9,13 @@ import com.tangem.data.account.store.AccountsResponseStore
import com.tangem.data.account.store.AccountsResponseStoreFactory import com.tangem.data.account.store.AccountsResponseStoreFactory
import com.tangem.data.account.store.ArchivedAccountsStore import com.tangem.data.account.store.ArchivedAccountsStore
import com.tangem.data.account.store.ArchivedAccountsStoreFactory import com.tangem.data.account.store.ArchivedAccountsStoreFactory
import com.tangem.data.account.utils.toUserTokensResponse
import com.tangem.data.common.account.WalletAccountsSaver import com.tangem.data.common.account.WalletAccountsSaver
import com.tangem.data.common.cache.etag.ETagsStore import com.tangem.data.common.cache.etag.ETagsStore
import com.tangem.data.common.currency.UserTokensSaver import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
import com.tangem.datasource.api.tangemTech.models.account.toUserTokensResponse
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.datasource.utils.getSyncOrNull import com.tangem.datasource.utils.getSyncOrNull
import com.tangem.domain.account.models.AccountList import com.tangem.domain.account.models.AccountList

View file

@ -9,11 +9,11 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.data.account.store.AccountsResponseStoreFactory import com.tangem.data.account.store.AccountsResponseStoreFactory
import com.tangem.data.account.utils.assignTokens import com.tangem.data.account.utils.assignTokens
import com.tangem.data.account.utils.toUserTokensResponse
import com.tangem.data.common.currency.UserTokensSaver import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
import com.tangem.datasource.api.tangemTech.models.account.toUserTokensResponse
import com.tangem.datasource.utils.getSyncOrNull import com.tangem.datasource.utils.getSyncOrNull
import com.tangem.domain.account.tokens.MainAccountTokensMigration import com.tangem.domain.account.tokens.MainAccountTokensMigration
import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.account.DerivationIndex

View file

@ -1,14 +1,16 @@
package com.tangem.data.account.utils package com.tangem.data.account.utils
import com.tangem.data.account.converter.CryptoPortfolioConverter import com.tangem.data.account.converter.CryptoPortfolioConverter
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.common.currency.UserTokensResponseFactory import com.tangem.data.common.currency.UserTokensResponseFactory
import com.tangem.data.common.network.NetworkFactory
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
import com.tangem.domain.account.models.AccountList import com.tangem.domain.account.models.AccountList
import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.account.Account import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import javax.inject.Inject import javax.inject.Inject
@ -19,7 +21,7 @@ import javax.inject.Inject
* @property userWalletsListRepository repository to get user wallet information * @property userWalletsListRepository repository to get user wallet information
* @property cryptoPortfolioCF converter factory to convert crypto portfolio accounts * @property cryptoPortfolioCF converter factory to convert crypto portfolio accounts
* @property userTokensResponseFactory factory to create [UserTokensResponse] * @property userTokensResponseFactory factory to create [UserTokensResponse]
* @property cardCryptoCurrencyFactory factory to get default coins for multi-currency wallet * @property networkFactory factory to create network derivation path
* *
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
*/ */
@ -27,7 +29,7 @@ internal class DefaultWalletAccountsResponseFactory @Inject constructor(
private val userWalletsListRepository: UserWalletsListRepository, private val userWalletsListRepository: UserWalletsListRepository,
private val cryptoPortfolioCF: CryptoPortfolioConverter.Factory, private val cryptoPortfolioCF: CryptoPortfolioConverter.Factory,
private val userTokensResponseFactory: UserTokensResponseFactory, private val userTokensResponseFactory: UserTokensResponseFactory,
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, private val networkFactory: NetworkFactory,
) { ) {
suspend fun create(userWalletId: UserWalletId, userTokensResponse: UserTokensResponse?): GetWalletAccountsResponse { suspend fun create(userWalletId: UserWalletId, userTokensResponse: UserTokensResponse?): GetWalletAccountsResponse {
@ -59,10 +61,12 @@ internal class DefaultWalletAccountsResponseFactory @Inject constructor(
private fun UserTokensResponse?.orDefault(userWallet: UserWallet?): UserTokensResponse { private fun UserTokensResponse?.orDefault(userWallet: UserWallet?): UserTokensResponse {
if (this != null) return this if (this != null) return this
return userTokensResponseFactory.createUserTokensResponse( return userTokensResponseFactory.createDefaultResponse(
currencies = userWallet?.let(cardCryptoCurrencyFactory::createDefaultCoinsForMultiCurrencyWallet).orEmpty(), userWallet = userWallet,
isGroupedByNetwork = false, networkFactory = networkFactory,
isSortedByBalance = false, accountId = userWallet?.let {
AccountId.forCryptoPortfolio(userWalletId = it.walletId, derivationIndex = DerivationIndex.Main)
},
) )
} }
} }

View file

@ -6,20 +6,6 @@ import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResp
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
/** Flattens the tokens from all wallet accounts into a single list */
internal fun GetWalletAccountsResponse.flattenTokens(): List<UserTokensResponse.Token> {
return accounts.flatMap { it.tokens.orEmpty() }
}
/** Converts the [GetWalletAccountsResponse] into a [UserTokensResponse] */
internal fun GetWalletAccountsResponse.toUserTokensResponse(): UserTokensResponse {
return UserTokensResponse(
group = wallet.group,
sort = wallet.sort,
tokens = flattenTokens(),
)
}
/** /**
* Assigns tokens from a [UserTokensResponse] to the wallet accounts in the [GetWalletAccountsResponse] * Assigns tokens from a [UserTokensResponse] to the wallet accounts in the [GetWalletAccountsResponse]
* *

View file

@ -29,6 +29,8 @@ class DefaultSingleAccountListFetcherTest {
// Arrange // Arrange
val params = SingleAccountListFetcher.Params(userWalletId = userWalletId) val params = SingleAccountListFetcher.Params(userWalletId = userWalletId)
coEvery { walletAccountsFetcher.fetch(userWalletId) } returns mockk()
// Act // Act
val actual = fetcher.invoke(params) val actual = fetcher.invoke(params)

View file

@ -199,7 +199,7 @@ class DefaultWalletAccountsFetcherTest {
@Test @Test
fun `fetch should call error handler when getWalletAccounts returns error`() = runTest { fun `fetch should call error handler when getWalletAccounts returns error`() = runTest {
// Arrange // Arrange
val savedAccountsResponse = null val savedAccountsResponse = createGetWalletAccountsResponse(userWalletId)
val apiError = ApiResponse.Error(ApiResponseError.NetworkException()) val apiError = ApiResponse.Error(ApiResponseError.NetworkException())
accountsResponseStoreFlow.value = savedAccountsResponse accountsResponseStoreFlow.value = savedAccountsResponse
@ -212,7 +212,7 @@ class DefaultWalletAccountsFetcherTest {
fetchWalletAccountsErrorHandler.handle( fetchWalletAccountsErrorHandler.handle(
error = apiError.cause, error = apiError.cause,
userWalletId = userWalletId, userWalletId = userWalletId,
savedAccountsResponse = null, savedAccountsResponse = savedAccountsResponse,
pushWalletAccounts = any(), pushWalletAccounts = any(),
storeWalletAccounts = any(), storeWalletAccounts = any(),
) )
@ -231,7 +231,7 @@ class DefaultWalletAccountsFetcherTest {
fetchWalletAccountsErrorHandler.handle( fetchWalletAccountsErrorHandler.handle(
error = apiError.cause, error = apiError.cause,
userWalletId = userWalletId, userWalletId = userWalletId,
savedAccountsResponse = null, savedAccountsResponse = savedAccountsResponse,
pushWalletAccounts = any(), pushWalletAccounts = any(),
storeWalletAccounts = any(), storeWalletAccounts = any(),
) )

View file

@ -3,13 +3,13 @@ package com.tangem.data.account.fetcher
import com.tangem.data.account.converter.createGetWalletAccountsResponse import com.tangem.data.account.converter.createGetWalletAccountsResponse
import com.tangem.data.account.converter.createWalletAccountDTO import com.tangem.data.account.converter.createWalletAccountDTO
import com.tangem.data.account.utils.DefaultWalletAccountsResponseFactory import com.tangem.data.account.utils.DefaultWalletAccountsResponseFactory
import com.tangem.data.account.utils.toUserTokensResponse
import com.tangem.data.common.currency.UserTokensSaver import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.ApiResponseError
import com.tangem.datasource.api.common.response.ApiResponseError.HttpException.Code import com.tangem.datasource.api.common.response.ApiResponseError.HttpException.Code
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
import com.tangem.datasource.api.tangemTech.models.account.toUserTokensResponse
import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import io.mockk.clearMocks import io.mockk.clearMocks
@ -53,6 +53,8 @@ class FetchWalletAccountsErrorHandlerTest {
@Test @Test
fun `does not update accounts when response is up to date`() = runTest { fun `does not update accounts when response is up to date`() = runTest {
// Arrange // Arrange
val response = createGetWalletAccountsResponse(userWalletId)
val error = ApiResponseError.HttpException( val error = ApiResponseError.HttpException(
code = Code.NOT_MODIFIED, code = Code.NOT_MODIFIED,
message = "Not Modified", message = "Not Modified",
@ -63,7 +65,7 @@ class FetchWalletAccountsErrorHandlerTest {
handler.handle( handler.handle(
error = error, error = error,
userWalletId = userWalletId, userWalletId = userWalletId,
savedAccountsResponse = null, savedAccountsResponse = response,
pushWalletAccounts = pushWalletAccounts, pushWalletAccounts = pushWalletAccounts,
storeWalletAccounts = storeWalletAccounts, storeWalletAccounts = storeWalletAccounts,
) )

View file

@ -7,10 +7,10 @@ import com.tangem.data.account.converter.createWalletAccountDTO
import com.tangem.data.account.store.AccountsResponseStore import com.tangem.data.account.store.AccountsResponseStore
import com.tangem.data.account.store.AccountsResponseStoreFactory import com.tangem.data.account.store.AccountsResponseStoreFactory
import com.tangem.data.account.tokens.DefaultMainAccountTokensMigration import com.tangem.data.account.tokens.DefaultMainAccountTokensMigration
import com.tangem.data.account.utils.toUserTokensResponse
import com.tangem.data.common.currency.UserTokensSaver import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
import com.tangem.datasource.api.tangemTech.models.account.toUserTokensResponse
import com.tangem.domain.models.account.AccountId import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId

View file

@ -3,8 +3,8 @@ package com.tangem.data.account.utils
import com.google.common.truth.Truth import com.google.common.truth.Truth
import com.tangem.data.account.converter.CryptoPortfolioConverter import com.tangem.data.account.converter.CryptoPortfolioConverter
import com.tangem.data.account.converter.createWalletAccountDTO import com.tangem.data.account.converter.createWalletAccountDTO
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.common.currency.UserTokensResponseFactory import com.tangem.data.common.currency.UserTokensResponseFactory
import com.tangem.data.common.network.NetworkFactory
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
import com.tangem.domain.account.models.AccountList import com.tangem.domain.account.models.AccountList
@ -27,13 +27,13 @@ class DefaultWalletAccountsResponseFactoryTest {
private val cryptoPortfolioCF = mockk<CryptoPortfolioConverter.Factory>() private val cryptoPortfolioCF = mockk<CryptoPortfolioConverter.Factory>()
private val cryptoPortfolioConverter = mockk<CryptoPortfolioConverter>() private val cryptoPortfolioConverter = mockk<CryptoPortfolioConverter>()
private val userTokensResponseFactory = mockk<UserTokensResponseFactory>() private val userTokensResponseFactory = mockk<UserTokensResponseFactory>()
private val cardCryptoCurrencyFactory = mockk<CardCryptoCurrencyFactory>() private val networkFactory = mockk<NetworkFactory>()
private val factory = DefaultWalletAccountsResponseFactory( private val factory = DefaultWalletAccountsResponseFactory(
userWalletsListRepository = userWalletsListRepository, userWalletsListRepository = userWalletsListRepository,
cryptoPortfolioCF = cryptoPortfolioCF, cryptoPortfolioCF = cryptoPortfolioCF,
userTokensResponseFactory = userTokensResponseFactory, userTokensResponseFactory = userTokensResponseFactory,
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, networkFactory = networkFactory,
) )
private val userWalletId = UserWalletId("011") private val userWalletId = UserWalletId("011")
@ -50,7 +50,7 @@ class DefaultWalletAccountsResponseFactoryTest {
cryptoPortfolioCF, cryptoPortfolioCF,
cryptoPortfolioConverter, cryptoPortfolioConverter,
userTokensResponseFactory, userTokensResponseFactory,
cardCryptoCurrencyFactory, networkFactory,
) )
} }
@ -65,10 +65,10 @@ class DefaultWalletAccountsResponseFactoryTest {
coEvery { userWalletsListRepository.userWalletsSync() } returns emptyList() coEvery { userWalletsListRepository.userWalletsSync() } returns emptyList()
every { every {
userTokensResponseFactory.createUserTokensResponse( userTokensResponseFactory.createDefaultResponse(
currencies = emptyList(), userWallet = null,
isGroupedByNetwork = false, networkFactory = networkFactory,
isSortedByBalance = false, accountId = null,
) )
} returns userTokensResponse } returns userTokensResponse
@ -89,10 +89,10 @@ class DefaultWalletAccountsResponseFactoryTest {
coVerifyOrder { coVerifyOrder {
userWalletsListRepository.userWalletsSync() userWalletsListRepository.userWalletsSync()
userTokensResponseFactory.createUserTokensResponse( userTokensResponseFactory.createDefaultResponse(
currencies = emptyList(), userWallet = null,
isGroupedByNetwork = false, networkFactory = networkFactory,
isSortedByBalance = false, accountId = null,
) )
} }
} }
@ -104,27 +104,24 @@ class DefaultWalletAccountsResponseFactoryTest {
every { walletId } returns userWalletId every { walletId } returns userWalletId
} }
val defaultCoins = listOf(mockk<CryptoCurrency.Coin>()) val accounts = AccountList.empty(userWallet.walletId).accounts
.filterIsInstance<Account.CryptoPortfolio>()
coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(userWallet) coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(userWallet)
every { cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet) } returns defaultCoins
val defaultResponse = UserTokensResponse( val defaultResponse = UserTokensResponse(
group = UserTokensResponse.GroupType.NETWORK, group = UserTokensResponse.GroupType.NETWORK,
sort = UserTokensResponse.SortType.BALANCE, sort = UserTokensResponse.SortType.BALANCE,
tokens = listOf(mockk(relaxed = true)), tokens = listOf(mockk(relaxed = true)),
) )
every { every {
userTokensResponseFactory.createUserTokensResponse( userTokensResponseFactory.createDefaultResponse(
currencies = defaultCoins, userWallet = userWallet,
isGroupedByNetwork = false, networkFactory = networkFactory,
isSortedByBalance = false, accountId = accounts.first().accountId,
) )
} returns defaultResponse } returns defaultResponse
val accounts = AccountList.empty(userWallet.walletId).accounts
.filterIsInstance<Account.CryptoPortfolio>()
val accountsDTO = createWalletAccountDTO(userWalletId) val accountsDTO = createWalletAccountDTO(userWalletId)
every { cryptoPortfolioConverter.convertListBack(accounts) } returns listOf(accountsDTO) every { cryptoPortfolioConverter.convertListBack(accounts) } returns listOf(accountsDTO)
@ -147,11 +144,10 @@ class DefaultWalletAccountsResponseFactoryTest {
coVerifyOrder { coVerifyOrder {
userWalletsListRepository.userWalletsSync() userWalletsListRepository.userWalletsSync()
cryptoPortfolioConverter.convertListBack(accounts) cryptoPortfolioConverter.convertListBack(accounts)
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet) userTokensResponseFactory.createDefaultResponse(
userTokensResponseFactory.createUserTokensResponse( userWallet = userWallet,
currencies = defaultCoins, networkFactory = networkFactory,
isGroupedByNetwork = false, accountId = accounts.first().accountId,
isSortedByBalance = false,
) )
} }
} }
@ -162,22 +158,26 @@ class DefaultWalletAccountsResponseFactoryTest {
val userWallet = mockk<UserWallet>(relaxed = true) { val userWallet = mockk<UserWallet>(relaxed = true) {
every { walletId } returns userWalletId every { walletId } returns userWalletId
} }
val accounts = AccountList.empty(userWallet.walletId).accounts
.filterIsInstance<Account.CryptoPortfolio>()
coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(userWallet) coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(userWallet)
every { cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet) } returns emptyList()
val defaultResponse = UserTokensResponse( val defaultResponse = UserTokensResponse(
group = UserTokensResponse.GroupType.NETWORK, group = UserTokensResponse.GroupType.NETWORK,
sort = UserTokensResponse.SortType.BALANCE, sort = UserTokensResponse.SortType.BALANCE,
tokens = emptyList(), tokens = emptyList(),
) )
every { every {
userTokensResponseFactory.createUserTokensResponse( userTokensResponseFactory.createDefaultResponse(
currencies = emptyList(), userWallet = userWallet,
isGroupedByNetwork = false, networkFactory = networkFactory,
isSortedByBalance = false, accountId = accounts.first().accountId,
) )
} returns defaultResponse } returns defaultResponse
val accounts = AccountList.empty(userWallet.walletId).accounts
.filterIsInstance<Account.CryptoPortfolio>()
every { cryptoPortfolioConverter.convertListBack(accounts) } returns emptyList() every { cryptoPortfolioConverter.convertListBack(accounts) } returns emptyList()
// Act // Act

View file

@ -4,6 +4,8 @@ import com.google.common.truth.Truth
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
import com.tangem.datasource.api.tangemTech.models.account.flattenTokens
import com.tangem.datasource.api.tangemTech.models.account.toUserTokensResponse
import com.tangem.domain.models.account.AccountId import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId

View file

@ -1,5 +1,6 @@
package com.tangem.data.common.account package com.tangem.data.common.account
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
/** /**
@ -11,5 +12,8 @@ interface WalletAccountsFetcher {
/** Fetch wallet accounts by [userWalletId] */ /** Fetch wallet accounts by [userWalletId] */
@Throws @Throws
suspend fun fetch(userWalletId: UserWalletId) suspend fun fetch(userWalletId: UserWalletId): GetWalletAccountsResponse
/** Get saved wallet accounts by [userWalletId] */
suspend fun getSaved(userWalletId: UserWalletId): GetWalletAccountsResponse?
} }

View file

@ -1,12 +1,15 @@
package com.tangem.data.common.currency package com.tangem.data.common.currency
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.data.common.account.WalletAccountsFetcher
import com.tangem.data.common.tokens.getDefaultWalletBlockchains
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.api.tangemTech.models.account.toUserTokensResponse
import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.card.common.TapWorkarounds.isTestCard import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.card.common.util.cardTypesResolver import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.demo.models.DemoConfig import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
@ -23,10 +26,13 @@ import com.tangem.domain.models.wallet.isMultiCurrency
* @property userWalletsStore user wallets store * @property userWalletsStore user wallets store
* @property userTokensResponseStore user tokens response store * @property userTokensResponseStore user tokens response store
*/ */
@Suppress("LongParameterList")
internal class DefaultCardCryptoCurrencyFactory( internal class DefaultCardCryptoCurrencyFactory(
private val demoConfig: DemoConfig, private val demoConfig: DemoConfig,
private val excludedBlockchains: ExcludedBlockchains, private val excludedBlockchains: ExcludedBlockchains,
private val userWalletsStore: UserWalletsStore, private val userWalletsStore: UserWalletsStore,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val walletAccountsFetcher: WalletAccountsFetcher,
private val userTokensResponseStore: UserTokensResponseStore, private val userTokensResponseStore: UserTokensResponseStore,
private val responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory, private val responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory,
) : CardCryptoCurrencyFactory { ) : CardCryptoCurrencyFactory {
@ -99,25 +105,7 @@ internal class DefaultCardCryptoCurrencyFactory(
override fun createDefaultCoinsForMultiCurrencyWallet(userWallet: UserWallet): List<CryptoCurrency.Coin> { override fun createDefaultCoinsForMultiCurrencyWallet(userWallet: UserWallet): List<CryptoCurrency.Coin> {
require(userWallet.isMultiCurrency) { "It isn't multi-currency wallet" } require(userWallet.isMultiCurrency) { "It isn't multi-currency wallet" }
val blockchains = when (userWallet) { val blockchains = getDefaultWalletBlockchains(userWallet, demoConfig)
is UserWallet.Cold -> {
val card = userWallet.scanResponse.card
var blockchainsInternal = if (demoConfig.isDemoCardId(card.cardId)) {
demoConfig.demoBlockchains
} else {
listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
}
if (card.isTestCard) {
blockchainsInternal = blockchainsInternal.mapNotNull { it.getTestnetVersion() }
}
blockchainsInternal
}
is UserWallet.Hot -> listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
}
return blockchains.mapNotNull { return blockchains.mapNotNull {
cryptoCurrencyFactory.createCoin( cryptoCurrencyFactory.createCoin(
@ -152,7 +140,7 @@ internal class DefaultCardCryptoCurrencyFactory(
userWallet: UserWallet, userWallet: UserWallet,
networks: Set<Network>, networks: Set<Network>,
): Map<Network, List<CryptoCurrency>> { ): Map<Network, List<CryptoCurrency>> {
val response = userTokensResponseStore.getSyncOrNull(userWalletId = userWallet.walletId) val response = getUserTokensResponse(userWalletId = userWallet.walletId)
?: return emptyMap() ?: return emptyMap()
val existingNetworkWithCurrencies = responseCryptoCurrenciesFactory.createCurrencies( val existingNetworkWithCurrencies = responseCryptoCurrenciesFactory.createCurrencies(
@ -170,7 +158,7 @@ internal class DefaultCardCryptoCurrencyFactory(
userWallet: UserWallet, userWallet: UserWallet,
rawIds: Set<Network.RawID>, rawIds: Set<Network.RawID>,
): Map<Network.RawID, List<CryptoCurrency>> { ): Map<Network.RawID, List<CryptoCurrency>> {
val response = userTokensResponseStore.getSyncOrNull(userWalletId = userWallet.walletId) val response = getUserTokensResponse(userWalletId = userWallet.walletId)
?: return emptyMap() ?: return emptyMap()
val networkIds = rawIds.map { it.toBlockchain().toNetworkId() } val networkIds = rawIds.map { it.toBlockchain().toNetworkId() }
@ -182,6 +170,14 @@ internal class DefaultCardCryptoCurrencyFactory(
.groupBy { it.network.id.rawId } .groupBy { it.network.id.rawId }
} }
private suspend fun getUserTokensResponse(userWalletId: UserWalletId): UserTokensResponse? {
return if (accountsFeatureToggles.isFeatureEnabled) {
walletAccountsFetcher.getSaved(userWalletId)?.toUserTokensResponse()
} else {
userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId)
}
}
private fun getSingleWalletCurrencies(userWallet: UserWallet.Cold): SingleWalletCurrencies { private fun getSingleWalletCurrencies(userWallet: UserWallet.Cold): SingleWalletCurrencies {
val resolver = userWallet.cardTypesResolver val resolver = userWallet.cardTypesResolver
val blockchain = resolver.getBlockchain() val blockchain = resolver.getBlockchain()

View file

@ -1,8 +1,15 @@
package com.tangem.data.common.currency package com.tangem.data.common.currency
import com.tangem.blockchainsdk.utils.toCoinId
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.data.common.network.NetworkFactory
import com.tangem.data.common.tokens.getDefaultWalletBlockchains
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.models.account.AccountId import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.derivations.derivationStyleProvider
import javax.inject.Inject import javax.inject.Inject
// TODO: [REDACTED_JIRA] // TODO: [REDACTED_JIRA]
@ -43,4 +50,38 @@ class UserTokensResponseFactory @Inject constructor() {
) )
} }
} }
fun createDefaultResponse(
userWallet: UserWallet?,
networkFactory: NetworkFactory,
accountId: AccountId? = null,
): UserTokensResponse {
val tokens = userWallet?.let {
getDefaultWalletBlockchains(userWallet = it, demoConfig = DemoConfig())
.map { blockchain ->
val derivationPath = networkFactory.createDerivationPath(
blockchain = blockchain,
extraDerivationPath = null,
cardDerivationStyleProvider = userWallet.derivationStyleProvider,
).value
UserTokensResponse.Token(
id = blockchain.toCoinId(),
accountId = accountId?.value,
networkId = blockchain.toNetworkId(),
derivationPath = derivationPath,
name = blockchain.getCoinName(),
symbol = blockchain.currency,
decimals = blockchain.decimals(),
contractAddress = null,
)
}
}
return UserTokensResponse(
group = UserTokensResponse.GroupType.NONE,
sort = UserTokensResponse.SortType.MANUAL,
tokens = tokens.orEmpty(),
)
}
} }

View file

@ -1,6 +1,7 @@
package com.tangem.data.common.di package com.tangem.data.common.di
import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.data.common.account.WalletAccountsFetcher
import com.tangem.data.common.cache.etag.DefaultETagsStore import com.tangem.data.common.cache.etag.DefaultETagsStore
import com.tangem.data.common.cache.etag.ETagsStore import com.tangem.data.common.cache.etag.ETagsStore
import com.tangem.data.common.currency.* import com.tangem.data.common.currency.*
@ -30,6 +31,8 @@ internal object DataCommonModule {
fun provideCardCryptoCurrencyFactory( fun provideCardCryptoCurrencyFactory(
excludedBlockchains: ExcludedBlockchains, excludedBlockchains: ExcludedBlockchains,
userWalletsStore: UserWalletsStore, userWalletsStore: UserWalletsStore,
accountsFeatureToggles: AccountsFeatureToggles,
walletAccountsFetcher: WalletAccountsFetcher,
userTokensResponseStore: UserTokensResponseStore, userTokensResponseStore: UserTokensResponseStore,
responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory, responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory,
): CardCryptoCurrencyFactory { ): CardCryptoCurrencyFactory {
@ -37,6 +40,8 @@ internal object DataCommonModule {
demoConfig = DemoConfig(), demoConfig = DemoConfig(),
excludedBlockchains = excludedBlockchains, excludedBlockchains = excludedBlockchains,
userWalletsStore = userWalletsStore, userWalletsStore = userWalletsStore,
accountsFeatureToggles = accountsFeatureToggles,
walletAccountsFetcher = walletAccountsFetcher,
userTokensResponseStore = userTokensResponseStore, userTokensResponseStore = userTokensResponseStore,
responseCryptoCurrenciesFactory = responseCryptoCurrenciesFactory, responseCryptoCurrenciesFactory = responseCryptoCurrenciesFactory,
) )

View file

@ -7,10 +7,10 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.domain.card.common.extensions.canHandleToken import com.tangem.domain.card.common.extensions.canHandleToken
import com.tangem.domain.wallets.derivations.derivationStyleProvider
import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.derivations.DerivationStyleProvider import com.tangem.domain.wallets.derivations.DerivationStyleProvider
import com.tangem.domain.wallets.derivations.derivationStyleProvider
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -130,7 +130,7 @@ class NetworkFactory @Inject constructor(
return true return true
} }
private fun createDerivationPath( fun createDerivationPath(
blockchain: Blockchain, blockchain: Blockchain,
extraDerivationPath: String?, extraDerivationPath: String?,
cardDerivationStyleProvider: DerivationStyleProvider?, cardDerivationStyleProvider: DerivationStyleProvider?,
@ -326,8 +326,8 @@ class NetworkFactory @Inject constructor(
Blockchain.Pepecoin, Blockchain.PepecoinTestnet, Blockchain.Pepecoin, Blockchain.PepecoinTestnet,
Blockchain.Hyperliquid, Blockchain.HyperliquidTestnet, Blockchain.Hyperliquid, Blockchain.HyperliquidTestnet,
Blockchain.Quai, Blockchain.QuaiTestnet, Blockchain.Quai, Blockchain.QuaiTestnet,
// Blockchain.Linea, Blockchain.LineaTestnet, Blockchain.Linea, Blockchain.LineaTestnet,
// Blockchain.ArbitrumNova, Blockchain.ArbitrumNova,
-> Network.TransactionExtrasType.NONE -> Network.TransactionExtrasType.NONE
// endregion // endregion
} }

View file

@ -0,0 +1,33 @@
package com.tangem.data.common.tokens
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.card.common.TapWorkarounds.isTestCard
import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.models.wallet.UserWallet
/**
* Returns the default blockchains for the multi-currency wallet.
*
* @param userWallet The user's wallet, which can be either a cold or hot wallet.
* @param demoConfig Configuration for demo cards, which may specify different default blockchains.
*/
fun getDefaultWalletBlockchains(userWallet: UserWallet, demoConfig: DemoConfig): Collection<Blockchain> {
return when (userWallet) {
is UserWallet.Cold -> {
val card = userWallet.scanResponse.card
var blockchainsInternal = if (demoConfig.isDemoCardId(card.cardId)) {
demoConfig.demoBlockchains
} else {
listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
}
if (card.isTestCard) {
blockchainsInternal = blockchainsInternal.mapNotNull { it.getTestnetVersion() }
}
blockchainsInternal
}
is UserWallet.Hot -> listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
}
}

View file

@ -9,10 +9,12 @@ import com.tangem.common.test.domain.card.MockScanResponseFactory
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
import com.tangem.common.test.domain.wallet.MockUserWalletFactory import com.tangem.common.test.domain.wallet.MockUserWalletFactory
import com.tangem.common.test.utils.ProvideTestModels import com.tangem.common.test.utils.ProvideTestModels
import com.tangem.data.common.account.WalletAccountsFetcher
import com.tangem.data.common.network.NetworkFactory import com.tangem.data.common.network.NetworkFactory
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.card.common.util.cardTypesResolver import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.card.configs.GenericCardConfig import com.tangem.domain.card.configs.GenericCardConfig
import com.tangem.domain.demo.models.DemoConfig import com.tangem.domain.demo.models.DemoConfig
@ -37,6 +39,8 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
private val userWalletsStore: UserWalletsStore = mockk() private val userWalletsStore: UserWalletsStore = mockk()
private val userTokensResponseStore: UserTokensResponseStore = mockk() private val userTokensResponseStore: UserTokensResponseStore = mockk()
private val excludedBlockchains = ExcludedBlockchains() private val excludedBlockchains = ExcludedBlockchains()
private val accountsFeatureToggles = mockk<AccountsFeatureToggles>()
private val walletAccountsFetcher = mockk<WalletAccountsFetcher>()
private val factory = DefaultCardCryptoCurrencyFactory( private val factory = DefaultCardCryptoCurrencyFactory(
demoConfig = DemoConfig(), demoConfig = DemoConfig(),
@ -46,6 +50,8 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
responseCryptoCurrenciesFactory = ResponseCryptoCurrenciesFactory( responseCryptoCurrenciesFactory = ResponseCryptoCurrenciesFactory(
networkFactory = NetworkFactory(excludedBlockchains = excludedBlockchains), networkFactory = NetworkFactory(excludedBlockchains = excludedBlockchains),
), ),
accountsFeatureToggles = accountsFeatureToggles,
walletAccountsFetcher = walletAccountsFetcher,
) )
private val cryptoCurrencyFactory = MockCryptoCurrencyFactory() private val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
@ -57,7 +63,7 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
@BeforeEach @BeforeEach
fun init() { fun init() {
clearMocks(userWalletsStore, userTokensResponseStore, iconUri) clearMocks(userWalletsStore, userTokensResponseStore, accountsFeatureToggles, walletAccountsFetcher, iconUri)
mockkStatic(Uri::class) mockkStatic(Uri::class)
every { Uri.parse(any()) } returns iconUri every { Uri.parse(any()) } returns iconUri
@ -75,6 +81,7 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
val userTokensResponse = model.userTokensResponse val userTokensResponse = model.userTokensResponse
val network = ethereum.network val network = ethereum.network
every { accountsFeatureToggles.isFeatureEnabled } returns false
coEvery { userWalletsStore.getSyncStrict(key = userWallet.walletId) } returns userWallet coEvery { userWalletsStore.getSyncStrict(key = userWallet.walletId) } returns userWallet
coEvery { userTokensResponseStore.getSyncOrNull(userWallet.walletId) } returns userTokensResponse coEvery { userTokensResponseStore.getSyncOrNull(userWallet.walletId) } returns userTokensResponse
@ -236,6 +243,7 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
val networks = setOf(ethereum.network, bitcoin.network) val networks = setOf(ethereum.network, bitcoin.network)
val userTokensResponse = model.userTokensResponse val userTokensResponse = model.userTokensResponse
every { accountsFeatureToggles.isFeatureEnabled } returns false
coEvery { userTokensResponseStore.getSyncOrNull(userWallet.walletId) } returns userTokensResponse coEvery { userTokensResponseStore.getSyncOrNull(userWallet.walletId) } returns userTokensResponse
// Act // Act

View file

@ -1,17 +1,23 @@
package com.tangem.datasource.exchangeservice.swap package com.tangem.data.express
import arrow.core.Either
import arrow.core.raise.either
import com.tangem.data.express.converter.ExpressAssetConverter
import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.express.TangemExpressApi import com.tangem.datasource.api.express.TangemExpressApi
import com.tangem.datasource.api.express.models.request.AssetsRequestBody import com.tangem.datasource.api.express.models.request.AssetsRequestBody
import com.tangem.datasource.api.express.models.request.LeastTokenInfo import com.tangem.datasource.api.express.models.request.LeastTokenInfo
import com.tangem.datasource.api.express.models.response.Asset
import com.tangem.datasource.exchangeservice.swap.ExpressUtils.getRefCode import com.tangem.datasource.exchangeservice.swap.ExpressUtils.getRefCode
import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.token.ExpressAssetsStore import com.tangem.datasource.local.token.ExpressAssetsStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.core.lce.Lce import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.utils.catchOn
import com.tangem.domain.core.utils.lceContent import com.tangem.domain.core.utils.lceContent
import com.tangem.domain.core.utils.lceError import com.tangem.domain.core.utils.lceError
import com.tangem.domain.core.utils.lceLoading import com.tangem.domain.core.utils.lceLoading
import com.tangem.domain.express.ExpressServiceFetcher
import com.tangem.domain.express.models.ExpressAsset
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -19,56 +25,72 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
typealias InitializationStatusFlow = MutableStateFlow<Lce<Throwable, List<Asset>>> typealias InitializationStatusFlow = MutableStateFlow<Lce<Throwable, List<ExpressAsset>>>
/** /**
* Default implementation of [ExpressServiceLoader] * Default implementation of [ExpressServiceFetcher]
* *
* @property tangemExpressApi express api * @property tangemExpressApi express api
* @property expressAssetsStore local storage * @property expressAssetsStore local storage
* *
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
*/ */
internal class DefaultExpressServiceLoader @Inject constructor( internal class DefaultExpressServiceFetcher @Inject constructor(
private val tangemExpressApi: TangemExpressApi, private val tangemExpressApi: TangemExpressApi,
private val expressAssetsStore: ExpressAssetsStore, private val expressAssetsStore: ExpressAssetsStore,
private val appPreferencesStore: AppPreferencesStore, private val appPreferencesStore: AppPreferencesStore,
private val userWalletsStore: UserWalletsStore,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
) : ExpressServiceLoader { ) : ExpressServiceFetcher {
private val initializationStatuses = private val initializationStatuses =
MutableStateFlow<Map<UserWalletId, InitializationStatusFlow>>(value = emptyMap()) MutableStateFlow<Map<UserWalletId, InitializationStatusFlow>>(value = emptyMap())
override suspend fun update(userWallet: UserWallet, userTokens: List<LeastTokenInfo>) { override suspend fun fetch(userWalletId: UserWalletId, assetIds: Set<ExpressAsset.ID>): Either<Throwable, Unit> =
withContext(dispatchers.io) { either {
val userWallet = arrow.core.raise.catch(
block = { userWalletsStore.getSyncStrict(userWalletId) },
catch = ::raise,
)
fetch(userWallet = userWallet, assetIds = assetIds).bind()
}
override suspend fun fetch(userWallet: UserWallet, assetIds: Set<ExpressAsset.ID>): Either<Throwable, Unit> {
return Either.catchOn(dispatchers.io) {
val initializationStatus = getInitializationStatusInternal(userWallet.walletId) val initializationStatus = getInitializationStatusInternal(userWallet.walletId)
try { try {
if (userTokens.isNotEmpty()) { if (assetIds.isNotEmpty()) {
val tokenList = assetIds.map {
LeastTokenInfo(contractAddress = it.contractAddress, network = it.networkId)
}
val response = tangemExpressApi.getAssets( val response = tangemExpressApi.getAssets(
userWalletId = userWallet.walletId.stringValue, userWalletId = userWallet.walletId.stringValue,
refCode = getRefCode(userWallet, appPreferencesStore), refCode = getRefCode(userWallet, appPreferencesStore),
body = AssetsRequestBody(tokensList = userTokens), body = AssetsRequestBody(tokensList = tokenList),
).getOrThrow() ).getOrThrow()
expressAssetsStore.store(userWallet.walletId, response) expressAssetsStore.store(userWallet.walletId, response)
initializationStatus.update { response.lceContent() } val expressAssets = ExpressAssetConverter.convertList(response)
initializationStatus.update { expressAssets.lceContent() }
} }
} catch (e: Throwable) { } catch (e: Throwable) {
if (expressAssetsStore.getSyncOrNull(userWallet.walletId) == null) { if (expressAssetsStore.getSyncOrNull(userWallet.walletId) == null) {
initializationStatus.update { e.lceError() } initializationStatus.update { e.lceError() }
} }
Timber.e(e, "Unable to fetch assets for: ${userWallet.walletId.stringValue}") Timber.e(e, "Unable to fetch assets for: ${userWallet.walletId.stringValue}")
throw e
} }
} }
} }
override fun getInitializationStatus(userWalletId: UserWalletId): Flow<Lce<Throwable, List<Asset>>> { override fun getInitializationStatus(userWalletId: UserWalletId): Flow<Lce<Throwable, List<ExpressAsset>>> {
return flow { getInitializationStatusInternal(userWalletId).collect { emit(it) } } return flow { getInitializationStatusInternal(userWalletId).collect { emit(it) } }
} }
@ -77,7 +99,7 @@ internal class DefaultExpressServiceLoader @Inject constructor(
val initializationStatus = initializationStatuses.value[userWalletId] val initializationStatus = initializationStatuses.value[userWalletId]
if (initializationStatus != null) return initializationStatus if (initializationStatus != null) return initializationStatus
val cached = expressAssetsStore.getSyncOrNull(userWalletId) val cached = expressAssetsStore.getSyncOrNull(userWalletId)?.let(ExpressAssetConverter::convertList)
val default: InitializationStatusFlow = MutableStateFlow(value = cached?.lceContent() ?: lceLoading()) val default: InitializationStatusFlow = MutableStateFlow(value = cached?.lceContent() ?: lceLoading())
initializationStatuses.update { statuses -> initializationStatuses.update { statuses ->

View file

@ -0,0 +1,24 @@
package com.tangem.data.express.converter
import com.tangem.datasource.api.express.models.response.Asset
import com.tangem.domain.express.models.ExpressAsset
import com.tangem.utils.converter.Converter
/**
* Converts an [Asset] from the data layer to an [ExpressAsset] in the domain layer.
*
[REDACTED_AUTHOR]
*/
internal object ExpressAssetConverter : Converter<Asset, ExpressAsset> {
override fun convert(value: Asset): ExpressAsset {
return ExpressAsset(
id = ExpressAsset.ID(
networkId = value.network,
contractAddress = value.contractAddress,
),
isExchangeAvailable = value.exchangeAvailable,
isOnrampAvailable = value.onrampAvailable,
)
}
}

View file

@ -2,6 +2,7 @@ package com.tangem.data.express.di
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import com.tangem.data.express.DefaultExpressRepository import com.tangem.data.express.DefaultExpressRepository
import com.tangem.data.express.DefaultExpressServiceFetcher
import com.tangem.data.express.converter.ExpressErrorConverter import com.tangem.data.express.converter.ExpressErrorConverter
import com.tangem.data.express.error.DefaultExpressErrorResolver import com.tangem.data.express.error.DefaultExpressErrorResolver
import com.tangem.datasource.api.express.TangemExpressApi import com.tangem.datasource.api.express.TangemExpressApi
@ -10,6 +11,7 @@ import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.express.ExpressErrorResolver import com.tangem.domain.express.ExpressErrorResolver
import com.tangem.domain.express.ExpressRepository import com.tangem.domain.express.ExpressRepository
import com.tangem.domain.express.ExpressServiceFetcher
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@ -43,4 +45,10 @@ internal object ExpressDataModule {
dispatchers = dispatchers, dispatchers = dispatchers,
) )
} }
@Provides
@Singleton
fun provideExpressServiceFetcher(impl: DefaultExpressServiceFetcher): ExpressServiceFetcher {
return impl
}
} }

View file

@ -238,7 +238,7 @@ internal class DefaultCustomTokensRepository(
"User tokens not found for user wallet [$userWalletId] while removing currency" "User tokens not found for user wallet [$userWalletId] while removing currency"
} }
val token = userTokensResponseFactory.createResponseToken(cryptoCurrency) val token = userTokensResponseFactory.createResponseToken(currency = cryptoCurrency, accountId = null)
userTokensSaver.storeAndPush( userTokensSaver.storeAndPush(
userWalletId = userWalletId, userWalletId = userWalletId,
response = storedCurrencies.copy(tokens = storedCurrencies.tokens.filterNot { it == token }), response = storedCurrencies.copy(tokens = storedCurrencies.tokens.filterNot { it == token }),
@ -249,6 +249,27 @@ internal class DefaultCustomTokensRepository(
} }
} }
override suspend fun convertToCryptoCurrency(
userWalletId: UserWalletId,
currency: ManagedCryptoCurrency.Custom,
): CryptoCurrency {
return when (currency) {
is ManagedCryptoCurrency.Custom.Coin -> createCoin(
userWalletId = userWalletId,
networkId = currency.network.id,
derivationPath = currency.network.derivationPath,
)
is ManagedCryptoCurrency.Custom.Token -> cryptoCurrencyFactory.createToken(
network = currency.network,
rawId = currency.currencyId.rawCurrencyId,
name = currency.name,
symbol = currency.symbol,
decimals = currency.decimals,
contractAddress = currency.contractAddress,
)
}
}
override suspend fun getSupportedNetworks(userWalletId: UserWalletId): List<Network> = withContext(dispatchers.io) { override suspend fun getSupportedNetworks(userWalletId: UserWalletId): List<Network> = withContext(dispatchers.io) {
val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
"User wallet [$userWalletId] not found while getting supported networks" "User wallet [$userWalletId] not found while getting supported networks"

View file

@ -6,6 +6,7 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.data.common.account.WalletAccountsFetcher
import com.tangem.data.common.api.safeApiCall import com.tangem.data.common.api.safeApiCall
import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.common.currency.UserTokensResponseFactory import com.tangem.data.common.currency.UserTokensResponseFactory
@ -21,15 +22,13 @@ import com.tangem.datasource.local.config.testnet.TestnetTokensStorage
import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.card.common.TapWorkarounds.isTestCard import com.tangem.domain.card.common.TapWorkarounds.isTestCard
import com.tangem.domain.card.common.extensions.canHandleBlockchain import com.tangem.domain.card.common.extensions.*
import com.tangem.domain.card.common.extensions.canHandleToken
import com.tangem.domain.card.common.extensions.hotWalletExcludedBlockchains
import com.tangem.domain.card.common.extensions.supportedBlockchains
import com.tangem.domain.card.common.extensions.supportedTokens
import com.tangem.domain.card.common.util.cardTypesResolver import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.managetokens.model.* import com.tangem.domain.managetokens.model.*
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
import com.tangem.domain.managetokens.repository.ManageTokensRepository import com.tangem.domain.managetokens.repository.ManageTokensRepository
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
@ -40,7 +39,7 @@ import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher.Request
import com.tangem.pagination.toBatchFlow import com.tangem.pagination.toBatchFlow
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@Suppress("LongParameterList") @Suppress("LongParameterList", "LargeClass")
internal class DefaultManageTokensRepository( internal class DefaultManageTokensRepository(
private val tangemTechApi: TangemTechApi, private val tangemTechApi: TangemTechApi,
private val userWalletsStore: UserWalletsStore, private val userWalletsStore: UserWalletsStore,
@ -51,6 +50,7 @@ internal class DefaultManageTokensRepository(
private val excludedBlockchains: ExcludedBlockchains, private val excludedBlockchains: ExcludedBlockchains,
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
private val walletAccountsFetcher: WalletAccountsFetcher,
networkFactory: NetworkFactory, networkFactory: NetworkFactory,
) : ManageTokensRepository { ) : ManageTokensRepository {
@ -94,7 +94,6 @@ internal class DefaultManageTokensRepository(
}, },
) )
@Suppress("ComplexCondition")
private suspend fun fetchCurrencies( private suspend fun fetchCurrencies(
userWallet: UserWallet?, userWallet: UserWallet?,
request: Request<ManageTokensListConfig>, request: Request<ManageTokensListConfig>,
@ -128,20 +127,22 @@ internal class DefaultManageTokensRepository(
) )
val tokensResponse = request.params.userWalletId?.let { userWalletId -> val tokensResponse = request.params.userWalletId?.let { userWalletId ->
if (loadUserTokensFromRemote && userWallet != null) { when (val params = request.params) {
safeApiCall({ tangemTechApi.getUserTokens(userWalletId.stringValue).bind() }) { is ManageTokensListConfig.Account -> {
// save tokens response only if loadUserTokensFromRemote is true and it means onboarding call fetchUserTokens(userWallet, userWalletId, params, loadUserTokensFromRemote)
createAndSaveDefaultUserTokensResponse(userWallet = userWallet) }
is ManageTokensListConfig.Wallet -> {
fetchUserTokensLegacy(userWallet, userWalletId, loadUserTokensFromRemote)
} }
} else {
getSavedUserTokensResponseSync(userWalletId)
} }
} }
val items = if (isFirstBatchFetching &&
val isCreateWithCustom = isFirstBatchFetching &&
tokensResponse != null && tokensResponse != null &&
userWallet != null && userWallet != null &&
query == null query == null
) {
val items = if (isCreateWithCustom) {
managedCryptoCurrencyFactory.createWithCustomTokens( managedCryptoCurrencyFactory.createWithCustomTokens(
coinsResponse = updatedCoinsResponse, coinsResponse = updatedCoinsResponse,
tokensResponse = tokensResponse, tokensResponse = tokensResponse,
@ -162,6 +163,56 @@ internal class DefaultManageTokensRepository(
) )
} }
private suspend fun fetchUserTokens(
userWallet: UserWallet?,
userWalletId: UserWalletId,
params: ManageTokensListConfig.Account,
loadUserTokensFromRemote: Boolean,
): UserTokensResponse? {
val accountId = when {
params.accountId == null -> {
return null
}
loadUserTokensFromRemote -> {
AccountId.forCryptoPortfolio(userWalletId = userWalletId, derivationIndex = DerivationIndex.Main)
}
else -> requireNotNull(params.accountId)
}
val response = if (loadUserTokensFromRemote && userWallet != null) {
runCatching { walletAccountsFetcher.fetch(userWalletId = userWallet.walletId) }.getOrNull()
} else {
walletAccountsFetcher.getSaved(userWalletId)
}
val account = response?.accounts?.firstOrNull { it.id == accountId.value }
?: return null
return UserTokensResponse(
group = response.wallet.group,
sort = response.wallet.sort,
tokens = account.tokens.orEmpty(),
)
}
private suspend fun fetchUserTokensLegacy(
userWallet: UserWallet?,
userWalletId: UserWalletId,
loadUserTokensFromRemote: Boolean,
): UserTokensResponse? {
return if (loadUserTokensFromRemote && userWallet != null) {
safeApiCall(
call = { tangemTechApi.getUserTokens(userWalletId.stringValue).bind() },
onError = {
// save tokens response only if loadUserTokensFromRemote is true and it means onboarding call
createAndSaveDefaultUserTokensResponse(userWallet = userWallet)
},
)
} else {
getSavedUserTokensResponseSync(userWalletId)
}
}
private suspend fun createAndSaveDefaultUserTokensResponse(userWallet: UserWallet): UserTokensResponse { private suspend fun createAndSaveDefaultUserTokensResponse(userWallet: UserWallet): UserTokensResponse {
val userTokensResponse = createDefaultUserTokensResponse(userWallet) val userTokensResponse = createDefaultUserTokensResponse(userWallet)
userTokenSaver.store(userWallet.walletId, userTokensResponse, useEnricher = false) userTokenSaver.store(userWallet.walletId, userTokensResponse, useEnricher = false)

View file

@ -1,6 +1,7 @@
package com.tangem.data.managetokens.di package com.tangem.data.managetokens.di
import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.data.common.account.WalletAccountsFetcher
import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.common.currency.UserTokensSaver import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.data.common.network.NetworkFactory import com.tangem.data.common.network.NetworkFactory
@ -38,6 +39,7 @@ internal object ManageTokensDataModule {
excludedBlockchains: ExcludedBlockchains, excludedBlockchains: ExcludedBlockchains,
cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
networkFactory: NetworkFactory, networkFactory: NetworkFactory,
walletAccountsFetcher: WalletAccountsFetcher,
): ManageTokensRepository { ): ManageTokensRepository {
return DefaultManageTokensRepository( return DefaultManageTokensRepository(
tangemTechApi = tangemTechApi, tangemTechApi = tangemTechApi,
@ -50,6 +52,7 @@ internal object ManageTokensDataModule {
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
networkFactory = networkFactory, networkFactory = networkFactory,
dispatchers = dispatchers, dispatchers = dispatchers,
walletAccountsFetcher = walletAccountsFetcher,
) )
} }

View file

@ -29,6 +29,7 @@ dependencies {
implementation(projects.domain.walletManager) implementation(projects.domain.walletManager)
implementation(projects.domain.appTheme.models) implementation(projects.domain.appTheme.models)
implementation(projects.domain.models) implementation(projects.domain.models)
implementation(projects.domain.express.models)
// region DI // region DI

View file

@ -12,7 +12,6 @@ import com.tangem.data.onramp.converters.error.OnrampErrorConverter
import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.ApiResponseError
import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.express.TangemExpressApi import com.tangem.datasource.api.express.TangemExpressApi
import com.tangem.datasource.api.express.models.TangemExpressValues
import com.tangem.datasource.api.express.models.response.ExchangeProvider import com.tangem.datasource.api.express.models.response.ExchangeProvider
import com.tangem.datasource.api.express.models.response.ExchangeProviderType import com.tangem.datasource.api.express.models.response.ExchangeProviderType
import com.tangem.datasource.api.express.models.response.ExpressErrorResponse import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
@ -38,6 +37,7 @@ import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObject import com.tangem.datasource.local.preferences.utils.getObject
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
import com.tangem.datasource.local.preferences.utils.storeObject import com.tangem.datasource.local.preferences.utils.storeObject
import com.tangem.domain.express.models.ExpressAsset
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
@ -560,7 +560,7 @@ internal class DefaultOnrampRepository(
} }
private fun CryptoCurrency.getContractAddress(): String = when (this) { private fun CryptoCurrency.getContractAddress(): String = when (this) {
is CryptoCurrency.Coin -> TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE is CryptoCurrency.Coin -> ExpressAsset.EMPTY_CONTRACT_ADDRESS_VALUE
is CryptoCurrency.Token -> this.contractAddress is CryptoCurrency.Token -> this.contractAddress
} }

View file

@ -160,7 +160,7 @@ public val Blockchain.mercuryoNetwork: String?
Blockchain.Pepecoin, Blockchain.PepecoinTestnet -> null Blockchain.Pepecoin, Blockchain.PepecoinTestnet -> null
Blockchain.Hyperliquid, Blockchain.HyperliquidTestnet -> null Blockchain.Hyperliquid, Blockchain.HyperliquidTestnet -> null
Blockchain.Quai, Blockchain.QuaiTestnet -> null Blockchain.Quai, Blockchain.QuaiTestnet -> null
// Blockchain.Linea, Blockchain.LineaTestnet -> null Blockchain.Linea, Blockchain.LineaTestnet -> null
// Blockchain.ArbitrumNova -> null Blockchain.ArbitrumNova -> null
} }
} }

View file

@ -28,6 +28,7 @@ dependencies {
implementation(projects.domain.card) implementation(projects.domain.card)
implementation(projects.domain.core) implementation(projects.domain.core)
implementation(projects.domain.demo) implementation(projects.domain.demo)
implementation(projects.domain.express)
implementation(projects.domain.legacy) implementation(projects.domain.legacy)
implementation(projects.domain.models) implementation(projects.domain.models)
implementation(projects.domain.staking) implementation(projects.domain.staking)

View file

@ -1,9 +1,13 @@
package com.tangem.data.tokens package com.tangem.data.tokens
import arrow.core.Either import arrow.core.Either
import arrow.core.right
import com.tangem.data.common.account.WalletAccountsFetcher import com.tangem.data.common.account.WalletAccountsFetcher
import com.tangem.datasource.api.tangemTech.models.account.flattenTokens
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.core.utils.catchOn import com.tangem.domain.core.utils.catchOn
import com.tangem.domain.express.ExpressServiceFetcher
import com.tangem.domain.express.models.ExpressAsset
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isMultiCurrency import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher
@ -15,6 +19,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
* *
* @property userWalletsStore [UserWallet]'s store * @property userWalletsStore [UserWallet]'s store
* @property walletAccountsFetcher instance of [WalletAccountsFetcher] to fetch accounts for a multi wallet * @property walletAccountsFetcher instance of [WalletAccountsFetcher] to fetch accounts for a multi wallet
* @property expressServiceFetcher fetcher of express service
* @property dispatchers dispatchers * @property dispatchers dispatchers
* *
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
@ -22,14 +27,26 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
internal class AccountListCryptoCurrenciesFetcher( internal class AccountListCryptoCurrenciesFetcher(
private val userWalletsStore: UserWalletsStore, private val userWalletsStore: UserWalletsStore,
private val walletAccountsFetcher: WalletAccountsFetcher, private val walletAccountsFetcher: WalletAccountsFetcher,
private val expressServiceFetcher: ExpressServiceFetcher,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
) : MultiWalletCryptoCurrenciesFetcher { ) : MultiWalletCryptoCurrenciesFetcher {
override suspend fun invoke(params: Params) = Either.catchOn(dispatchers.default) { override suspend fun invoke(params: Params): Either<Throwable, Unit> {
val userWallet = userWalletsStore.getSyncStrict(key = params.userWalletId) return Either.catchOn(dispatchers.default) {
val userWallet = userWalletsStore.getSyncStrict(key = params.userWalletId)
if (!userWallet.isMultiCurrency) error("${this::class.simpleName} supports only multi-currency wallet") if (!userWallet.isMultiCurrency) error("${this::class.simpleName} supports only multi-currency wallet")
walletAccountsFetcher.fetch(userWalletId = params.userWalletId) val response = walletAccountsFetcher.fetch(userWalletId = params.userWalletId)
expressServiceFetcher.fetch(
userWallet = userWallet,
assetIds = response.flattenTokens().mapTo(hashSetOf()) {
ExpressAsset.ID(networkId = it.networkId, contractAddress = it.contractAddress)
},
)
Unit.right()
}
} }
} }

View file

@ -7,15 +7,14 @@ import com.tangem.data.common.currency.UserTokensResponseFactory
import com.tangem.data.common.currency.UserTokensSaver import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.data.tokens.utils.CustomTokensMerger import com.tangem.data.tokens.utils.CustomTokensMerger
import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.ApiResponseError
import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE
import com.tangem.datasource.api.express.models.request.LeastTokenInfo
import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader
import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.core.utils.catchOn import com.tangem.domain.core.utils.catchOn
import com.tangem.domain.demo.models.DemoConfig import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.express.ExpressServiceFetcher
import com.tangem.domain.express.models.ExpressAsset
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isMultiCurrency import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher
@ -32,7 +31,7 @@ import timber.log.Timber
* @property userTokensResponseStore store of [UserTokensResponse] * @property userTokensResponseStore store of [UserTokensResponse]
* @property userTokensSaver user tokens saver * @property userTokensSaver user tokens saver
* @property cardCryptoCurrencyFactory factory for creating crypto currencies for specified card * @property cardCryptoCurrencyFactory factory for creating crypto currencies for specified card
* @property expressServiceLoader express service loader * @property expressServiceFetcher express service loader
* @property dispatchers dispatchers * @property dispatchers dispatchers
* *
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
@ -46,7 +45,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcher(
private val userTokensResponseStore: UserTokensResponseStore, private val userTokensResponseStore: UserTokensResponseStore,
private val userTokensSaver: UserTokensSaver, private val userTokensSaver: UserTokensSaver,
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
private val expressServiceLoader: ExpressServiceLoader, private val expressServiceFetcher: ExpressServiceFetcher,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
) : MultiWalletCryptoCurrenciesFetcher { ) : MultiWalletCryptoCurrenciesFetcher {
@ -109,14 +108,14 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcher(
} }
private suspend fun fetchExpressAssetsByNetworkIds(userWallet: UserWallet, userTokens: UserTokensResponse) { private suspend fun fetchExpressAssetsByNetworkIds(userWallet: UserWallet, userTokens: UserTokensResponse) {
val tokens = userTokens.tokens.map { token -> val tokens = userTokens.tokens.mapTo(hashSetOf()) { token ->
LeastTokenInfo( ExpressAsset.ID(
contractAddress = token.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE, networkId = token.networkId,
network = token.networkId, contractAddress = token.contractAddress,
) )
} }
expressServiceLoader.update(userWallet = userWallet, userTokens = tokens) expressServiceFetcher.fetch(userWallet = userWallet, assetIds = tokens)
} }
private fun createDefaultUserTokensResponse(userWallet: UserWallet): UserTokensResponse { private fun createDefaultUserTokensResponse(userWallet: UserWallet): UserTokensResponse {
@ -124,6 +123,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcher(
currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet), currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet),
isGroupedByNetwork = false, isGroupedByNetwork = false,
isSortedByBalance = false, isSortedByBalance = false,
accountId = null,
) )
} }
} }

View file

@ -7,11 +7,11 @@ import com.tangem.data.tokens.AccountListCryptoCurrenciesFetcher
import com.tangem.data.tokens.DefaultMultiWalletCryptoCurrenciesFetcher import com.tangem.data.tokens.DefaultMultiWalletCryptoCurrenciesFetcher
import com.tangem.data.tokens.utils.CustomTokensMerger import com.tangem.data.tokens.utils.CustomTokensMerger
import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader
import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.demo.models.DemoConfig import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.express.ExpressServiceFetcher
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module import dagger.Module
@ -33,7 +33,7 @@ internal class MultiWalletCryptoCurrenciesFetcherModule {
userTokensResponseStore: UserTokensResponseStore, userTokensResponseStore: UserTokensResponseStore,
userTokensSaver: UserTokensSaver, userTokensSaver: UserTokensSaver,
cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
expressServiceLoader: ExpressServiceLoader, expressServiceFetcher: ExpressServiceFetcher,
walletAccountsFetcher: WalletAccountsFetcher, walletAccountsFetcher: WalletAccountsFetcher,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): MultiWalletCryptoCurrenciesFetcher { ): MultiWalletCryptoCurrenciesFetcher {
@ -41,6 +41,7 @@ internal class MultiWalletCryptoCurrenciesFetcherModule {
AccountListCryptoCurrenciesFetcher( AccountListCryptoCurrenciesFetcher(
userWalletsStore = userWalletsStore, userWalletsStore = userWalletsStore,
walletAccountsFetcher = walletAccountsFetcher, walletAccountsFetcher = walletAccountsFetcher,
expressServiceFetcher = expressServiceFetcher,
dispatchers = dispatchers, dispatchers = dispatchers,
) )
} else { } else {
@ -56,7 +57,7 @@ internal class MultiWalletCryptoCurrenciesFetcherModule {
userTokensResponseStore = userTokensResponseStore, userTokensResponseStore = userTokensResponseStore,
userTokensSaver = userTokensSaver, userTokensSaver = userTokensSaver,
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
expressServiceLoader = expressServiceLoader, expressServiceFetcher = expressServiceFetcher,
dispatchers = dispatchers, dispatchers = dispatchers,
) )
} }

View file

@ -5,22 +5,14 @@ import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
import com.tangem.data.common.currency.UserTokensSaver import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.data.tokens.repository.DefaultCurrenciesRepository import com.tangem.data.tokens.repository.*
import com.tangem.data.tokens.repository.DefaultCurrencyChecksRepository
import com.tangem.data.tokens.repository.DefaultPolkadotAccountHealthCheckRepository
import com.tangem.data.tokens.repository.DefaultTokenReceiveWarningsViewedRepository
import com.tangem.data.tokens.repository.DefaultYieldSupplyWarningsViewedRepository
import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader
import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.token.TokenReceiveWarningActionStore import com.tangem.datasource.local.token.TokenReceiveWarningActionStore
import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.express.ExpressServiceFetcher
import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.tokens.repository.*
import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository
import com.tangem.domain.tokens.repository.TokenReceiveWarningsViewedRepository
import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module import dagger.Module
@ -42,7 +34,7 @@ internal object TokensDataModule {
walletManagersFacade: WalletManagersFacade, walletManagersFacade: WalletManagersFacade,
cacheRegistry: CacheRegistry, cacheRegistry: CacheRegistry,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
expressServiceLoader: ExpressServiceLoader, expressServiceFetcher: ExpressServiceFetcher,
excludedBlockchains: ExcludedBlockchains, excludedBlockchains: ExcludedBlockchains,
cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
tokensSaver: UserTokensSaver, tokensSaver: UserTokensSaver,
@ -54,7 +46,7 @@ internal object TokensDataModule {
walletManagersFacade = walletManagersFacade, walletManagersFacade = walletManagersFacade,
cacheRegistry = cacheRegistry, cacheRegistry = cacheRegistry,
userTokensResponseStore = userTokensResponseStore, userTokensResponseStore = userTokensResponseStore,
expressServiceLoader = expressServiceLoader, expressServiceFetcher = expressServiceFetcher,
dispatchers = dispatchers, dispatchers = dispatchers,
excludedBlockchains = excludedBlockchains, excludedBlockchains = excludedBlockchains,
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,

View file

@ -10,17 +10,16 @@ import com.tangem.data.common.currency.*
import com.tangem.data.tokens.utils.CustomTokensMerger import com.tangem.data.tokens.utils.CustomTokensMerger
import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.ApiResponseError
import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE
import com.tangem.datasource.api.express.models.request.LeastTokenInfo
import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader
import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.card.CardTypesResolver import com.tangem.domain.card.CardTypesResolver
import com.tangem.domain.card.common.util.cardTypesResolver import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.core.error.DataError import com.tangem.domain.core.error.DataError
import com.tangem.domain.demo.models.DemoConfig import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.express.ExpressServiceFetcher
import com.tangem.domain.express.models.ExpressAsset
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.Network
@ -40,7 +39,7 @@ internal class DefaultCurrenciesRepository(
private val userWalletsStore: UserWalletsStore, private val userWalletsStore: UserWalletsStore,
private val walletManagersFacade: WalletManagersFacade, private val walletManagersFacade: WalletManagersFacade,
private val cacheRegistry: CacheRegistry, private val cacheRegistry: CacheRegistry,
private val expressServiceLoader: ExpressServiceLoader, private val expressServiceFetcher: ExpressServiceFetcher,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
private val userTokensSaver: UserTokensSaver, private val userTokensSaver: UserTokensSaver,
@ -74,26 +73,6 @@ internal class DefaultCurrenciesRepository(
userTokensSaver.storeAndPush(userWalletId, response) userTokensSaver.storeAndPush(userWalletId, response)
} }
override suspend fun saveCurrenciesLocal(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
withContext(dispatchers.io) {
val savedResponse = requireNotNull(
value = getSavedUserTokensResponseSync(key = userWalletId),
lazyMessage = { "Saved tokens empty. Can not perform add currencies action." },
)
val updatedResponse = savedResponse.copy(
tokens = currencies.map(userTokensResponseFactory::createResponseToken),
)
userTokensSaver.store(userWalletId = userWalletId, response = updatedResponse)
fetchExpressAssetsByNetworkIds(
userWallet = userWalletsStore.getSyncStrict(key = userWalletId),
userTokens = updatedResponse,
)
}
}
override suspend fun addCurrenciesCache( override suspend fun addCurrenciesCache(
userWalletId: UserWalletId, userWalletId: UserWalletId,
currencies: List<CryptoCurrency>, currencies: List<CryptoCurrency>,
@ -667,16 +646,17 @@ internal class DefaultCurrenciesRepository(
return demoConfig.isDemoCardId(userWallet.cardId) && response == null return demoConfig.isDemoCardId(userWallet.cardId) && response == null
} }
// TODO [REDACTED_JIRA]
private suspend fun fetchExpressAssetsByNetworkIds(userWallet: UserWallet, userTokens: UserTokensResponse) { private suspend fun fetchExpressAssetsByNetworkIds(userWallet: UserWallet, userTokens: UserTokensResponse) {
val tokens = userTokens.tokens.map { token -> val tokens = userTokens.tokens.mapTo(hashSetOf()) { token ->
LeastTokenInfo( ExpressAsset.ID(
contractAddress = token.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE, networkId = token.networkId,
network = token.networkId, contractAddress = token.contractAddress,
) )
} }
coroutineScope { coroutineScope {
launch { expressServiceLoader.update(userWallet, tokens) } launch { expressServiceFetcher.fetch(userWallet, tokens) }
} }
} }
@ -685,11 +665,11 @@ internal class DefaultCurrenciesRepository(
cryptoCurrencies: List<CryptoCurrency>, cryptoCurrencies: List<CryptoCurrency>,
refresh: Boolean = false, refresh: Boolean = false,
) { ) {
val tokens = cryptoCurrencies.map { currency -> val tokens = cryptoCurrencies.mapTo(hashSetOf()) { currency ->
val tokenCurrency = currency as? CryptoCurrency.Token val tokenCurrency = currency as? CryptoCurrency.Token
LeastTokenInfo( ExpressAsset.ID(
contractAddress = tokenCurrency?.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE, networkId = currency.network.backendId,
network = currency.network.backendId, contractAddress = tokenCurrency?.contractAddress,
) )
} }
cacheRegistry.invokeOnExpire( cacheRegistry.invokeOnExpire(
@ -697,7 +677,7 @@ internal class DefaultCurrenciesRepository(
skipCache = refresh, skipCache = refresh,
block = { block = {
coroutineScope { coroutineScope {
launch { expressServiceLoader.update(userWallet, tokens) } launch { expressServiceFetcher.fetch(userWallet, tokens) }
} }
}, },
) )
@ -731,6 +711,7 @@ internal class DefaultCurrenciesRepository(
), ),
isGroupedByNetwork = false, isGroupedByNetwork = false,
isSortedByBalance = false, isSortedByBalance = false,
accountId = null,
) )
private fun ensureIsCorrectUserWallet(userWalletId: UserWalletId, isMultiCurrencyWalletExpected: Boolean) { private fun ensureIsCorrectUserWallet(userWalletId: UserWalletId, isMultiCurrencyWalletExpected: Boolean) {

View file

@ -1,10 +1,13 @@
package com.tangem.data.tokens package com.tangem.data.tokens
import arrow.core.left import arrow.core.left
import arrow.core.right
import com.tangem.common.test.utils.assertEither import com.tangem.common.test.utils.assertEither
import com.tangem.common.test.utils.assertEitherRight import com.tangem.common.test.utils.assertEitherRight
import com.tangem.data.common.account.WalletAccountsFetcher import com.tangem.data.common.account.WalletAccountsFetcher
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.express.ExpressServiceFetcher
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isMultiCurrency import com.tangem.domain.models.wallet.isMultiCurrency
@ -21,11 +24,13 @@ internal class AccountListCryptoCurrenciesFetcherTest {
private val userWalletsStore: UserWalletsStore = mockk(relaxUnitFun = true) private val userWalletsStore: UserWalletsStore = mockk(relaxUnitFun = true)
private val walletAccountsFetcher: WalletAccountsFetcher = mockk(relaxUnitFun = true) private val walletAccountsFetcher: WalletAccountsFetcher = mockk(relaxUnitFun = true)
private val expressServiceFetcher: ExpressServiceFetcher = mockk()
private val dispatchers = TestingCoroutineDispatcherProvider() private val dispatchers = TestingCoroutineDispatcherProvider()
private val fetcher = AccountListCryptoCurrenciesFetcher( private val fetcher = AccountListCryptoCurrenciesFetcher(
userWalletsStore = userWalletsStore, userWalletsStore = userWalletsStore,
walletAccountsFetcher = walletAccountsFetcher, walletAccountsFetcher = walletAccountsFetcher,
expressServiceFetcher = expressServiceFetcher,
dispatchers = dispatchers, dispatchers = dispatchers,
) )
@ -59,8 +64,11 @@ internal class AccountListCryptoCurrenciesFetcherTest {
// Arrange // Arrange
val params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId) val params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId)
val mockUserWallet = mockk<UserWallet> { every { isMultiCurrency } returns true } val mockUserWallet = mockk<UserWallet> { every { isMultiCurrency } returns true }
val response = mockk<GetWalletAccountsResponse>(relaxed = true)
every { userWalletsStore.getSyncStrict(key = params.userWalletId) } returns mockUserWallet every { userWalletsStore.getSyncStrict(key = params.userWalletId) } returns mockUserWallet
coEvery { walletAccountsFetcher.fetch(userWalletId = params.userWalletId) } returns response
coEvery { expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = emptySet()) } returns Unit.right()
// Act // Act
val actual = fetcher(params) val actual = fetcher(params)
@ -71,6 +79,7 @@ internal class AccountListCryptoCurrenciesFetcherTest {
coVerify(ordering = Ordering.SEQUENCE) { coVerify(ordering = Ordering.SEQUENCE) {
userWalletsStore.getSyncStrict(key = params.userWalletId) userWalletsStore.getSyncStrict(key = params.userWalletId)
walletAccountsFetcher.fetch(userWalletId = params.userWalletId) walletAccountsFetcher.fetch(userWalletId = params.userWalletId)
expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = emptySet())
} }
} }

View file

@ -11,14 +11,13 @@ import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.data.tokens.utils.CustomTokensMerger import com.tangem.data.tokens.utils.CustomTokensMerger
import com.tangem.datasource.api.common.response.ApiResponse import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.ApiResponseError
import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE
import com.tangem.datasource.api.express.models.request.LeastTokenInfo
import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader
import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.demo.models.DemoConfig import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.express.ExpressServiceFetcher
import com.tangem.domain.express.models.ExpressAsset
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isMultiCurrency import com.tangem.domain.models.wallet.isMultiCurrency
@ -45,7 +44,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
private val userTokensResponseStore: UserTokensResponseStore = mockk(relaxUnitFun = true) private val userTokensResponseStore: UserTokensResponseStore = mockk(relaxUnitFun = true)
private val userTokensSaver: UserTokensSaver = mockk(relaxUnitFun = true) private val userTokensSaver: UserTokensSaver = mockk(relaxUnitFun = true)
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk() private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk()
private val expressServiceLoader: ExpressServiceLoader = mockk(relaxUnitFun = true) private val expressServiceFetcher: ExpressServiceFetcher = mockk(relaxUnitFun = true)
private val fetcher = DefaultMultiWalletCryptoCurrenciesFetcher( private val fetcher = DefaultMultiWalletCryptoCurrenciesFetcher(
demoConfig = DemoConfig(), demoConfig = DemoConfig(),
@ -55,7 +54,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
userTokensResponseStore = userTokensResponseStore, userTokensResponseStore = userTokensResponseStore,
userTokensSaver = userTokensSaver, userTokensSaver = userTokensSaver,
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
expressServiceLoader = expressServiceLoader, expressServiceFetcher = expressServiceFetcher,
dispatchers = TestingCoroutineDispatcherProvider(), dispatchers = TestingCoroutineDispatcherProvider(),
) )
@ -67,7 +66,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
userTokensResponseStore, userTokensResponseStore,
userTokensSaver, userTokensSaver,
cardCryptoCurrencyFactory, cardCryptoCurrencyFactory,
expressServiceLoader, expressServiceFetcher,
) )
} }
@ -131,6 +130,13 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse) customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse)
} returns userTokensResponse } returns userTokensResponse
coEvery {
expressServiceFetcher.fetch(
userWallet = mockUserWallet,
assetIds = userTokensResponse.toAssetId(),
)
} returns Unit.right()
// Act // Act
val actual = fetcher(params) val actual = fetcher(params)
@ -144,7 +150,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(mockUserWallet) cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(mockUserWallet)
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse) customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse)
userTokensSaver.store(userWalletId = params.userWalletId, response = userTokensResponse) userTokensSaver.store(userWalletId = params.userWalletId, response = userTokensResponse)
expressServiceLoader.update(userWallet = mockUserWallet, userTokens = userTokensResponse.toLeastTokens()) expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = userTokensResponse.toAssetId())
} }
} }
@ -170,6 +176,13 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = apiResponse.data) customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = apiResponse.data)
} returns apiResponse.data } returns apiResponse.data
coEvery {
expressServiceFetcher.fetch(
userWallet = mockUserWallet,
assetIds = defaultResponse.toAssetId(),
)
} returns Unit.right()
// Act // Act
val actual = fetcher(params) val actual = fetcher(params)
@ -183,7 +196,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
tangemTechApi.getUserTokens(userId = params.userWalletId.stringValue) tangemTechApi.getUserTokens(userId = params.userWalletId.stringValue)
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = apiResponse.data) customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = apiResponse.data)
userTokensSaver.store(userWalletId = params.userWalletId, response = apiResponse.data) userTokensSaver.store(userWalletId = params.userWalletId, response = apiResponse.data)
expressServiceLoader.update(userWallet = mockUserWallet, userTokens = defaultResponse.toLeastTokens()) expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = defaultResponse.toAssetId())
} }
coVerify(inverse = true) { coVerify(inverse = true) {
@ -212,6 +225,13 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = apiResponse.data) customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = apiResponse.data)
} returns apiResponse.data } returns apiResponse.data
coEvery {
expressServiceFetcher.fetch(
userWallet = mockUserWallet,
assetIds = apiResponse.data.toAssetId(),
)
} returns Unit.right()
// Act // Act
val actual = fetcher(params) val actual = fetcher(params)
@ -224,7 +244,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
tangemTechApi.getUserTokens(userId = params.userWalletId.stringValue) tangemTechApi.getUserTokens(userId = params.userWalletId.stringValue)
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = apiResponse.data) customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = apiResponse.data)
userTokensSaver.store(userWalletId = params.userWalletId, response = apiResponse.data) userTokensSaver.store(userWalletId = params.userWalletId, response = apiResponse.data)
expressServiceLoader.update(userWallet = mockUserWallet, userTokens = defaultResponse.toLeastTokens()) expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = defaultResponse.toAssetId())
} }
coVerify(inverse = true) { coVerify(inverse = true) {
@ -273,6 +293,13 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse) customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse)
} returns userTokensResponse } returns userTokensResponse
coEvery {
expressServiceFetcher.fetch(
userWallet = mockUserWallet,
assetIds = userTokensResponse.toAssetId(),
)
} returns Unit.right()
// Act // Act
val actual = fetcher(params) val actual = fetcher(params)
@ -286,7 +313,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId) userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId)
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse) customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse)
userTokensSaver.store(userWalletId = params.userWalletId, response = userTokensResponse) userTokensSaver.store(userWalletId = params.userWalletId, response = userTokensResponse)
expressServiceLoader.update(userWallet = mockUserWallet, userTokens = userTokensResponse.toLeastTokens()) expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = userTokensResponse.toAssetId())
} }
coVerify(inverse = true) { coVerify(inverse = true) {
@ -317,6 +344,13 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = defaultResponse) customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = defaultResponse)
} returns defaultResponse } returns defaultResponse
coEvery {
expressServiceFetcher.fetch(
userWallet = mockUserWallet,
assetIds = defaultResponse.toAssetId(),
)
} returns Unit.right()
// Act // Act
val actual = fetcher(params) val actual = fetcher(params)
@ -330,7 +364,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId) userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId)
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = defaultResponse) customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = defaultResponse)
userTokensSaver.store(userWalletId = params.userWalletId, response = defaultResponse) userTokensSaver.store(userWalletId = params.userWalletId, response = defaultResponse)
expressServiceLoader.update(userWallet = mockUserWallet, userTokens = defaultResponse.toLeastTokens()) expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = defaultResponse.toAssetId())
} }
coVerify(inverse = true) { coVerify(inverse = true) {
@ -383,6 +417,13 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse) customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse)
} returns userTokensResponse } returns userTokensResponse
coEvery {
expressServiceFetcher.fetch(
userWallet = mockUserWallet,
assetIds = userTokensResponse.toAssetId(),
)
} returns Unit.right()
// Act // Act
val actual = fetcher(params) val actual = fetcher(params)
@ -398,7 +439,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
userTokensSaver.push(userWalletId = params.userWalletId, response = userTokensResponse) userTokensSaver.push(userWalletId = params.userWalletId, response = userTokensResponse)
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse) customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse)
userTokensSaver.store(userWalletId = params.userWalletId, response = userTokensResponse) userTokensSaver.store(userWalletId = params.userWalletId, response = userTokensResponse)
expressServiceLoader.update(userWallet = mockUserWallet, userTokens = userTokensResponse.toLeastTokens()) expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = userTokensResponse.toAssetId())
} }
} }
@ -429,6 +470,13 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = defaultResponse) customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = defaultResponse)
} returns defaultResponse } returns defaultResponse
coEvery {
expressServiceFetcher.fetch(
userWallet = mockUserWallet,
assetIds = defaultResponse.toAssetId(),
)
} returns Unit.right()
// Act // Act
val actual = fetcher(params) val actual = fetcher(params)
@ -443,7 +491,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
userTokensSaver.push(userWalletId = params.userWalletId, response = defaultResponse) userTokensSaver.push(userWalletId = params.userWalletId, response = defaultResponse)
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = defaultResponse) customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = defaultResponse)
userTokensSaver.store(userWalletId = params.userWalletId, response = defaultResponse) userTokensSaver.store(userWalletId = params.userWalletId, response = defaultResponse)
expressServiceLoader.update(userWallet = mockUserWallet, userTokens = defaultResponse.toLeastTokens()) expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = defaultResponse.toAssetId())
} }
coVerify(inverse = true) { coVerify(inverse = true) {
@ -471,11 +519,11 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
), ),
) )
fun UserTokensResponse.toLeastTokens(): List<LeastTokenInfo> { fun UserTokensResponse.toAssetId(): Set<ExpressAsset.ID> {
return tokens.map { token -> return tokens.mapTo(hashSetOf()) { token ->
LeastTokenInfo( ExpressAsset.ID(
contractAddress = token.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE, networkId = token.networkId,
network = token.networkId, contractAddress = token.contractAddress,
) )
} }
} }

View file

@ -1,8 +1,10 @@
package com.tangem.data.pay.repository package com.tangem.data.pay.repository
import com.squareup.moshi.Moshi
import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.visa.utils.TangemPayTxHistoryItemConverter import com.tangem.data.visa.utils.TangemPayTxHistoryItemConverter
import com.tangem.datasource.api.pay.TangemPayApi import com.tangem.datasource.api.pay.TangemPayApi
import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.local.visa.TangemPayTxHistoryItemsStore import com.tangem.datasource.local.visa.TangemPayTxHistoryItemsStore
import com.tangem.domain.tangempay.model.TangemPayTxHistoryListBatchFlow import com.tangem.domain.tangempay.model.TangemPayTxHistoryListBatchFlow
import com.tangem.domain.tangempay.model.TangemPayTxHistoryListBatchingContext import com.tangem.domain.tangempay.model.TangemPayTxHistoryListBatchingContext
@ -26,8 +28,11 @@ internal class DefaultTangemPayTxHistoryRepository @Inject constructor(
private val cacheRegistry: CacheRegistry, private val cacheRegistry: CacheRegistry,
private val txHistoryItemsStore: TangemPayTxHistoryItemsStore, private val txHistoryItemsStore: TangemPayTxHistoryItemsStore,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
@NetworkMoshi private val moshi: Moshi,
) : TangemPayTxHistoryRepository { ) : TangemPayTxHistoryRepository {
private val txHistoryItemConverter by lazy { TangemPayTxHistoryItemConverter(moshi) }
override fun getTxHistoryBatchFlow( override fun getTxHistoryBatchFlow(
batchSize: Int, batchSize: Int,
context: TangemPayTxHistoryListBatchingContext, context: TangemPayTxHistoryListBatchingContext,
@ -84,7 +89,7 @@ internal class DefaultTangemPayTxHistoryRepository @Inject constructor(
val result = requestPerformer.request { authHeader -> val result = requestPerformer.request { authHeader ->
visaApi.getTangemPayTxHistory(authHeader = authHeader, limit = pageSize, cursor = cursor) visaApi.getTangemPayTxHistory(authHeader = authHeader, limit = pageSize, cursor = cursor)
}.result }.result
val items = TangemPayTxHistoryItemConverter.convertList(result.transactions).filterNotNull() val items = txHistoryItemConverter.convertList(result.transactions).filterNotNull()
txHistoryItemsStore.store(key = customerWalletAddress, cursor = cursor ?: INITIAL_CURSOR, value = items) txHistoryItemsStore.store(key = customerWalletAddress, cursor = cursor ?: INITIAL_CURSOR, value = items)
}.onLeft { error(it.toString()) } }.onLeft { error(it.toString()) }
} }

View file

@ -1,14 +1,19 @@
package com.tangem.data.visa.utils package com.tangem.data.visa.utils
import com.squareup.moshi.Moshi
import com.tangem.datasource.api.pay.models.response.TangemPayTxHistoryResponse import com.tangem.datasource.api.pay.models.response.TangemPayTxHistoryResponse
import com.tangem.domain.visa.model.TangemPayTxHistoryItem import com.tangem.domain.visa.model.TangemPayTxHistoryItem
import com.tangem.utils.converter.Converter import com.tangem.utils.converter.Converter
import timber.log.Timber import timber.log.Timber
import java.util.Currency import java.util.Currency
internal object TangemPayTxHistoryItemConverter : internal class TangemPayTxHistoryItemConverter(moshi: Moshi) :
Converter<TangemPayTxHistoryResponse.Transaction, TangemPayTxHistoryItem?> { Converter<TangemPayTxHistoryResponse.Transaction, TangemPayTxHistoryItem?> {
private val spendAdapter = moshi.adapter(TangemPayTxHistoryResponse.Spend::class.java)
private val paymentAdapter = moshi.adapter(TangemPayTxHistoryResponse.Payment::class.java)
private val feeAdapter = moshi.adapter(TangemPayTxHistoryResponse.Fee::class.java)
override fun convert(value: TangemPayTxHistoryResponse.Transaction): TangemPayTxHistoryItem? { override fun convert(value: TangemPayTxHistoryResponse.Transaction): TangemPayTxHistoryItem? {
return value.spend?.let { convertSpend(id = value.id, spend = it) } return value.spend?.let { convertSpend(id = value.id, spend = it) }
?: value.payment?.let { convertPayment(id = value.id, payment = it) } ?: value.payment?.let { convertPayment(id = value.id, payment = it) }
@ -22,6 +27,7 @@ internal object TangemPayTxHistoryItemConverter :
private fun convertSpend(id: String, spend: TangemPayTxHistoryResponse.Spend): TangemPayTxHistoryItem.Spend { private fun convertSpend(id: String, spend: TangemPayTxHistoryResponse.Spend): TangemPayTxHistoryItem.Spend {
return TangemPayTxHistoryItem.Spend( return TangemPayTxHistoryItem.Spend(
id = id, id = id,
jsonRepresentation = spendAdapter.toJson(spend),
// If postedAt is null, it means transaction wasn't posted and was likely declined. Use authorizedAt // If postedAt is null, it means transaction wasn't posted and was likely declined. Use authorizedAt
date = spend.postedAt ?: spend.authorizedAt, date = spend.postedAt ?: spend.authorizedAt,
amount = spend.amount, amount = spend.amount,
@ -41,15 +47,18 @@ internal object TangemPayTxHistoryItemConverter :
): TangemPayTxHistoryItem.Payment { ): TangemPayTxHistoryItem.Payment {
return TangemPayTxHistoryItem.Payment( return TangemPayTxHistoryItem.Payment(
id = id, id = id,
jsonRepresentation = paymentAdapter.toJson(payment),
date = payment.postedAt, date = payment.postedAt,
currency = Currency.getInstance(payment.currency), currency = Currency.getInstance(payment.currency),
amount = payment.amount, amount = payment.amount,
transactionHash = payment.transactionHash,
) )
} }
private fun convertFee(id: String, fee: TangemPayTxHistoryResponse.Fee): TangemPayTxHistoryItem.Fee { private fun convertFee(id: String, fee: TangemPayTxHistoryResponse.Fee): TangemPayTxHistoryItem.Fee {
return TangemPayTxHistoryItem.Fee( return TangemPayTxHistoryItem.Fee(
id = id, id = id,
jsonRepresentation = feeAdapter.toJson(fee),
date = fee.postedAt, date = fee.postedAt,
currency = Currency.getInstance(fee.currency), currency = Currency.getInstance(fee.currency),
amount = fee.amount, amount = fee.amount,

View file

@ -0,0 +1,16 @@
package com.tangem.domain.account.producer
import com.tangem.domain.core.flow.FlowProducer
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
/**
* Produces a flow of [Account.CryptoPortfolio] for a single account identified by [Params.accountId].
* The flow emits updates whenever the account's portfolio changes.
*/
interface SingleAccountProducer : FlowProducer<Account.CryptoPortfolio> {
data class Params(val accountId: AccountId)
interface Factory : FlowProducer.Factory<Params, SingleAccountProducer>
}

View file

@ -0,0 +1,17 @@
package com.tangem.domain.account.supplier
import com.tangem.domain.account.producer.SingleAccountProducer
import com.tangem.domain.core.flow.FlowCachingSupplier
import com.tangem.domain.models.account.Account
/**
* Supplies instances of [SingleAccountProducer] that produce flows of [Account.CryptoPortfolio]
* for individual accounts. Each producer is uniquely identified by its [SingleAccountProducer.Params].
*
* @property factory A factory to create instances of [SingleAccountProducer].
* @property keyCreator A function that generates a unique key for caching based on [SingleAccountProducer.Params].
*/
abstract class SingleAccountSupplier(
override val factory: SingleAccountProducer.Factory,
override val keyCreator: (SingleAccountProducer.Params) -> String,
) : FlowCachingSupplier<SingleAccountProducer, SingleAccountProducer.Params, Account.CryptoPortfolio>()

View file

@ -19,6 +19,7 @@ dependencies {
api(projects.domain.account) api(projects.domain.account)
api(projects.domain.core) api(projects.domain.core)
api(projects.domain.common) api(projects.domain.common)
api(projects.domain.express)
api(projects.domain.quotes) api(projects.domain.quotes)
api(projects.domain.models) api(projects.domain.models)
api(projects.domain.networks) api(projects.domain.networks)

View file

@ -4,9 +4,11 @@ import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.account.status.usecase.GetAccountCurrencyByAddressUseCase import com.tangem.domain.account.status.usecase.GetAccountCurrencyByAddressUseCase
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
import com.tangem.domain.account.status.usecase.SaveCryptoCurrenciesUseCase import com.tangem.domain.account.status.usecase.GetCryptoCurrencyActionsUseCaseV2
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
import com.tangem.domain.account.supplier.SingleAccountListSupplier import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.express.ExpressServiceFetcher
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
import com.tangem.domain.networks.utils.NetworksCleaner import com.tangem.domain.networks.utils.NetworksCleaner
@ -14,6 +16,7 @@ import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.staking.StakingIdFactory import com.tangem.domain.staking.StakingIdFactory
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.utils.StakingCleaner import com.tangem.domain.staking.utils.StakingCleaner
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.derivations.DerivationsRepository import com.tangem.domain.wallets.derivations.DerivationsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -21,6 +24,8 @@ import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module
@ -41,6 +46,20 @@ internal object AccountStatusUseCaseModule {
) )
} }
@Provides
@Singleton
fun provideGetCryptoCurrencyActionsUseCaseV2(
accountsCRUDRepository: AccountsCRUDRepository,
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase,
): GetCryptoCurrencyActionsUseCaseV2 {
return GetCryptoCurrencyActionsUseCaseV2(
accountsCRUDRepository = accountsCRUDRepository,
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
getCryptoCurrencyActionsUseCase = getCryptoCurrencyActionsUseCase,
)
}
@Provides @Provides
@Singleton @Singleton
fun provideGetAccountCurrencyStatusUseCase( fun provideGetAccountCurrencyStatusUseCase(
@ -62,9 +81,10 @@ internal object AccountStatusUseCaseModule {
stakingIdFactory: StakingIdFactory, stakingIdFactory: StakingIdFactory,
networksCleaner: NetworksCleaner, networksCleaner: NetworksCleaner,
stakingCleaner: StakingCleaner, stakingCleaner: StakingCleaner,
expressServiceFetcher: ExpressServiceFetcher,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): SaveCryptoCurrenciesUseCase { ): ManageCryptoCurrenciesUseCase {
return SaveCryptoCurrenciesUseCase( return ManageCryptoCurrenciesUseCase(
singleAccountListSupplier = singleAccountListSupplier, singleAccountListSupplier = singleAccountListSupplier,
accountsCRUDRepository = accountsCRUDRepository, accountsCRUDRepository = accountsCRUDRepository,
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
@ -75,6 +95,8 @@ internal object AccountStatusUseCaseModule {
stakingIdFactory = stakingIdFactory, stakingIdFactory = stakingIdFactory,
networksCleaner = networksCleaner, networksCleaner = networksCleaner,
stakingCleaner = stakingCleaner, stakingCleaner = stakingCleaner,
expressServiceFetcher = expressServiceFetcher,
parallelUpdatingScope = CoroutineScope(SupervisorJob() + dispatchers.default),
dispatchers = dispatchers, dispatchers = dispatchers,
) )
} }

Some files were not shown because too many files have changed in this diff Show more