diff --git a/common/test/src/main/java/com/tangem/common/test/domain/token/MockCryptoCurrencyFactory.kt b/common/test/src/main/java/com/tangem/common/test/domain/token/MockCryptoCurrencyFactory.kt
index ee2579d3ca..c56e3718ef 100644
--- a/common/test/src/main/java/com/tangem/common/test/domain/token/MockCryptoCurrencyFactory.kt
+++ b/common/test/src/main/java/com/tangem/common/test/domain/token/MockCryptoCurrencyFactory.kt
@@ -65,6 +65,10 @@ class MockCryptoCurrencyFactory(private val userWallet: UserWallet.Cold = defaul
hasFiatFeeRate = blockchain.feePaidCurrency() !is FeePaidCurrency.FeeResource,
canHandleTokens = false,
transactionExtrasType = Network.TransactionExtrasType.NONE,
+ nameResolvingType = when (blockchain) {
+ Blockchain.Ethereum, Blockchain.EthereumTestnet -> Network.NameResolvingType.ENS
+ else -> Network.NameResolvingType.NONE
+ },
)
return factory.createCoin(network = network)
@@ -95,6 +99,7 @@ class MockCryptoCurrencyFactory(private val userWallet: UserWallet.Cold = defaul
hasFiatFeeRate = true,
canHandleTokens = true,
transactionExtrasType = Network.TransactionExtrasType.NONE,
+ nameResolvingType = Network.NameResolvingType.NONE,
),
name = "NEVER-MIND",
symbol = "NEVER-MIND",
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index 2ee799d6f3..8623c22b55 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -64,6 +64,10 @@
Add Wallet
Select a wallet to log in
Welcome back!
+
+ - %d card
+ - %d cards
+
Phone Wallet
You successfully backed up your wallet.
These words can’t be recovered if lost. Make sure to keep it somewhere secure.
@@ -866,6 +870,7 @@
%1$s, %2$s
Destination Tag
Enter address
+ Enter ENS name or address
Address is the same as wallet address
Minimum amount is %s
Minimum change is %s
@@ -947,11 +952,15 @@
Invalid amount
Fee exceeds balance
Total amount exceeds balance
+ Are you sure you want to change the token? After changing, previous data will be reset.
+ Changing token
Swap and send
Send any token, and we’ll convert it on the way. Your recipient gets exactly what they need—seamlessly.
Recipient will receive
Will be sent a recipient
Amount to receive
+ Are you sure you want to cancel the conversion? After changing, previous data will be reset.
+ Confirm cancelation
Send with swap
Transaction sent
Prepare to scan card or ring you want to set up.
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt
index b48f8fd849..418341d870 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt
@@ -10,6 +10,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment.Companion.CenterEnd
@@ -19,6 +20,7 @@ import androidx.compose.ui.draw.clip
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.core.ui.R
import com.tangem.core.ui.components.buttons.small.TangemIconButton
import com.tangem.core.ui.components.fields.SimpleTextField
@@ -69,6 +71,7 @@ fun InputRowRecipient(
showDivider: Boolean = false,
isLoading: Boolean = false,
isValuePasted: Boolean = false,
+ resolvedAddress: String? = null,
) {
val (titleText, color) = if (isError && error != null) {
error to TangemTheme.colors.text.warning
@@ -144,11 +147,15 @@ fun InputRowRecipient(
} else {
TangemTheme.colors.text.primary2
},
- modifier = Modifier
- .padding(start = TangemTheme.dimens.spacing8),
+ modifier = Modifier.padding(start = TangemTheme.dimens.spacing8),
)
}
}
+
+ ResolvedAddressRow(
+ isLoading = isLoading,
+ resolvedAddress = resolvedAddress,
+ )
}
}
}
@@ -203,6 +210,38 @@ private fun RowScope.InputIcon(isLoading: Boolean, value: String) {
}
}
+@Composable
+private fun ResolvedAddressRow(isLoading: Boolean, resolvedAddress: String?) {
+ AnimatedContent(
+ targetState = if (resolvedAddress.isNullOrBlank() || isLoading) {
+ ResolvedState.Hide
+ } else {
+ ResolvedState.Show(resolvedAddress)
+ },
+ label = "Resolved Address",
+ ) { state ->
+ if (state is ResolvedState.Show) {
+ Column {
+ HorizontalDivider(
+ thickness = 0.5.dp,
+ modifier = Modifier.padding(top = 12.dp, bottom = 12.dp),
+ color = TangemTheme.colors.stroke.primary,
+ )
+ Text(
+ text = state.address,
+ style = TangemTheme.typography.caption2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ }
+ }
+ }
+}
+
+private sealed interface ResolvedState {
+ data object Hide : ResolvedState
+ data class Show(val address: String) : ResolvedState
+}
+
//region preview
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@@ -224,6 +263,7 @@ private fun InputRowRecipientPreview(
onQrCodeClick = {},
modifier = Modifier.background(TangemTheme.colors.background.primary),
isRedesignEnabled = false,
+ resolvedAddress = value.resolvedAddress,
)
}
}
@@ -232,6 +272,7 @@ private data class InputRowRecipientPreviewData(
val value: String,
val isError: Boolean,
val isLoading: Boolean = false,
+ val resolvedAddress: String? = null,
)
private class InputRowRecipientPreviewDataProvider : PreviewParameterProvider {
@@ -250,6 +291,12 @@ private class InputRowRecipientPreviewDataProvider : PreviewParameterProvider Network.NameResolvingType.ENS
+ else -> Network.NameResolvingType.NONE
+ }
+ }
+
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun createNetworkStandardType(blockchain: Blockchain) = getNetworkStandardType(blockchain)
diff --git a/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiYieldBalanceFetcher.kt b/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiYieldBalanceFetcher.kt
index 2e21a957da..28f27f5139 100644
--- a/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiYieldBalanceFetcher.kt
+++ b/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiYieldBalanceFetcher.kt
@@ -149,7 +149,9 @@ internal class DefaultMultiYieldBalanceFetcher @Inject constructor(
// TODO: in the future, consider optimizing this part
.chunked(size = 15) // StakeKitApi limitation: no more than 15 requests at the same time
.map {
- async(dispatchers.io) { stakeKitApi.getMultipleYieldBalances(it).bind() }
+ async(dispatchers.io) {
+ stakeKitApi.getMultipleYieldBalances(it).bind()
+ }
}
.awaitAll()
.flatten()
diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/network/Network.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/network/Network.kt
index 95d2ea6ad0..a955b53e54 100644
--- a/domain/models/src/main/kotlin/com/tangem/domain/models/network/Network.kt
+++ b/domain/models/src/main/kotlin/com/tangem/domain/models/network/Network.kt
@@ -20,6 +20,7 @@ import kotlinx.serialization.Serializable
* currency (for those blockchains that have FeeResource instead of a standard type of fee)
* @property canHandleTokens indicates whether the network can handle tokens
* @property transactionExtrasType the type of extras supported for sending a transaction
+ * @property nameResolvingType the type of on-chain name resolution supported by the network (e.g., ENS, SNS etc)
*/
@Serializable
data class Network(
@@ -33,6 +34,7 @@ data class Network(
val hasFiatFeeRate: Boolean,
val canHandleTokens: Boolean,
val transactionExtrasType: TransactionExtrasType,
+ val nameResolvingType: NameResolvingType,
) {
/** Raw ID */
@@ -161,4 +163,16 @@ data class Network(
-> true
}
}
+
+ /**
+ * Represents the type of on-chain name resolution supported by the network.
+ */
+ enum class NameResolvingType {
+
+ /** No name resolution supported */
+ NONE,
+
+ /** Ethereum Name Service (ENS) */
+ ENS,
+ }
}
\ No newline at end of file
diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt
index a717323abe..8f3529b210 100644
--- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt
+++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt
@@ -24,6 +24,7 @@ internal object MockNetworks {
hasFiatFeeRate = true,
canHandleTokens = true,
transactionExtrasType = Network.TransactionExtrasType.NONE,
+ nameResolvingType = Network.NameResolvingType.NONE,
)
val network2 = Network(
@@ -37,6 +38,7 @@ internal object MockNetworks {
hasFiatFeeRate = true,
canHandleTokens = true,
transactionExtrasType = Network.TransactionExtrasType.NONE,
+ nameResolvingType = Network.NameResolvingType.NONE,
)
val network3 = Network(
@@ -50,6 +52,7 @@ internal object MockNetworks {
hasFiatFeeRate = true,
canHandleTokens = true,
transactionExtrasType = Network.TransactionExtrasType.NONE,
+ nameResolvingType = Network.NameResolvingType.NONE,
)
val verifiedNetworksStatuses: NonEmptySet
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenSelectorComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenSelectorComponent.kt
index 80da5da487..eca3ff5cc5 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenSelectorComponent.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenSelectorComponent.kt
@@ -65,6 +65,7 @@ internal class PreviewCustomTokenSelectorComponent(
hasFiatFeeRate = false,
canHandleTokens = false,
transactionExtrasType = Network.TransactionExtrasType.NONE,
+ nameResolvingType = Network.NameResolvingType.NONE,
),
name = "Network $index",
type = "N$index",
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt
index 55ac0bba8f..33f07fbef6 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt
@@ -163,6 +163,7 @@ internal class PreviewManageTokensComponent(
hasFiatFeeRate = false,
canHandleTokens = false,
transactionExtrasType = Network.TransactionExtrasType.NONE,
+ nameResolvingType = Network.NameResolvingType.NONE,
),
name = "NETWORK$networkIndex",
type = "N$networkIndex",
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewOnboardingManageTokensComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewOnboardingManageTokensComponent.kt
index bc1276188d..28da1876e7 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewOnboardingManageTokensComponent.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewOnboardingManageTokensComponent.kt
@@ -104,6 +104,7 @@ internal class PreviewOnboardingManageTokensComponent(
hasFiatFeeRate = false,
canHandleTokens = false,
transactionExtrasType = Network.TransactionExtrasType.NONE,
+ nameResolvingType = Network.NameResolvingType.NONE,
),
name = "NETWORK$networkIndex",
type = "N$networkIndex",
diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationInitialStateTransformer.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationInitialStateTransformer.kt
index e2cbe95c33..39cb1fd77b 100644
--- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationInitialStateTransformer.kt
+++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationInitialStateTransformer.kt
@@ -22,6 +22,10 @@ internal class SendDestinationInitialStateTransformer(
Network.TransactionExtrasType.MEMO -> R.string.send_extras_hint_memo
Network.TransactionExtrasType.DESTINATION_TAG -> R.string.send_destination_tag_field
}
+ val placeholder = when (cryptoCurrency.network.nameResolvingType) {
+ Network.NameResolvingType.NONE -> resourceReference(R.string.send_enter_address_field)
+ Network.NameResolvingType.ENS -> resourceReference(R.string.send_enter_address_field_ens)
+ }
return DestinationUM.Content(
isPrimaryButtonEnabled = false,
isInitialized = isInitialized,
@@ -32,7 +36,7 @@ internal class SendDestinationInitialStateTransformer(
keyboardType = KeyboardType.Text,
),
error = null,
- placeholder = resourceReference(R.string.send_enter_address_field),
+ placeholder = placeholder,
label = resourceReference(R.string.send_recipient),
isValuePasted = false,
),
diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/DestinationBlock.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/DestinationBlock.kt
index bb84ed1e1e..e1c1b5da9f 100644
--- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/DestinationBlock.kt
+++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/DestinationBlock.kt
@@ -76,11 +76,21 @@ private fun AddressBlock(address: DestinationTextFieldUM.RecipientAddress) {
.clip(RoundedCornerShape(TangemTheme.dimens.radius18))
.background(TangemTheme.colors.background.tertiary),
)
- Text(
- text = address.value,
- style = TangemTheme.typography.body2,
- color = TangemTheme.colors.text.primary1,
- )
+ Column(modifier = Modifier.weight(1f)) {
+ Text(
+ text = address.value,
+ style = TangemTheme.typography.body2,
+ color = TangemTheme.colors.text.primary1,
+ )
+ val blockchainAddress = address.blockchainAddress
+ if (!blockchainAddress.isNullOrBlank()) {
+ Text(
+ text = blockchainAddress,
+ style = TangemTheme.typography.caption2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ }
+ }
}
}
@@ -120,12 +130,21 @@ private fun AddressWithMemoBlock(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24),
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
) {
- Text(
- modifier = Modifier.weight(1f),
- text = address.value,
- style = TangemTheme.typography.body2,
- color = TangemTheme.colors.text.primary1,
- )
+ Column(modifier = Modifier.weight(1f)) {
+ Text(
+ text = address.value,
+ style = TangemTheme.typography.body2,
+ color = TangemTheme.colors.text.primary1,
+ )
+ val blockchainAddress = address.blockchainAddress
+ if (!blockchainAddress.isNullOrBlank()) {
+ Text(
+ text = blockchainAddress,
+ style = TangemTheme.typography.caption2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ }
+ }
IdentIcon(
address = address.value,
modifier = Modifier
@@ -134,6 +153,7 @@ private fun AddressWithMemoBlock(
.background(TangemTheme.colors.background.tertiary),
)
}
+
if (memo != null && memo.value.isNotBlank()) {
Text(
text = stringResourceSafe(R.string.send_memo, memo.value),
@@ -162,64 +182,51 @@ private fun DestinationBlockPreview(
}
private class DestinationBlockPreviewProvider : PreviewParameterProvider {
+ val previewItem = DestinationUM.Content(
+ isPrimaryButtonEnabled = true,
+ addressTextField = DestinationTextFieldUM.RecipientAddress(
+ value = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE",
+ keyboardOptions = KeyboardOptions.Default,
+ placeholder = TextReference.Str("Enter address"),
+ label = TextReference.Str("Recipient Address"),
+ isError = false,
+ error = null,
+ isValuePasted = false,
+ ),
+ memoTextField = DestinationTextFieldUM.RecipientMemo(
+ value = "Test memo for transaction",
+ keyboardOptions = KeyboardOptions.Default,
+ placeholder = TextReference.Str("Enter memo (optional)"),
+ label = TextReference.Str("Memo"),
+ isError = false,
+ error = null,
+ disabledText = TextReference.Str("Memo disabled"),
+ isEnabled = true,
+ isValuePasted = false,
+ ),
+ recent = emptyList().toImmutableList(),
+ wallets = emptyList().toImmutableList(),
+ networkName = "Ethereum",
+ isValidating = false,
+ isInitialized = true,
+ isRedesignEnabled = false,
+ )
+
override val values: Sequence
get() = sequenceOf(
- DestinationUM.Content(
- isPrimaryButtonEnabled = true,
- addressTextField = DestinationTextFieldUM.RecipientAddress(
- value = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE",
- keyboardOptions = KeyboardOptions.Default,
- placeholder = TextReference.Str("Enter address"),
- label = TextReference.Str("Recipient Address"),
- isError = false,
- error = null,
- isValuePasted = false,
+ previewItem,
+ previewItem.copy(
+ addressTextField = previewItem.addressTextField.copy(
+ value = "vitalik.eth",
+ blockchainAddress = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE",
),
- memoTextField = DestinationTextFieldUM.RecipientMemo(
- value = "Test memo for transaction",
- keyboardOptions = KeyboardOptions.Default,
- placeholder = TextReference.Str("Enter memo (optional)"),
- label = TextReference.Str("Memo"),
- isError = false,
- error = null,
- disabledText = TextReference.Str("Memo disabled"),
- isEnabled = true,
- isValuePasted = false,
- ),
- recent = emptyList().toImmutableList(),
- wallets = emptyList().toImmutableList(),
- networkName = "Ethereum",
- isValidating = false,
- isInitialized = true,
- isRedesignEnabled = false,
),
- DestinationUM.Content(
- isPrimaryButtonEnabled = true,
- addressTextField = DestinationTextFieldUM.RecipientAddress(
- value = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE",
- keyboardOptions = KeyboardOptions.Default,
- placeholder = TextReference.Str("Enter address"),
- label = TextReference.Str("Recipient Address"),
- isError = false,
- error = null,
- isValuePasted = false,
+ previewItem.copy(isRedesignEnabled = true),
+ previewItem.copy(
+ addressTextField = previewItem.addressTextField.copy(
+ value = "vitalik.eth",
+ blockchainAddress = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE",
),
- memoTextField = DestinationTextFieldUM.RecipientMemo(
- value = "Test memo for transaction",
- keyboardOptions = KeyboardOptions.Default,
- placeholder = TextReference.Str("Enter memo (optional)"),
- label = TextReference.Str("Memo"),
- isError = false,
- error = null,
- disabledText = TextReference.Str("Memo disabled"),
- isEnabled = true,
- isValuePasted = false,
- ),
- recent = emptyList().toImmutableList(),
- wallets = emptyList().toImmutableList(),
- networkName = "Ethereum",
- isValidating = false,
- isInitialized = true,
isRedesignEnabled = true,
),
)
diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt
index 31966f7310..da247fa248 100644
--- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt
+++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt
@@ -142,6 +142,7 @@ private fun LazyListScope.addressItem(
color = TangemTheme.colors.background.action,
shape = TangemTheme.shapes.roundedCornersXMedium,
),
+ resolvedAddress = address.blockchainAddress,
)
}
}
diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountContentPreview.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountContentPreview.kt
index b40ecc6c80..d7fd561378 100644
--- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountContentPreview.kt
+++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountContentPreview.kt
@@ -39,6 +39,7 @@ internal data object SwapAmountContentPreview {
hasFiatFeeRate = false,
canHandleTokens = false,
transactionExtrasType = Network.TransactionExtrasType.NONE,
+ nameResolvingType = Network.NameResolvingType.NONE,
),
name = "Bitcoin",
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheetStateProvider.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheetStateProvider.kt
index 8e123b32df..fc4a214ee0 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheetStateProvider.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheetStateProvider.kt
@@ -107,5 +107,6 @@ class ExpressStatusBottomSheetStateProvider : PreviewParameterProvider