Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-27 12:15:14 +05:00
commit ac621b6bad
466 changed files with 12729 additions and 3984 deletions

View file

@ -113,12 +113,14 @@ dependencies {
implementation(projects.domain.legacy)
implementation(projects.libs.blockchainSdk)
implementation(projects.domain.account)
implementation(projects.domain.account.status)
implementation(projects.domain.models)
implementation(projects.domain.core)
api(projects.domain.common)
implementation(projects.domain.card)
implementation(projects.domain.demo)
implementation(projects.domain.demo.models)
implementation(projects.domain.express)
implementation(projects.domain.wallets)
implementation(projects.domain.wallets.models)
implementation(projects.domain.settings)

View file

@ -5,7 +5,10 @@ object TestConstants {
const val RECIPIENT_ADDRESS = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0"
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_LONG = 30_000L

View file

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

View file

@ -116,3 +116,27 @@ fun BaseTestCase.checkMultiCurrencyMainScreen(
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.title
import com.tangem.screens.ScanWarningDialogPageObject
import com.tangem.screens.onActionIsUnavailableDialog
import com.tangem.screens.onFailedTransactionDialog
import io.qameta.allure.kotlin.Allure.step
@ -64,3 +65,15 @@ fun checkAlreadyUsedWalletDialog() {
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,41 @@
package com.tangem.scenarios
import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.screens.onReceiveAssetsBottomSheet
import com.tangem.screens.onTokenReceiveQrCodeBottomSheet
import com.tangem.screens.onTokenReceiveWarningBottomSheet
import io.qameta.allure.kotlin.Allure.step
fun BaseTestCase.goToQrCodeBottomSheet() {
step("Assert 'Token receive warning' bottom sheet is displayed") {
onTokenReceiveWarningBottomSheet { bottomSheet.assertIsDisplayed() }
}
step("Click on 'Got it' button") {
onTokenReceiveWarningBottomSheet { gotItButton.performClick() }
}
step("Click on 'Show QR code' button") {
onReceiveAssetsBottomSheet { showQrCodeButton.clickWithAssertion() }
}
}
fun BaseTestCase.checkQrCodeBottomSheetScenario() {
step("Assert bottom sheet with QR code title is displayed") {
onTokenReceiveQrCodeBottomSheet { title.assertIsDisplayed() }
}
step("Assert QR code is displayed") {
onTokenReceiveQrCodeBottomSheet { qrCode.assertIsDisplayed() }
}
step("Assert address title is displayed") {
onTokenReceiveQrCodeBottomSheet { addressTitle.assertIsDisplayed() }
}
step("Assert address is displayed") {
onTokenReceiveQrCodeBottomSheet { address.assertIsDisplayed() }
}
step("Assert 'Copy' button is displayed") {
onTokenReceiveQrCodeBottomSheet { copyButton.assertIsDisplayed() }
}
step("Assert 'Share' button is displayed") {
onTokenReceiveQrCodeBottomSheet { shareButton.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)
}
val title: KNode = child {
hasTestTag(BaseDialogTestTags.TITLE)
}
val text: KNode = child {
hasTestTag(BaseDialogTestTags.TEXT)
}
val cancelButton: KNode = child {
hasTestTag(BaseButtonTestTags.BUTTON)
hasText(getResourceString(R.string.common_cancel))

View file

@ -0,0 +1,30 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.R
import com.tangem.core.ui.test.BaseButtonTestTags
import com.tangem.core.ui.test.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 OperationIsUnavailableDialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<OperationIsUnavailableDialogPageObject>(semanticsProvider = semanticsProvider) {
val text: KNode = child {
hasTestTag(BaseDialogTestTags.TEXT)
hasText(getResourceString(CommonUIR.string.token_button_unavailability_generic_description))
useUnmergedTree = true
}
val okButton: KNode = child {
hasTestTag(BaseButtonTestTags.BUTTON)
hasText(getResourceString(R.string.common_ok))
}
}
internal fun BaseTestCase.onOperationIsUnavailableDialog(function: OperationIsUnavailableDialogPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

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

View file

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

View file

@ -0,0 +1,35 @@
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 SwapIsNotSupportedDialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<SwapIsNotSupportedDialogPageObject>(semanticsProvider = semanticsProvider) {
fun text(currencyName: String): KNode = child {
hasTestTag(BaseDialogTestTags.TEXT)
hasText(
getResourceString(
CommonUIR.string.token_button_unavailability_reason_not_exchangeable,
currencyName
)
)
useUnmergedTree = true
}
val okButton: KNode = child {
hasTestTag(BaseButtonTestTags.BUTTON)
hasText(getResourceString(R.string.common_ok))
}
}
internal fun BaseTestCase.onSwapIsNotSupportedDialog(function: SwapIsNotSupportedDialogPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -97,25 +97,31 @@ class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvide
)
@OptIn(ExperimentalTestApi::class)
val swapButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
fun receiveButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_receive))
}
@OptIn(ExperimentalTestApi::class)
fun swapButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_swap))
}
@OptIn(ExperimentalTestApi::class)
val sellButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
fun sellButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_sell))
}
@OptIn(ExperimentalTestApi::class)
val buyButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
fun buyButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_buy))
}
@OptIn(ExperimentalTestApi::class)
val sendButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
fun sendButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
hasText(getResourceString(R.string.common_send))
}

View file

@ -4,11 +4,16 @@ import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.CARDANO_ADDRESS
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.extensions.pullToRefresh
import com.tangem.common.ui.R
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.*
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 io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
@ -29,6 +34,9 @@ class BlockchainTest : BaseTestCase() {
val scenarioName = "user_tokens_api"
val scenarioState = "Cardano"
val invalidAmountTitleResId = R.string.send_notification_invalid_amount_title
val invalidAmountMessageResId = R.string.send_notification_invalid_minimum_amount_text
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(scenarioName)
@ -47,7 +55,7 @@ class BlockchainTest : BaseTestCase() {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
}
step("Click on 'Send' button") {
onTokenDetailsScreen { sendButton.performClick() }
onTokenDetailsScreen { sendButton().performClick() }
}
step("Type '$errorSendAmount' in input text field") {
onSendScreen {
@ -64,14 +72,12 @@ class BlockchainTest : BaseTestCase() {
step("Click on 'Next' button") {
onSendAddressScreen { nextButton.clickWithAssertion() }
}
step("Assert 'Invalid amount' error title is displayed") {
onSendConfirmScreen { minimumSendAmountErrorTitle.assertIsDisplayed() }
}
step("Assert 'Invalid amount' error icon is displayed") {
onSendConfirmScreen { minimumSendAmountErrorIcon(minAmount).assertIsDisplayed() }
}
step("Assert 'Invalid amount' error message is displayed") {
onSendConfirmScreen { minimumSendAmountErrorMessage(minAmount).assertIsDisplayed() }
step("Assert 'Invalid amount warning' is displayed") {
checkSendWarning(
titleResId = invalidAmountTitleResId,
messageResId = invalidAmountMessageResId,
amount = minAmount
)
}
step("Press system 'Back' button") {
device.uiDevice.pressBack()
@ -97,14 +103,13 @@ class BlockchainTest : BaseTestCase() {
step("Click on 'Next' button") {
onSendAddressScreen { nextButton.clickWithAssertion() }
}
step("Assert 'Invalid amount' error title is not displayed") {
onSendConfirmScreen { minimumSendAmountErrorTitle.assertIsNotDisplayed() }
}
step("Assert 'Invalid amount' error icon is not displayed") {
onSendConfirmScreen { minimumSendAmountErrorIcon(minAmount).assertIsNotDisplayed() }
}
step("Assert 'Invalid amount' error message is not displayed") {
onSendConfirmScreen { minimumSendAmountErrorMessage(minAmount).assertIsNotDisplayed() }
step("Assert 'Invalid amount warning' is not displayed") {
checkSendWarning(
titleResId = invalidAmountTitleResId,
messageResId = invalidAmountMessageResId,
amount = minAmount,
isDisplayed = false
)
}
}
}
@ -136,10 +141,16 @@ class BlockchainTest : BaseTestCase() {
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
}
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'") {
setWireMockScenarioState(scenarioName = rippleAccountLinesScenarioName, state = rippleAccountLinesErrorState)
setWireMockScenarioState(
scenarioName = rippleAccountLinesScenarioName,
state = rippleAccountLinesErrorState
)
}
step("Open 'Main Screen'") {
openMainScreen()
@ -169,10 +180,16 @@ class BlockchainTest : BaseTestCase() {
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
}
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'") {
setWireMockScenarioState(scenarioName = rippleAccountLinesScenarioName, state = rippleAccountLinesStartedState)
setWireMockScenarioState(
scenarioName = rippleAccountLinesScenarioName,
state = rippleAccountLinesStartedState
)
}
step("Pull to refresh") {
pullToRefresh()

View file

@ -73,7 +73,7 @@ class FeedbackTest : BaseTestCase() {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
}
step("Click 'Send' button") {
onTokenDetailsScreen { sendButton.performClick() }
onTokenDetailsScreen { sendButton().performClick() }
}
step("Type '$sendAmount' in input text field") {
onSendScreen {

View file

@ -47,7 +47,7 @@ class SwapTokenTest : BaseTestCase() {
onTokenDetailsScreen { title.assertIsDisplayed() }
}
step("Click on 'Swap' button") {
onTokenDetailsScreen { swapButton.performClick() }
onTokenDetailsScreen { swapButton().performClick() }
}
step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() }
@ -131,7 +131,7 @@ class SwapTokenTest : BaseTestCase() {
disableMobileData()
}
step("Click on 'Swap' button") {
onTokenDetailsScreen { swapButton.performClick() }
onTokenDetailsScreen { swapButton().performClick() }
}
step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() }
@ -175,7 +175,7 @@ class SwapTokenTest : BaseTestCase() {
onTokenDetailsScreen { title.assertIsDisplayed() }
}
step("Click on 'Swap' button") {
onTokenDetailsScreen { swapButton.performClick() }
onTokenDetailsScreen { swapButton().performClick() }
}
step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() }

View file

@ -5,11 +5,12 @@ import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.BITCOIN_ADDRESS
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.utils.assertClipboardTextEquals
import com.tangem.common.utils.clearClipboard
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.synchronizeAddresses
import com.tangem.common.extensions.*
import com.tangem.common.utils.*
import com.tangem.scenarios.*
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 io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
@ -281,37 +282,13 @@ class MainScreenActionButtonsTest : BaseTestCase() {
step("Click on 'Receive' button") {
onTokenActionsBottomSheet { receiveButton.performClick() }
}
step("Assert 'Token receive warning' bottom sheet is displayed") {
waitForIdle()
step("Go to QR code bottom sheet") {
flakySafely(WAIT_UNTIL_TIMEOUT) {
onTokenReceiveWarningBottomSheet {
bottomSheet.assertIsDisplayed()
goToQrCodeBottomSheet()
}
}
}
step("Click on 'Got it' button") {
onTokenReceiveWarningBottomSheet { gotItButton.performClick() }
}
step("Click on 'Show QR code' button") {
onReceiveAssetsBottomSheet { showQrCodeButton.clickWithAssertion() }
}
step("Assert bottom sheet with QR code title is displayed") {
onTokenReceiveQrCodeBottomSheet { title.assertIsDisplayed() }
}
step("Assert QR code is displayed") {
onTokenReceiveQrCodeBottomSheet { qrCode.assertIsDisplayed() }
}
step("Assert address title is displayed") {
onTokenReceiveQrCodeBottomSheet { addressTitle.assertIsDisplayed() }
}
step("Assert address is displayed") {
onTokenReceiveQrCodeBottomSheet { address.assertIsDisplayed() }
}
step("Assert 'Copy' button is displayed") {
onTokenReceiveQrCodeBottomSheet { copyButton.assertIsDisplayed() }
}
step("Assert 'Share' button is displayed") {
onTokenReceiveQrCodeBottomSheet { shareButton.assertIsDisplayed() }
step("Check QR code bottom sheet") {
checkQrCodeBottomSheetScenario()
}
}
}
@ -342,10 +319,10 @@ class MainScreenActionButtonsTest : BaseTestCase() {
}
}
}
step("Assert 'Receive' button is displayed") {
step("Assert 'Sell' button is displayed") {
onTokenActionsBottomSheet { sellButton.assertIsDisplayed() }
}
step("Click on 'Receive' button") {
step("Click on 'Sell' button") {
onTokenActionsBottomSheet { sellButton.performClick() }
}
step("Assert Chrome Browser is opened") {
@ -391,4 +368,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,255 @@
package com.tangem.tests.actionButtons
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
import com.tangem.common.extensions.SwipeDirection
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.extensions.swipeVertical
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.scenarios.checkQrCodeBottomSheetScenario
import com.tangem.scenarios.goToQrCodeBottomSheet
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.synchronizeAddresses
import com.tangem.screens.*
import com.tangem.screens.onMainScreen
import com.tangem.screens.onSwapStoriesScreen
import com.tangem.screens.onSwapTokenScreen
import com.tangem.screens.onTokenDetailsScreen
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 TokenDetailsScreenActionButtonsTest : BaseTestCase() {
@AllureId("594")
@DisplayName("Action buttons (token details screen): validate UI")
@Test
fun actionButtonsValidateUiTest() {
val tokenTitle = "Bitcoin"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
}
step("Assert 'Receive' button is displayed") {
onTokenDetailsScreen { receiveButton().assertIsDisplayed() }
}
step("Assert 'Buy' button is displayed") {
onTokenDetailsScreen { buyButton().assertIsDisplayed() }
}
step("Assert 'Send' button is displayed") {
onTokenDetailsScreen { sendButton().assertIsDisplayed() }
}
step("Assert 'Swap' button is displayed") {
onTokenDetailsScreen { swapButton().assertIsDisplayed() }
}
step("Assert 'Sell' button is displayed") {
onTokenDetailsScreen { sellButton().assertIsDisplayed() }
}
}
}
@AllureId("593")
@DisplayName("Action buttons (token details screen): check buttons state")
@Test
fun checkActionButtonsStateTest() {
val tokenTitle = "Bitcoin"
val actionButtonIsNotDimmed = "Action button is not dimmed"
val actionButtonIsDimmed = "Action button is dimmed"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
}
step("Assert 'Receive' button is not dimmed") {
onTokenDetailsScreen { receiveButton().assertContentDescriptionEquals(actionButtonIsNotDimmed) }
}
step("Assert 'Buy' button is not dimmed") {
onTokenDetailsScreen { buyButton().assertContentDescriptionEquals(actionButtonIsNotDimmed) }
}
step("Assert 'Send' button is dimmed") {
onTokenDetailsScreen { sendButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
}
step("Assert 'Swap' button is dimmed") {
onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
}
step("Assert 'Sell' button is dimmed") {
onTokenDetailsScreen { sellButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
}
}
}
@AllureId("4459")
@DisplayName("Action buttons (token details screen): check 'Swap' button (success)")
@Test
fun checkSwapButtonSuccessTest() {
val tokenTitle = "Ethereum"
val tokenSymbol = "ETH"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
}
step("Click on 'Swap' button") {
onTokenDetailsScreen { swapButton().performClick() }
}
step("Close 'Stories' screen") {
onSwapStoriesScreen { closeButton.clickWithAssertion() }
}
step("Assert 'Swap' screen title is displayed") {
onSwapTokenScreen { title.assertIsDisplayed() }
}
step("Assert token symbol: '$tokenSymbol' is displayed") {
onSwapTokenScreen { tokenSymbol(tokenSymbol).assertIsDisplayed() }
}
}
}
@AllureId("4460")
@DisplayName("Action buttons (token details screen): check 'Swap' button (provider error)")
@Test
fun checkSwapButtonProviderErrorTest() {
val tokenTitle = "POL (ex-MATIC)"
val actionButtonIsDimmed = "Action button is dimmed"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Swipe up") {
swipeVertical(SwipeDirection.UP)
}
step("Click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
}
step("Assert 'Swap' button is dimmed") {
onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
}
step("Click on 'Swap' button") {
onTokenDetailsScreen { swapButton().performClick() }
}
step("Assert swapping $tokenTitle is not supported dialog text is displayed") {
onSwapIsNotSupportedDialog { text(tokenTitle).assertIsDisplayed() }
}
step("Assert 'Ok' button is displayed") {
onSwapIsNotSupportedDialog { okButton.assertIsDisplayed() }
}
step("Click on 'Ok' button") {
onSwapIsNotSupportedDialog { okButton.performClick() }
}
step("Assert 'Swap' button is dimmed") {
onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
}
}
}
@AllureId("4461")
@DisplayName("Action buttons (token details screen): check 'Swap' button (Express error)")
@Test
fun checkSwapButtonExpressErrorTest() {
val tokenTitle = "Polygon"
val actionButtonIsDimmed = "Action button is dimmed"
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("Synchronize addresses") {
synchronizeAddresses()
}
step("Swipe up") {
swipeVertical(SwipeDirection.UP)
}
step("Click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
}
step("Assert 'Swap' button is dimmed") {
onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
}
step("Click on 'Swap' button") {
onTokenDetailsScreen { swapButton().performClick() }
}
step("Assert operation is unavailable dialog text is displayed") {
onOperationIsUnavailableDialog { text.assertIsDisplayed() }
}
step("Assert 'Ok' button is displayed") {
onOperationIsUnavailableDialog { okButton.assertIsDisplayed() }
}
step("Click on 'Ok' button") {
onOperationIsUnavailableDialog { okButton.performClick() }
}
step("Assert 'Swap' button is dimmed") {
onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
}
}
}
@AllureId("3590")
@DisplayName("Action buttons (token details screen): validate UI")
@Test
fun checkReceiveButtonTest() {
val tokenTitle = "Bitcoin"
setupHooks().run {
step("Open 'Main Screen'") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Click on token with name: '$tokenTitle'") {
waitForIdle()
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
}
step("Assert 'Receive' button is displayed") {
onTokenDetailsScreen { receiveButton().performClick() }
}
step("Go to QR code bottom sheet") {
flakySafely(WAIT_UNTIL_TIMEOUT) {
goToQrCodeBottomSheet()
}
}
step("Check QR code bottom sheet") {
checkQrCodeBottomSheetScenario()
}
}
}
}

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.utils.AnalyticsContextProxy
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.addHotWalletContext
import com.tangem.tap.common.extensions.eraseContext
import com.tangem.tap.common.extensions.removeContext
import com.tangem.tap.common.extensions.setContext
import com.tangem.tap.common.extensions.setHotWalletContext
/**
[REDACTED_AUTHOR]
@ -17,6 +20,14 @@ internal class DefaultAnalyticsContextProxy : AnalyticsContextProxy {
Analytics.setContext(scanResponse)
}
override fun addContext(userWallet: UserWallet) {
Analytics.addContext(userWallet)
}
override fun setHotWalletContext() {
Analytics.setHotWalletContext()
}
override fun eraseContext() {
Analytics.eraseContext()
}
@ -25,6 +36,10 @@ internal class DefaultAnalyticsContextProxy : AnalyticsContextProxy {
Analytics.addContext(scanResponse)
}
override fun addHotWalletContext() {
Analytics.addHotWalletContext()
}
override fun removeContext() {
Analytics.removeContext()
}

View file

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

View file

@ -1,6 +1,8 @@
package com.tangem.tap.common.analytics.handlers
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.ExceptionHandlerOutput
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.core.analytics.api.AnalyticsErrorHandler
import com.tangem.tap.common.analytics.events.BlockchainApiExceptionEvent
import javax.inject.Inject
@ -8,12 +10,13 @@ import javax.inject.Inject
class BlockchainExceptionHandler @Inject constructor(
private val analyticsErrorHandler: AnalyticsErrorHandler,
) : ExceptionHandlerOutput {
override fun handleApiSwitch(currentHost: String, nextHost: String, message: String) {
override fun handleApiSwitch(currentHost: String, nextHost: String, message: String, blockchain: Blockchain) {
analyticsErrorHandler.sendErrorEvent(
BlockchainApiExceptionEvent(
selectedHost = nextHost,
exceptionHost = currentHost,
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(
scanResponse: ScanResponse,
val parent: LinkedCardContextInterceptor? = null,
val parent: ParamsInterceptor? = null,
) : ParamsInterceptor {
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.wallet.UserWallet
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.tap.common.analytics.paramsInterceptor.HotWalletContextInterceptor
import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterceptor
/**
@ -24,11 +25,21 @@ fun Analytics.setContext(scanResponse: ScanResponse) {
fun Analytics.setContext(userWallet: UserWallet) {
setUserId(userWallet.walletId.stringValue)
// TODO add product type for hot ([REDACTED_TASK_KEY] [Hot Wallet] Analytics)
if (userWallet is UserWallet.Cold) {
when (userWallet) {
is UserWallet.Cold -> {
removeParamsInterceptor(HotWalletContextInterceptor.id())
addParamsInterceptor(LinkedCardContextInterceptor(userWallet.scanResponse))
}
is UserWallet.Hot -> {
removeParamsInterceptor(LinkedCardContextInterceptor.id())
addParamsInterceptor(HotWalletContextInterceptor())
}
}
}
fun Analytics.setHotWalletContext() {
addParamsInterceptor(HotWalletContextInterceptor())
}
/**
@ -37,6 +48,23 @@ fun Analytics.setContext(userWallet: UserWallet) {
fun Analytics.eraseContext() {
clearUserId()
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)
}
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id())
?: removeParamsInterceptor(HotWalletContextInterceptor.id())
val newContext = LinkedCardContextInterceptor(scanResponse, parent = currentContext)
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.
*/
fun Analytics.removeContext() {
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor
val previousContext = currentContext?.parent ?: return
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id())
?: removeParamsInterceptor(HotWalletContextInterceptor.id())
val previousContext = when (currentContext) {
is LinkedCardContextInterceptor -> currentContext.parent
is HotWalletContextInterceptor -> currentContext.parent
else -> null
} ?: return
addParamsInterceptor(previousContext)
}

