diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 43dc8567c8..d77020adb5 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -433,6 +433,14 @@
+
+
+
+
+
diff --git a/app/src/main/java/com/tangem/tap/data/DefaultOfframpRepository.kt b/app/src/main/java/com/tangem/tap/data/DefaultOfframpRepository.kt
index bce83f2ec7..368f58cf49 100644
--- a/app/src/main/java/com/tangem/tap/data/DefaultOfframpRepository.kt
+++ b/app/src/main/java/com/tangem/tap/data/DefaultOfframpRepository.kt
@@ -4,6 +4,7 @@ import androidx.datastore.core.DataStore
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.offramp.model.PendingOfframp
+import com.tangem.domain.offramp.model.PendingOfframp.Companion.EXPIRY_MS
import com.tangem.domain.offramp.repository.OfframpRepository
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
import com.tangem.tap.data.converter.PendingOfframpEntryConverter
@@ -59,7 +60,7 @@ internal class DefaultOfframpRepository(
requestId
}
- override suspend fun consumePendingOfframp(
+ override suspend fun resolvePendingOfframp(
requestId: String,
userWalletId: UserWalletId,
currencyId: String,
@@ -73,9 +74,9 @@ internal class DefaultOfframpRepository(
entry.currencyId == currencyId &&
!entry.isExpired(now)
}
- // Remove only the fully-matched record (single-use); always prune expired ones. A request_id that
- // matches but with a mismatched wallet/currency is left intact so a tampered redirect cannot burn it.
- stored.filter { it != matched }.filterNotExpired(now)
+ // Keep the matched record so the same redirect can be followed again until it expires; only prune the
+ // expired ones. The record is dropped naturally once it ages past EXPIRY_MS.
+ stored.filterNotExpired(now)
}
matched?.let(converter::convert)
}
@@ -84,8 +85,10 @@ internal class DefaultOfframpRepository(
pendingOfframpStore.data.first().map(converter::convert)
}
+ // Returns the same instance when nothing is expired, so DataStore.updateData sees an unchanged value and skips
+ // both the extra allocation and the write.
private fun List.filterNotExpired(now: Long): List =
- filterNot { it.isExpired(now) }
+ if (none { now - it.createdAt >= EXPIRY_MS }) this else filter { now - it.createdAt < EXPIRY_MS }
private fun PendingOfframpEntry.isExpired(now: Long): Boolean = converter.convert(this).isExpired(now)
}
\ No newline at end of file
diff --git a/app/src/test/kotlin/com/tangem/tap/data/DefaultOfframpRepositoryTest.kt b/app/src/test/kotlin/com/tangem/tap/data/DefaultOfframpRepositoryTest.kt
index 99151c5a99..0bd4d697cb 100644
--- a/app/src/test/kotlin/com/tangem/tap/data/DefaultOfframpRepositoryTest.kt
+++ b/app/src/test/kotlin/com/tangem/tap/data/DefaultOfframpRepositoryTest.kt
@@ -156,13 +156,13 @@ internal class DefaultOfframpRepositoryTest {
}
@Test
- fun `GIVEN registered pending offramp WHEN consume with matching wallet and currency THEN returns record`() =
+ fun `GIVEN registered pending offramp WHEN resolve with matching wallet and currency THEN returns record`() =
runTest {
// Arrange
val storedRequestId = repository.registerPendingOfframp(userWalletId, currencyId)
// Act
- val pending = repository.consumePendingOfframp(storedRequestId, userWalletId, currencyId)
+ val pending = repository.resolvePendingOfframp(storedRequestId, userWalletId, currencyId)
// Assert
assertThat(pending).isNotNull()
@@ -172,35 +172,36 @@ internal class DefaultOfframpRepositoryTest {
}
@Test
- fun `GIVEN unknown request id WHEN consume THEN returns null`() = runTest {
+ fun `GIVEN unknown request id WHEN resolve THEN returns null`() = runTest {
repository.registerPendingOfframp(userWalletId, currencyId)
- assertThat(repository.consumePendingOfframp("unknown", userWalletId, currencyId)).isNull()
+ assertThat(repository.resolvePendingOfframp("unknown", userWalletId, currencyId)).isNull()
}
@Test
- fun `GIVEN already consumed pending offramp WHEN consume again THEN returns null`() = runTest {
+ fun `GIVEN already resolved pending offramp WHEN resolve again THEN still returns record`() = runTest {
// Arrange
val storedRequestId = repository.registerPendingOfframp(userWalletId, currencyId)
- // Act
- val first = repository.consumePendingOfframp(storedRequestId, userWalletId, currencyId)
- val second = repository.consumePendingOfframp(storedRequestId, userWalletId, currencyId)
+ // Act — resolving does NOT consume the record; the same redirect may be followed again until it expires
+ val first = repository.resolvePendingOfframp(storedRequestId, userWalletId, currencyId)
+ val second = repository.resolvePendingOfframp(storedRequestId, userWalletId, currencyId)
// Assert
assertThat(first).isNotNull()
- assertThat(second).isNull()
+ assertThat(second).isNotNull()
+ assertThat(second?.requestId).isEqualTo(storedRequestId)
}
@Test
- fun `GIVEN mismatched currency WHEN consume THEN returns null and does NOT burn the pending sell`() = runTest {
+ fun `GIVEN mismatched currency WHEN resolve THEN returns null and leaves the pending sell resolvable`() = runTest {
// Arrange
val storedRequestId = repository.registerPendingOfframp(userWalletId, currencyId)
- // Act — a tampered redirect with the right request_id but a wrong currency must not consume the token
- val mismatched = repository.consumePendingOfframp(storedRequestId, userWalletId, currencyId = "ethereum")
- // ...so the legitimate redirect can still succeed afterwards
- val legitimate = repository.consumePendingOfframp(storedRequestId, userWalletId, currencyId)
+ // Act — a tampered redirect with the right request_id but a wrong currency must not match
+ val mismatched = repository.resolvePendingOfframp(storedRequestId, userWalletId, currencyId = "ethereum")
+ // ...and the legitimate redirect must still resolve afterwards
+ val legitimate = repository.resolvePendingOfframp(storedRequestId, userWalletId, currencyId)
// Assert
assertThat(mismatched).isNull()
@@ -209,16 +210,16 @@ internal class DefaultOfframpRepositoryTest {
}
@Test
- fun `GIVEN mismatched wallet WHEN consume THEN returns null`() = runTest {
+ fun `GIVEN mismatched wallet WHEN resolve THEN returns null`() = runTest {
val storedRequestId = repository.registerPendingOfframp(userWalletId, currencyId)
- val result = repository.consumePendingOfframp(storedRequestId, UserWalletId("ffeeddccbbaa9988"), currencyId)
+ val result = repository.resolvePendingOfframp(storedRequestId, UserWalletId("ffeeddccbbaa9988"), currencyId)
assertThat(result).isNull()
}
@Test
- fun `GIVEN expired pending offramp WHEN consume THEN returns null`() = runTest {
+ fun `GIVEN expired pending offramp WHEN resolve THEN returns null`() = runTest {
// Arrange — seed a record created 2 hours ago (past the 1h expiry)
val expiredId = "expired-id"
pendingStoreState.value = listOf(
@@ -231,7 +232,7 @@ internal class DefaultOfframpRepositoryTest {
)
// Act
- val pending = repository.consumePendingOfframp(expiredId, userWalletId, currencyId)
+ val pending = repository.resolvePendingOfframp(expiredId, userWalletId, currencyId)
// Assert
assertThat(pending).isNull()
@@ -245,7 +246,7 @@ internal class DefaultOfframpRepositoryTest {
// Act
val stored = repository.getAllStoredOfframps()
// ...the record must survive the read so it can still be consumed afterwards
- val consumed = repository.consumePendingOfframp(storedRequestId, userWalletId, currencyId)
+ val consumed = repository.resolvePendingOfframp(storedRequestId, userWalletId, currencyId)
// Assert
assertThat(stored).hasSize(1)
diff --git a/common/ui/src/main/java/com/tangem/common/ui/earn/EarnBlock.kt b/common/ui/src/main/java/com/tangem/common/ui/earn/EarnBlock.kt
index ebd2c78785..a371efa5e8 100644
--- a/common/ui/src/main/java/com/tangem/common/ui/earn/EarnBlock.kt
+++ b/common/ui/src/main/java/com/tangem/common/ui/earn/EarnBlock.kt
@@ -13,7 +13,6 @@ import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.BlurredEdgeTreatment
-import androidx.compose.ui.draw.blur
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.innerShadow
import androidx.compose.ui.platform.testTag
@@ -35,6 +34,7 @@ import com.tangem.common.ui.earn.EarnBlockUM.Type
import com.tangem.core.ui.R
import com.tangem.core.ui.components.CircleShimmer
import com.tangem.core.ui.components.RectangleShimmer
+import com.tangem.core.ui.components.haze.hazeForegroundEffectTangem
import com.tangem.core.ui.ds.button.*
import com.tangem.core.ui.ds.image.TangemIcon
import com.tangem.core.ui.ds.image.TangemIconUM
@@ -44,6 +44,8 @@ import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
+import dev.chrisbanes.haze.HazeStyle
+import dev.chrisbanes.haze.HazeTint
import com.tangem.core.res.R as CoreResR
private const val TINTED_BACKGROUND_ALPHA = 0.1f
@@ -365,7 +367,14 @@ private fun EarnBlockIcon(type: Type, iconUM: EarnBlockUM.IconUM, modifier: Modi
Box(
modifier = Modifier
.size(TangemTheme.dimens2.x6)
- .blur(radius = TangemTheme.dimens2.x4, edgeTreatment = BlurredEdgeTreatment.Unbounded)
+ .hazeForegroundEffectTangem(
+ style = HazeStyle(
+ tint = HazeTint(Color.Transparent),
+ blurRadius = TangemTheme.dimens2.x4,
+ ),
+ ) {
+ blurredEdgeTreatment = BlurredEdgeTreatment.Unbounded
+ }
.background(color = type.accentGlow().copy(alpha = GLOW_ALPHA), shape = glowShape),
)
}
diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml
index 92c99b8dfa..26eb9b5062 100644
--- a/core/res/src/main/res/values-de/strings.xml
+++ b/core/res/src/main/res/values-de/strings.xml
@@ -274,7 +274,7 @@
%s fehlgeschlagen
Aktivieren
Hinzufügen
- Guthaben hinzufügen
+ Einzahlen
Zum Portfolio hinzufügen
Token hinzufügen
Token hinzufügen
@@ -725,6 +725,11 @@
Eine Transaktion kann nicht gesendet werden
Fehler in der Coinbeschreibung
Portfolio prüfen und Verdienstmöglichkeiten erkunden
+ Verdienstmöglichkeiten
+ Alle Ihre Ressourcen sind im Einsatz – entdecken Sie neue Möglichkeiten
+ Erhalten Sie jährlich bis zu %1$s
+ Maximale potenzielle Gewinne %1$s
+ %1$s/Jahr
Portfolio-Überprüfung
Für dich
Jetzt aktualisieren
@@ -923,20 +928,22 @@
Die Daten dieses Abschnitts stammen aus den folgenden Netzwerken: %s
Die Daten konnten nicht geladen werden...
Keine Daten
- **Hinzufügen zu Ihrem Portfolio**, um mit dem Kauf, Tausch oder Erhalt dieses Vermögenswerts zu beginnen
- Zum Portfolio hinzufügen
+ **Füge dieses Produkt Deinem Portfolio hinzu**, um es zu kaufen, zu tauschen oder zu erhalten.
+ In Deinem Portfolio
Dein Portfolio
**Token nicht unterstützt**. Dieser Token wird derzeit in der Wallet nicht unterstützt.
+ Andere zulässige Token
Marktimpuls
Schnelle Aktionen
Alles löschen
Token suchen
- Neueste
+ Letzte Suchanfragen
In Ihrem Portfolio
+ Aktuelle Token
Ergebnis
Token unter 100k USD Marktkapitalisierung anzeigen
Token anzeigen
- Krypto, Nachrichten und mehr
+ Krypto, Neuigkeiten und mehr
Kein Ergebnis
Netzwerk auswählen
Wallet auswählen
@@ -1010,7 +1017,7 @@
Position im Krypto-Rating zwischen allen Coins basierend auf der Marktkapitalisierung
Marktposition
Maximale Versorgung
- Zirkulation und maximale Versorgung
+ Umlauf- und Maximalversorgung
Die maximale Anzahl von Coins oder Tokens, die jemals für eine bestimmte Kryptowährung existieren können
Maximale Versorgung
Metriken
@@ -1028,7 +1035,7 @@
Handelsvolumen (24h)
Der Gesamtbetrag einer Kryptowährung, der innerhalb der letzten 24 Stunden gehandelt wurde, wobei das Aktivitäts- und Liquiditätsniveau auf dem Markt angegeben wird
Handelsvolumen (24h)
- %s insgesamt
+ %s in Summe
Volumen
Rufe dies auf oder tippe auf die Suchleiste, um Token direkt vom Markt hinzuzufügen
Token hinzufügen
@@ -1352,10 +1359,10 @@
Unbekannte Parameter
Kreditkarte oder Bankkonto
Teilen deine Adresse oder dein QR-Code
- Sicherer Verkauf von Kryptowährungen
+ Kryptowährung sicher verkaufen
Senden Sie mit Tausch an ein anderes Token
An eine andere Wallet senden
- Zwischen deinen Portfolios
+ Krypto gegen Krypto
Andere
Schnell aufladen
Kein Memo erforderlich
@@ -1917,6 +1924,10 @@
- %d Karte
- %d Karten
+ Dies geschah aufgrund Ihres verdächtigen Verhaltens. Wenden Sie sich an den Support, um mehr zu erfahren.
+ Cashback deaktiviert
+ Wird eingezahlt am %1$s
+ %1$s Cashback in %2$s
PIN-Code ändern
Kehren Sie zur App zurück, falls Sie ihn vergessen.
Karte
@@ -1980,6 +1991,7 @@
KYC-Sperre ausblenden
Leider konnten wir Ihre Identität
nicht verifizieren.
+ Tarif auswählen
Du kannst bis zu 3 Karten haben. Lösche eine, um eine neue Karte hinzuzufügen.
Maximale Anzahl ausgegebener Karten
Ja — für die Nutzung einer regulierten Visa-Karte ist eine Identitätsprüfung Pflicht. Das KYC wird von Sumsub abgewickelt, dem Compliance-Partner.
@@ -2043,6 +2055,7 @@
Neue PIN einrichten
PIN einstellen
Konto geschlossen
+ Inaktiv
Ersetzen deine Karte
Karte oder Ring verwenden, um die Sitzung zu verlängern
Karte oder Ring verwenden, um die Sitzung zu verlängern
@@ -2058,6 +2071,8 @@
Laden Sie Ihr Konto mit einem beliebigen Token aus Ihrer Wallet auf
Aus Ihrer Tangem Wallet
USDC im Polygon
+ Bitte versuche es später erneut. Sollte das Problem weiterhin bestehen, kontaktiere bitte unseren Support. Wir helfen Dir gerne bei der Lösung.
+ Die Bankdaten konnten nicht geladen werden
Visavorteile
Gelder aus erstatteten Käufen werden nicht auf Ihr On-Chain-Guthaben zurückerstattet und stehen nicht für Abhebungen zur Verfügung, bleiben aber auf Ihrem Kartenguthaben für Einkäufe verfügbar
Bitte beachten Sie
@@ -2622,7 +2637,7 @@
Die Gebühr wird abgezogen und Dein Vermögen wird erneut verliehen.
Um weiterhin Geld verdienen zu können, ist eine Genehmigung erforderlich.
Genehmigung bestätigen
- Durchschnittlicher Jahreszins %1$s%%
+ Aktueller effektiver Jahreszins %1$s%%
Deine Gelder werden derzeit dem Aave-Protokoll bereitgestellt, Du kannst sie jedoch jederzeit verwalten.
Deine%s ist in Aave hinterlegt
Chart konnte nicht geladen werden...
diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml
index d74f406f10..b841808ce4 100644
--- a/core/res/src/main/res/values-es/strings.xml
+++ b/core/res/src/main/res/values-es/strings.xml
@@ -709,6 +709,7 @@
Ocurrió un error
Ocurrió un error. Código: %s
Requiere memo
+ Obtener %1$s
Esta transacción
Al aprobar, permite que el contrato inteligente utilice sus tokens en transacciones futuras.
Montante %s
@@ -725,6 +726,8 @@
Generación de claves
Todas las operaciones criptográficas ocurren dentro del chip seguro, certificado contra la clonación y la manipulación física.
Seguridad a nivel de hardware
+ La actividad en la red es alta. Puede continuar ahora o intentarlo de nuevo más tarde, cuando las tarifas sean más bajas.
+ La tarifa de red es más alta de lo habitual.
Agregar Billetera Existente
Crear Nueva Billetera
Pedir Tangem
@@ -880,12 +883,14 @@
**Añadir a su portafolio** para empezar a comprar, intercambiar o recibir este activo
En su portafolio
Su portafolio
+ **Token no soportado**. Este token no está soportado actualmente en la billetera.
Análisis del Mercado
Acciones rápidas
Borrar todo
Buscar tokens
- Recientes
+ Búsquedas recientes
En su portafolio
+ Tokens recientes
Resultado
Ver tokens con marketcap inferior a 100.000$
Mostrar tokens
@@ -1256,6 +1261,21 @@
Por saldo
Organizar tokens
Desagrupar
+ El reembolso elegible se distribuirá a:
+ Ya está inscrito en %1$s
+ Tokens elegibles
+ Inscribirse
+ Se ha inscrito correctamente en %1$s
+ Esta campaña ya no existe o ha caducado.
+ Campaña inactiva
+ Gane un reembolso de 0.5% por cada intercambio superior a 500 $, en cualquier par de divisas, excepto entre stablecoins. El pago máximo es de 50 $ por intercambio.\n\nRealice cinco intercambios válidos y desbloquee una bonificación adicional de 10 $.\n\nLas recompensas se abonan semanalmente en USDT o USDC en la dirección seleccionada.
+ Seleccione una cuenta de reembolso
+ Seleccionar token
+ Inscríbase en %1$s
+ Estoy de acuerdo con %1$s
+ Estoy de acuerdo con
+ %1$s Términos
+ Consiga reembolsos en cada intercambio a partir de 10,000 $ hasta finales de Julio.\n\nLas tasas aumentan según el volumen: 0.10% a partir de 10,000 $, 0,20% a partir de 20,000 $ y 0.50% a partir de 100,000 $.\n\nPago máximo: 500 $ por intercambio y 10,000 $ por billetera por dirección de intercambio mientras dure la campaña. Quedan excluidos los intercambios de monedas estables por otras monedas estables.\n\nEl pago se realiza semanalmente en la dirección de USDT o USDC que elija.
Soporte de %s
Las notificaciones push están activadas, pero no funcionarán hasta que las autorice
Permitir notificaciones
@@ -1293,7 +1313,7 @@
Venda criptomonedas de forma segura
Enviar con intercambio a otro token
Enviar a otra billetera
- Entre sus portafolios
+ Cambia una criptomoneda por otra
Otro
Recarga rápida
No se requiere nota
diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml
index bebe09f137..2c96087d42 100644
--- a/core/res/src/main/res/values-fr/strings.xml
+++ b/core/res/src/main/res/values-fr/strings.xml
@@ -83,6 +83,7 @@
Ajouter des fonds
Échanger
Transférer
+ Ajouter à votre portfolio
Ajouter des jetons
Choisissez le réseau
Ajouter un jeton personnalisé
@@ -268,6 +269,7 @@
Comptes
Activer
Ajouter
+ Ajouter fonds
Ajouter au portfolio
Ajouter un jeton
Ajouter des jetons
@@ -349,6 +351,7 @@
Du
De %s
Synchroniser les adresses
+ Obtenir du
Commencer
Obtenir un token
Aller au fournisseur
@@ -538,6 +541,7 @@
L’envoi d’actifs sur d’autres réseaux entraînera une perte définitive.
%s réseau
Envoyez des fonds en utilisant uniquement
+ Adresse dynamique
Meilleures opportunités
Effacer le filtre
Tous les réseaux
@@ -658,6 +662,7 @@
Une erreur s\'est produite
Une erreur s\'est produite. Code:%s
Un mémo est requis
+ Obtenir des %1$s
Cette transaction
Spécifiez la limite approuvée pour le jeton sélectionné
Montant %s
@@ -673,6 +678,8 @@
Génération de clés
Toutes les opérations cryptographiques s\'effectuent à l\'intérieur de la puce sécurisée, certifiée contre le clonage et la falsification physique.
Sécurité au niveau matériel
+ L\'activité du réseau est élevée. Vous pouvez continuer maintenant ou réessayer plus tard, lorsque les frais seront peut-être moins élevés.
+ Les frais de réseau sont plus élevés que d\'habitude
Ajouter un Portefeuille existant
Créer un nouveau Portefeuille
Commandez
@@ -821,12 +828,19 @@
Les données de cette section proviennent des réseaux suivants : %s
Impossible de charger les données…
Aucune donnée
+ **Ajouter à votre portfolio** pour acheter, échanger ou recevoir cet asset
+ Dans votre portfolio
+ Votre portfolio
+ **Token non pris en charge**. Ce token n\'est actuellement pas pris en charge dans le portefeuille
Rythme du marché
Actions rapides
Rechercher sur le marché
+ Recherches récentes
+ Tokens récents
Résultat
Voir les jetons de moins de 100 000 $ de capitalisation boursière
Afficher les jetons
+ Crypto, actualités et plus
Aucun résultat
Sélectionnez un réseau
Sélectionnez un portefeuille
@@ -895,6 +909,7 @@
Position dans le classement des crypto-monnaies entre toutes les pièces en fonction de la capitalisation boursière
Évaluation du marché
Approvisionnement maximal
+ Offre en circulation et offre maximale
Le nombre maximal de pièces ou de jetons pouvant exister pour une crypto-monnaie particulière
Approvisionnement maximal
Métriques
@@ -910,6 +925,7 @@
Volume des échanges (24h)
Le montant total d\'une crypto-monnaie qui a été échangé au cours des dernières 24 heures, indiquant le niveau d\'activité et de liquidité du marché
Volume des échanges (24h)
+ %s au total
Volume
Tirez vers le haut ou appuyez sur la barre de recherche pour ajouter des jetons directement depuis le marché
Ajouter des jetons
@@ -1163,6 +1179,8 @@
Disponible auprès de
Disponible jusqu\'à
Vous obtenez
+ Ce token n\'est pas pris en charge. Veuillez choisir un autre token à acheter.
+ %s n\'est pas pris en charge
Le service est fourni par un prestataire externe. Tangem n\'est pas responsable.
Vous pouvez vérifier l\'état de la transaction à partir de la page du jeton
Jusqu\'à
@@ -1175,6 +1193,21 @@
Par solde
Organiser les jetons
Dégrouper
+ Le cashback éligible sera distribué à :
+ Vous êtes déjà inscrit(e) à %1$s
+ Tokens éligibles
+ S\'inscrire
+ Vous êtes bien inscrit(e) à %1$s
+ Cette campagne n\'existe plus ou a expiré
+ Campagne non active
+ Gagnez 0,5% de cashback sur chaque swap de plus de 500 $, sur toute paire sauf stablecoin vers stablecoin. Gain maximum : 50 $ par swap.\n\nComplétez cinq swaps éligibles et débloquez un bonus supplémentaire de 10 $.\n\nLes récompenses sont versées chaque semaine en USDT ou USDC à l\'adresse sélectionnée.
+ Sélectionner le compte pour recevoir le cashback
+ Sélectionner le token
+ S\'inscrire à %1$s
+ J\'accepte les %1$s
+ J\'accepte les
+ Conditions de %1$s
+ Gagnez du cashback sur chaque swap à partir de 10 000 $ jusqu\'à fin juillet.\n\nLes taux augmentent selon le montant : 0,10% à partir de 10 000 $, 0,20% à partir de 20 000 $, 0,50% à partir de 100 000 $.\n\nGain maximum : 500 $ par swap, et 10 000 $ par wallet et par sens de swap pendant toute la durée de la campagne. Les swaps stablecoin vers stablecoin sont exclus.\n\nLe paiement arrive chaque semaine à l\'adresse USDT ou USDC de votre choix.
Assistance %s
Les notifications push sont activées mais ne fonctionneront pas avant que vous les autorisiez
Autoriser les notifications
@@ -1200,7 +1233,12 @@
Aucun jeton pris en charge n\'a été trouvé
Ce code QR contient des paramètres non reconnus : %s. Si vous continuez, certaines informations de paiement risquent d\'être perdues.
Paramètres inconnus
+ Carte ou compte en banque
+ Partager votre adresse ou code QR
+ Vendre de la crypto en sécurité
Envoyer en échangeant vers un autre token
+ Envoyer vers un autre portefeuille
+ Une crypto contre une autre
Recharge rapide
Aucun mémo requis
%1$s (%2$s) sur le réseau %3$s
@@ -1614,6 +1652,7 @@
Tangem Pay en version bêta
Carte gelée
Paiement par carte
+ Vous ne pouvez pas clôturer la dernière carte
Il disparaîtra de l’application
Clôturer la carte
Retour
@@ -1776,6 +1815,7 @@
Désolé, nous n\'avons pas pu vérifier
votre identité.
Vous pouvez avoir jusqu\'à 3 cartes. Supprimez une carte pour pouvoir en ajouter une nouvelle.
+ Nombre maximum de cartes émises
Oui — pour utiliser une carte Visa réglementée, la vérification d’identité est obligatoire. Le KYC est géré par Sumsub, partenaire conformité.
Dois-je fournir mes documents ?
Non. Le KYC s’applique uniquement au compte Tangem Pay. Votre Tangem Wallet reste un environnement distinct, en self-custody et sans KYC.
@@ -1820,6 +1860,9 @@
Déposez des USDC sur le compte de paiement pour couvrir les frais d’émission
Impossible de couvrir les frais
Réémettre votre carte ?
+ Retirer le compte
+ Tangem Pay sera retiré de l\'écran principal et ne réapparaîtra plus, même après une réinstallation de l\'application.
+ Retirer le compte?
Nous réparons un problème technique. Veuillez réessayer plus tard.
Service temporairement indisponible
Les données ne peuvent pas être affichées pour le moment, mais les paiements par carte fonctionnent toujours.
@@ -2358,6 +2401,7 @@
Les frais seront prélevés et vos actifs seront à nouveau prêtés.
Pour continuer à percevoir des revenus, une autorisation est nécessaire.
Confirmer l\'approbation
+ APY actuel %1$s%%
Vos fonds sont actuellement affectés au protocole Aave, mais vous pouvez les gérer à tout moment.
Vos %s sont déposés dans Aave.
Impossible de charger le graphique...
diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml
index 5038b93bf1..34feb66471 100644
--- a/core/res/src/main/res/values-ja/strings.xml
+++ b/core/res/src/main/res/values-ja/strings.xml
@@ -717,6 +717,7 @@
エラーが発生しました
エラーが発生しました。コード: %s 。
メモが必要
+ %1$sを入手
取引
承認すると、このスマートコントラクトが今後の取引でトークンを利用できるようになります。
金額%s
@@ -733,6 +734,8 @@
鍵生成
すべての暗号処理は、複製や物理的な改ざんに対する認証を受けたセキュアチップ内で行われます。
ハードウェアレベルのセキュリティ
+ ネットワークが混雑しています。今すぐ続けることもできますが、後でお試しいただくと、手数料が安くなる場合があります。
+ ネットワーク手数料が通常より高くなっています
既存のウォレットを追加
新しいウォレットを作成する
Tangemを注文
@@ -887,12 +890,14 @@
**ポートフォリオに追加して**、この資産の買付・交換・受け取りを始めましょう
ポートフォリオ内
ポートフォリオ
+ **トークンはサポートされていません**。このトークンは現在、このウォレットではサポートされていません
マーケット動向
クイックアクション
すべてクリア
トークンを探す
最近の検索
ポートフォリオ内
+ 最近表示したトークン
結果
時価総額10万ドル以下のトークンを見る
トークンを表示
@@ -1238,6 +1243,8 @@
利用可能:
利用可能金額
以下が手に入ります。
+ このトークンはサポートされていません。別のトークンを選択して購入してください。
+ %sはサポートされていません
サービスは外部プロバイダーによって提供されます。 \nTangemは責任を負いません。
この画面を閉じて、トークンの詳細画面で取引状況を確認できます。
最大
@@ -1301,7 +1308,7 @@
暗号資産を安全に売却
別のトークンにスワップして送る
別のウォレットに送信
- ポートフォリオ間で
+ 暗号資産を別の暗号資産に交換
その他
クイック入金
メモ不要
@@ -2511,7 +2518,7 @@
手数料が差し引かれ、暗号資産が補充されます。
利息を継続的に生み出すには承認が必要です。
承認を確定する
- 平均APY %1$s%%
+ 現在のAPY %1$s%%
あなたの資金は現在Aaveプロトコルに預けられていますが、いつでも自由に管理できます。
%sはAaveに供給されています
チャートを読み込めません・・
diff --git a/core/res/src/main/res/values-pt-rBR/strings.xml b/core/res/src/main/res/values-pt-rBR/strings.xml
index 7e2292db62..019a3eebb1 100644
--- a/core/res/src/main/res/values-pt-rBR/strings.xml
+++ b/core/res/src/main/res/values-pt-rBR/strings.xml
@@ -131,6 +131,7 @@
Ainda não há contatos.
Os contatos que você adicionar aparecerão aqui.
Remover endereço
+ Salvar endereço
Salvar contato
Salvar na carteira
Este contato será vinculado à agenda de endereços desta carteira.
@@ -638,6 +639,8 @@
Tempo de transação longo
O valor da transação foi reembolsado em %1$s para sua carteira devido às regras do OKX ou da ponte. %2$s
O valor foi reembolsado em %1$s (%2$s rede)
+ Seus fundos foram reembolsados em %1$s para sua carteira no %2$s rede, de acordo com as regras da bolsa OKX.
+ Reembolsado em %s
Visite o site do fornecedor para verificação.
Verificação KYC exigida pelo fornecedor
Compra concluída
@@ -924,12 +927,14 @@
Em seu portfólio
Seu portfólio
**Token não suportado**. Este token não é suportado na carteira neste momento.
+ Outros tokens elegíveis
Pulso do mercado
Ações rápidas
Limpar tudo
Pesquisar tokens
- Recentes
+ Pesquisas recentes
Em seu portfólio
+ Tokens recentes
Resultado
Veja tokens com capitalização de mercado inferior a 100 mil dólares.
Mostrar tokens
@@ -1305,9 +1310,11 @@
Tokens elegíveis
Inscreva-se
Você se inscreveu com sucesso em %1$s
- Esta campanha não existe mais ou expirou.
+ Esta campanha não existe ou expirou.
Campanha inativa
+ Ganhe 0,5% Cashback em todas as trocas acima de US$ 500, em qualquer par, exceto entre pares estáveis. Pagamento máximo de US$ 50 por troca.\n\nComplete cinco trocas qualificadas e desbloqueie um bônus extra de US$ 10.\n\nAs recompensas são pagas semanalmente em USDT ou USDC no endereço selecionado.
Selecione a conta de cashback
+ Selecionar token
Inscreva-se em %1$s
Concordo com %1$s
Concordo com
@@ -1350,7 +1357,7 @@
Venda criptomoedas com segurança
Enviar com troca para outro token
Enviar para outra carteira
- Entre seus portfólios
+ Uma cripto por outra
Outro
Recarga rápida
Não é necessário memorando
@@ -1975,6 +1982,7 @@
Ocultar bloco KYC
Desculpe, não foi possível verificar.
Seu perfil.
+ Selecione o plano
Você pode ter até 3 cartões. Exclua um para adicionar um novo.
Número máximo de cartões emitidos
Sim — para usar um cartão Visa regulado, a verificação de identidade é obrigatória. O KYC é feito pela Sumsub, parceira de compliance.
diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml
index ae70e6b4a6..b01216084f 100644
--- a/core/res/src/main/res/values-ru/strings.xml
+++ b/core/res/src/main/res/values-ru/strings.xml
@@ -377,7 +377,7 @@
Из
Из %s
Синхронизировать адреса
- Приобрести
+ Пополнить
Начать
Получить токен
К провайдеру
@@ -749,6 +749,7 @@
Произошла ошибка
Произошла ошибка. Код: %s.
Требуется memo
+ Пополнить %1$s
Транзакция
Укажите лимит доступа к выбранному токену
Количество %s
@@ -765,6 +766,8 @@
Генерация ключа
Операции с криптографией выполняются внутри защищённого чипа, устойчивого к клонированию и физическому взлому.
Аппаратная безопасность
+ Высокая активность в сети. Вы можете продолжить сейчас или попробовать позже, когда комиссии будут ниже.
+ Комиссия сети выше, чем обычно
Добавить существующий кошелек
Создать новый кошелек
Купить
@@ -922,12 +925,14 @@
**Добавьте в свой портфель**, чтобы покупать, обменивать или получать этот актив.
В вашем портфеле
Ваш портфель
+ **Токен не поддерживается**. В настоящее время этот токен не поддерживается в кошельке
Пульс рынка
Быстрые действия
Очистить всё
Поиск токенов
- Недавние
+ Недавние запросы
В вашем портфеле
+ Недавние токены
Результат
Токены с капитализацией меньше 100к USD
Показать токены
@@ -1306,6 +1311,8 @@
Доступно от
Доступно до
Вы получите
+ Этот токен не поддерживается. Пожалуйста, выберите другой токен для покупки.
+ %s не поддерживается
Услуга предоставляется сторонним провайдером.\nTangem не несет ответственности.
Вы можете закрыть этот экран и проверить статус транзакции на экране информации о токене.
До
@@ -1318,6 +1325,20 @@
По балансу
Упорядочить токены
Список
+ Начисленный кешбэк будет отправлен на:
+ Вы уже участвуете в %1$s
+ Доступные токены
+ Участвовать
+ Вы успешно стали участником в %1$s
+ Эта кампания больше не существует или срок ее действия истек
+ Кампания неактивна
+ Получайте 0.5% кешбэка за каждый обмен от $500 в любой паре, кроме стейблкоина на стейблкоин. Максимальная выплата: $50 за обмен.\n\nСовершите пять соответствующих условиям обменов и разблокируйте дополнительный бонус в $10.\n\nНаграды выплачиваются еженедельно в USDT или USDC на выбранный адрес.
+ Выберите счет для кешбэка
+ Выберите токен
+ Участвовать в %1$s
+ Я принимаю %1$s
+ %1$s Условия
+ Получайте кешбэк за каждый обмен от $10K до конца июля.\n\nСтавка растет вместе с суммой: 0.10% от $10K, 0.20% от $20K, 0.50% от $100K.\n\nМаксимальная выплата: $500 за обмен и $10,000 на кошелек для каждого направления обмена, пока длится кампания. Обмены стейблкоина на стейблкоин исключены.\n\nВыплата поступает еженедельно на выбранный вами адрес USDT или USDC.
Поддержка %s
Push-уведомления включены, но не будут работать без вашего разрешения
Разрешить уведомления
@@ -1350,12 +1371,12 @@
Поддерживаемые токены не найдены
Этот QR-код содержит параметры, которые не распознаны: %s. Некоторые данные платежа могут быть утеряны, если вы продолжите.
Неизвестные параметры
- Банковская карта или банковский счет
+ Криптовалюту картой, переводом и не только
Поделитесь своим адресом или QR-кодом
Продавайте криптовалюту безопасно
Отправить с обменом на другой токен
Отправить на другой кошелек
- Между вашими портфелями
+ Обменяйте одну криптовалюту на другую
Другие
Быстрое пополнение
Memo не требуется
diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml
index 87fbf2490b..eac9ee3549 100644
--- a/core/res/src/main/res/values-uk-rUA/strings.xml
+++ b/core/res/src/main/res/values-uk-rUA/strings.xml
@@ -377,6 +377,7 @@
З
З %s
Синхронізувати адреси
+ Поповнити
Почати
Отримати токен
Перейти до провайдера
@@ -748,6 +749,7 @@
Виникла помилка
Виникла помилка. Код: %s.
Вимагається memo
+ Поповнити %1$s
Транзакція
Вкажіть ліміт доступу для обраного токена
Кількість %s
@@ -764,6 +766,8 @@
Генерація ключа
Операції з криптографією відбуваються всередині захищеного чіпу, стійкого до клонування та фізичному взлому.
Безпека на апаратному рівні
+ Висока активність у мережі. Ви можете продовжити зараз або спробувати пізніше, коли комісії будуть нижчими.
+ Комісія мережі вища, ніж зазвичай
Додати існуючий гаманець
Створити новий гаманець
Купити
@@ -921,12 +925,14 @@
**Додайте до свого портфеля**, щоб купувати, обмінювати або отримувати цей актив
У вашому портфелі
Ваш портфель
+ **Токен не підтримується**. Наразі цей токен не підтримується в гаманці
Пульс ринку
Швидкі дії
Очистити все
Шукати на маркеті
- Нещодавні
+ Нещодавні запити
У вашому портфелі
+ Нещодавні токени
Результат
Переглянути токени до 100к USD ринкової капіталізації
Показати токени
@@ -1305,6 +1311,8 @@
Доступно з
Доступно до
Ви отримаєте
+ Цей токен не підтримується. Будь ласка, виберіть інший токен для купівлі.
+ %s не підтримується
Послуга надається зовнішнім провайдером. \nTangem не несе відповідальності.
Ви можете перевірити статус транзакції на сторінці токена
До
@@ -1317,6 +1325,20 @@
За балансом
Сортування токенів
Список
+ Нарахований кешбек буде надіслано на:
+ Ви вже долучилися до %1$s
+ Доступні токени
+ Долучитися
+ Ви успішно долучилися до %1$s
+ Ця кампанія більше не існує або термін її дії закінчився
+ Кампанія неактивна
+ Отримуйте 0.5% кешбеку за кожний обмін від $500 у будь-якій парі, крім стейблкоїна на стейблкоїн. Максимальна виплата: $50 за обмін.\n\nЗдійсніть п\'ять відповідних умовам обмінів та розблокуйте додатковий бонус у $10.\n\nНагороди виплачуються щотижня в USDT або USDC на обрану адресу.
+ Оберіть рахунок для кешбеку
+ Оберіть токен
+ Долучитися до %1$s
+ Я приймаю %1$s
+ %1$s Умови
+ Отримуйте кешбек за кожний обмін від $10K до кінця липня.\n\nСтавка зростає разом із сумою: 0.10% від $10K, 0.20% від $20K, 0.50% від $100K.\n\nМаксимальна виплата: $500 за обмін та $10,000 на гаманець для кожного напрямку обмінів, поки триває кампанія. Обміни стейблкоїна на стейблкоїн виключено.\n\nВиплата надходить щотижня на обрану вами адресу USDT або USDC.
Підтримка %s
Push-сповіщення ввімкнені, але не працюватимуть без вашого дозволу
Дозволити сповіщення
@@ -1354,7 +1376,7 @@
Безпечно продавайте криптовалюту
Надіслати з обміном на інший токен
Надіслати на інший гаманець
- Між вашими портфелями
+ Обміняйте одну криптовалюту на іншу
Інші
Швидке поповнення
Memo не вимагається
@@ -2010,6 +2032,7 @@
Поповніть платіжний рахунок в USDC, щоб покрити комісію за випуск
Неможливо покрити комісію
Перевипустити картку?
+ Видалити аккаунт
Ми усуваємо технічну проблему. Будь ласка, спробуйте пізніше.
Сервіс тимчасово недоступний
Не можемо показати дані картки, але оплати продовжують працювати.
@@ -2534,7 +2557,7 @@
Комісія буде знята, а ваші активи знову почнуть приносити дохід.
Щоб продовжити заробляти, потрібне схвалення.
Підтвердити дозвіл
- Середній APY %1$s%%
+ Поточний APY %1$s%%
Наразі ваші кошти розміщені у протоколі Aave, але ви можете керувати ними в будь-який час.
Ваш %s внесений у Aave
Неможливо завантажити графік
diff --git a/core/res/src/main/res/values-zh-rCN/strings.xml b/core/res/src/main/res/values-zh-rCN/strings.xml
index b2e532969f..4c000aebd7 100644
--- a/core/res/src/main/res/values-zh-rCN/strings.xml
+++ b/core/res/src/main/res/values-zh-rCN/strings.xml
@@ -713,6 +713,7 @@
发生错误
发生错误。错误代码: %s。
需要备忘录
+ 获取 %1$s
交易
批准后,您即允许智能合约在未来的交易中使用您的代币。
金额 %s
@@ -729,6 +730,8 @@
密钥生成
所有加密操作都在安全芯片内部进行,该芯片经过认证,可防止克隆和物理篡改。
硬件级安全
+ 网络流量较大,您现在可以继续,也可以稍后再试,届时手续费可能会更低。
+ 网络费用比平时高
添加现有钱包
创建新钱包
订购 Tangem
@@ -887,8 +890,9 @@
快速行动
全部清除
搜索代币
- 最近的
+ 最近搜索
在您的投资组合中
+ 近期代币
结果
查看市值低于 10 万美元的代币
显示代币
@@ -1234,6 +1238,8 @@
可从
最高可提供
你得到
+ 不支持该代币。请选择其他代币进行购买。
+ %s 不支持
服务由外部供应商提供。\nTangem 不承担任何责任。
您可以关闭此屏幕,并在代币详情屏幕上查看交易状态。
至多
@@ -1940,6 +1946,9 @@
将USDC存入支付账户以支付发行费用
无法支付费用
要重新发行您的卡片吗?
+ 删除账户
+ Tangem Pay 将从主屏幕中移除,即使重新安装该应用,它也不会再次出现。
+ 删除账号?
我们正在修复技术问题,请稍后再试。
服务暂时不可用
无法显示详细信息。但刷卡支付功能仍然可用。
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index 41593daacd..1f4da59adb 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -93,7 +93,7 @@
Credit card or bank account
Fund token
Share your address or QR-code
- Between your portfolios
+ Exchange one crypto for another
You receive
Add address
Add address and select network
@@ -726,6 +726,11 @@
Can\'t send a transaction
Coin description error
Review portfolio and explore earn opportunities
+ Earn opportunities
+ All your assets are at work, explore new opportunities
+ Get up to %1$s annually
+ Max potential rewards %1$s
+ %1$s/year
Portfolio review
For You
Update now
@@ -933,8 +938,9 @@
Quick actions
Clear all
Search tokens
- Recent\'s
+ Recent searches
In your portfolio
+ Recent tokens
Result
See tokens under 100k USD market cap
Show tokens
@@ -1352,12 +1358,12 @@
No supported tokens found
This QR code contains parameters that are not recognized: %s. Some payment details may be lost if you continue.
Unknown Parameters
- Credit card or bank account
+ Get crypto by card, bank transfer & more
Share your address or QR-code
- Sell crypto securely
- Send with swap to another token
- Send to another wallet
- Between your portfolios
+ Convert crypto to fiat currency
+ Exchange and send in one step
+ Transfer crypto to another wallet
+ Exchange one crypto for another
Other
Quick top up
No memo required
@@ -1986,6 +1992,7 @@
Hide KYC block
Sorry, we couldn\'t verify
your profile.
+ Select plan
You can have up to 3 cards. Delete one to add a new card.
Maximum Cards Issued
Yes – to operate a regulated Visa card, identity verification is mandatory. KYC is handled by Sumsub (compliance partner).
@@ -2019,8 +2026,6 @@
We\'ll set up a wallet
Get your Tangem Pay Card in minutes
Pay Support
- Select plan
- Inactive
Payment account
Session expired
Invalid PIN: avoid sequences or repeats
@@ -2051,6 +2056,7 @@
Set up new PIN
Set PIN
Account closed
+ Inactive
Replacing your card
Use your card or ring to renew session
Use your card or ring to renew session
@@ -2066,6 +2072,8 @@
Use crypto from your wallet to top up your payment account
From your Tangem Wallet
USDC on Polygon network
+ Please try again or contact support if the issue persists
+ Couldn\'t load banking details
Visa Benefits
Funds from refunded purchases won’t be returned your on-chain Polygon balance or be available for withdrawal, but will stay on your card balance for purchases
Please note
diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt
index 1e0de1fd63..16871f2f13 100644
--- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt
+++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt
@@ -73,7 +73,7 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
return@flow
}
- val dAppUri = URI(sdkSessionProposal.url)
+ val dAppUri = URI(sdkVerifyContext.getDappOriginUrl())
if (dAppUri.host.isNullOrEmpty()) {
emit(WcPairState.Error(WcPairError.InvalidDomainURL))
return@flow
diff --git a/domain/offramp/src/main/java/com/tangem/domain/offramp/repository/OfframpRepository.kt b/domain/offramp/src/main/java/com/tangem/domain/offramp/repository/OfframpRepository.kt
index 0aeaa27839..85149b2fa1 100644
--- a/domain/offramp/src/main/java/com/tangem/domain/offramp/repository/OfframpRepository.kt
+++ b/domain/offramp/src/main/java/com/tangem/domain/offramp/repository/OfframpRepository.kt
@@ -15,8 +15,8 @@ interface OfframpRepository {
* @param cryptoCurrency crypto currency to sell
* @param fiatCurrencyCode fiat currency code (e.g., "USD", "EUR")
* @param walletAddress wallet address for the refund
- * @param requestId single-use nonce embedded into the provider redirect URL to authenticate the
- * returning `redirect_sell` deeplink
+ * @param requestId nonce embedded into the provider redirect URL to authenticate the returning
+ * `redirect_sell` deeplink
* @return URL for offramp service or null if not available
*/
fun getOfframpUrl(
@@ -28,16 +28,19 @@ interface OfframpRepository {
/**
* Registers a new app-initiated sell for [userWalletId] / [currencyId], prunes expired records, and returns a
- * fresh single-use `request_id` to embed in the provider redirect URL.
+ * fresh `request_id` to embed in the provider redirect URL. The record stays valid until it expires.
*/
suspend fun registerPendingOfframp(userWalletId: UserWalletId, currencyId: String): String
/**
- * Returns and removes (single-use) the pending sell matching [requestId] only when it is not expired and was
- * registered for the same [userWalletId] and [currencyId]. Returns `null` otherwise, leaving a non-matching
- * record untouched so a tampered redirect cannot burn a legitimate pending sell.
+ * Returns the pending sell matching [requestId] when it is not expired and was registered for the same
+ * [userWalletId] and [currencyId]; returns `null` otherwise.
+ *
+ * The matching record is **not** removed — it remains valid until it expires, so the same `redirect_sell`
+ * deeplink can be followed repeatedly within that window (e.g. the user re-opens it). Expired records are pruned
+ * as a side effect.
*/
- suspend fun consumePendingOfframp(
+ suspend fun resolvePendingOfframp(
requestId: String,
userWalletId: UserWalletId,
currencyId: String,
diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/DefaultAddToPortfolioComponent.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/DefaultAddToPortfolioComponent.kt
index d3652b2d02..9add0105d2 100644
--- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/DefaultAddToPortfolioComponent.kt
+++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/DefaultAddToPortfolioComponent.kt
@@ -7,6 +7,7 @@ import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.stack.backStack
import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.router.stack.pop
+import com.tangem.common.ui.markets.action.TokenActionsContext
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.context.childByContext
@@ -64,6 +65,7 @@ internal class DefaultAddToPortfolioComponent @AssistedInject constructor(
callbacks = model,
data = model.tokenActionsData,
bottomAction = flowOf(BottomAction.GoToToken),
+ context = TokenActionsContext.AddFunds,
),
)
}
diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/market/state/SwapMarketCategory.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/market/state/SwapMarketCategory.kt
index 926aeecb47..234641740d 100644
--- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/market/state/SwapMarketCategory.kt
+++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/market/state/SwapMarketCategory.kt
@@ -28,6 +28,14 @@ internal enum class SwapMarketCategory(
title = resourceReference(R.string.markets_sort_by_top_losers_title),
order = TokenMarketListConfig.Order.TopLosers,
),
+ ExperiencedBuyers(
+ title = resourceReference(R.string.markets_sort_by_experienced_buyers_title),
+ order = TokenMarketListConfig.Order.Buyers,
+ ),
+ Trending(
+ title = resourceReference(R.string.markets_sort_by_trending_title),
+ order = TokenMarketListConfig.Order.Trending,
+ ),
}
/**
diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/ChooseTokenModel.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/ChooseTokenModel.kt
index 50f79c7a7a..ad2a03c358 100644
--- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/ChooseTokenModel.kt
+++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/ChooseTokenModel.kt
@@ -9,13 +9,9 @@ import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.features.commonfeatures.api.R
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager
-import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenAnalyticsPayload
-import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridge
-import com.tangem.features.commonfeatures.api.choosetoken.ChooserBlock
+import com.tangem.features.commonfeatures.api.choosetoken.*
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridgeInternal.SearchQuery
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridgeInternal.SearchQuery.Companion.isSearchingState
-import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenComponent
-import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenResult
import com.tangem.features.commonfeatures.impl.choosetoken.AddToPortfolioRoute
import com.tangem.features.commonfeatures.impl.choosetoken.converter.SearchBarToggleTransformer
import com.tangem.features.commonfeatures.impl.choosetoken.converter.SearchBarUpdateQueryTransformer
@@ -24,7 +20,9 @@ import com.tangem.features.commonfeatures.impl.choosetoken.ui.ChooseTokenFullUM
import com.tangem.features.commonfeatures.impl.choosetoken.ui.ChooseTokenInitialUM
import com.tangem.features.commonfeatures.impl.choosetoken.ui.state.ChooserBlockUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.launch
import javax.inject.Inject
@Suppress("LongParameterList")
@@ -106,6 +104,13 @@ internal class ChooseTokenModel @Inject constructor(
)
init {
+ if (bridge.settings.chooserBlock == ChooserBlock.Market) {
+ modelScope.launch {
+ delay(MARKETS_INITIAL_LOAD_DELAY)
+ marketBlockDelegate.loadDefaultMarkets()
+ }
+ }
+
addToPortfolioManager.onDismiss.receiveAsFlow()
.onEach { bottomSheetNavigation.dismiss() }
.launchIn(modelScope)
@@ -159,5 +164,8 @@ internal class ChooseTokenModel @Inject constructor(
companion object {
const val DEBOUNCE_SEARCH_DELAY = 500L
+
+ /** Roughly the bottom sheet entrance animation duration. */
+ private const val MARKETS_INITIAL_LOAD_DELAY = 400L
}
}
\ No newline at end of file
diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/MarketBlockDelegate.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/MarketBlockDelegate.kt
index 3c08434a2b..ac3f875a2e 100644
--- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/MarketBlockDelegate.kt
+++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/MarketBlockDelegate.kt
@@ -34,6 +34,8 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
+private const val MARKET_PULSE_ITEM_LIMIT = 5
+
@Suppress("LongParameterList")
internal class MarketBlockDelegate @AssistedInject constructor(
private val marketsListBatchFlowManagerFactory: MarketsListBatchFlowManager.Factory,
@@ -77,7 +79,7 @@ internal class MarketBlockDelegate @AssistedInject constructor(
* When single-currency wallets aren't selectable here (e.g. swap), the wallet is always
* multi-currency, so we skip the per-wallet logic entirely and return [baseMarketsStateFlow].
*/
- val marketsStateFlow: Flow = if (!shouldShowSingleCurrencyWallets) {
+ private val walletAwareMarketsStateFlow: Flow = if (!shouldShowSingleCurrencyWallets) {
baseMarketsStateFlow
} else {
selectedWalletFlow
@@ -85,6 +87,10 @@ internal class MarketBlockDelegate @AssistedInject constructor(
.distinctUntilChanged()
}
+ val marketsStateFlow: Flow = walletAwareMarketsStateFlow
+ .map { it.limitMarketPulseItems() }
+ .distinctUntilChanged()
+
private val defaultMarketsListManager by lazy {
marketsListBatchFlowManagerFactory.create(
batchFlowType = GetMarketsTokenListFlowUseCase.BatchFlowType.Main,
@@ -113,9 +119,6 @@ internal class MarketBlockDelegate @AssistedInject constructor(
}
.launchIn(modelScope)
- // Initial load of default markets
- defaultMarketsListManager.reload()
-
visibleMarketItemIds
.mapNotNull { rawIDS ->
if (rawIDS.isNotEmpty()) {
@@ -144,14 +147,22 @@ internal class MarketBlockDelegate @AssistedInject constructor(
.launchIn(modelScope)
}
+ /**
+ * Starts the initial load of the default markets list. Not invoked in [init] on purpose:
+ * the caller decides *if* (market block may be disabled entirely, e.g. Transfer flow) and
+ * *when* (deferred past the bottom sheet entrance animation, [REDACTED_TASK_KEY]) to trigger it.
+ */
+ fun loadDefaultMarkets() {
+ defaultMarketsListManager.reload()
+ }
+
private fun createDefaultMarketsFlow(): Flow {
val marketsTitle = TextReference.Res(R.string.markets_pulse_common_title)
return combine(
flow = defaultMarketsListManager.uiItems,
flow2 = defaultMarketsListManager.isInInitialLoadingErrorState,
- flow3 = defaultMarketsListManager.totalCount,
- flow4 = selectedCategoryFlow,
- ) { uiItems, isError, total, selectedCategory ->
+ flow3 = selectedCategoryFlow,
+ ) { uiItems, isError, selectedCategory ->
val categories = buildCategoriesUM(selectedCategory)
when {
isError -> SwapMarketState.LoadingError(
@@ -167,10 +178,10 @@ internal class MarketBlockDelegate @AssistedInject constructor(
)
else -> SwapMarketState.Content(
items = uiItems,
- loadMore = { defaultMarketsListManager.loadMore() },
+ loadMore = {},
onItemClick = { item -> addToPortfolioItem(item) },
visibleIdsChanged = { visibleDefaultMarketItemIds.value = it },
- total = total ?: uiItems.size,
+ total = uiItems.size,
marketsTitle = marketsTitle,
shouldAssetsCount = false,
categories = categories,
@@ -258,6 +269,13 @@ internal class MarketBlockDelegate @AssistedInject constructor(
}
}
+ private fun SwapMarketState?.limitMarketPulseItems(): SwapMarketState? {
+ if (this !is SwapMarketState.Content || shouldAssetsCount) return this
+ if (items.size <= MARKET_PULSE_ITEM_LIMIT) return this
+ val limitedItems = items.take(MARKET_PULSE_ITEM_LIMIT).toImmutableList()
+ return copy(items = limitedItems, total = limitedItems.size)
+ }
+
private fun addToPortfolioItem(item: MarketsListItemUM) {
val tokenMarket = defaultMarketsListManager.getTokenMarketById(item.id)
?: searchMarketsListManager.getTokenMarketById(item.id) ?: return
diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/PortfolioListBlockDelegate.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/PortfolioListBlockDelegate.kt
index 515cabd4d1..024433610f 100644
--- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/PortfolioListBlockDelegate.kt
+++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/PortfolioListBlockDelegate.kt
@@ -19,12 +19,14 @@ import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenResult
import com.tangem.features.commonfeatures.api.choosetoken.model.TokenListUMData
import com.tangem.features.commonfeatures.impl.choosetoken.SettingContextUseCase
import com.tangem.features.commonfeatures.impl.choosetoken.converter.ChooseTokenListItemConverter
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.mapNotNullValues
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
@Suppress("LongParameterList")
@@ -33,6 +35,7 @@ internal class PortfolioListBlockDelegate @AssistedInject constructor(
private val settingContext: SettingContextUseCase,
private val multiAccountStatusListSupplier: MultiAccountStatusListSupplier,
private val getWalletsUseCase: GetWalletsUseCase,
+ dispatchers: CoroutineDispatcherProvider,
@Assisted private val modelScope: CoroutineScope,
@Assisted private val searchQueryState: StateFlow,
@Assisted private val featureSettings: ChooseTokenBridge.Settings,
@@ -48,6 +51,8 @@ internal class PortfolioListBlockDelegate @AssistedInject constructor(
val portfolioList: SharedFlow