View file

@ -1,23 +0,0 @@
package com.tangem.tap.common.extensions
import java.math.BigDecimal
import java.math.RoundingMode
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.util.Locale
// TODO: move extensions to utils
fun BigDecimal.toFormattedString(
decimals: Int,
roundingMode: RoundingMode = RoundingMode.DOWN,
locale: Locale = Locale.US,
): String {
val symbols = DecimalFormatSymbols(locale)
val df = DecimalFormat()
df.decimalFormatSymbols = symbols
df.maximumFractionDigits = decimals
df.minimumFractionDigits = 0
df.isGroupingUsed = true
df.roundingMode = roundingMode
return df.format(this)
}

View file

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

View file

@ -3,6 +3,8 @@ package com.tangem.tap.di.domain
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.domain.account.status.usecase.RecoverCryptoPortfolioUseCase
import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher
import com.tangem.domain.account.tokens.MainAccountTokensMigration
import com.tangem.domain.account.usecase.*
import dagger.Module
@ -50,10 +52,12 @@ internal object AccountDomainModule {
fun provideRecoverCryptoPortfolioUseCase(
accountsCRUDRepository: AccountsCRUDRepository,
mainAccountTokensMigration: MainAccountTokensMigration,
cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
): RecoverCryptoPortfolioUseCase {
return RecoverCryptoPortfolioUseCase(
crudRepository = accountsCRUDRepository,
mainAccountTokensMigration = mainAccountTokensMigration,
cryptoCurrencyBalanceFetcher = cryptoCurrencyBalanceFetcher,
)
}

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
@Singleton
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.operations.BaseCurrencyStatusOperations
import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
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.repository.*
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
import com.tangem.domain.walletmanager.WalletManagersFacade
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
@Singleton
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
@Singleton
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),
),
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),
),
DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
@ -185,7 +185,7 @@ object BackupWalletMockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),
@ -244,7 +244,7 @@ object BackupWalletMockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),

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),
),
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),
),
DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
@ -185,7 +185,7 @@ object DevWalletMockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),
@ -244,7 +244,7 @@ object DevWalletMockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),

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),
),
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),
),
DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
@ -181,7 +181,7 @@ object Firmware412MockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),
@ -240,7 +240,7 @@ object Firmware412MockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),

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),
),
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),
),
DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
@ -185,7 +185,7 @@ object RingMockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),
@ -244,7 +244,7 @@ object RingMockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),

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),
),
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),
),
DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
@ -185,7 +185,7 @@ object ShibaMockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),
@ -244,7 +244,7 @@ object ShibaMockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),

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),
),
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),
),
DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
@ -185,7 +185,7 @@ object ShibaNoBackupMockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),
@ -244,7 +244,7 @@ object ShibaNoBackupMockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),

View file

@ -138,7 +138,7 @@ object ShibaNoBackupNoWalletsMockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),
@ -197,7 +197,7 @@ object ShibaNoBackupNoWalletsMockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),

View file

@ -229,7 +229,7 @@ object Wallet2MockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),
@ -281,7 +281,7 @@ object Wallet2MockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),

View file

@ -229,7 +229,7 @@ object Wallet2NoBackupMockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),
@ -281,7 +281,7 @@ object Wallet2NoBackupMockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),

View file

@ -141,7 +141,7 @@ object Wallet2NoBackupNoWalletsMockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),
@ -193,7 +193,7 @@ object Wallet2NoBackupNoWalletsMockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),

View file

@ -229,7 +229,7 @@ object Wallet2WithSeedPhraseMockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),
@ -281,7 +281,7 @@ object Wallet2WithSeedPhraseMockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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),

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),
),
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),
),
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),
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(
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'/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(
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,
),
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),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
@ -235,6 +243,13 @@ object WalletMockContent : MockContent {
parentFingerprint = byteArrayOf(0, 0, 0, 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(
@ -244,14 +259,21 @@ object WalletMockContent : MockContent {
ExtendedPublicKeysMap(
mapOf(
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),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
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),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
@ -290,14 +312,14 @@ object WalletMockContent : MockContent {
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
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'/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),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
@ -312,19 +334,26 @@ object WalletMockContent : MockContent {
ExtendedPublicKeysMap(
mapOf(
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),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
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, -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

@ -17,7 +17,6 @@ import com.tangem.tap.domain.tasks.UserWalletIdPreflightReadFilter
*
[REDACTED_AUTHOR]
*/
// TODO remove it after test after resolve [REDACTED_JIRA]
internal class ResetBackupCardTask(
private val userWalletId: UserWalletId,
) : CardSessionRunnable<Boolean> {

View file

@ -2,44 +2,14 @@ package com.tangem.tap.network.exchangeServices
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.data.common.currency.CryptoCurrencyFactory
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.utils.converter.TwoWayConverter
import com.tangem.utils.converter.Converter
internal class CryptoCurrencyConverter(
private val excludedBlockchains: ExcludedBlockchains,
) : TwoWayConverter<Currency, CryptoCurrency> {
internal object CryptoCurrencyConverter : Converter<CryptoCurrency, Currency> {
private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory(excludedBlockchains) }
override fun convert(value: Currency): CryptoCurrency {
return when (value) {
is Currency.Blockchain -> requireNotNull(
cryptoCurrencyFactory.createCoin(
blockchain = value.blockchain,
extraDerivationPath = value.derivationPath,
userWallet = getSelectedWallet(),
),
)
is Currency.Token -> requireNotNull(
cryptoCurrencyFactory.createToken(
sdkToken = value.token,
blockchain = value.blockchain,
extraDerivationPath = value.derivationPath,
userWallet = getSelectedWallet(),
),
)
}
}
override fun convertBack(value: CryptoCurrency): Currency {
override fun convert(value: CryptoCurrency): Currency {
val blockchain = value.network.toBlockchain()
if (blockchain == Blockchain.Unknown) error("CryptoCurrencyConverter convertBack Unknown blockchain")
return when (value) {
@ -60,15 +30,4 @@ internal class CryptoCurrencyConverter(
)
}
}
fun getSelectedWallet(): UserWallet {
val userWalletListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
val userWalletsListRepository = store.inject(DaggerGraphState::userWalletsListRepository)
val hotWalletFeatureToggles = store.inject(DaggerGraphState::hotWalletFeatureToggles)
return if (hotWalletFeatureToggles.isHotWalletEnabled) {
requireNotNull(userWalletsListRepository.selectedUserWallet.value)
} else {
requireNotNull(userWalletListManager.selectedUserWalletSync)
}
}
}

View file

@ -5,13 +5,11 @@ import arrow.core.raise.catch
import arrow.core.raise.either
import arrow.core.raise.ensure
import arrow.core.right
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.exchange.ExpressAvailabilityState
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.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
@ -26,17 +24,13 @@ import com.tangem.utils.isNullOrZero
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
@Suppress("LongParameterList")
internal class DefaultRampManager(
private val sellService: Provider<ExchangeService>,
private val expressServiceLoader: ExpressServiceLoader,
private val expressServiceFetcher: ExpressServiceFetcher,
private val currenciesRepository: CurrenciesRepository,
private val dispatchers: CoroutineDispatcherProvider,
excludedBlockchains: ExcludedBlockchains,
) : RampStateManager {
private val cryptoCurrencyConverter = CryptoCurrencyConverter(excludedBlockchains)
override suspend fun availableForBuy(
userWallet: UserWallet,
cryptoCurrency: CryptoCurrency,
@ -56,7 +50,7 @@ internal class DefaultRampManager(
return either {
val isSellSupportedByService = catch(
block = {
val serviceCurrency = cryptoCurrencyConverter.convertBack(status.currency)
val serviceCurrency = CryptoCurrencyConverter.convert(status.currency)
sellService().availableForSell(currency = serviceCurrency)
},
@ -107,7 +101,7 @@ internal class DefaultRampManager(
}
override fun getExpressInitializationStatus(userWalletId: UserWalletId): Flow<ExchangeServiceInitializationStatus> {
return expressServiceLoader.getInitializationStatus(userWalletId)
return expressServiceFetcher.getInitializationStatus(userWalletId)
}
override suspend fun getSendUnavailabilityReason(
@ -151,14 +145,14 @@ internal class DefaultRampManager(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): ExpressAvailabilityState {
val asset = expressServiceLoader.getInitializationStatus(userWalletId).firstOrNull()
val asset = expressServiceFetcher.getInitializationStatus(userWalletId).firstOrNull()
?: return ExpressAvailabilityState.Loading
return when (asset) {
is Lce.Error -> ExpressAvailabilityState.Error
is Lce.Loading -> ExpressAvailabilityState.Loading
is Lce.Content -> {
val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(it) }
foundAsset?.exchangeAvailable?.toSwapAvailabilityState()
val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(assetId = it.id) }
foundAsset?.isExchangeAvailable?.toSwapAvailabilityState()
?: ExpressAvailabilityState.AssetNotFound
}
}
@ -168,15 +162,15 @@ internal class DefaultRampManager(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): ExpressAvailabilityState {
val asset = expressServiceLoader.getInitializationStatus(userWalletId).firstOrNull()
val asset = expressServiceFetcher.getInitializationStatus(userWalletId).firstOrNull()
?: return ExpressAvailabilityState.Loading
return when (asset) {
is Lce.Error -> ExpressAvailabilityState.Error
is Lce.Loading -> ExpressAvailabilityState.Loading
is Lce.Content -> {
val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(it) }
foundAsset?.onrampAvailable?.toOnrampAvailabilityState()
val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(assetId = it.id) }
foundAsset?.isOnrampAvailable?.toOnrampAvailabilityState()
?: ExpressAvailabilityState.AssetNotFound
}
}
@ -211,8 +205,13 @@ internal class DefaultRampManager(
}
}
private fun CryptoCurrency.findAssetPredicate(asset: Asset): Boolean {
val contractAddress = (this as? CryptoCurrency.Token)?.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE
return asset.network == network.backendId && asset.contractAddress.equals(contractAddress, ignoreCase = true)
private fun CryptoCurrency.findAssetPredicate(assetId: ExpressAsset.ID): Boolean {
val currencyAssedId = ExpressAsset.ID(
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
Hyperliquid, HyperliquidTestnet -> null
Quai, QuaiTestnet -> null
// Linea, LineaTestnet -> null
// ArbitrumNova -> null
Linea, LineaTestnet -> 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.staking.api.StakingComponent
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.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.wallet.WalletEntryComponent
import com.tangem.features.walletconnect.components.WalletConnectEntryComponent
@ -62,6 +63,7 @@ internal class ChildFactory @Inject constructor(
private val detailsComponentFactory: DetailsComponent.Factory,
private val walletSettingsComponentFactory: WalletSettingsComponent.Factory,
private val walletBackupComponentFactory: WalletBackupComponent.Factory,
private val walletHardwareBackupComponentFactory: WalletHardwareBackupComponent.Factory,
private val disclaimerComponentFactory: DisclaimerComponent.Factory,
private val manageTokensComponentFactory: ManageTokensComponent.Factory,
private val marketsTokenDetailsComponentFactory: MarketsTokenDetailsComponent.Factory,
@ -100,6 +102,7 @@ internal class ChildFactory @Inject constructor(
private val chooseManagedTokensComponentFactory: ChooseManagedTokensComponent.Factory,
private val createWalletSelectionComponentFactory: CreateWalletSelectionComponent.Factory,
private val createWalletStartComponentFactory: CreateWalletStartComponent.Factory,
private val createHardwareWalletComponentFactory: CreateHardwareWalletComponent.Factory,
private val createMobileWalletComponentFactory: CreateMobileWalletComponent.Factory,
private val upgradeWalletComponentFactory: UpgradeWalletComponent.Factory,
private val addExistingWalletComponentFactory: AddExistingWalletComponent.Factory,
@ -107,8 +110,9 @@ internal class ChildFactory @Inject constructor(
private val createWalletBackupComponentFactory: CreateWalletBackupComponent.Factory,
private val updateAccessCodeComponentFactory: UpdateAccessCodeComponent.Factory,
private val viewPhraseComponentFactory: ViewPhraseComponent.Factory,
private val forgetWalletComponentFactory: ForgetWalletComponent.Factory,
private val sendEntryPointComponentFactory: SendEntryPointComponent.Factory,
private val tangemPayDetailsComponentFactory: TangemPayDetailsComponent.Factory,
private val tangemPayDetailsContainerComponentFactory: TangemPayDetailsContainerComponent.Factory,
private val tangemPayOnboardingComponentFactory: TangemPayOnboardingComponent.Factory,
private val kycComponentFactory: KycComponent.Factory,
private val yieldSupplyPromoComponentFactory: YieldSupplyPromoComponent.Factory,
@ -138,7 +142,6 @@ internal class ChildFactory @Inject constructor(
is AppRoute.ManageTokens -> {
val source = when (route.source) {
AppRoute.ManageTokens.Source.SETTINGS -> ManageTokensSource.SETTINGS
AppRoute.ManageTokens.Source.ONBOARDING -> ManageTokensSource.ONBOARDING
AppRoute.ManageTokens.Source.STORIES -> ManageTokensSource.STORIES
}
@ -187,6 +190,15 @@ internal class ChildFactory @Inject constructor(
componentFactory = walletBackupComponentFactory,
)
}
is AppRoute.WalletHardwareBackup -> {
createComponentChild(
context = context,
params = WalletHardwareBackupComponent.Params(
userWalletId = route.userWalletId,
),
componentFactory = walletHardwareBackupComponentFactory,
)
}
is AppRoute.MarketsTokenDetails -> {
createComponentChild(
context = context,
@ -293,7 +305,7 @@ internal class ChildFactory @Inject constructor(
context = context,
params = StakingComponent.Params(
userWalletId = route.userWalletId,
cryptoCurrencyId = route.cryptoCurrencyId,
cryptoCurrency = route.cryptoCurrency,
yieldId = route.yieldId,
),
componentFactory = stakingComponentFactory,
@ -496,6 +508,13 @@ internal class ChildFactory @Inject constructor(
componentFactory = createWalletSelectionComponentFactory,
)
}
is AppRoute.CreateHardwareWallet -> {
createComponentChild(
context = context,
params = Unit,
componentFactory = createHardwareWalletComponentFactory,
)
}
is AppRoute.CreateMobileWallet -> {
createComponentChild(
context = context,
@ -533,6 +552,7 @@ internal class ChildFactory @Inject constructor(
context = context,
params = CreateWalletBackupComponent.Params(
userWalletId = route.userWalletId,
isUpgradeFlow = route.isUpgradeFlow,
),
componentFactory = createWalletBackupComponentFactory,
)
@ -555,6 +575,15 @@ internal class ChildFactory @Inject constructor(
componentFactory = viewPhraseComponentFactory,
)
}
is AppRoute.ForgetWallet -> {
createComponentChild(
context = context,
params = ForgetWalletComponent.Params(
userWalletId = route.userWalletId,
),
componentFactory = forgetWalletComponentFactory,
)
}
is AppRoute.SendEntryPoint -> {
createComponentChild(
context = context,
@ -604,8 +633,11 @@ internal class ChildFactory @Inject constructor(
is AppRoute.TangemPayDetails -> {
createComponentChild(
context = context,
params = TangemPayDetailsComponent.Params(userWalletId = route.userWalletId, config = route.config),
componentFactory = tangemPayDetailsComponentFactory,
params = TangemPayDetailsContainerComponent.Params(
userWalletId = route.userWalletId,
config = route.config,
),
componentFactory = tangemPayDetailsContainerComponentFactory,
)
}
is AppRoute.TangemPayOnboarding -> {

View file

@ -126,9 +126,12 @@ sealed class AppRoute(val path: String) : Route {
val portfolioId: PortfolioId? = null,
) : 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 {
STORIES,
ONBOARDING,
SETTINGS,
}
}
@ -192,9 +195,9 @@ sealed class AppRoute(val path: String) : Route {
@Serializable
data class Staking(
val userWalletId: UserWalletId,
val cryptoCurrencyId: CryptoCurrency.ID,
val cryptoCurrency: CryptoCurrency,
val yieldId: String,
) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrencyId.value}/$yieldId")
) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrency.id.value}/$yieldId")
@Serializable
data class PushNotification(
@ -217,6 +220,11 @@ sealed class AppRoute(val path: String) : Route {
val userWalletId: UserWalletId,
) : AppRoute(path = "/wallet_backup/${userWalletId.stringValue}")
@Serializable
data class WalletHardwareBackup(
val userWalletId: UserWalletId,
) : AppRoute(path = "/wallet_hardware_backup/${userWalletId.stringValue}")
@Serializable
data object Markets : AppRoute(path = "/markets")
@ -322,6 +330,9 @@ sealed class AppRoute(val path: String) : Route {
}
}
@Serializable
object CreateHardwareWallet : AppRoute(path = "/create_hardware_wallet")
@Serializable
object CreateMobileWallet : AppRoute(path = "/create_mobile_wallet")
@ -341,6 +352,7 @@ sealed class AppRoute(val path: String) : Route {
@Serializable
data class CreateWalletBackup(
val userWalletId: UserWalletId,
val isUpgradeFlow: Boolean,
) : AppRoute(path = "/create_wallet_backup/${userWalletId.stringValue}")
@Serializable
@ -353,6 +365,11 @@ sealed class AppRoute(val path: String) : Route {
val userWalletId: UserWalletId,
) : AppRoute(path = "/view_seed_phrase/${userWalletId.stringValue}")
@Serializable
data class ForgetWallet(
val userWalletId: UserWalletId,
) : AppRoute(path = "/forget_wallet/${userWalletId.stringValue}")
@Serializable
data class SendEntryPoint(
val userWalletId: UserWalletId,

View file

@ -1,21 +1,30 @@
package com.tangem.common.ui.account
import com.tangem.common.ui.R
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter
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.Subtitle2State
import com.tangem.core.ui.extensions.pluralReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.percent
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.quote.PriceChange
import com.tangem.utils.converter.Converter
import com.tangem.utils.extensions.isZero
import java.math.BigDecimal
class AccountCryptoPortfolioItemStateConverter(
private val appCurrency: AppCurrency,
private val account: Account.CryptoPortfolio,
private val priceChangeLce: Lce<Unit, PriceChange>? = null,
private val onItemClick: ((Account.CryptoPortfolio) -> Unit)? = null,
private val onItemLongClick: ((Account.CryptoPortfolio) -> Unit)? = null,
) : Converter<TotalFiatBalance, TokenItemState> {
@ -31,6 +40,14 @@ class AccountCryptoPortfolioItemStateConverter(
private fun Account.CryptoPortfolio.mapToContentState(
fiatBalance: TotalFiatBalance.Loaded,
): TokenItemState.Content {
val subtitle2State = when (fiatBalance.amount.isZero()) {
true -> null
false -> priceChangeLce?.fold(
ifLoading = { priceChange -> priceChange?.toSubtitle2State() ?: Subtitle2State.Loading },
ifError = { null },
ifContent = { priceChange -> priceChange.toSubtitle2State() },
)
}
return TokenItemState.Content(
id = account.accountId.value,
iconState = AccountIconItemStateConverter.convert(this),
@ -50,14 +67,14 @@ class AccountCryptoPortfolioItemStateConverter(
.format { fiat(fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol) },
isFlickering = fiatBalance.source == StatusSource.CACHE,
),
subtitle2State = null,
subtitle2State = subtitle2State,
onItemClick = onItemClick?.let { onItemClick -> { onItemClick(account) } },
onItemLongClick = onItemLongClick?.let { onItemLongClick -> { onItemLongClick(account) } },
)
}
private fun Account.CryptoPortfolio.mapToLoadingState(): TokenItemState.Loading {
return TokenItemState.Loading(
private fun Account.CryptoPortfolio.mapToLoadingState(): TokenItemState.Content {
return TokenItemState.Content(
id = account.accountId.value,
iconState = AccountIconItemStateConverter.convert(account),
titleState = TokenItemState.TitleState.Content(
@ -71,6 +88,10 @@ class AccountCryptoPortfolioItemStateConverter(
),
isAvailable = false,
),
fiatAmountState = FiatAmountState.Loading,
subtitle2State = Subtitle2State.Loading,
onItemLongClick = null,
onItemClick = onItemClick?.let { onItemClick -> { onItemClick(account) } },
)
}
@ -97,4 +118,14 @@ class AccountCryptoPortfolioItemStateConverter(
},
)
}
private fun BigDecimal.getPriceChangeType(): PriceChangeType = PriceChangeConverter.fromBigDecimal(value = this)
private fun StatusSource.isFlickering(): Boolean = this == StatusSource.CACHE
private fun PriceChange.toSubtitle2State(): Subtitle2State = Subtitle2State.PriceChangeContent(
priceChangePercent = this.value.format { percent() },
type = this.value.getPriceChangeType(),
isFlickering = this.source.isFlickering(),
)
}

View file

@ -33,7 +33,7 @@ sealed interface AccountNameUM {
*
* @property raw the raw string value of the custom account name
*/
class Custom(internal val raw: String) : AccountNameUM {
data class Custom(internal val raw: String) : AccountNameUM {
override val value: TextReference = stringReference(value = raw)
}

View file

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

View file

@ -0,0 +1,119 @@
package com.tangem.common.ui.account
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.R
import com.tangem.core.ui.components.SpacerW12
import com.tangem.core.ui.components.account.AccountIconSize
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.models.account.AccountName
@Composable
fun PortfolioSelectRow(state: PortfolioSelectUM, modifier: Modifier = Modifier) {
Row(
modifier = modifier
.clickable(enabled = state.isMultiChoice, onClick = state.onClick)
.padding(12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
val leftText = if (state.isAccountMode) R.string.account_details_title else R.string.wc_common_wallet
Text(
modifier = Modifier.weight(1f),
text = stringResourceSafe(leftText),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
)
SpacerW12()
if (state.icon != null) {
AccountIcon(
name = state.name,
icon = state.icon,
size = AccountIconSize.Small,
)
}
Text(
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = 4.dp),
text = state.name.resolveReference(),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
)
if (state.isMultiChoice) {
Icon(
modifier = Modifier
.size(width = 18.dp, height = 24.dp),
painter = painterResource(id = R.drawable.ic_select_18_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
)
}
}
}
@Immutable
data class PortfolioSelectUM(
val icon: CryptoPortfolioIconUM?,
val name: TextReference,
val isAccountMode: Boolean,
val isMultiChoice: Boolean,
val onClick: () -> Unit,
)
@Preview(widthDp = 360, showBackground = true)
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PortfolioSelectRowPreview(@PreviewParameter(PreviewProvider::class) state: PortfolioSelectUM) {
TangemThemePreview {
PortfolioSelectRow(
state = state,
modifier = Modifier.background(TangemTheme.colors.background.tertiary),
)
}
}
private class PreviewProvider : PreviewParameterProvider<PortfolioSelectUM> {
val account
get() = PortfolioSelectUM(
icon = AccountIconPreviewData.randomAccountIcon(),
name = AccountName.DefaultMain.toUM().value,
isAccountMode = true,
isMultiChoice = true,
onClick = {},
)
val wallet
get() = PortfolioSelectUM(
icon = null,
name = stringReference("Wallet Name"),
isMultiChoice = false,
isAccountMode = false,
onClick = {},
)
override val values: Sequence<PortfolioSelectUM>
get() = sequenceOf(account, wallet)
}

View file

@ -9,6 +9,7 @@ import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.shorted
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import java.math.BigDecimal
@ -274,6 +275,14 @@ sealed class NotificationUM(val config: NotificationConfig) {
title = resourceReference(id = R.string.yield_module_balance_info_sheet_title, wrappedList(tokenName)),
subtitle = resourceReference(id = R.string.yield_module_balance_info_sheet_subtitle),
)
data class YieldSupplyNotAllAmountSupplied(val formattedAmount: String, val symbol: String) : Warning(
title = resourceReference(
id = R.string.yield_module_amount_not_transfered_to_aave_title,
formatArgs = wrappedList(formattedAmount, symbol),
),
subtitle = TextReference.EMPTY,
)
}
open class Info(
@ -391,11 +400,16 @@ sealed class NotificationUM(val config: NotificationConfig) {
data class RentExemptionDestination(
private val rentExemptionAmount: BigDecimal,
private val cryptoCurrency: CryptoCurrency,
) : Error(
title = TextReference.Res(R.string.send_notification_invalid_amount_title),
subtitle = TextReference.Res(
id = R.string.send_notification_invalid_amount_rent_destination,
formatArgs = wrappedList(rentExemptionAmount),
formatArgs = wrappedList(
rentExemptionAmount.format {
crypto(cryptoCurrency)
},
),
),
)
}

View file

@ -368,6 +368,7 @@ object NotificationsFactory {
is BlockchainSdkError.DestinationTagRequired -> addRequireDestinationTagErrorNotification()
is BlockchainSdkError.Solana.DestinationRentExemption -> addRentExemptionDestinationNotification(
rentExemptionAmount = validationError.rentAmount,
cryptoCurrency = cryptoCurrency,
)
is BlockchainSdkError.TransactionDustChangeError -> add(
NotificationUM.Error.MinimumAmountError(
@ -462,10 +463,14 @@ object NotificationsFactory {
add(NotificationUM.Solana.RentInfo(rentWarning))
}
fun MutableList<NotificationUM>.addRentExemptionDestinationNotification(rentExemptionAmount: BigDecimal) {
private fun MutableList<NotificationUM>.addRentExemptionDestinationNotification(
rentExemptionAmount: BigDecimal,
cryptoCurrency: CryptoCurrency,
) {
add(
NotificationUM.Solana.RentExemptionDestination(
rentExemptionAmount = rentExemptionAmount,
cryptoCurrency = cryptoCurrency,
),
)
}

View file

@ -20,9 +20,9 @@ import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.currency.yieldSupplyKey
import com.tangem.domain.models.currency.yieldSupplyNotAllAmountSupplied
import com.tangem.domain.models.staking.YieldBalance
import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance
import com.tangem.lib.crypto.BlockchainUtils
import com.tangem.utils.StringsSigns.DASH_SIGN
import com.tangem.utils.converter.Converter
import com.tangem.utils.extensions.orZero
@ -186,6 +186,7 @@ class TokenItemStateConverter(
}
}
// polygon-pos_0xc2132d05d31c914a87c6611c10748aeb04b58e8f
private fun resolveEarnApy(
cryptoCurrencyStatus: CryptoCurrencyStatus,
yieldModuleApyMap: Map<String, String>,
@ -193,7 +194,12 @@ class TokenItemStateConverter(
): Pair<TextReference?, Boolean> {
val token = cryptoCurrencyStatus.currency as? CryptoCurrency.Token
if (token != null && yieldModuleApyMap.isNotEmpty()) {
val yieldSupplyApy = yieldModuleApyMap[token.yieldSupplyKey()]
val yieldSupplyApy = yieldModuleApyMap.entries.firstOrNull {
it.key.equals(
other = token.yieldSupplyKey(),
ignoreCase = BlockchainUtils.isCaseInsensitiveContractAddress(token.network.rawId),
)
}?.value
if (yieldSupplyApy != null) {
val isActive = cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive ?: false
return resourceReference(
@ -271,8 +277,7 @@ class TokenItemStateConverter(
isFlickering = status.value.isFlickering(),
icons = buildList {
if (status.value.yieldSupplyStatus?.isActive == true &&
status.value.yieldSupplyStatus?.isAllowedToSpend == false ||
status.yieldSupplyNotAllAmountSupplied()
status.value.yieldSupplyStatus?.isAllowedToSpend == false
) {
TokenItemState.FiatAmountState.Content.IconUM(
iconRes = R.drawable.ic_alert_triangle_20,

View file

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

View file

@ -30,9 +30,5 @@
{
"name": "zklink",
"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

@ -21,3 +21,17 @@ data class GetWalletAccountsResponse(
@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

@ -1675,30 +1675,82 @@
<string name="xtz_withdrawal_message_ignore">Nein, alles senden</string>
<string name="xtz_withdrawal_message_reduce">Um %s XTZ reduziert</string>
<string name="xtz_withdrawal_message_warning">Damit Sie beim nächsten Aufladen Ihrer Brieftasche keine erhöhte Provision zahlen, soll der Betrag um %s XTZ reduziert werden</string>
<string name="yield_module_alert_description">Wenn der Yield-Modus aktiviert ist, gehen alle zukünftigen Einzahlungen an diese Adresse an Aave. Du kannst über Dein Guthaben weiterhin frei verfügen.</string>
<string name="yield_module_alert_title">Deine %s wird an Aave übermittelt</string>
<string name="yield_module_amount_not_transfered_to_aave_title">Die Bereitstellung von %1$s %2$s für Aave steht aus</string>
<string name="yield_module_approve_needed_notification_cta">Genehmigen</string>
<string name="yield_module_approve_needed_notification_description">Die Genehmigung Deines Tokens wurde widerrufen. Erteilen diese erneut, um die Servicefunktionalität fortzusetzen.</string>
<string name="yield_module_approve_needed_notification_title">Genehmigung erforderlich</string>
<string name="yield_module_approve_sheet_fee_note">Die Gebühr wird abgezogen und Dein Vermögen wird erneut verliehen.</string>
<string name="yield_module_approve_sheet_subtitle">Um weiterhin Geld verdienen zu können, ist eine Genehmigung erforderlich.</string>
<string name="yield_module_approve_sheet_title">Genehmigung bestätigen</string>
<string name="yield_module_balance_info_sheet_subtitle">Deine Gelder werden derzeit dem Aave-Protokoll bereitgestellt, Du kannst sie jedoch jederzeit verwalten.</string>
<string name="yield_module_balance_info_sheet_title">Deine%s ist in Aave hinterlegt</string>
<string name="yield_module_chart_loading_error">Chart konnte nicht geladen werden...</string>
<string name="yield_module_deposit_error_notification_title">Der erhaltene Betrag %1$s %2$s wurde nicht auf Aave eingezahlt.</string>
<string name="yield_module_earn_badge">APY %1$s%%</string>
<string name="yield_module_earn_sheet_available_title">Verfügbar</string>
<string name="yield_module_earn_sheet_current_apy_title">Aktueller effektiver Jahreszins</string>
<string name="yield_module_earn_sheet_fee_description">Beim Aufladen zum Ausleihen wird eine Netzwerkgebühr von maximal %1$s vom Guthaben abgezogen.</string>
<string name="yield_module_earn_sheet_high_fee_description">Die Netzwerkgebühr ist derzeit zu hoch, um die Kreditvergabe durchzuführen. Die Mittel werden bereitgestellt, sobald sie auf %1$s oder darunter fallen. </string>
<string name="yield_module_earn_sheet_my_funds_title">Meine Mittel</string>
<string name="yield_module_earn_sheet_provider_description">Dein %1$s ist nun bei Aave hinterlegt und bringt Zinsen ein. Du besitzt einen %2$s-Token, der Dein Guthaben repräsentiert und mit der Zeit wächst. Wenn Du mehr Geld einzahlst, wird dieses an Aave weitergeleitet, um Zinsen abzüglich einer Transaktionsgebühr zu erwirtschaften.</string>
<string name="yield_module_earn_sheet_title">Ertragsmodus</string>
<string name="yield_module_earn_sheet_total_earnings_title">Gesamtverdienst</string>
<string name="yield_module_earn_sheet_transfers_title">Übertragungen zu Aave</string>
<string name="yield_module_explore_sheet_explore_aave_button_title">Entdecke Aave</string>
<string name="yield_module_fee_policy_sheet_current_fee_note">Dies ist die aktuelle Liefergebühr auf %s. Die tatsächlichen Kosten werden auf der Registerkarte \"Aktivierung\" angezeigt.</string>
<string name="yield_module_fee_policy_sheet_current_fee_title">Aktuelle Gebühr</string>
<string name="yield_module_fee_policy_sheet_description">Alle zukünftigen %s-Einzahlungen werden automatisch an Aave geliefert, wobei die Transaktionsgebühr abgezogen wird.</string>
<string name="yield_module_fee_policy_sheet_fee_note">Bei jeder zukünftigen Aufladung wird eine Netzwerkgebühr von ungefähr %1$s (%2$s) abgezogen, die Ihr Limit von %3$s (%4$s) nicht überschreitet.</string>
<string name="yield_module_fee_policy_sheet_max_fee_note">Wenn die Netzwerkgebühren über die maximale Gebühr steigen, wird die Transaktion erst durchgeführt, wenn diese sinken. Du kannst dieses Limit später ändern.</string>
<string name="yield_module_fee_policy_sheet_max_fee_title">Maximale Gebühr</string>
<string name="yield_module_fee_policy_sheet_min_amount_note">Der Mindestbetrag wird aus der aktuellen Netzwerkgebühr berechnet, um sicherzustellen, dass er 4%% nicht überschreitet, was den Mindestbetrag %1$s (%2$s) ergibt.</string>
<string name="yield_module_fee_policy_sheet_min_amount_title">Mindestaufladung</string>
<string name="yield_module_fee_policy_sheet_title">Gebührenpolitik</string>
<string name="yield_module_high_fee_error">Die Netzwerkgebühr ist derzeit zu hoch. Warte, bis sie unter Dein Limit fällt.</string>
<string name="yield_module_fee_policy_tangem_service_fee_title">Tangem erhebt außerdem eine Servicegebühr von 3% auf den erzielten Ertrag.</string>
<string name="yield_module_high_fee_error">Deine Gelder werden automatisch an Aave überwiesen, sobald die Netzwerkgebühren niedriger sind oder Dein Guthaben den erforderlichen Mindestbetrag erreicht.</string>
<string name="yield_module_historical_returns">Historische Renditen</string>
<string name="yield_module_promo_screen_cash_out_title">Sofortige Auszahlung</string>
<string name="yield_module_main_view_approve_notification_description">Die Freigabe für Deine Token im Yield-Modus wurde widerrufen. Öffne den Token, um die Berechtigung erneut zu erteilen.</string>
<string name="yield_module_main_view_approve_notification_title">Token-Genehmigung erforderlich</string>
<string name="yield_module_network_fee_unreachable_notification_description">Prüfe Deine Netzwerkverbindung</string>
<string name="yield_module_network_fee_unreachable_notification_title">Informationen zu den Netzwerkgebühren nicht erreichbar</string>
<string name="yield_module_promo_screen_auto_balance_subtitle">Jede Einzahlung, die Du tätigst, wird automatisch an Aave weitergeleitet.</string>
<string name="yield_module_promo_screen_auto_balance_title">Automatische Übertragung zu Aave</string>
<string name="yield_module_promo_screen_cash_out_subtitle">Senden, tauschen oder verkaufen Deine Gelder sofort, wann immer Du willst.</string>
<string name="yield_module_promo_screen_cash_out_title">Jederzeit Zugriff auf Dein Geld</string>
<string name="yield_module_promo_screen_how_it_works_button_title">Wie funktioniert das?</string>
<string name="yield_module_promo_screen_self_custodial_subtitle">Aave ist ein dezentrales Protokoll, das einen Gesamtwert von über 81,9 Milliarden US-Dollar verwaltet.</string>
<string name="yield_module_promo_screen_self_custodial_title">Dezentral und selbstverwahrend</string>
<string name="yield_module_promo_screen_terms_disclaimer">Durch die Nutzung dieses Dienstes erklärst Du Dich mit provider\n%1$s und %2$s einverstanden</string>
<string name="yield_module_promo_screen_title">Mit Aave verbinden</string>
<string name="yield_module_promo_screen_variable_rate_info">Aave %1$s • Variabler Zinssatz</string>
<string name="yield_module_promo_screen_variable_rate_info">Aave %1$s%% • Variabler Zinssatz</string>
<string name="yield_module_provider">Aave</string>
<string name="yield_module_rate_info_sheet_chart_average">Durchschnitt %s</string>
<string name="yield_module_rate_info_sheet_chart_title">Renditen des letzten Jahres</string>
<string name="yield_module_rate_info_sheet_description">Der aktuelle Zinssatz ist immer variabel und wird automatisch vom Aave On-Chain-Smart-Contract auf der Grundlage von Angebot und Nachfrage in Echtzeit berechnet.</string>
<string name="yield_module_rate_info_sheet_powered_by">Unterstützt durch</string>
<string name="yield_module_rate_info_sheet_title">Der Zinssatz ist variabel</string>
<string name="yield_module_receive_sheet_description">Wenn Du dein Guthaben auflädst, wird es automatisch an Aave weitergeleitet, um Zinsen zu verdienen. zur Deckung der Transaktionsgebühr wird ein Betrag von %s abgezogen.</string>
<string name="yield_module_start_earning">Beginn des Verdienstes</string>
<string name="yield_module_start_earning_sheet_description">Dein %s wird an Aave übermittelt, bleibt aber verwaltbar.</string>
<string name="yield_module_start_earning_sheet_fee_policy">Siehe Gebührenrichtlinie</string>
<string name="yield_module_start_earning_sheet_next_deposits">Deine nächste Aufladung wird automatisch an Aave weitergeleitet.</string>
<string name="yield_module_status_active">Aktiv</string>
<string name="yield_module_status_paused">Pausiert</string>
<string name="yield_module_stop_earning">Deaktiviere den Yield-Modus</string>
<string name="yield_module_stop_earning_sheet_description">Wenn Du diese Option deaktivierst, wird Dein Geld von Aave auf %s in Deiner Wallet abgehoben und es werden keine Belohnungen mehr verdient.</string>
<string name="yield_module_stop_earning_sheet_fee_note">Die Netzwerkgebühr wird von dem Betrag, den Du abhebst, abgezogen.</string>
<string name="yield_module_supply_apr">Effektiver Jahreszins für Versorgung</string>
<string name="yield_module_token_details_earn_notification_apy">Effektiver Jahreszins</string>
<string name="yield_module_token_details_earn_notification_description">Lass Dein Geld arbeiten verdiene Zinsen auf Dein Guthaben.</string>
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Verdienst auf Dein Guthaben</string>
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_subtitle">Zinsen fallen automatisch an</string>
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Ertragsmodus</string>
<string name="yield_module_token_details_earn_notification_processing">Bearbeitung Deiner Einzahlung</string>
<string name="yield_module_token_details_earn_notification_title">Lass dein Guthaben arbeiten</string>
<string name="yield_module_transfer_mode_automatic">Automatisch</string>
<string name="yield_module_unable_to_cover_fee_description">Einzahlung von %1$s %2$s zur Deckung der Netzwerkgebühr für Transaktionen</string>
<string name="yield_module_unable_to_cover_fee_title">Die Gebühr %s kann nicht gedeckt werden</string>
<string name="yield_module_unavailable_subtitle">Der Stakingservice ist derzeit nicht verfügbar. Bitte versuche es später erneut.</string>
<string name="yield_module_unavailable_title">Einnahmen nicht verfügbar</string>
<string name="yield_supply_chart_loading_error">Chart konnte nicht geladen werden...</string>

View file

@ -1106,6 +1106,7 @@
<string name="story_web3_title">Compatible con Web 3.0</string>
<string name="sui_not_enough_coin_for_fee_description">Se requiere una transacción entrante de al menos %1$s para proceder</string>
<string name="sui_not_enough_coin_for_fee_title">Fondos insuficientes</string>
<string name="swap_approve_description">Al aprobar, permites que el contrato inteligente use tus tokens en futuras transacciones.</string>
<string name="swap_fixed_rate">Tasa Fija</string>
<string name="swap_give_permission_fee_footer">La red cobrará una tasa de aprobación del token para verificar que usted autoriza el uso de su token para el intercambio.</string>
<string name="swap_promo_text">Intercambie más tokens a mejores tasas directamente en su billetera.</string>
@ -1400,6 +1401,7 @@
<string name="wc_alert_wrong_card_title">Tenemos algún tipo de problema</string>
<string name="wc_all_dapps_disconnected">Todas las dApps desconectadas</string>
<string name="wc_allow_to_spend">Permitir gastar</string>
<string name="wc_approve_description">Al aprobar, permites que la dApp o el contrato inteligente utilicen los tokens en futuras transacciones.</string>
<string name="wc_common_address">Dirección</string>
<string name="wc_common_connect">Conectar</string>
<string name="wc_common_loading">Cargando</string>
@ -1474,4 +1476,82 @@
<string name="xtz_withdrawal_message_ignore">No, enviar todo</string>
<string name="xtz_withdrawal_message_reduce">Reducir en %s XTZ</string>
<string name="xtz_withdrawal_message_warning">Para evitar pagar una comisión mayor la próxima vez que recargue su billetera, reduzca el importe en %s XTZ</string>
<string name="yield_module_alert_description">Con el modo Rendimiento activo, todos los depósitos futuros a esta dirección irán a Aave. Puede seguir gestionando sus fondos libremente.</string>
<string name="yield_module_alert_title">Su %s se suministra a Aave</string>
<string name="yield_module_amount_not_transfered_to_aave_title">El suministro de %1$s %2$s a Aave está pendiente</string>
<string name="yield_module_approve_needed_notification_cta">Aprobar</string>
<string name="yield_module_approve_needed_notification_description">Se ha revocado la aprobación de su token. Concédalo de nuevo para reanudar el servicio.</string>
<string name="yield_module_approve_needed_notification_title">Aprobación necesaria</string>
<string name="yield_module_approve_sheet_fee_note">Se le descontará la comisión y se le volverán a prestar sus activos.</string>
<string name="yield_module_approve_sheet_subtitle">Para seguir ganando, se requiere aprobación.</string>
<string name="yield_module_approve_sheet_title">Confirmar aprobación</string>
<string name="yield_module_balance_info_sheet_subtitle">Sus fondos se suministran actualmente al protocolo Aave, pero puede gestionarlos en cualquier momento.</string>
<string name="yield_module_balance_info_sheet_title">Su %s está depositado en Aave</string>
<string name="yield_module_chart_loading_error">No se puede cargar el gráfico...</string>
<string name="yield_module_deposit_error_notification_title">La cantidad recibida, %1$s %2$s no fue suministrada a Aave.</string>
<string name="yield_module_earn_badge">APY %1$s%%</string>
<string name="yield_module_earn_sheet_available_title">Disponible</string>
<string name="yield_module_earn_sheet_current_apy_title">APY actual</string>
<string name="yield_module_earn_sheet_fee_description">En las recargas para préstamos, se deducirá del saldo una comisión de red no superior a %1$s.</string>
<string name="yield_module_earn_sheet_high_fee_description">La tasa de red es actualmente demasiado alta para ejecutar préstamos. Los fondos se suministrarán una vez que baje a %1$s o menos.</string>
<string name="yield_module_earn_sheet_my_funds_title">Mis fondos</string>
<string name="yield_module_earn_sheet_provider_description">Su %1$s está ahora depositado en Aave y devenga intereses. Tiene un token%2$s, que representa su saldo y crece con el tiempo. Cuando deposite más fondos, estos se suministrarán a Aave para ganar intereses, menos una comisión por transacción.</string>
<string name="yield_module_earn_sheet_title">Modo de rendimiento</string>
<string name="yield_module_earn_sheet_total_earnings_title">Ganancias totales</string>
<string name="yield_module_earn_sheet_transfers_title">Transferencias a Aave</string>
<string name="yield_module_explore_sheet_explore_aave_button_title">Explore Aave</string>
<string name="yield_module_fee_policy_sheet_current_fee_note">Esta es la tarifa de suministro actual en %s. El coste real se mostrará en la pestaña de activación.</string>
<string name="yield_module_fee_policy_sheet_current_fee_title">Tarifa actual</string>
<string name="yield_module_fee_policy_sheet_description">Todos los depósitos futuros %s se proporcionarán a Aave automáticamente, con la tarifa de transacción deducida.</string>
<string name="yield_module_fee_policy_sheet_fee_note">Se deducirá una comisión de red aproximada de %1$s (%2$s) de cada recarga futura, y no superará su límite de %3$s (%4$s).</string>
<string name="yield_module_fee_policy_sheet_max_fee_note">Si las tarifas de red superan la tarifa máxima, la transacción no se realizará hasta que disminuyan. Puede cambiar este límite más adelante.</string>
<string name="yield_module_fee_policy_sheet_max_fee_title">Tarifa máxima</string>
<string name="yield_module_fee_policy_sheet_min_amount_note">El monto mínimo se calcula a partir de la tarifa de red actual para garantizar que no exceda 4%%, lo que hace que el mínimo sea %1$s (%2$s).</string>
<string name="yield_module_fee_policy_sheet_min_amount_title">Recarga mínima</string>
<string name="yield_module_fee_policy_sheet_title">Política de tarifas</string>
<string name="yield_module_fee_policy_tangem_service_fee_title">Tangem también cobra una comisión de servicio del 3% sobre el rendimiento obtenido.</string>
<string name="yield_module_high_fee_error">Sus fondos se suministrarán automáticamente a Aave una vez que las comisiones de red sean más bajas o su saldo alcance el importe mínimo requerido.</string>
<string name="yield_module_historical_returns">Rendimientos históricos</string>
<string name="yield_module_main_view_approve_notification_description">Se ha revocado la autorización para su token en el modo Rendimiento. Abra el token para volver a conceder el permiso.</string>
<string name="yield_module_main_view_approve_notification_title">Se necesita la aprobación de Token</string>
<string name="yield_module_network_fee_unreachable_notification_description">Compruebe su conexión de red</string>
<string name="yield_module_network_fee_unreachable_notification_title">Información de tarifas de red inaccesible</string>
<string name="yield_module_promo_screen_auto_balance_subtitle">Cada depósito que realice se suministrará a Aave automáticamente.</string>
<string name="yield_module_promo_screen_auto_balance_title">Transferencia automática a Aave</string>
<string name="yield_module_promo_screen_cash_out_subtitle">Envíe, intercambie o venda sus fondos al instante, cuando quiera.</string>
<string name="yield_module_promo_screen_cash_out_title">Acceda a sus fondos en cualquier momento</string>
<string name="yield_module_promo_screen_how_it_works_button_title">¿Cómo funciona?</string>
<string name="yield_module_promo_screen_self_custodial_subtitle">Aave es un protocolo descentralizado que administra más de 81.9 billones de dólares en valor total.</string>
<string name="yield_module_promo_screen_self_custodial_title">Descentralizado y autocustodiado</string>
<string name="yield_module_promo_screen_terms_disclaimer">Al utilizar este servicio, usted acepta que el proveedor\n%1$s y %2$s</string>
<string name="yield_module_promo_screen_title">Conectar Aave</string>
<string name="yield_module_promo_screen_variable_rate_info">Aave %1$s%% - Tipo de interés variable</string>
<string name="yield_module_provider">Aave</string>
<string name="yield_module_rate_info_sheet_chart_average">Promedio %s</string>
<string name="yield_module_rate_info_sheet_chart_title">Resultados del año pasado</string>
<string name="yield_module_rate_info_sheet_description">La tasa de interés actual siempre es variable y calculada automáticamente por el contrato inteligente onchain de Aave, en función de la oferta y la demanda en tiempo real.</string>
<string name="yield_module_rate_info_sheet_powered_by">Desarrollado por</string>
<string name="yield_module_rate_info_sheet_title">El tipo de interés es variable</string>
<string name="yield_module_receive_sheet_description">Cuando recargue, sus fondos se enviarán automáticamente a Aave para comenzar a ganar intereses. %s se deducirá para cubrir la tarifa de transacción.</string>
<string name="yield_module_start_earning">Suministro de activos</string>
<string name="yield_module_start_earning_sheet_description">Su %s se suministrará a Aave, pero seguirá siendo gestionable.</string>
<string name="yield_module_start_earning_sheet_fee_policy">Ver política de tarifas</string>
<string name="yield_module_start_earning_sheet_next_deposits">Sus próximas recargas se suministrarán automáticamente a Aave.</string>
<string name="yield_module_status_active">Activo</string>
<string name="yield_module_status_paused">En pausa</string>
<string name="yield_module_stop_earning">Desactivar el modo de rendimiento</string>
<string name="yield_module_stop_earning_sheet_description">Al desactivar esto, retirará sus fondos de Aave a %s en su billetera y dejará de ganar recompensas.</string>
<string name="yield_module_stop_earning_sheet_fee_note">La comisión de red se deducirá del importe que retire.</string>
<string name="yield_module_supply_apr">Suministro APY</string>
<string name="yield_module_token_details_earn_notification_apy">APY</string>
<string name="yield_module_token_details_earn_notification_description">Deje que sus fondos hagan el trabajo mientras usted mantiene el control.</string>
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_subtitle">Los intereses se devengan automáticamente</string>
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Modo de rendimiento</string>
<string name="yield_module_token_details_earn_notification_processing">Procesando su depósito</string>
<string name="yield_module_token_details_earn_notification_title">Haga que sus activos trabajen para usted</string>
<string name="yield_module_transfer_mode_automatic">Automático</string>
<string name="yield_module_unable_to_cover_fee_description">Deposite algo de %1$s %2$s para cubrir la tarifa de red para las transacciones</string>
<string name="yield_module_unable_to_cover_fee_title">No se puede cubrir la tarifa %s</string>
<string name="yield_module_unavailable_subtitle">El servicio de intereses no está disponible en este momento. Vuelva a intentarlo más tarde.</string>
<string name="yield_module_unavailable_title">Ganancias no disponibles</string>
</resources>

View file

@ -1080,6 +1080,7 @@
<string name="story_web3_title">Compatible avec Web 3.0</string>
<string name="sui_not_enough_coin_for_fee_description">Une transaction entrante d\'au moins de %1$s est requise pour continuer</string>
<string name="sui_not_enough_coin_for_fee_title">Fonds insuffisants</string>
<string name="swap_approve_description">En approuvant, vous autorisez le contrat intelligent à utiliser vos jetons dans de futures transactions.</string>
<string name="swap_fixed_rate">Taux fixe</string>
<string name="swap_give_permission_fee_footer">Le réseau facturera des frais d\'approbation de jeton pour vérifier que vous autorisez l\'utilisation de votre jeton pour l\'échange.</string>
<string name="swap_promo_text">Échangez plus de jetons à de meilleurs taux directement dans votre portefeuille.</string>
@ -1386,6 +1387,7 @@
<string name="wc_alert_wrong_card_title">Nous avons un problème.</string>
<string name="wc_all_dapps_disconnected">Toutes les dApps sont déconnectées</string>
<string name="wc_allow_to_spend">Autoriser à dépenser</string>
<string name="wc_approve_description">En approuvant, vous autorisez la dApp ou le contrat intelligent à utiliser les jetons dans de futures transactions.</string>
<string name="wc_common_address">Adresse</string>
<string name="wc_common_connect">Connecter</string>
<string name="wc_common_loading">Chargement</string>
@ -1453,4 +1455,80 @@
<string name="xtz_withdrawal_message_ignore">Non, envoyer toute la somme</string>
<string name="xtz_withdrawal_message_reduce">Réduire de %s XTZ</string>
<string name="xtz_withdrawal_message_warning">Pour ne pas payer un fraid de commissions élevé la prochaine fois que vous rechargez votre portefeuille, veuillez réduire le montant de %s XTZ</string>
<string name="yield_module_alert_description">Vos fonds sont actuellement fournis au protocole Aave, mais vous pouvez les gérer à tout moment.</string>
<string name="yield_module_alert_title">Vos %s sont fournis à Aave.</string>
<string name="yield_module_amount_not_transfered_to_aave_title">Le transfert de %1$s %2$s vers Aave est en attente.</string>
<string name="yield_module_approve_needed_notification_cta">Donnez votre accord</string>
<string name="yield_module_approve_needed_notification_description">Un problème est survenu lors de votre précédente approbation, nous avons donc besoin d\'une nouvelle approbation. Choisissez comment vous souhaitez procéder.</string>
<string name="yield_module_approve_needed_notification_title">Autorisation nécessaire</string>
<string name="yield_module_approve_sheet_fee_note">Les frais seront prélevés et vos actifs seront à nouveau prêtés.</string>
<string name="yield_module_approve_sheet_subtitle">Pour continuer à percevoir des revenus, une autorisation est nécessaire.</string>
<string name="yield_module_approve_sheet_title">Confirmer l\'approbation</string>
<string name="yield_module_balance_info_sheet_title">Vos %s sont déposés dans Aave.</string>
<string name="yield_module_chart_loading_error">Impossible de charger le graphique...</string>
<string name="yield_module_deposit_error_notification_title">Le montant reçu, %1$s %2$s, n\'a pas été fourni à Aave.</string>
<string name="yield_module_earn_badge">APY %1$s%%</string>
<string name="yield_module_earn_sheet_available_title">Disponible</string>
<string name="yield_module_earn_sheet_current_apy_title">APY actuel</string>
<string name="yield_module_earn_sheet_fee_description">Lors du rechargement pour le dépôt, des frais de réseau ne dépassant pas %1$s seront déduits du solde.</string>
<string name="yield_module_earn_sheet_high_fee_description">Les frais de réseau sont actuellement trop élevés pour permettre l\'exécution des prêts. Les fonds seront versés dès qu\'ils auront baissé à %1$s ou moins.</string>
<string name="yield_module_earn_sheet_my_funds_title">Mes fonds</string>
<string name="yield_module_earn_sheet_provider_description">Vos %1$s sont désormais déposés chez Aave et rapportent des intérêts. Vous détenez %2$s tokens, ce qui représente votre solde et augmente au fil du temps. Lorsque vous déposez davantage de fonds, ceux-ci sont fournis à Aave afin de rapporter des intérêts, moins les frais de transaction.</string>
<string name="yield_module_earn_sheet_title">Gagner</string>
<string name="yield_module_earn_sheet_total_earnings_title">Total des gains</string>
<string name="yield_module_earn_sheet_transfers_title">Transferts vers Aave</string>
<string name="yield_module_explore_sheet_explore_aave_button_title">Découvrez Aave</string>
<string name="yield_module_fee_policy_sheet_current_fee_note">Il s\'agit des frais d\'approvisionnement actuels sur %s. Le coût réel sera indiqué dans l\'onglet Activation.</string>
<string name="yield_module_fee_policy_sheet_current_fee_title">Frais actuels</string>
<string name="yield_module_fee_policy_sheet_description">Tous les futurs dépôts %s seront automatiquement fournis à Aave, après déduction des frais de transaction.</string>
<string name="yield_module_fee_policy_sheet_fee_note">Des frais de réseau d\'environ %1$s (%2$s) seront déduits de chaque recharge future, sans dépasser votre limite de %3$s (%4$s).</string>
<string name="yield_module_fee_policy_sheet_max_fee_note">Si les frais de réseau dépassent le montant maximal, la transaction ne sera pas effectuée tant qu\'ils n\'auront pas diminué. Vous pouvez modifier cette limite ultérieurement.</string>
<string name="yield_module_fee_policy_sheet_max_fee_title">Frais maximaux</string>
<string name="yield_module_fee_policy_sheet_min_amount_note">Le montant minimum est calculé à partir des frais de réseau actuels afin de garantir qu\'il ne dépasse pas 4 %%, ce qui donne un minimum de %1$s (%2$s).</string>
<string name="yield_module_fee_policy_sheet_min_amount_title">Recharge minimale</string>
<string name="yield_module_fee_policy_sheet_title">Politique tarifaire</string>
<string name="yield_module_fee_policy_tangem_service_fee_title">Tangem prélève également des frais de service de 3% sur les revenus générés.</string>
<string name="yield_module_high_fee_error">Vos fonds seront automatiquement transférés vers Aave dès que les frais de réseau seront moins élevés ou que votre solde atteindra le montant minimum requis.</string>
<string name="yield_module_historical_returns">Rendements historiques</string>
<string name="yield_module_main_view_approve_notification_title">Approbations de tokens nécessaires</string>
<string name="yield_module_network_fee_unreachable_notification_description">Vérifiez votre connexion réseau.</string>
<string name="yield_module_network_fee_unreachable_notification_title">Informations sur les frais de réseau inaccessibles</string>
<string name="yield_module_promo_screen_auto_balance_subtitle">Chaque dépôt que vous effectuez sera automatiquement transféré à Aave.</string>
<string name="yield_module_promo_screen_auto_balance_title">Transfert automatique vers Aave</string>
<string name="yield_module_promo_screen_cash_out_subtitle">Envoyez, échangez ou vendez vos fonds instantanément, quand vous le souhaitez.</string>
<string name="yield_module_promo_screen_cash_out_title">Accédez à vos fonds à tout moment</string>
<string name="yield_module_promo_screen_how_it_works_button_title">Comment ça marche ?</string>
<string name="yield_module_promo_screen_self_custodial_subtitle">Aave est un protocole décentralisé qui gère plus de 81,9 milliards de dollars en valeur totale.</string>
<string name="yield_module_promo_screen_self_custodial_title">Décentralisé et auto-détenu</string>
<string name="yield_module_promo_screen_terms_disclaimer">En utilisant ce service, vous acceptez les conditions générales du fournisseur %1$s et %2$s.</string>
<string name="yield_module_promo_screen_title">Connecter Aave</string>
<string name="yield_module_promo_screen_variable_rate_info">Aave %1$s%% • Taux d\'intérêt variable</string>
<string name="yield_module_provider">Aave</string>
<string name="yield_module_rate_info_sheet_chart_average">Moyenne %s</string>
<string name="yield_module_rate_info_sheet_chart_title">Rendements de l\'année dernière</string>
<string name="yield_module_rate_info_sheet_description">Le taux d\'intérêt actuel est toujours variable et calculé automatiquement par le contrat intelligent Aave, en fonction de l\'offre et de la demande en temps réel.</string>
<string name="yield_module_rate_info_sheet_powered_by">Alimenté par</string>
<string name="yield_module_rate_info_sheet_title">Le taux d\'intérêt est variable.</string>
<string name="yield_module_receive_sheet_description">Lorsque vous effectuez un dépôt, vos fonds sont automatiquement transférés vers Aave afin de commencer à générer des intérêts. %s sera déduit pour couvrir les frais de transaction.</string>
<string name="yield_module_start_earning">Actifs d\'approvisionnement</string>
<string name="yield_module_start_earning_sheet_description">Vos %s seront fournis à Aave, mais resteront gérables.</string>
<string name="yield_module_start_earning_sheet_fee_policy">Voir la politique tarifaire</string>
<string name="yield_module_start_earning_sheet_next_deposits">Vos prochains dépôts seront automatiquement transférés vers Aave.</string>
<string name="yield_module_status_active">Actif</string>
<string name="yield_module_status_paused">En pause</string>
<string name="yield_module_stop_earning">Désactiver le mode rendement</string>
<string name="yield_module_stop_earning_sheet_description">En désactivant cela, vos fonds seront retirés d\'Aave vers %s dans votre portefeuille et vous ne gagnerez plus de récompenses.</string>
<string name="yield_module_stop_earning_sheet_fee_note">Les frais de réseau seront déduits du montant que vous retirez.</string>
<string name="yield_module_supply_apr">Rendement annuel brut (APY)</string>
<string name="yield_module_token_details_earn_notification_apy">APY</string>
<string name="yield_module_token_details_earn_notification_description">Laissez vos fonds travailler pour vous tout en gardant le contrôle.</string>
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_subtitle">Les intérêts sont cumulés automatiquement.</string>
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Mode de rendement</string>
<string name="yield_module_token_details_earn_notification_processing">Traitement de votre dépôt</string>
<string name="yield_module_token_details_earn_notification_title">Faites fructifier vos actifs</string>
<string name="yield_module_transfer_mode_automatic">Automatique</string>
<string name="yield_module_unable_to_cover_fee_description">Déposez %1$s %2$s pour couvrir les frais de réseau liés aux transactions.</string>
<string name="yield_module_unable_to_cover_fee_title">Impossible de couvrir les frais %s</string>
<string name="yield_module_unavailable_subtitle">Le service d\'intérêt n\'est pas disponible pour le moment. Veuillez réessayer plus tard.</string>
<string name="yield_module_unavailable_title">Gains indisponibles</string>
</resources>

View file

@ -23,6 +23,8 @@
<string name="account_archived_recover_error_title">アカウントを復元できません</string>
<string name="account_archived_title">アーカイブ済み</string>
<string name="account_could_not_archive">アカウントをアーカイブできませんでした。しばらくしてからもう一度お試しください。</string>
<string name="account_could_not_archive_referral_program_message">このアカウントは紹介プログラムに参加しています。</string>
<string name="account_could_not_archive_referral_program_title">このアカウントはアーカイブできません。</string>
<string name="account_could_not_create">アカウントを作成できませんでした。しばらくしてからもう一度お試しください。</string>
<string name="account_create_success_message">アカウントを作成しました</string>
<string name="account_details_archive">アカウントをアーカイブする</string>
@ -30,6 +32,7 @@
<string name="account_details_archive_description">このアカウントをアーカイブしますが、いつでも復元できます。</string>
<string name="account_details_title">アカウント</string>
<string name="account_edit_success_message">アカウントが保存されました</string>
<string name="account_for_rewards">報酬用のアカウント</string>
<string name="account_form_account_index">アカウント番号%s — アドレス導出に使用されます。</string>
<string name="account_form_create_button">アカウントを追加</string>
<string name="account_form_edit_button">保存</string>
@ -41,6 +44,8 @@
<string name="account_form_title_edit">アカウントを編集</string>
<string name="account_label_tokens_info">%1$s ( %2$s内)</string>
<string name="account_main_account_title">メインアカウント</string>
<string name="account_recover_limit_dialog_description">すでにアクティブアカウントの上限%1$s件を超えています。復元するには、1つをアーカイブしてください。</string>
<string name="account_recover_limit_dialog_title">アカウントを復元できません</string>
<string name="account_recover_success_message">アカウントが回復しました</string>
<string name="account_reorder_description">アカウントを長押しして並べ替える</string>
<string name="account_unsaved_dialog_action_first">編集を続ける</string>
@ -545,6 +550,7 @@
<string name="hw_backup_section_other_title">その他の方法</string>
<string name="hw_backup_seed_description">秘密鍵をオフラインで安全に保存する物理デバイス。</string>
<string name="hw_backup_seed_title">リカバリーフレーズ</string>
<string name="hw_backup_to_upgrade_description">ウォレットをハードウェアにアップグレードするには、まずバックアップしてください。</string>
<string name="hw_create_keys_title">鍵はアプリに保存されます</string>
<string name="hw_create_seed_title">シードフレーズのバックアップ</string>
<string name="hw_create_title">モバイルウォレットを作成する</string>
@ -969,6 +975,10 @@
<string name="reset_card_to_factory_condition_2">このカードを使用して、現在のウォレットの他のカードのアクセスコードを回復させられないことを認識しています。</string>
<string name="reset_card_with_backup_to_factory_message">工場出荷時設定にリセットすると、選択したカードやリングからウォレットが完全に削除されます。現在のウォレットを復元したり、カードやリングを使用してアクセスコードを復元することはできません。</string>
<string name="reset_card_without_backup_to_factory_message">工場出荷時の状態にリセットすると、選択したカードやリングからウォレットが完全に削除され、アプリから削除されます。現在のウォレットを復元することはできません。</string>
<string name="reset_cards_dialog_complete_description">すべてのTangemデバイスがリセットされました。</string>
<string name="reset_cards_dialog_first_description">アクティベーション処理中に問題が発生しました。カードを1枚ずつリセットしてください。</string>
<string name="reset_cards_dialog_first_title">カード認証に失敗しました</string>
<string name="reset_cards_dialog_next_device_description">続行するには次のデバイスをリセットしてください</string>
<string name="ring_promo_text">Tangem Ringユーザーは、11/15日までChangelly経由でスワップを3回手数料ゼロで行えます</string>
<string name="ring_promo_title">今すぐ手数料0% でスワップしましょう!</string>
<string name="save_user_wallet_agreement_access_description">アプリにログインして、カードまたはリングをスキャンせずに残高を確認できます</string>
@ -1298,6 +1308,8 @@
<string name="tangem_pay_transaction_fee_notification_text">この手数料は、送金処理にかかるコストをカバーするためのものです。</string>
<string name="tangem_pay_withdrawal">出金</string>
<string name="tangempay_card_details_change_pin">PINを変更する</string>
<string name="tangempay_card_details_change_pin_success_description">カードは支払いの準備が整いました。</string>
<string name="tangempay_card_details_change_pin_success_title">PINコードを作成しました</string>
<string name="tangempay_card_details_error_text">データの読み込みに失敗しました。しばらくしてからもう一度お試しください。</string>
<string name="tangempay_card_details_freeze_card">カードの一時停止</string>
<string name="tangempay_card_details_hide_text">非表示</string>
@ -1728,75 +1740,77 @@
<string name="xtz_withdrawal_message_ignore">いいえ、すべて送信します</string>
<string name="xtz_withdrawal_message_reduce">%s XTZを減らす</string>
<string name="xtz_withdrawal_message_warning">次回ウォレットにチャージするときに手数料の増加を避けるには、金額を%s XTZ減らしてください。</string>
<string name="yield_module_alert_description">資産残高に関するテキスト [プレースホルダー]</string>
<string name="yield_module_alert_title">%sはAaveに預けられています</string>
<string name="yield_module_alert_description">あなたの資金は現在Aaveプロトコルに供給されていますが、いつでも管理できます。</string>
<string name="yield_module_alert_title">%sはAaveに供給されています</string>
<string name="yield_module_amount_not_transfered_to_aave_title">Aaveへの%1$s %2$sの供給は保留中です</string>
<string name="yield_module_approve_needed_notification_cta">承認する</string>
<string name="yield_module_approve_needed_notification_description">前回の承認に問題があったため、新しい承認が必要です。手続き方法を選択してください。</string>
<string name="yield_module_approve_needed_notification_title">承認が必要</string>
<string name="yield_module_approve_sheet_fee_note">手数料が差し引かれ、あなたの資産は再び貸し出されます。</string>
<string name="yield_module_approve_sheet_subtitle">引き続き収益を得るには承認が必要です。</string>
<string name="yield_module_approve_sheet_title">承認を確定する</string>
<string name="yield_module_balance_info_sheet_subtitle">資産残高に関するテキスト [プレースホルダー]</string>
<string name="yield_module_balance_info_sheet_title">あなたの%sはAaveに預けられています</string>
<string name="yield_module_chart_loading_error">チャートを読み込めません・・</string>
<string name="yield_module_deposit_error_notification_title">受け取った金額%1$s %2$sはAaveに入金されませんでした。</string>
<string name="yield_module_earn_badge">年利%1$s%%</string>
<string name="yield_module_earn_sheet_available_title">利用可能</string>
<string name="yield_module_earn_sheet_current_apy_title">現在のAPY</string>
<string name="yield_module_earn_sheet_fee_description">貸付のために入金する際は、残高から%1$s以下のネットワーク手数料が差し引かれます。</string>
<string name="yield_module_earn_sheet_high_fee_description">現在、ネットワーク手数料が高すぎるため貸付を実行できません。手数料が%1$s以下に下がり次第、資金が供給されます。</string>
<string name="yield_module_earn_sheet_my_funds_title">私の資金</string>
<string name="yield_module_earn_sheet_provider_description">あなたの%1$sはAaveに預けられ、利息が付きます。あなたは%2$sトークンを保有しており、これは残高を表し、時間の経過とともに増加します。入金すると、資金はAaveに預け入れられ、取引手数料を差し引いた利息が付きます。</string>
<string name="yield_module_earn_sheet_provider_description">あなたの%1$sはAaveに預けられ、利息が付きます。あなたは%2$sトークンを保有しており、これは残高を表し、時間の経過とともに増加します。入金すると資金はAaveに供給され、取引手数料を差し引いた利息が付きます。</string>
<string name="yield_module_earn_sheet_title">利回りを得る</string>
<string name="yield_module_earn_sheet_total_earnings_title">総収益</string>
<string name="yield_module_earn_sheet_transfers_title">Aaveへの送金</string>
<string name="yield_module_explore_sheet_explore_aave_button_title">Aaveの詳細を見る</string>
<string name="yield_module_fee_policy_sheet_current_fee_note">これは%sの現在の供給手数料です。実際のコストは受取画面に表示されます。</string>
<string name="yield_module_fee_policy_sheet_current_fee_note">これは%sの現在の供給手数料です。実際のコストはアクティベーションタブに表示されます。</string>
<string name="yield_module_fee_policy_sheet_current_fee_title">現在の手数料</string>
<string name="yield_module_fee_policy_sheet_description">今後の%sの入金はすべて、取引手数料が差し引かれて自動的にAaveに供給されます。</string>
<string name="yield_module_fee_policy_sheet_fee_note">今後のチャージごとに、おおよそ%1$s%2$sのネットワーク手数料が差し引かれますが、上限の%3$s%4$sを超えることはありません。</string>
<string name="yield_module_fee_policy_sheet_max_fee_note">ネットワーク手数料が上限手数料を超えた場合、手数料が下がるまで取引は成立しません。この制限は後で変更できます。</string>
<string name="yield_module_fee_policy_sheet_max_fee_title">最大手数料</string>
<string name="yield_module_fee_policy_sheet_min_amount_note">取引手数料は入金額の4%未満である必要があります。残高がこの条件を満たすのに十分な金額になった場合にのみ、TangemはAaveに資金を送ります。</string>
<string name="yield_module_fee_policy_sheet_min_amount_note">最小金額は現在のネットワーク手数料に基づいて計算されており、手数料が4を超えないように設定されています。その結果、最小金額は%1$s%2$sとなります。</string>
<string name="yield_module_fee_policy_sheet_min_amount_title">最低入金額</string>
<string name="yield_module_fee_policy_sheet_title">手数料ポリシー</string>
<string name="yield_module_fee_policy_tangem_service_fee_title">Tangemはまた、得られた利回りに対して3%のサービス手数料を差し引きます。</string>
<string name="yield_module_high_fee_error">ネットワーク手数料が現在高すぎます。設定した上限を下回るまで待機しています。</string>
<string name="yield_module_high_fee_error">ネットワーク手数料が下がるか、残高が最低必要額に達すると、資金は自動的にAaveに供給されます。</string>
<string name="yield_module_historical_returns">過去のリターン</string>
<string name="yield_module_main_view_approve_notification_description">ここに説明を入力してください。1〜3行が理想的です。[プレースホルダー]</string>
<string name="yield_module_main_view_approve_notification_title">トークン承認が必要</string>
<string name="yield_module_main_view_approve_notification_title">トークンの承認が必要</string>
<string name="yield_module_network_fee_unreachable_notification_description">ネットワーク接続を確認してください</string>
<string name="yield_module_network_fee_unreachable_notification_title">ネットワーク手数料についての情報にアクセスできません</string>
<string name="yield_module_promo_screen_auto_balance_subtitle">アカウントへの入金はすべて自動的にAaveに貸し出されます。</string>
<string name="yield_module_promo_screen_auto_balance_title">残高は自動的に計算されます</string>
<string name="yield_module_promo_screen_auto_balance_subtitle">あなたが行うすべての入金は、自動的にAaveへ供給されます。</string>
<string name="yield_module_promo_screen_auto_balance_title">Aaveへの自動送金</string>
<string name="yield_module_promo_screen_cash_out_subtitle">いつでも、即座に資金を送信、交換、売却できます。</string>
<string name="yield_module_promo_screen_cash_out_title">いつでも資金にアクセス可能</string>
<string name="yield_module_promo_screen_how_it_works_button_title">使い方</string>
<string name="yield_module_promo_screen_self_custodial_subtitle">Aaveは、総額819億ドル以上の資産を管理する分散型プロトコルです。</string>
<string name="yield_module_promo_screen_self_custodial_title">分散型・自己管理型</string>
<string name="yield_module_promo_screen_terms_disclaimer">サービスを利用することにより、プロバイダー\n %1$sおよび%2$sに同意したことになります</string>
<string name="yield_module_promo_screen_terms_disclaimer">このサービスを利用することにより、プロバイダー\n%1$sおよび%2$sに同意するものとします</string>
<string name="yield_module_promo_screen_title">Aave を接続</string>
<string name="yield_module_promo_screen_variable_rate_info">Aave %1$s%% • 変動金利</string>
<string name="yield_module_provider">Aave</string>
<string name="yield_module_rate_info_sheet_chart_average">平均%s</string>
<string name="yield_module_rate_info_sheet_chart_title">昨年のリターン</string>
<string name="yield_module_rate_info_sheet_description">現在の金利は常に変動し、リアルタイムの需要と供給に基づいて、AAVEオンチェーンスマートコントラクトによって自動的に計算されます。</string>
<string name="yield_module_rate_info_sheet_description">現在の金利は常に変動し、リアルタイムの需要と供給に基づいて、Aaveオンチェーンスマートコントラクトによって自動的に計算されます。</string>
<string name="yield_module_rate_info_sheet_powered_by">提供</string>
<string name="yield_module_rate_info_sheet_title">金利は変動します</string>
<string name="yield_module_receive_sheet_description">入金すると、資金は自動的にAaveに送金され、利息が付き始めます。取引手数料として%s相当の少額の手数料が差し引かれます。</string>
<string name="yield_module_receive_sheet_description">入金すると、資金は自動的にAaveに送金され、利息が付き始めます。%sが取引手数料として差し引かれます。</string>
<string name="yield_module_start_earning">資産を供給する</string>
<string name="yield_module_start_earning_sheet_description">%sはAaveに供給され、すぐに利用可能になります</string>
<string name="yield_module_start_earning_sheet_description">%sはAaveに供給されますが、管理可能な状態のままになります。</string>
<string name="yield_module_start_earning_sheet_fee_policy">手数料ポリシーを見る</string>
<string name="yield_module_start_earning_sheet_next_deposits">次回以降の入金は自動的にAaveに供給されます。</string>
<string name="yield_module_start_earning_sheet_next_deposits">次回の入金は自動的にAaveに供給されます。</string>
<string name="yield_module_status_active">アクティブ</string>
<string name="yield_module_status_paused">停止中</string>
<string name="yield_module_stop_earning">収益を停止する</string>
<string name="yield_module_stop_earning_sheet_description">オフにすると、Aaveから資金が引き出され、ウォレットの%sに戻され、報酬の獲得が停止されます。</string>
<string name="yield_module_stop_earning">利回りモードを無効にする</string>
<string name="yield_module_stop_earning_sheet_description">これをオフにすると、Aave からウォレットの %s に資金が引き出され、報酬の獲得が停止します。</string>
<string name="yield_module_stop_earning_sheet_fee_note">出金金額からネットワーク手数料が差し引かれます。</string>
<string name="yield_module_supply_apr">供給APY</string>
<string name="yield_module_token_details_earn_notification_apy">APY</string>
<string name="yield_module_token_details_earn_notification_description">あなたの資産を眠らせない — 残高を運用して利息を得ましょう。</string>
<string name="yield_module_token_details_earn_notification_description">自分で管理しながら、資金に働かせましょう。</string>
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_subtitle">利息は自動的に発生します</string>
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Aaveの利回り</string>
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">利回りモード</string>
<string name="yield_module_token_details_earn_notification_processing">入金の処理中</string>
<string name="yield_module_token_details_earn_notification_title">残高を活用</string>
<string name="yield_module_token_details_earn_notification_title">資産を有効活用しましょう</string>
<string name="yield_module_transfer_mode_automatic">自動</string>
<string name="yield_module_unable_to_cover_fee_description">取引のネットワーク手数料をカバーするために、 %1$s %2$sを入金してください</string>
<string name="yield_module_unable_to_cover_fee_title">%s手数料を支払えません</string>

View file

@ -395,6 +395,7 @@
<string name="express_more_providers_soon">Больше провайдеров на подходе.\nСледите за обновлениями!</string>
<string name="express_provider">Провайдер</string>
<string name="express_provider_best_rate">Лучший курс</string>
<string name="express_provider_great_rate">Лучший выбор</string>
<string name="express_provider_max_amount">Доступно до %s</string>
<string name="express_provider_min_amount">Доступно с %s</string>
<string name="express_provider_not_available">Недоступно для этой пары</string>
@ -1236,6 +1237,7 @@
<string name="token_button_unavailability_reason_pending_transaction_send">Отправка средств станет доступной после завершения транзакции(-ий) в сети %s</string>
<string name="token_button_unavailability_reason_sell_unavailable">Продажа %s в данный момент не поддерживается ни одним провайдером. Мы работаем над добавлением новых возможностей. Следите за нашими новостями.</string>
<string name="token_button_unavailability_reason_staking_unavailable">Стейкинг %s в данный момент не поддерживается ни одним провайдером. Мы работаем над добавлением новых возможностей. Следите за нашими новостями.</string>
<string name="token_button_unavailability_reason_yield_supply_approval">Разрешение было отозвано. Ваши средства остаются в Yield сервисе. Чтобы совершать операции, перейдите в Yield сервис и снова выдайте разрешение.</string>
<string name="token_details_generate_xpub">Сгенерировать XPUB</string>
<string name="token_details_hide_alert_hide">Скрыть</string>
<string name="token_details_hide_alert_message">Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами.</string>
@ -1494,6 +1496,7 @@
<string name="wc_alert_wrong_card_title">Похоже, возникла проблема</string>
<string name="wc_all_dapps_disconnected">Все dapps отключены</string>
<string name="wc_allow_to_spend">Разрешение на использование</string>
<string name="wc_approve_description">Подтверждая, вы разрешаете смарт-контракту использовать ваши токены в будущих транзакциях.</string>
<string name="wc_common_address">Адрес</string>
<string name="wc_common_connect">Подключение</string>
<string name="wc_common_loading">Загрузка</string>
@ -1569,13 +1572,15 @@
<string name="xtz_withdrawal_message_ignore">Нет, отправить все</string>
<string name="xtz_withdrawal_message_reduce">Уменьшить на %s XTZ</string>
<string name="xtz_withdrawal_message_warning">Чтобы не платить повышенную комиссию при следующем пополнении кошелька, уменьшите сумму на %s XTZ</string>
<string name="yield_module_alert_description">При активном режиме доходности все будущие депозиты на этот адрес будут направляться в Aave. Вы по-прежнему можете свободно управлять своими средствами.</string>
<string name="yield_module_alert_title">Ваш %s внесён в Aave</string>
<string name="yield_module_approve_needed_notification_cta">Выдать разрешение</string>
<string name="yield_module_approve_needed_notification_description">Что-то пошло не так с вашим предыдущим разрешением, поэтому требуется новое. Выберите, как хотите продолжить.</string>
<string name="yield_module_approve_needed_notification_description">Разрешение для вашего токена было отозвано. Выдайте его снова, чтобы продолжить работу сервиса.</string>
<string name="yield_module_approve_needed_notification_title">Необходимо разрешение</string>
<string name="yield_module_approve_sheet_fee_note">Комиссия будет списана, и ваши активы снова начнут приносить доход.</string>
<string name="yield_module_approve_sheet_subtitle">Чтобы продолжить зарабатывать, нужно выдать разрешение.</string>
<string name="yield_module_approve_sheet_title">Подтвердить разрешение</string>
<string name="yield_module_balance_info_sheet_subtitle">Ваши средства в данный момент размещены в протоколе Aave, но вы можете воспользоваться ими в любое время.</string>
<string name="yield_module_balance_info_sheet_title">Ваш %s внесён в Aave</string>
<string name="yield_module_chart_loading_error">Невозможно загрузить график</string>
<string name="yield_module_deposit_error_notification_title">Полученная сумма, %1$s %2$s, не была зачислена на Aave.</string>
@ -1592,17 +1597,18 @@
<string name="yield_module_fee_policy_sheet_description">Все следующие пополнения %s автоматически поступят в Aave с удержанием комиссии за транзакцию.</string>
<string name="yield_module_fee_policy_sheet_max_fee_note">Если комиссия сети превысит максимальный лимит, транзакция не будет выполнена до тех пор, пока комиссия не снизится. Вы сможете изменить этот лимит позже.</string>
<string name="yield_module_fee_policy_sheet_max_fee_title">Максимальная комиссия</string>
<string name="yield_module_fee_policy_sheet_min_amount_note">Комиссия за транзакцию должна быть ниже 4% от суммы депозита. Tangem переведёт средства в Aave, как только это условие будет выполнено.</string>
<string name="yield_module_fee_policy_sheet_min_amount_note">Это минимальный депозит, который можно отправить в Aave. Чтобы депозиты оставались прибыльными, мы не обрабатываем их, когда комиссия сети превышает %1$s%2$sот суммы.</string>
<string name="yield_module_fee_policy_sheet_min_amount_title">Минимальный депозит</string>
<string name="yield_module_fee_policy_sheet_title">Политика комиссий</string>
<string name="yield_module_fee_policy_tangem_service_fee_title">Tangem взимает комиссию за обслуживание в размере 3% от полученного дохода.</string>
<string name="yield_module_high_fee_error">Ваши средства будут автоматически переведены в Aave, как только комиссия сети снизится или баланс достигнет минимально необходимой суммы.</string>
<string name="yield_module_historical_returns">Историческая доходность</string>
<string name="yield_module_main_view_approve_notification_description">Разрешение для вашего токена в Yield сервисе было отозвано. Откройте токен, чтобы выдать разрешение снова.</string>
<string name="yield_module_main_view_approve_notification_title">Необходимо разрешение для токена</string>
<string name="yield_module_network_fee_unreachable_notification_description">Проверьте ваше интернет соединение</string>
<string name="yield_module_network_fee_unreachable_notification_title">Информация о комиссии недоступна</string>
<string name="yield_module_promo_screen_auto_balance_subtitle">Каждое пополнение вашего адреса автоматически будет отправляться в Aave.</string>
<string name="yield_module_promo_screen_auto_balance_title">Ваш баланс работает автоматически</string>
<string name="yield_module_promo_screen_auto_balance_title">Автоперевод в AAVE</string>
<string name="yield_module_promo_screen_cash_out_subtitle">Отправляйте, обменивайте или продавайте свои средства мгновенно, когда захотите.</string>
<string name="yield_module_promo_screen_cash_out_title">Мгновенный вывод средств</string>
<string name="yield_module_promo_screen_how_it_works_button_title">Как это работает?</string>
@ -1631,13 +1637,13 @@
<string name="yield_module_token_details_earn_notification_apy">APY</string>
<string name="yield_module_token_details_earn_notification_description">Пусть ваши деньги работают — зарабатывайте проценты на свой баланс.</string>
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_subtitle">Проценты начисляются автоматически.</string>
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Доходность</string>
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Режим доходности</string>
<string name="yield_module_token_details_earn_notification_processing">Отправка ваших средств</string>
<string name="yield_module_token_details_earn_notification_title">Пусть ваш баланс работает на вас!</string>
<string name="yield_module_transfer_mode_automatic">Автоматически</string>
<string name="yield_module_unable_to_cover_fee_description">Внесите немного %1$s %2$s, чтобы покрыть комиссию сети за транзакции.</string>
<string name="yield_module_unable_to_cover_fee_title">Невозможно покрыть комиссию в %s</string>
<string name="yield_module_unavailable_subtitle">Сервис начисления процентов в данный момент недоступен. Пожалуйста, попробуйте позже.</string>
<string name="yield_module_unavailable_title">Данные о доходе недоступны</string>
<string name="yield_module_unavailable_subtitle">Сервис доходности в данный момент недоступен. Пожалуйста, попробуйте позже.</string>
<string name="yield_module_unavailable_title">Режим доходности недоступен</string>
<string name="yield_supply_chart_loading_error">Невозможно загрузить график</string>
</resources>

View file

@ -1086,6 +1086,7 @@
<string name="story_web3_title">Web 3.0 сумісність</string>
<string name="sui_not_enough_coin_for_fee_description">Для відправки потрібна вхідна транзакція на суму не менше %1$s</string>
<string name="sui_not_enough_coin_for_fee_title">Недостатньо коштів</string>
<string name="swap_approve_description">Підтверджуючи, ви дозволяєте смартконтракту використовувати ваші токени в майбутніх транзакціях.</string>
<string name="swap_give_permission_fee_footer">Мережа стягує комісію за схвалення токену за підтвердження, що саме ви дозволяєте використовувати ваш токен для обміну.</string>
<string name="swap_promo_text">Обмінюйте більше токенів за вигіднішим курсом прямо у своєму гаманці.</string>
<string name="swap_promo_title">З\'явився новий провайдер обмінів!</string>
@ -1366,6 +1367,7 @@
<string name="wc_alert_wrong_card_description">Обрана не вірна картка або кільце</string>
<string name="wc_alert_wrong_card_title">Схоже, виникла проблема</string>
<string name="wc_all_dapps_disconnected">Усі dApps відключені</string>
<string name="wc_approve_description">Підтверджуючи, ви дозволяєте dApp або смартконтракту використовувати токени в майбутніх транзакціях.</string>
<string name="wc_common_address">Адреса</string>
<string name="wc_common_connect">Підключення</string>
<string name="wc_common_network">Мережа</string>
@ -1430,4 +1432,6 @@
<string name="xtz_withdrawal_message_ignore">Ні, відправити все</string>
<string name="xtz_withdrawal_message_reduce">Зменшити на %s XTZ</string>
<string name="xtz_withdrawal_message_warning">Щоб не платити підвищену комісію при наступному поповненні гаманця, зменште суму на %s XTZ</string>
<string name="yield_module_fee_policy_sheet_min_amount_note">Це найменший депозит, який можна надіслати в Aave. Щоб депозити залишалися прибутковими, ми не обробляємо їх, коли комісія мережі перевищує %1$s%2$s від суми.</string>
<string name="yield_module_main_view_approve_notification_description">Дозвіл для вашого токена в режимі Yield було відкликано. Відкрийте токен, щоб надати дозвіл знову.</string>
</resources>

View file

@ -23,6 +23,8 @@
<string name="account_archived_recover_error_title">Can\'t recover account</string>
<string name="account_archived_title">Archived</string>
<string name="account_could_not_archive">We couldnt archive account. Please try again later.</string>
<string name="account_could_not_archive_referral_program_message">This account participates in the referral program.</string>
<string name="account_could_not_archive_referral_program_title">This account cannot be archived.</string>
<string name="account_could_not_create">We couldnt create account. Please try again later.</string>
<string name="account_create_success_message">Account created</string>
<string name="account_details_archive">Archive account</string>
@ -42,6 +44,8 @@
<string name="account_form_title_edit">Edit account</string>
<string name="account_label_tokens_info">%1$s in %2$s</string>
<string name="account_main_account_title">Main account</string>
<string name="account_recover_limit_dialog_description">You have already exceeded the limit of %1$s active accounts. Archive one to recover</string>
<string name="account_recover_limit_dialog_title">Cant recover account</string>
<string name="account_recover_success_message">Account recovered</string>
<string name="account_reorder_description">Long tap on an account to reorder accounts</string>
<string name="account_unsaved_dialog_action_first">Keep Editing</string>
@ -245,6 +249,7 @@
<string name="common_enable">Enable</string>
<string name="common_enabled">Enabled</string>
<string name="common_error">Error</string>
<string name="common_estimated_fee">Estimated fee</string>
<string name="common_exchange">Swap</string>
<string name="common_explore">Explore</string>
<string name="common_explore_transaction_history">Explore transaction history</string>
@ -480,7 +485,7 @@
<string name="express_provider">Provider</string>
<string name="express_provider_best_rate">Best rate</string>
<string name="express_provider_fca_warning_list">FCA Warning List</string>
<string name="express_provider_great_rate">Great rate</string>
<string name="express_provider_great_rate">Best choice</string>
<string name="express_provider_in_fca_warning_list">Provider in FCA warning list</string>
<string name="express_provider_max_amount">Available up to %s</string>
<string name="express_provider_min_amount">Available from %s</string>
@ -555,6 +560,7 @@
<string name="hw_backup_section_other_title">Other methods</string>
<string name="hw_backup_seed_description">Physical devices that securely store your private key offline.</string>
<string name="hw_backup_seed_title">Recovery phrase</string>
<string name="hw_backup_to_upgrade_description">To upgrade your wallet to hardware, back it up first.</string>
<string name="hw_create_keys_description">Your private keys are securely encrypted and stored on your phone</string>
<string name="hw_create_keys_title">Keys are stored in the app</string>
<string name="hw_create_seed_description">Create or restore your wallet using a recovery phrase — your built-in backup.</string>
@ -993,7 +999,7 @@
<string name="reset_card_with_backup_to_factory_message">Factory Reset will completely delete the wallet from the selected card or ring. You will not be able to restore the current wallet or use the card or ring to recover the access code.</string>
<string name="reset_card_without_backup_to_factory_message">Factory Reset will completely delete the wallet from the selected card or ring and remove it from the app. You will not be able to restore the current wallet.</string>
<string name="reset_cards_dialog_complete_description">All Tangem devices have been reset.</string>
<string name="reset_cards_dialog_first_description">Something went wrong with activation process. Please reset cards one by one.</string>
<string name="reset_cards_dialog_first_description">Something went wrong with the activation process. Please reset the cards one by one.</string>
<string name="reset_cards_dialog_first_title">Card verification failed</string>
<string name="reset_cards_dialog_next_device_description">Please reset the next device to continue</string>
<string name="ring_promo_text">Ring owners get 3 commission-free swaps on Changelly until 15.11!</string>
@ -1326,6 +1332,8 @@
<string name="tangem_pay_transaction_fee_notification_text">This fee goes to cover the cost of handling your transfer.</string>
<string name="tangem_pay_withdrawal">Withdrawal</string>
<string name="tangempay_card_details_change_pin">Change PIN</string>
<string name="tangempay_card_details_change_pin_success_description">The card is fully ready for payments.</string>
<string name="tangempay_card_details_change_pin_success_title">PIN code created</string>
<string name="tangempay_card_details_error_text">Failed to load data. Try again later.</string>
<string name="tangempay_card_details_freeze_card">Freeze Card</string>
<string name="tangempay_card_details_hide_text">Hide</string>
@ -1362,7 +1370,7 @@
<string name="token_button_unavailability_reason_pending_transaction_send">Sending funds will be available once the pending transaction(s) in network %s is complete</string>
<string name="token_button_unavailability_reason_sell_unavailable">Selling %s is not supported by current providers, but we are working to add more options.</string>
<string name="token_button_unavailability_reason_staking_unavailable">Staking %s is not supported by current providers, but we are working to add more options.</string>
<string name="token_button_unavailability_reason_yield_supply_approval">Text here</string>
<string name="token_button_unavailability_reason_yield_supply_approval">Approval has been revoked. Your funds remain in Yield mode. To perform actions, please go to Yield mode and grant permission again.</string>
<string name="token_details_generate_xpub">Generate XPUB</string>
<string name="token_details_hide_alert_hide">Hide</string>
<string name="token_details_hide_alert_message">You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page.</string>
@ -1805,82 +1813,83 @@
<string name="xtz_withdrawal_message_ignore">No, send all</string>
<string name="xtz_withdrawal_message_reduce">Reduce by %s XTZ</string>
<string name="xtz_withdrawal_message_warning">To avoid paying an increased commission the next time you top up your wallet, reduce the amount by %s XTZ</string>
<string name="yield_module_alert_description">Text about your money in balance [PLACEHOLDER]</string>
<string name="yield_module_alert_title">Your %s is deposited in Aave</string>
<string name="yield_module_amount_not_transfered_to_aave_title">The amount received, %1$s %2$s was not deposited to Aave.</string>
<string name="yield_module_alert_description">With Yield mode active, all future deposits to this address will go to Aave. You can still manage your funds freely.</string>
<string name="yield_module_alert_title">Your %s is supplied to Aave</string>
<string name="yield_module_amount_not_transfered_to_aave_title">Supplying %1$s %2$s to Aave is pending</string>
<string name="yield_module_approve_needed_notification_cta">Give Approve</string>
<string name="yield_module_approve_needed_notification_description">Something went wrong with your previous approval, so we need a new one. Choose how youd like to proceed.</string>
<string name="yield_module_approve_needed_notification_description">Your tokens approval has been revoked. Grant it again to resume service functionality.</string>
<string name="yield_module_approve_needed_notification_title">Approve needed</string>
<string name="yield_module_approve_sheet_fee_note">The fee will be taken out, and your assets will be lent again.</string>
<string name="yield_module_approve_sheet_subtitle">To continue earning, approval is required.</string>
<string name="yield_module_approve_sheet_title">Confirm approval</string>
<string name="yield_module_balance_info_sheet_subtitle">Text about your money in balance [PLACEHOLDER]</string>
<string name="yield_module_balance_info_sheet_subtitle">Your funds are currently supplied to the Aave protocol, but you can manage them at any time.</string>
<string name="yield_module_balance_info_sheet_title">Your %s is deposited in Aave</string>
<string name="yield_module_chart_loading_error">Unable to load chart...</string>
<string name="yield_module_deposit_error_notification_title">The amount received, %1$s %2$s was not deposited to Aave.</string>
<string name="yield_module_deposit_error_notification_title">The amount received, %1$s %2$s was not supplied to Aave.</string>
<string name="yield_module_earn_badge">APY %1$s%%</string>
<string name="yield_module_earn_sheet_available_title">Available</string>
<string name="yield_module_earn_sheet_current_apy_title">Current APY</string>
<string name="yield_module_earn_sheet_fee_description">When topping up for lending, a network fee will be deducted from the amount — never more than %1$s</string>
<string name="yield_module_earn_sheet_fee_description">When topping up for lending, a network fee not exceeding %1$s will be deducted from the balance.</string>
<string name="yield_module_earn_sheet_high_fee_description">The network fee is currently too high to execute lending. Funds will be supplied once it drops to %1$s or below. </string>
<string name="yield_module_earn_sheet_my_funds_title">My funds</string>
<string name="yield_module_earn_sheet_provider_description">Your %1$s is now deposited in Aave and earning interest. You hold a%2$s token, which represents your balance and grows over time. When you top up, funds go to Aave to earn interest, minus a transaction fee.</string>
<string name="yield_module_earn_sheet_title">Earn</string>
<string name="yield_module_earn_sheet_provider_description">Your %1$s is now deposited in Aave and earning interest. You hold a%2$s token, which represents your balance and grows over time. When you deposit more funds, they\'ll be supplied to Aave to earn interest, minus a transaction fee.</string>
<string name="yield_module_earn_sheet_title">Yield mode</string>
<string name="yield_module_earn_sheet_total_earnings_title">Total earnings</string>
<string name="yield_module_earn_sheet_transfers_title">Transfers to Aave</string>
<string name="yield_module_explore_sheet_explore_aave_button_title">Explore Aave</string>
<string name="yield_module_fee_policy_sheet_current_fee_note">This is the current supply fee on %s. The live cost will be shown on the Receive Screen.</string>
<string name="yield_module_fee_policy_sheet_current_fee_note">This is the current supply fee on %s. The actual cost will be shown on the activation tab.</string>
<string name="yield_module_fee_policy_sheet_current_fee_title">Current fee</string>
<string name="yield_module_fee_policy_sheet_description">All future %s top-ups will be supplied to Aave automatically, with the transaction fee deducted.</string>
<string name="yield_module_fee_policy_sheet_description">All future %s deposits will be supplied to Aave automatically, with the transaction fee deducted.</string>
<string name="yield_module_fee_policy_sheet_fee_note">An approximate network fee of %1$s (%2$s) will be deducted from each future top-up, and it wont exceed your %3$s (%4$s) limit.</string>
<string name="yield_module_fee_policy_sheet_max_fee_note">If network fees rise above maximum fee, the transaction wont go through until they decrease. You can change this limit later.</string>
<string name="yield_module_fee_policy_sheet_max_fee_title">Maximum fee</string>
<string name="yield_module_fee_policy_sheet_min_amount_note">The transaction fee must stay below 4% of your deposit. Tangem will transfer funds to Aave only once your balance is large enough to meet this condition.</string>
<string name="yield_module_fee_policy_sheet_min_amount_title">Minimal top-up</string>
<string name="yield_module_fee_policy_sheet_min_amount_note">The minimum amount is calculated from the current network fee to ensure it does not exceed 4%%, which makes the minimum %1$s (%2$s).</string>
<string name="yield_module_fee_policy_sheet_min_amount_title">Minimum top-up</string>
<string name="yield_module_fee_policy_sheet_title">Fee policy</string>
<string name="yield_module_fee_policy_tangem_service_fee_title">Tangem also takes a 3% service fee on the yield earned.</string>
<string name="yield_module_high_fee_error">Your funds will be automatically transferred to Aave once network fees are lower or your balance meets the minimum required amount.</string>
<string name="yield_module_fee_policy_tangem_service_fee_title">Tangem also takes a 3% service fee on yield earned.</string>
<string name="yield_module_high_fee_error">Your funds will be automatically supplied to Aave once network fees are lower or your balance meets the minimum required amount.</string>
<string name="yield_module_historical_returns">Historical returns</string>
<string name="yield_module_main_view_approve_notification_description">Write description here. In one, two or three lines will be awesome. [PLACEHOLDER]</string>
<string name="yield_module_main_view_approve_notification_title">Some token approve needed</string>
<string name="yield_module_main_view_approve_notification_description">Approval for your token in the Yield mode has been revoked. Open the token to grant permission again.</string>
<string name="yield_module_main_view_approve_notification_title">Token approval needed</string>
<string name="yield_module_network_fee_unreachable_notification_description">Check your network connection</string>
<string name="yield_module_network_fee_unreachable_notification_title">Network fee info unreachable</string>
<string name="yield_module_promo_screen_auto_balance_subtitle">Every top-up of your account will be lended to Aave automatically.</string>
<string name="yield_module_promo_screen_auto_balance_title">Your balance works automatically</string>
<string name="yield_module_promo_screen_auto_balance_subtitle">Every deposit you make will be supplied to Aave automatically.</string>
<string name="yield_module_promo_screen_auto_balance_title">Auto-Transfer to Aave</string>
<string name="yield_module_promo_screen_cash_out_subtitle">Send, swap, or sell your funds instantly, anytime you want.</string>
<string name="yield_module_promo_screen_cash_out_title">Access to funds at any time</string>
<string name="yield_module_promo_screen_cash_out_title">Access your funds anytime</string>
<string name="yield_module_promo_screen_how_it_works_button_title">How it works?</string>
<string name="yield_module_promo_screen_self_custodial_subtitle">Aave is a decentralized protocol managing over $81.9billion in total value.</string>
<string name="yield_module_promo_screen_self_custodial_title">Decentralized and self-custodial</string>
<string name="yield_module_promo_screen_terms_disclaimer">By using service, you agree with provider\n%1$s and %2$s</string>
<string name="yield_module_promo_screen_terms_disclaimer">By using this service, you agree with provider\n%1$s and %2$s</string>
<string name="yield_module_promo_screen_title">Connect Aave</string>
<string name="yield_module_promo_screen_variable_rate_info">Aave %1$s%% • Variable Interest Rate</string>
<string name="yield_module_provider">Aave</string>
<string name="yield_module_rate_info_sheet_chart_average">Avg %s</string>
<string name="yield_module_rate_info_sheet_chart_title">Last year returns</string>
<string name="yield_module_rate_info_sheet_description">Current interest rate is always variable and automatically computed by AAVE on-chain smart-contract based on real-time supply and demand.</string>
<string name="yield_module_rate_info_sheet_chart_title">Last year\'s returns</string>
<string name="yield_module_rate_info_sheet_description">Current interest rate is always variable and automatically computed by the Aave on-chain smart-contract, based on real-time supply and demand.</string>
<string name="yield_module_rate_info_sheet_powered_by">Powered by</string>
<string name="yield_module_rate_info_sheet_title">Interest rate is variable</string>
<string name="yield_module_receive_sheet_description">When you top up, your funds will be automatically sent to Aave to start earning interest. A small fee equal to %s will be deducted to cover the transaction.</string>
<string name="yield_module_receive_sheet_description">When you top up, your funds will be automatically sent to Aave to start earning interest. %s will be deducted to cover the transaction fee.</string>
<string name="yield_module_start_earning">Supply assets</string>
<string name="yield_module_start_earning_sheet_description">Your %s will be supplied to Aave and will stay instantly available</string>
<string name="yield_module_start_earning_sheet_description">Your %s will be supplied to Aave, but will remain manageable.</string>
<string name="yield_module_start_earning_sheet_fee_policy">See fee policy</string>
<string name="yield_module_start_earning_sheet_next_deposits">Your next top-ups will be automatically supplied to Aave.</string>
<string name="yield_module_status_active">Active</string>
<string name="yield_module_status_paused">Paused</string>
<string name="yield_module_stop_earning">Disable yield mode</string>
<string name="yield_module_stop_earning_sheet_description">Turning off will withdraw your funds from Aave, return them to %s in your wallet, and stop earning rewards.</string>
<string name="yield_module_stop_earning_sheet_description">Turning this off will withdraw your funds from Aave to %s in your wallet and it will stop earning rewards.</string>
<string name="yield_module_stop_earning_sheet_fee_note">The network fee will be deducted from the amount you withdraw.</string>
<string name="yield_module_supply_apr">Supply APY</string>
<string name="yield_module_token_details_earn_notification_apy">APY</string>
<string name="yield_module_token_details_earn_notification_description">Let your funds work in the background while you stay in control.</string>
<string name="yield_module_token_details_earn_notification_description">Let your funds do the work while you stay in control.</string>
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_subtitle">Interest accrues automatically</string>
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Yield mode</string>
<string name="yield_module_token_details_earn_notification_processing">Processing your deposit</string>
<string name="yield_module_token_details_earn_notification_title">Make your balance work for you</string>
<string name="yield_module_token_details_earn_notification_title">Make your assets work for you</string>
<string name="yield_module_transfer_mode_automatic">Automatic</string>
<string name="yield_module_unable_to_cover_fee_description">Deposit some %1$s %2$s to cover the network fee for transactions</string>
<string name="yield_module_unable_to_cover_fee_title">Unable to cover %s fee</string>
<string name="yield_module_unavailable_subtitle">The interest service isnt available at the moment. Please try again later.</string>
<string name="yield_module_unavailable_title">Earnings unavailable</string>
<string name="yield_module_unavailable_subtitle">The Yield mode service isnt available at the moment. Please try again later.</string>
<string name="yield_module_unavailable_title">Yield mode unavailable</string>
<string name="yield_supply_chart_loading_error">Unable to load chart...</string>
</resources>

View file

@ -6,18 +6,21 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@ -34,6 +37,7 @@ data class SmallButtonConfig(
val onClick: () -> Unit,
val icon: TangemButtonIconPosition = TangemButtonIconPosition.None,
val isEnabled: Boolean = true,
val isLoading: Boolean = false,
)
/**
@ -68,7 +72,7 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier:
label = "Update background color",
)
Row(
Box(
modifier = modifier
.defaultMinSize(
minWidth = TangemTheme.dimens.size46,
@ -79,7 +83,7 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier:
color = backgroundColor,
shape = shape,
)
.clickable(enabled = config.isEnabled, onClick = config.onClick)
.clickable(enabled = !config.isLoading && config.isEnabled, onClick = config.onClick)
.padding(
paddingValues = when (config.icon) {
is TangemButtonIconPosition.None -> PaddingValues(
@ -95,6 +99,17 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier:
)
},
),
contentAlignment = Alignment.Center,
) {
if (config.isLoading) {
CircularProgressIndicator(
strokeWidth = TangemTheme.dimens.size2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier.size(TangemTheme.dimens.size16),
)
}
Row(
modifier = Modifier.conditional(config.isLoading) { alpha(0f) },
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) {
@ -133,6 +148,7 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier:
)
}
}
}
@Composable
private fun RowScope.ContentContainer(
@ -172,6 +188,7 @@ private fun ButtonsSample() {
)
PrimarySmallButton(config = config)
SecondarySmallButton(config = config.copy(text = TextReference.Str(value = "Add")))
SecondarySmallButton(config = config.copy(text = TextReference.Str(value = "Add"), isLoading = true))
SecondarySmallButton(
config = config.copy(
text = TextReference.Str(value = "Rating"),
@ -191,5 +208,12 @@ private fun ButtonsSample() {
isEnabled = false,
),
)
SecondarySmallButton(
config = config.copy(
text = TextReference.Str(value = "Add token"),
icon = TangemButtonIconPosition.Start(iconResId = R.drawable.ic_plus_24),
isLoading = true,
),
)
}
}

View file

@ -22,6 +22,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -133,6 +135,13 @@ fun ActionBaseButton(
}
}
.clip(shape)
.semantics {
contentDescription = if (config.shouldDimContent) {
"Action button is dimmed"
} else {
"Action button is not dimmed"
}
}
.combinedClickable(
enabled = config.isEnabled,
onClick = config.onClick,

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
*/
@Composable
internal fun InputRowAsyncImage(
fun InputRowAsyncImage(
imageUrl: String,
modifier: Modifier = Modifier,
isGrayscale: Boolean = false,

View file

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

View file

@ -27,6 +27,7 @@ import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.components.token.internal.*
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.Subtitle2State
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.rememberHapticFeedback
import com.tangem.core.ui.extensions.stringReference
@ -691,7 +692,10 @@ object AccountItemPreviewData {
value = stringReference("24 tokens"),
isAvailable = false,
),
subtitle2State = null,
subtitle2State = Subtitle2State.PriceChangeContent(
priceChangePercent = "0,43 %",
type = PriceChangeType.UP,
),
onItemClick = {},
onItemLongClick = {},
)

View file

@ -11,7 +11,6 @@ import com.tangem.core.ui.R
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.audits.AuditLabel
import com.tangem.core.ui.components.text.applyBladeBrush
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.extensions.orMaskWithStars
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
@ -31,7 +30,7 @@ internal fun TokenCryptoAmount(
isFlickering = state.isFlickering,
)
}
is TokenItemState.Subtitle2State.LabelContent -> {
is TokenCryptoAmountState.LabelContent -> {
AuditLabel(state = state.auditLabelUM, modifier = modifier)
}
is TokenCryptoAmountState.Unreachable -> {
@ -46,6 +45,15 @@ internal fun TokenCryptoAmount(
is TokenCryptoAmountState.Locked -> {
LockedRectangle(modifier = modifier.placeholderSize())
}
is TokenCryptoAmountState.PriceChangeContent -> {
PriceBlock(
modifier = modifier,
price = null,
type = state.type,
priceChangePercent = state.priceChangePercent,
isFlickering = state.isFlickering,
)
}
null -> Unit
}
}

View file

@ -64,8 +64,8 @@ internal fun TokenPrice(state: TokenPriceState?, modifier: Modifier = Modifier)
}
@Composable
private fun PriceBlock(
price: String,
internal fun PriceBlock(
price: String?,
isFlickering: Boolean,
modifier: Modifier = Modifier,
type: PriceChangeType? = null,
@ -75,6 +75,7 @@ private fun PriceBlock(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
) {
if (price != null) {
PriceText(
modifier = Modifier.weight(weight = 1f, fill = false),
text = price,
@ -82,6 +83,7 @@ private fun PriceBlock(
)
SpacerW6()
}
if (type != null) {
PriceChangeIcon(

View file

@ -230,6 +230,12 @@ sealed class TokenItemState {
val isFlickering: Boolean = false,
) : Subtitle2State()
data class PriceChangeContent(
val priceChangePercent: String,
val type: PriceChangeType,
val isFlickering: Boolean = false,
) : Subtitle2State()
data class LabelContent(val auditLabelUM: AuditLabelUM) : Subtitle2State()
data object Unreachable : Subtitle2State()

View file

@ -20,6 +20,7 @@ object TangemColorPalette {
// region Light
val Light1 = Color(0xFFF5F5F5)
val Light1V2 = Color(0xFFF4F4F4)
val Light2 = Color(0xFFEBEBEB)
val Light3 = Color(0xFFD3D3D3)
val Light4 = Color(0xFFC9C9C9)
@ -27,6 +28,7 @@ object TangemColorPalette {
// endregion Light
// region Green
val Green = Color(0xFF0C9F3D)
val Meadow = Color(0xFF1ACE80)
val MagicMint = Color(0xFFA3EBCC)
val DarkGreen = Color(0xFF06311F)
@ -45,4 +47,9 @@ object TangemColorPalette {
val Tangerine = Color(0xFFFFB71B)
val Mustard = Color(0xFFFDDE55)
// endregion Yellow
// region Overlay
val Overlay1 = Color(0x66000000)
val Overlay2 = Color(0xB2000000)
// endregion Overlay
}

View file

@ -0,0 +1,536 @@
@file:Suppress("LongParameterList")
package com.tangem.core.ui.res
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
@Stable
class TangemColors2 internal constructor(
val text: Text,
val graphic: Graphic,
val button: Button,
val surface: Surface,
val controls: Controls,
val field: Field,
val overlay: Overlay,
val border: Border,
val fill: Fill,
val skeleton: Skeleton,
val markers: Markers,
) {
@Stable
class Text internal constructor(
val neutral: Neutral,
val status: Status,
) {
@Stable
class Neutral internal constructor(
primary: Color,
primaryInverted: Color,
secondary: Color,
tertiary: Color,
primaryInvertedConstant: Color,
) {
var primary by mutableStateOf(primary)
private set
var primaryInverted by mutableStateOf(primaryInverted)
private set
var secondary by mutableStateOf(secondary)
private set
var tertiary by mutableStateOf(tertiary)
private set
var primaryInvertedConstant by mutableStateOf(primaryInvertedConstant)
private set
fun update(other: Neutral) {
primary = other.primary
primaryInverted = other.primaryInverted
secondary = other.secondary
tertiary = other.tertiary
primaryInvertedConstant = other.primaryInvertedConstant
}
}
@Stable
class Status internal constructor(
disabled: Color,
accent: Color,
warning: Color,
attention: Color,
positive: Color,
) {
var disabled by mutableStateOf(disabled)
private set
var accent by mutableStateOf(accent)
private set
var warning by mutableStateOf(warning)
private set
var attention by mutableStateOf(attention)
private set
var positive by mutableStateOf(positive)
private set
fun update(other: Status) {
disabled = other.disabled
accent = other.accent
warning = other.warning
attention = other.attention
positive = other.positive
}
}
fun update(other: Text) {
neutral.update(other.neutral)
status.update(other.status)
}
}
@Stable
class Graphic internal constructor(
val neutral: Neutral,
val status: Status,
) {
@Stable
class Neutral internal constructor(
primary: Color,
primaryInverted: Color,
secondary: Color,
tertiary: Color,
quaternary: Color,
primaryInvertedConstant: Color,
tertiaryConstant: Color,
) {
var primary by mutableStateOf(primary)
private set
var primaryInverted by mutableStateOf(primaryInverted)
private set
var secondary by mutableStateOf(secondary)
private set
var tertiary by mutableStateOf(tertiary)
private set
var quaternary by mutableStateOf(quaternary)
private set
var primaryInvertedConstant by mutableStateOf(primaryInvertedConstant)
private set
var tertiaryConstant by mutableStateOf(tertiaryConstant)
private set
fun update(other: Neutral) {
primary = other.primary
primaryInverted = other.primaryInverted
secondary = other.secondary
tertiary = other.tertiary
quaternary = other.quaternary
primaryInvertedConstant = other.primaryInvertedConstant
tertiaryConstant = other.tertiaryConstant
}
}
@Stable
class Status internal constructor(
accent: Color,
warning: Color,
attention: Color,
) {
var accent by mutableStateOf(accent)
private set
var warning by mutableStateOf(warning)
private set
var attention by mutableStateOf(attention)
private set
fun update(other: Status) {
accent = other.accent
warning = other.warning
attention = other.attention
}
}
fun update(other: Graphic) {
neutral.update(other.neutral)
status.update(other.status)
}
}
@Stable
class Button internal constructor(
backgroundPrimary: Color,
backgroundSecondary: Color,
backgroundDisabled: Color,
backgroundPositive: Color,
textPrimary: Color,
textSecondary: Color,
textDisabled: Color,
iconPrimary: Color,
iconSecondary: Color,
iconDisabled: Color,
borderPrimary: Color,
) {
var backgroundPrimary by mutableStateOf(backgroundPrimary)
private set
var backgroundSecondary by mutableStateOf(backgroundSecondary)
private set
var backgroundDisabled by mutableStateOf(backgroundDisabled)
private set
var backgroundPositive by mutableStateOf(backgroundPositive)
private set
var textPrimary by mutableStateOf(textPrimary)
private set
var textSecondary by mutableStateOf(textSecondary)
private set
var textDisabled by mutableStateOf(textDisabled)
private set
var iconPrimary by mutableStateOf(iconPrimary)
private set
var iconSecondary by mutableStateOf(iconSecondary)
private set
var iconDisabled by mutableStateOf(iconDisabled)
private set
var borderPrimary by mutableStateOf(borderPrimary)
private set
fun update(other: Button) {
backgroundPrimary = other.backgroundPrimary
backgroundSecondary = other.backgroundSecondary
backgroundDisabled = other.backgroundDisabled
backgroundPositive = other.backgroundPositive
textPrimary = other.textPrimary
textSecondary = other.textSecondary
textDisabled = other.textDisabled
iconPrimary = other.iconPrimary
iconSecondary = other.iconSecondary
iconDisabled = other.iconDisabled
borderPrimary = other.borderPrimary
}
}
@Stable
class Surface internal constructor(
level1: Color,
level2: Color,
level3: Color,
level4: Color,
) {
var level1 by mutableStateOf(level1)
private set
var level2 by mutableStateOf(level2)
private set
var level3 by mutableStateOf(level3)
private set
var level4 by mutableStateOf(level4)
private set
fun update(other: Surface) {
level1 = other.level1
level2 = other.level2
level3 = other.level3
level4 = other.level4
}
}
@Stable
class Controls internal constructor(
backgroundDefault: Color,
backgroundChecked: Color,
iconDefault: Color,
iconDisabled: Color,
) {
var backgroundDefault by mutableStateOf(backgroundDefault)
private set
var backgroundChecked by mutableStateOf(backgroundChecked)
private set
var iconDefault by mutableStateOf(iconDefault)
private set
var iconDisabled by mutableStateOf(iconDisabled)
private set
fun update(other: Controls) {
backgroundDefault = other.backgroundDefault
backgroundChecked = other.backgroundChecked
iconDefault = other.iconDefault
iconDisabled = other.iconDisabled
}
}
@Stable
class Field internal constructor(
backgroundDefault: Color,
backgroundFocused: Color,
textPlaceholder: Color,
textDefault: Color,
textDisabled: Color,
iconDefault: Color,
iconDisabled: Color,
textInvalid: Color,
borderInvalid: Color,
) {
var backgroundDefault by mutableStateOf(backgroundDefault)
private set
var backgroundFocused by mutableStateOf(backgroundFocused)
private set
var textPlaceholder by mutableStateOf(textPlaceholder)
private set
var textDefault by mutableStateOf(textDefault)
private set
var textDisabled by mutableStateOf(textDisabled)
private set
var iconDefault by mutableStateOf(iconDefault)
private set
var iconDisabled by mutableStateOf(iconDisabled)
private set
var textInvalid by mutableStateOf(textInvalid)
private set
var borderInvalid by mutableStateOf(borderInvalid)
private set
fun update(other: Field) {
backgroundDefault = other.backgroundDefault
backgroundFocused = other.backgroundFocused
textPlaceholder = other.textPlaceholder
textDefault = other.textDefault
textDisabled = other.textDisabled
iconDefault = other.iconDefault
iconDisabled = other.iconDisabled
textInvalid = other.textInvalid
borderInvalid = other.borderInvalid
}
}
@Stable
class Overlay internal constructor(
overlayPrimary: Color,
overlaySecondary: Color,
) {
var overlayPrimary by mutableStateOf(overlayPrimary)
private set
var overlaySecondary by mutableStateOf(overlaySecondary)
private set
fun update(other: Overlay) {
overlayPrimary = other.overlayPrimary
overlaySecondary = other.overlaySecondary
}
}
@Stable
class Border internal constructor(
val neutral: Neutral,
val status: Status,
) {
@Stable
class Neutral internal constructor(
primary: Color,
secondary: Color,
) {
var primary by mutableStateOf(primary)
private set
var secondary by mutableStateOf(secondary)
private set
fun update(other: Neutral) {
primary = other.primary
secondary = other.secondary
}
}
@Stable
class Status internal constructor(
accent: Color,
warning: Color,
attention: Color,
) {
var accent by mutableStateOf(accent)
private set
var warning by mutableStateOf(warning)
private set
var attention by mutableStateOf(attention)
private set
fun update(other: Status) {
accent = other.accent
warning = other.warning
attention = other.attention
}
}
fun update(other: Border) {
neutral.update(other.neutral)
status.update(other.status)
}
}
@Stable
class Fill internal constructor(
val neutral: Neutral,
val status: Status,
) {
@Stable
class Neutral internal constructor(
primary: Color,
primaryInverted: Color,
primaryInvertedConstant: Color,
secondary: Color,
tertiaryConstant: Color,
quaternary: Color,
) {
var primary by mutableStateOf(primary)
private set
var primaryInverted by mutableStateOf(primaryInverted)
private set
var primaryInvertedConstant by mutableStateOf(primaryInvertedConstant)
private set
var secondary by mutableStateOf(secondary)
private set
var tertiaryConstant by mutableStateOf(tertiaryConstant)
private set
var quaternary by mutableStateOf(quaternary)
private set
fun update(other: Neutral) {
primary = other.primary
primaryInverted = other.primaryInverted
primaryInvertedConstant = other.primaryInvertedConstant
secondary = other.secondary
tertiaryConstant = other.tertiaryConstant
quaternary = other.quaternary
}
}
@Stable
class Status internal constructor(
accent: Color,
warning: Color,
attention: Color,
) {
var accent by mutableStateOf(accent)
private set
var warning by mutableStateOf(warning)
private set
var attention by mutableStateOf(attention)
private set
fun update(other: Status) {
accent = other.accent
warning = other.warning
attention = other.attention
}
}
fun update(other: Fill) {
neutral.update(other.neutral)
status.update(other.status)
}
}
@Stable
class Skeleton internal constructor(
backgroundPrimary: Color,
) {
var backgroundPrimary by mutableStateOf(backgroundPrimary)
private set
fun update(other: Skeleton) {
backgroundPrimary = other.backgroundPrimary
}
}
@Stable
class Markers internal constructor(
backgroundSolidGray: Color,
backgroundDisabled: Color,
backgroundSolidBlue: Color,
textGray: Color,
textDisabled: Color,
iconGray: Color,
iconDisabled: Color,
borderGray: Color,
backgroundTintedBlue: Color,
textBlue: Color,
backgroundSolidRed: Color,
backgroundTintedRed: Color,
iconBlue: Color,
iconRed: Color,
textRed: Color,
backgroundTintedGray: Color,
borderTintedBlue: Color,
borderTintedRed: Color,
) {
var backgroundSolidGray by mutableStateOf(backgroundSolidGray)
private set
var backgroundDisabled by mutableStateOf(backgroundDisabled)
private set
var backgroundSolidBlue by mutableStateOf(backgroundSolidBlue)
private set
var textGray by mutableStateOf(textGray)
private set
var textDisabled by mutableStateOf(textDisabled)
private set
var iconGray by mutableStateOf(iconGray)
private set
var iconDisabled by mutableStateOf(iconDisabled)
private set
var borderGray by mutableStateOf(borderGray)
private set
var backgroundTintedBlue by mutableStateOf(backgroundTintedBlue)
private set
var textBlue by mutableStateOf(textBlue)
private set
var backgroundSolidRed by mutableStateOf(backgroundSolidRed)
private set
var backgroundTintedRed by mutableStateOf(backgroundTintedRed)
private set
var iconBlue by mutableStateOf(iconBlue)
private set
var iconRed by mutableStateOf(iconRed)
private set
var textRed by mutableStateOf(textRed)
private set
var backgroundTintedGray by mutableStateOf(backgroundTintedGray)
private set
var borderTintedBlue by mutableStateOf(borderTintedBlue)
private set
var borderTintedRed by mutableStateOf(borderTintedRed)
private set
fun update(other: Markers) {
backgroundSolidGray = other.backgroundSolidGray
backgroundDisabled = other.backgroundDisabled
backgroundSolidBlue = other.backgroundSolidBlue
textGray = other.textGray
textDisabled = other.textDisabled
iconGray = other.iconGray
iconDisabled = other.iconDisabled
borderGray = other.borderGray
backgroundTintedBlue = other.backgroundTintedBlue
textBlue = other.textBlue
backgroundSolidRed = other.backgroundSolidRed
backgroundTintedRed = other.backgroundTintedRed
iconBlue = other.iconBlue
iconRed = other.iconRed
textRed = other.textRed
backgroundTintedGray = other.backgroundTintedGray
borderTintedBlue = other.borderTintedBlue
borderTintedRed = other.borderTintedRed
}
}
fun update(other: TangemColors2) {
text.update(other.text)
graphic.update(other.graphic)
button.update(other.button)
surface.update(other.surface)
controls.update(other.controls)
field.update(other.field)
overlay.update(other.overlay)
border.update(other.border)
fill.update(other.fill)
skeleton.update(other.skeleton)
markers.update(other.markers)
}
}

View file

@ -138,6 +138,11 @@ object TangemTheme {
@ReadOnlyComposable
get() = LocalTangemColors.current
val colors2: TangemColors2
@Composable
@ReadOnlyComposable
get() = LocalTangemColors2.current
val typography: TangemTypography
@Composable
@ReadOnlyComposable
@ -156,7 +161,7 @@ object TangemTheme {
@Stable
@Composable
private fun tangemColorScheme(colors: TangemColors): ColorScheme {
internal fun tangemColorScheme(colors: TangemColors): ColorScheme {
return ColorScheme(
primary = colors.background.primary,
onPrimary = colors.text.primary1,
@ -206,7 +211,7 @@ private fun tangemColorScheme(colors: TangemColors): ColorScheme {
@Composable
@ReadOnlyComposable
private fun lightThemeColors(): TangemColors {
internal fun lightThemeColors(redesign: Boolean = false): TangemColors {
return TangemColors(
text = TangemColors.Text(
primary1 = TangemColorPalette.Dark6,
@ -233,8 +238,8 @@ private fun lightThemeColors(): TangemColors {
),
background = TangemColors.Background(
primary = TangemColorPalette.White,
secondary = TangemColorPalette.Light1,
tertiary = TangemColorPalette.Light1,
secondary = if (redesign) TangemColorPalette.Light1V2 else TangemColorPalette.Light1,
tertiary = if (redesign) TangemColorPalette.Light1V2 else TangemColorPalette.Light1,
action = TangemColorPalette.White,
),
control = TangemColors.Control(
@ -248,7 +253,7 @@ private fun lightThemeColors(): TangemColors {
transparency = TangemColorPalette.White,
),
field = TangemColors.Field(
primary = TangemColorPalette.Light1,
primary = if (redesign) TangemColorPalette.Light1V2 else TangemColorPalette.Light1,
focused = TangemColorPalette.Light2,
),
overlay = TangemColors.Overlay(
@ -260,7 +265,7 @@ private fun lightThemeColors(): TangemColors {
@Composable
@ReadOnlyComposable
private fun darkThemeColors(): TangemColors {
internal fun darkThemeColors(): TangemColors {
return TangemColors(
text = TangemColors.Text(
primary1 = TangemColorPalette.White,
@ -320,12 +325,16 @@ private val TangemTextSelectionColors: TextSelectionColors
backgroundColor = TangemTheme.colors.text.accent.copy(alpha = 0.3f),
)
private val LocalTangemColors = staticCompositionLocalOf<TangemColors> {
internal val LocalTangemColors = staticCompositionLocalOf<TangemColors> {
error("No TangemColors provided")
}
private val LocalTangemTypography = staticCompositionLocalOf {
TangemTypography()
internal val LocalTangemColors2 = staticCompositionLocalOf<TangemColors2> {
error("No TangemColors2 provided")
}
internal val LocalTangemTypography = staticCompositionLocalOf {
TangemTypography(RobotoFamily)
}
private val LocalTangemDimens = staticCompositionLocalOf {

View file

@ -0,0 +1,309 @@
@file:Suppress("LongMethod")
package com.tangem.core.ui.res
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.*
/**
* Provides additional theming for redesigned components.
* Used together with [TangemTheme].
* @param content Composable content where the theme is applied.
*/
@Composable
fun TangemThemeRedesign(content: @Composable () -> Unit) {
val themeColors = if (LocalIsInDarkTheme.current) darkThemeColors() else lightThemeColors(redesign = true)
val rememberedColors = remember { themeColors }
.also { it.update(themeColors) }
val rootBackgroundColor = rememberedColors.background.secondary
MaterialTheme(
colorScheme = tangemColorScheme(colors = themeColors),
) {
CompositionLocalProvider(
LocalTangemColors provides themeColors,
LocalTangemColors2 provides if (LocalIsInDarkTheme.current) darkThemeColors2() else lightThemeColors2(),
LocalTangemTypography provides TangemTypography(InterFamily),
LocalRootBackgroundColor provides remember(rootBackgroundColor) { mutableStateOf(rootBackgroundColor) },
) {
content()
}
}
}
@Composable
@ReadOnlyComposable
private fun lightThemeColors2(): TangemColors2 {
val text = TangemColors2.Text(
neutral = TangemColors2.Text.Neutral(
primary = TangemColorPalette.Dark6,
primaryInverted = TangemColorPalette.White,
secondary = TangemColorPalette.Dark2,
tertiary = TangemColorPalette.Dark3,
primaryInvertedConstant = TangemColorPalette.White,
),
status = TangemColors2.Text.Status(
disabled = TangemColorPalette.Light4,
accent = TangemColorPalette.Azure,
warning = TangemColorPalette.Amaranth,
attention = TangemColorPalette.Tangerine,
positive = TangemColorPalette.Green,
),
)
val graphic = TangemColors2.Graphic(
neutral = TangemColors2.Graphic.Neutral(
primary = TangemColorPalette.Dark6,
primaryInverted = TangemColorPalette.White,
secondary = TangemColorPalette.Dark2,
tertiary = TangemColorPalette.Dark3,
quaternary = TangemColorPalette.Light4,
primaryInvertedConstant = TangemColorPalette.White,
tertiaryConstant = TangemColorPalette.Dark3,
),
status = TangemColors2.Graphic.Status(
accent = TangemColorPalette.Azure,
warning = TangemColorPalette.Amaranth,
attention = TangemColorPalette.Tangerine,
),
)
val border = TangemColors2.Border(
neutral = TangemColors2.Border.Neutral(
primary = TangemColorPalette.Light3,
secondary = TangemColorPalette.Light5,
),
status = TangemColors2.Border.Status(
accent = TangemColorPalette.Azure,
warning = TangemColorPalette.Amaranth,
attention = TangemColorPalette.Tangerine,
),
)
val overlay = TangemColors2.Overlay(
overlayPrimary = TangemColorPalette.Overlay1,
overlaySecondary = TangemColorPalette.Overlay2,
)
val fill = TangemColors2.Fill(
neutral = TangemColors2.Fill.Neutral(
primary = TangemColorPalette.Dark6,
primaryInverted = TangemColorPalette.White,
primaryInvertedConstant = TangemColorPalette.White,
secondary = TangemColorPalette.Dark3,
tertiaryConstant = TangemColorPalette.Dark3,
quaternary = TangemColorPalette.Light4,
),
status = TangemColors2.Fill.Status(
accent = TangemColorPalette.Azure,
warning = TangemColorPalette.Amaranth,
attention = TangemColorPalette.Tangerine,
),
)
val button = TangemColors2.Button(
backgroundPrimary = TangemColorPalette.Dark6,
backgroundSecondary = TangemColorPalette.Dark6.copy(alpha = 0.1f),
backgroundDisabled = TangemColorPalette.Light3,
backgroundPositive = TangemColorPalette.Azure,
textSecondary = TangemColorPalette.Dark6,
textPrimary = TangemColorPalette.Light2,
textDisabled = text.neutral.tertiary,
iconPrimary = TangemColorPalette.Dark6,
iconSecondary = TangemColorPalette.Light1V2,
iconDisabled = TangemColorPalette.Light2,
borderPrimary = TangemColorPalette.Dark6,
)
val surface = TangemColors2.Surface(
level1 = TangemColorPalette.White,
level2 = TangemColorPalette.Light1V2,
level3 = TangemColorPalette.Light1V2,
level4 = TangemColorPalette.White,
)
val controls = TangemColors2.Controls(
backgroundChecked = TangemColorPalette.Dark6,
backgroundDefault = TangemColorPalette.Light2,
iconDefault = TangemColorPalette.White,
iconDisabled = TangemColorPalette.White,
)
val field = TangemColors2.Field(
backgroundDefault = TangemColorPalette.Light1V2,
backgroundFocused = TangemColorPalette.Light3,
textPlaceholder = text.neutral.secondary,
textDefault = text.neutral.primary,
textDisabled = text.neutral.tertiary,
iconDefault = graphic.neutral.tertiary,
iconDisabled = graphic.neutral.quaternary,
textInvalid = text.status.warning,
borderInvalid = border.status.warning,
)
val skeleton = TangemColors2.Skeleton(
backgroundPrimary = TangemColorPalette.Light1V2,
)
val markers = TangemColors2.Markers(
backgroundSolidGray = TangemColorPalette.Light3,
backgroundDisabled = TangemColorPalette.Light3,
backgroundSolidBlue = TangemColorPalette.Azure,
textGray = TangemColorPalette.Dark2,
textDisabled = text.neutral.tertiary,
iconGray = TangemColorPalette.Dark1,
iconDisabled = TangemColorPalette.Light2,
borderGray = TangemColorPalette.Light3,
backgroundTintedBlue = TangemColorPalette.Azure.copy(alpha = 0.1f),
textBlue = text.status.accent,
backgroundSolidRed = TangemColorPalette.Amaranth,
backgroundTintedRed = TangemColorPalette.Amaranth.copy(alpha = 0.1f),
iconBlue = TangemColorPalette.Azure,
iconRed = TangemColorPalette.Amaranth,
textRed = TangemColorPalette.Amaranth,
backgroundTintedGray = TangemColorPalette.Dark6.copy(alpha = 0.1f),
borderTintedBlue = TangemColorPalette.Azure.copy(alpha = 0.1f),
borderTintedRed = TangemColorPalette.Amaranth.copy(alpha = 0.1f),
)
return TangemColors2(
text = text,
graphic = graphic,
border = border,
overlay = overlay,
fill = fill,
button = button,
surface = surface,
controls = controls,
field = field,
skeleton = skeleton,
markers = markers,
)
}
@Composable
@ReadOnlyComposable
private fun darkThemeColors2(): TangemColors2 {
val text = TangemColors2.Text(
neutral = TangemColors2.Text.Neutral(
primary = TangemColorPalette.White,
primaryInverted = TangemColorPalette.Dark6,
secondary = TangemColorPalette.Light5,
tertiary = TangemColorPalette.Dark1,
primaryInvertedConstant = TangemColorPalette.White,
),
status = TangemColors2.Text.Status(
disabled = TangemColorPalette.Dark3,
accent = TangemColorPalette.Azure,
warning = TangemColorPalette.Flamingo,
attention = TangemColorPalette.Mustard,
positive = TangemColorPalette.Green,
),
)
val graphic = TangemColors2.Graphic(
neutral = TangemColors2.Graphic.Neutral(
primary = TangemColorPalette.White,
primaryInverted = TangemColorPalette.Dark6,
secondary = TangemColorPalette.Light5,
tertiary = TangemColorPalette.Dark3,
quaternary = TangemColorPalette.Dark3,
tertiaryConstant = TangemColorPalette.Dark3,
primaryInvertedConstant = TangemColorPalette.White,
),
status = TangemColors2.Graphic.Status(
accent = TangemColorPalette.Azure,
warning = TangemColorPalette.Flamingo,
attention = TangemColorPalette.Mustard,
),
)
val border = TangemColors2.Border(
neutral = TangemColors2.Border.Neutral(
primary = TangemColorPalette.Dark4,
secondary = TangemColorPalette.Dark4,
),
status = TangemColors2.Border.Status(
accent = TangemColorPalette.Azure,
warning = TangemColorPalette.Flamingo,
attention = TangemColorPalette.Mustard,
),
)
val overlay = TangemColors2.Overlay(
overlayPrimary = TangemColorPalette.Overlay1,
overlaySecondary = TangemColorPalette.Overlay2,
)
val fill = TangemColors2.Fill(
neutral = TangemColors2.Fill.Neutral(
primary = TangemColorPalette.White,
primaryInverted = TangemColorPalette.Dark6,
primaryInvertedConstant = TangemColorPalette.White,
secondary = TangemColorPalette.Light5,
tertiaryConstant = TangemColorPalette.Dark1,
quaternary = TangemColorPalette.Dark3,
),
status = TangemColors2.Fill.Status(
accent = TangemColorPalette.Azure,
warning = TangemColorPalette.Flamingo,
attention = TangemColorPalette.Mustard,
),
)
val button = TangemColors2.Button(
backgroundPrimary = TangemColorPalette.Light1V2,
backgroundSecondary = TangemColorPalette.White.copy(alpha = 0.1f),
backgroundDisabled = TangemColorPalette.Dark5,
backgroundPositive = TangemColorPalette.Azure,
textSecondary = TangemColorPalette.Light4,
textPrimary = TangemColorPalette.Dark4,
textDisabled = text.neutral.secondary,
iconPrimary = TangemColorPalette.Light4,
iconSecondary = TangemColorPalette.Dark4,
iconDisabled = TangemColorPalette.Dark5,
borderPrimary = TangemColorPalette.Light4,
)
val surface = TangemColors2.Surface(
level1 = TangemColorPalette.Dark6,
level2 = TangemColorPalette.Black,
level3 = TangemColorPalette.Dark6,
level4 = TangemColorPalette.Dark5,
)
val controls = TangemColors2.Controls(
backgroundChecked = TangemColorPalette.Azure,
backgroundDefault = TangemColorPalette.Dark4,
iconDefault = TangemColorPalette.White,
iconDisabled = TangemColorPalette.White,
)
val field = TangemColors2.Field(
backgroundDefault = TangemColorPalette.Dark6,
backgroundFocused = TangemColorPalette.Dark4,
textPlaceholder = text.neutral.secondary,
textDefault = text.neutral.primary,
textDisabled = text.neutral.tertiary,
iconDefault = graphic.neutral.tertiary,
iconDisabled = graphic.neutral.quaternary,
textInvalid = text.status.warning,
borderInvalid = border.status.warning,
)
val skeleton = TangemColors2.Skeleton(
backgroundPrimary = TangemColorPalette.Dark5,
)
val markers = TangemColors2.Markers(
backgroundSolidGray = TangemColorPalette.Dark5,
backgroundDisabled = TangemColorPalette.Dark5,
backgroundSolidBlue = TangemColorPalette.Azure,
textGray = TangemColorPalette.Light4,
textDisabled = text.neutral.secondary,
iconGray = TangemColorPalette.Dark2,
iconDisabled = TangemColorPalette.Dark5,
borderGray = TangemColorPalette.White.copy(alpha = 0.2f),
backgroundTintedBlue = TangemColorPalette.Azure.copy(alpha = 0.1f),
textBlue = text.status.accent,
backgroundSolidRed = TangemColorPalette.Amaranth,
backgroundTintedRed = TangemColorPalette.Amaranth.copy(alpha = 0.1f),
iconBlue = TangemColorPalette.Azure,
iconRed = TangemColorPalette.Flamingo,
textRed = TangemColorPalette.Flamingo,
backgroundTintedGray = TangemColorPalette.White.copy(alpha = 0.1f),
borderTintedBlue = TangemColorPalette.Azure.copy(alpha = 0.1f),
borderTintedRed = TangemColorPalette.Amaranth.copy(alpha = 0.1f),
)
return TangemColors2(
text = text,
graphic = graphic,
border = border,
overlay = overlay,
fill = fill,
button = button,
surface = surface,
controls = controls,
field = field,
skeleton = skeleton,
markers = markers,
)
}

View file

@ -4,6 +4,7 @@ import androidx.compose.runtime.Immutable
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.LineHeightStyle
import androidx.compose.ui.unit.TextUnit
@ -11,15 +12,22 @@ import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.sp
import com.tangem.core.ui.R
private val RobotoFamily = FontFamily(
internal val RobotoFamily = FontFamily(
Font(R.font.roboto_regular, FontWeight.Normal),
Font(R.font.roboto_medium, FontWeight.Medium),
)
internal val InterFamily = FontFamily(
Font(R.font.inter_regular),
Font(R.font.inter_italic, style = FontStyle.Italic),
)
@Immutable
data class TangemTypography internal constructor(
class TangemTypography internal constructor(
fontFamily: FontFamily,
) {
val head: TextStyle = TextStyle(
fontFamily = RobotoFamily,
fontFamily = fontFamily,
fontSize = 34.sp,
fontWeight = FontWeight.SemiBold,
letterSpacing = TextUnit(value = 0f, type = TextUnitType.Sp),
@ -28,9 +36,9 @@ data class TangemTypography internal constructor(
alignment = LineHeightStyle.Alignment.Center,
trim = LineHeightStyle.Trim.None,
),
),
)
val h1: TextStyle = TextStyle(
fontFamily = RobotoFamily,
fontFamily = fontFamily,
fontSize = 34.sp,
fontWeight = FontWeight.Normal,
letterSpacing = TextUnit(value = 0f, type = TextUnitType.Sp),
@ -39,9 +47,9 @@ data class TangemTypography internal constructor(
alignment = LineHeightStyle.Alignment.Center,
trim = LineHeightStyle.Trim.None,
),
),
)
val h2: TextStyle = TextStyle(
fontFamily = RobotoFamily,
fontFamily = fontFamily,
fontSize = 24.sp,
fontWeight = FontWeight.Medium,
letterSpacing = TextUnit(value = 0.18f, type = TextUnitType.Sp),
@ -50,9 +58,9 @@ data class TangemTypography internal constructor(
alignment = LineHeightStyle.Alignment.Center,
trim = LineHeightStyle.Trim.None,
),
),
)
val h3: TextStyle = TextStyle(
fontFamily = RobotoFamily,
fontFamily = fontFamily,
fontSize = 20.sp,
fontWeight = FontWeight.Medium,
letterSpacing = TextUnit(value = 0.15f, type = TextUnitType.Sp),
@ -61,9 +69,9 @@ data class TangemTypography internal constructor(
alignment = LineHeightStyle.Alignment.Center,
trim = LineHeightStyle.Trim.None,
),
),
)
val subtitle1: TextStyle = TextStyle(
fontFamily = RobotoFamily,
fontFamily = fontFamily,
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
letterSpacing = TextUnit(value = 0.15f, type = TextUnitType.Sp),
@ -72,9 +80,9 @@ data class TangemTypography internal constructor(
alignment = LineHeightStyle.Alignment.Center,
trim = LineHeightStyle.Trim.None,
),
),
)
val subtitle2: TextStyle = TextStyle(
fontFamily = RobotoFamily,
fontFamily = fontFamily,
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
letterSpacing = TextUnit(value = 0.1f, type = TextUnitType.Sp),
@ -83,9 +91,9 @@ data class TangemTypography internal constructor(
alignment = LineHeightStyle.Alignment.Center,
trim = LineHeightStyle.Trim.None,
),
),
)
val body1: TextStyle = TextStyle(
fontFamily = RobotoFamily,
fontFamily = fontFamily,
fontSize = 16.sp,
fontWeight = FontWeight.Normal,
letterSpacing = TextUnit(value = 0.5f, type = TextUnitType.Sp),
@ -94,9 +102,9 @@ data class TangemTypography internal constructor(
alignment = LineHeightStyle.Alignment.Center,
trim = LineHeightStyle.Trim.None,
),
),
)
val body2: TextStyle = TextStyle(
fontFamily = RobotoFamily,
fontFamily = fontFamily,
fontSize = 14.sp,
fontWeight = FontWeight.Normal,
letterSpacing = TextUnit(value = 0.25f, type = TextUnitType.Sp),
@ -105,9 +113,9 @@ data class TangemTypography internal constructor(
alignment = LineHeightStyle.Alignment.Center,
trim = LineHeightStyle.Trim.None,
),
),
)
val button: TextStyle = TextStyle(
fontFamily = RobotoFamily,
fontFamily = fontFamily,
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
letterSpacing = TextUnit(value = 0.1f, type = TextUnitType.Sp),
@ -116,9 +124,9 @@ data class TangemTypography internal constructor(
alignment = LineHeightStyle.Alignment.Center,
trim = LineHeightStyle.Trim.None,
),
),
)
val caption1: TextStyle = TextStyle(
fontFamily = RobotoFamily,
fontFamily = fontFamily,
fontSize = 12.sp,
fontWeight = FontWeight.Medium,
letterSpacing = TextUnit(value = 0.4f, type = TextUnitType.Sp),
@ -127,9 +135,9 @@ data class TangemTypography internal constructor(
alignment = LineHeightStyle.Alignment.Center,
trim = LineHeightStyle.Trim.None,
),
),
)
val caption2: TextStyle = TextStyle(
fontFamily = RobotoFamily,
fontFamily = fontFamily,
fontSize = 12.sp,
fontWeight = FontWeight.Normal,
letterSpacing = TextUnit(value = 0.4f, type = TextUnitType.Sp),
@ -138,9 +146,9 @@ data class TangemTypography internal constructor(
alignment = LineHeightStyle.Alignment.Center,
trim = LineHeightStyle.Trim.None,
),
),
)
val overline: TextStyle = TextStyle(
fontFamily = RobotoFamily,
fontFamily = fontFamily,
fontSize = 10.sp,
fontWeight = FontWeight.Medium,
letterSpacing = TextUnit(value = 1.5f, type = TextUnitType.Sp),
@ -149,5 +157,5 @@ data class TangemTypography internal constructor(
alignment = LineHeightStyle.Alignment.Center,
trim = LineHeightStyle.Trim.None,
),
),
)
}

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

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>

Binary file not shown.

Binary file not shown.

View file

@ -21,7 +21,7 @@ object TangemBlogUrlBuilder {
const val RESOURCE_TO_LEARN_ABOUT_APPROVING_IN_SWAP = "https://tangem.com/en/blog/post/give-revoke-permission/"
const val YIELD_SUPPLY_HOW_IT_WORKS_URL = "https://tangem.com/en/blog/post/savings-account"
const val YIELD_SUPPLY_HOW_IT_WORKS_URL = "https://tangem.com/en/blog/post/yield-mode"
const val YIELD_SUPPLY_TOS_URL = "https://aave.com/terms-of-service"
const val YIELD_SUPPLY_PRIVACY_URL = "https://aave.com/privacy-policy"
}

View file

@ -29,4 +29,3 @@ class TestingCoroutineDispatcherProvider(
override val default: CoroutineDispatcher = Dispatchers.Unconfined,
override val single: CoroutineDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher(),
) : CoroutineDispatcherProvider

View file

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

View file

@ -29,7 +29,7 @@ internal class AccountListConverter @AssistedInject constructor(
override fun convert(value: GetWalletAccountsResponse): AccountList {
return AccountList(
userWalletId = userWallet.walletId,
accounts = value.accounts.map(cryptoPortfolioConverter::convert).toSet(),
accounts = value.accounts.map(cryptoPortfolioConverter::convert),
totalAccounts = value.wallet.totalAccounts,
sortType = TokensSortTypeConverter.convert(value.wallet.sort),
groupType = TokensGroupTypeConverter.convert(value.wallet.group),

View file

@ -36,6 +36,7 @@ internal class CryptoPortfolioConverter @AssistedInject constructor(
responseCryptoCurrenciesFactory.createCurrencies(
tokens = tokens,
userWallet = userWallet,
accountIndex = value.derivationIndex.toDerivationIndex(),
).toSet()
} else {
emptySet()

View file

@ -10,9 +10,9 @@ import com.tangem.data.account.store.ArchivedAccountsStoreFactory
import com.tangem.data.account.tokens.DefaultMainAccountTokensMigration
import com.tangem.data.common.account.WalletAccountsFetcher
import com.tangem.data.common.account.WalletAccountsSaver
import com.tangem.data.common.cache.etag.ETagsStore
import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.datastore.RuntimeStateStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.repository.AccountsCRUDRepository
@ -42,7 +42,6 @@ internal object AccountDataModule {
accountsResponseStoreFactory: AccountsResponseStoreFactory,
userWalletsStore: UserWalletsStore,
userTokensSaver: UserTokensSaver,
eTagsStore: ETagsStore,
accountConverterFactoryContainer: AccountConverterFactoryContainer,
dispatchers: CoroutineDispatcherProvider,
): AccountsCRUDRepository {
@ -53,7 +52,7 @@ internal object AccountDataModule {
archivedAccountsStoreFactory = ArchivedAccountsStoreFactory,
userWalletsStore = userWalletsStore,
userTokensSaver = userTokensSaver,
eTagsStore = eTagsStore,
archivedAccountsETagStore = RuntimeStateStore(emptyMap()),
convertersContainer = accountConverterFactoryContainer,
dispatchers = dispatchers,
)

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
}

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