diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml
index c4760bb8a5..5132421c98 100644
--- a/core/res/src/main/res/values-de/strings.xml
+++ b/core/res/src/main/res/values-de/strings.xml
@@ -404,6 +404,7 @@
Aufgrund der Beschränkungen von %1$s können nur %2$d UTXOs in eine einzige Transaktion passen. Das bedeutet, dass du nur %3$s oder weniger senden kannst. Du musst den Betrag reduzieren.
Wert kopiert
Meine Wallet
+ Warnung
Woche
mit
Ja
@@ -693,7 +694,7 @@
Lege die Karte oder Ring an
Deine Wallet ist eingerichtet und einsatzbereit!
Wallet erfolgreich importiert
- Wiederherstellung %d%
+ Wiederherstellung %d%%
Du hast deine biometrischen Daten aktualisiert, scanne deine Karte oder Ring, um einzutreten
Dein Guthaben sollte höher sein als der Gebührenwert, um eine Überweisung zu tätigen
Unzureichende Mittel
diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml
index 4fde82b922..1b40ed9ebf 100644
--- a/core/res/src/main/res/values-es/strings.xml
+++ b/core/res/src/main/res/values-es/strings.xml
@@ -401,6 +401,7 @@
Debido a limitaciones sobre %1$s, solo %2$d UTXO pueden caber en una sola transacción. Esto significa que solo puedes enviar %3$s o menos. Debe reducir la cantidad.
Valor copiado
Billeteras
+ Alerta
semana
con
Sí
diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml
index 89c9435c9b..772f4d92d2 100644
--- a/core/res/src/main/res/values-fr/strings.xml
+++ b/core/res/src/main/res/values-fr/strings.xml
@@ -400,6 +400,7 @@
Unstakez
En raison d\'une limitations sur les %1$s, seuls les %2$d UTXO peuvent s\'intégrer dans une seule transaction. Ce qui signifie vous ne pouvez envoyer que %3$s ou moins. Réduisez le montant.
Valeur copiée
+ Alerte
semaine
avec
Oui
diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml
index 9cd39c1c7a..0282ef7661 100644
--- a/core/res/src/main/res/values-it/strings.xml
+++ b/core/res/src/main/res/values-it/strings.xml
@@ -26,6 +26,7 @@
- %d gettone
- %d gettoni
+ Avviso
Codice di accesso
Prima di scansionare la carta sarà necessario inserire il codice di accesso corretto
Mantenimento della carta
diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml
index 6efdf8cd26..6dadf3d9d5 100644
--- a/core/res/src/main/res/values-ja/strings.xml
+++ b/core/res/src/main/res/values-ja/strings.xml
@@ -301,6 +301,7 @@
時間
インポート
進行中
+ 残高不足
後で
もっと詳しく
残り%1$s
@@ -397,6 +398,7 @@
%1$sの制限により、1つのトランザクションに収まるUTXOは%2$d個のみです。つまり、 %3$s以下しか送信できません。量を減らす必要があります。
値がコピーされました
ウォレット
+ 警告
週
で
はい
@@ -473,7 +475,7 @@
絞り込み
マイネットワーク
ネットワーク
- よく使われています
+ 人気
該当する結果はありません
ステーキング・利息モード
こんにちは、サポートチームの皆さん、コード %s のエラーが発生しました。
@@ -687,7 +689,7 @@
カードまたはリングをタップ
ウォレットは同期され、準備ができています。\n一部のトークンが見つかりませんか?
ウォレットのインポートが完了しました
- %d%を復元中
+ %d%%を復元中
生体認証を更新しました。カードまたはリングをスキャンして入ってください。
送金するには、残高が手数料額より高くなければなりません。
残高不足
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 04ca28b1d4..c6642f4bfa 100644
--- a/core/res/src/main/res/values-pt-rBR/strings.xml
+++ b/core/res/src/main/res/values-pt-rBR/strings.xml
@@ -306,6 +306,7 @@
hora
Importar
Em andamento
+ Saldo insuficiente
Mais tarde
Saber mais
%1$s falta
@@ -404,6 +405,7 @@
Devido a %1$s limitações apenas %2$d Os UTXOs podem caber em uma única transação. Isso significa que você só pode enviar %3$s ou menos. Você precisa reduzir a quantidade.
Valor copiado
Carteiras
+ Aviso
semana
com
Sim
@@ -691,6 +693,9 @@
Toque para digitalizar
Toque para assinar
Toque no Cartão ou no Anel
+ Sua carteira está sincronizada e pronta. \n Faltam alguns tokens?
+ Carteira importada com sucesso.
+ Restaurando %d%%
Você atualizou seus dados biométricos. Escaneie seu cartão ou toque a campainha para entrar.
Seu saldo deve ser superior ao valor da taxa para efetuar uma transferência.
Saldo insuficiente
@@ -751,6 +756,7 @@
Resultado
Veja tokens com capitalização de mercado inferior a 100 mil dólares.
Mostrar tokens
+ Criptomoedas, notícias e muito mais
Nenhum resultado
Selecione a rede
Selecione a carteira
@@ -823,9 +829,11 @@
Posição no ranking de criptomoedas entre todas as moedas com base na capitalização de mercado.
Posicionamento de mercado
Fornecimento máximo
+ Circulação e Fornecimento Máximo
O número máximo de moedas ou tokens que podem existir para uma determinada criptomoeda.
Fornecimento máximo
Métricas
+ Sem limite
Links oficiais
Desempenho de preço
Repositório
@@ -1100,6 +1108,12 @@
Configurações
Você não concedeu acesso à sua câmera.
Acesso à câmera negado
+ O token solicitado não foi adicionado à sua carteira. Adicione-o e tente novamente.
+ Token não adicionado
+ Desculpe, este código QR não pôde ser reconhecido.
+ Código QR não reconhecido
+ Esta rede não é compatível com nenhum dos tokens adicionados. Adicione um token compatível para enviar criptomoedas.
+ Nenhum token compatível encontrado
Não é necessário memorando
%1$s (%2$s) sobre %3$s rede
%1$s sobre %2$s rede
@@ -1245,6 +1259,11 @@
Limitação de transação
Opcional
Alinhe o seu código QR com o quadrado para escaneá-lo. Certifique-se de escanear corretamente. %s endereço de rede.
+ Ao utilizar uma taxa fixa, o valor que você recebe fica garantido no momento da troca. Isso protege você de variações de preço durante a transação.
+ Taxa fixa
+ Uma taxa flutuante significa que o valor final que você recebe pode variar ligeiramente com base nas condições de mercado entre o momento em que você inicia e conclui a operação de swap.
+ Taxa flutuante
+ A taxa é fixa.
Recente
Destinatário
Endereço inválido
@@ -1288,6 +1307,7 @@
O destinatário recebe %s
Tem certeza de que deseja cancelar a conversão? Seus dados anteriores serão apagados.
Remover conversão
+ Algo deu errado. Tente novamente.
Enviar com troca
Transação enviada
Prepare-se para escanear o Cartão ou Anel que deseja configurar.
@@ -1481,6 +1501,7 @@
Trocar essa quantidade de tokens selecionados causará um impacto significativo no preço e reduzirá seu resultado.
Alto impacto nos preços
Fundos insuficientes
+ Não há fundos suficientes para concluir esta transação. Reduza o valor a receber ou adicione mais fundos.
Conceder permissão
Trocar
Trocar...
@@ -1647,6 +1668,7 @@
Token em %%imagem%% %1$s rede
O %1$s (%2$sO token ) é a principal moeda da plataforma. %3$s rede e não pode ser ocultada enquanto você tiver outros tokens dessa rede na lista
Não foi possível ocultar %s
+ N/A
Mostrar código QR
Troque este token por outro em %1$s taxas de serviço a partir de fevereiro %2$s-%3$s.
Trocar com Changelly, %s tarifas
diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml
index a624ed8ccd..2654c8ae24 100644
--- a/core/res/src/main/res/values-ru/strings.xml
+++ b/core/res/src/main/res/values-ru/strings.xml
@@ -415,6 +415,7 @@
Из-за ограничений %1$s в одну транзакцию может поместиться только %2$d UTXO. Это означает, что вы можете отправить только %3$s или меньше. Вам нужно уменьшить сумму.
Значение скопировано
Кошельки
+ Предупреждение
неделю
с
Да
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 14557017ad..67cdf8af99 100644
--- a/core/res/src/main/res/values-uk-rUA/strings.xml
+++ b/core/res/src/main/res/values-uk-rUA/strings.xml
@@ -415,6 +415,7 @@
Через обмеження %1$s в одну транзакцію може поміститися тільки %2$d UTXO. Це означає, що ви можете відправити тільки %3$s або менше. Вам потрібно зменшити суму.
Скопійовано
Гаманці
+ Увага
тиждень
з
Так
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 be6b9c60c8..9a07214403 100644
--- a/core/res/src/main/res/values-zh-rCN/strings.xml
+++ b/core/res/src/main/res/values-zh-rCN/strings.xml
@@ -6,7 +6,7 @@
解鎖
您的錢包已同步並準備就緒。\n缺少某些代幣?
錢包導入成功
- 恢復%d%
+ 恢復%d%%
天
月
週
diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml
index c410e833bc..c18d1cec74 100644
--- a/core/res/src/main/res/values-zh-rTW/strings.xml
+++ b/core/res/src/main/res/values-zh-rTW/strings.xml
@@ -99,6 +99,7 @@
我了解
無法觸達
由於 %1$s 的限制,只有%2$d UTXO 可以放入單次交易中。這意味著您只能發送%3$s或更少數量。您需要減少數量。
+ 警告
是
已複製代幣地址
支持的網路
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index 5f645487de..fe0d7fd0a8 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -405,6 +405,7 @@
Due to %1$s limitations only %2$d UTXOs can fit in a single transaction. This means you can only send %3$s or less. You need to reduce the amount.
Value copied
Wallets
+ Warning
week
with
Yes
@@ -481,7 +482,7 @@
Filter by
My networks
Networks
- Mostly used
+ Popular
No results
Staking & Yield mode
Hi support team, I\'ve encountered an error with code: %s
@@ -695,7 +696,7 @@
Tap the card or ring
Your wallet is synced and ready.\nSome tokens missing?
Wallet successfully imported
- Restoring %d%
+ Restoring %d%%
You have updated biometrics, scan your card or ring to enter
Your balance should be higher than the fee value to make a transfer
Not enough balance
@@ -703,7 +704,7 @@
Not enough Mana
You can transfer only %s due to the Mana limit imposed by the Koinos network
Mana limit
- The Koinos network requires Mana for network fees. Your have %1$s/%2$s Mana
+ The Koinos network requires Mana for network fees. You have %1$s/%2$s Mana
Mana level
To begin tracking your crypto assets and transactions, add tokens
Manage tokens
@@ -752,7 +753,10 @@
No data
Market Pulse
Quick actions
+ Clear all
Search tokens
+ Recents
+ In your portfolio
Result
See tokens under 100k USD market cap
Show tokens
@@ -829,9 +833,11 @@
Position in crypto rating between all coins based on market capitalization
Market position
Max supply
+ Circulating and Max Supply
The maximum number of coins or tokens that can ever exist for a particular cryptocurrency
Max supply
Metrics
+ No limited
Official links
Price performance
Repository
@@ -1112,6 +1118,8 @@
Unrecognized QR Code
This network isn\'t supported by any of your added tokens. Add a supported token to send crypto.
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
No memo required
%1$s (%2$s) on %3$s network
%1$s on %2$s network
@@ -1497,6 +1505,7 @@
Fee estimation error. Please send feedback to support.
You swap
Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.
+ You may receive significantly less due to low liquidity. Try a smaller amount or another provider.
High price impact
Insufficient funds
Not enough funds to complete this transaction. Reduce the amount to receive or add more funds.
@@ -1506,6 +1515,8 @@
You receive
Choose token
not available
+ Not enough liquidity for this trade.\nReduce the amount or choose another provider.
+ Trade too large
We would be happy to receive your feedback
Tangem Pay is now in beta
Card frozen
@@ -1666,6 +1677,7 @@
Token in %%image%% %1$s network
The %1$s (%2$s) token is the main currency on the %3$s network and cannot be hidden as long as you have other tokens on this network in the list
Unable to hide %s
+ N/A
Show QR code
Exchange this token for another at %1$s service fees from February %2$s-%3$s.
Swap with Changelly, %s fees
diff --git a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/Bip321PaymentUriParser.kt b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/Bip321PaymentUriParser.kt
index 8e9dadca89..66a7d643a6 100644
--- a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/Bip321PaymentUriParser.kt
+++ b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/Bip321PaymentUriParser.kt
@@ -46,17 +46,26 @@ internal class Bip321PaymentUriParser(
)
}
- return PaymentUriParser.ParseResult.Success(
+ val result = PaymentUriParser.ParseResult.Success(
ClassifiedQrContent.PaymentUri(
address = parsed.address,
amount = parsed.amount,
- memo = parsed.memo,
+ memo = parsed.memo?.second,
matchingCurrencies = matchingCurrencies,
),
)
+
+ val unconsumed = parsed.remainingParams - PARAM_LABEL
+ return helper.validateParams(
+ result = result,
+ unconsumedParams = unconsumed,
+ memo = parsed.memo,
+ matchingCoins = matchingCoins,
+ )
}
private companion object {
+ const val PARAM_LABEL = "label"
val BLOCKCHAINS = listOf(
Blockchain.Bitcoin,
Blockchain.BitcoinTestnet,
diff --git a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/Eip681PaymentUriParser.kt b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/Eip681PaymentUriParser.kt
index c4c656be84..69e2c408b8 100644
--- a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/Eip681PaymentUriParser.kt
+++ b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/Eip681PaymentUriParser.kt
@@ -41,7 +41,7 @@ internal class Eip681PaymentUriParser(
}
val result = if (parsed.functionName == FUNCTION_TRANSFER) {
- val recipient = parsed.params[PARAM_ADDRESS]
+ val recipient = parsed.params[Param.ADDRESS.key]
if (recipient == null) {
return PaymentUriParser.ParseResult.RecognizedError(
ClassifiedQrContent.Error.Unrecognized(qrCode),
@@ -59,16 +59,30 @@ internal class Eip681PaymentUriParser(
} else {
resolveNativeTransfer(parsed, matchingCoins, allCurrencies)
}
- return if (result != null) {
- PaymentUriParser.ParseResult.Success(result)
- } else {
- PaymentUriParser.ParseResult.RecognizedError(
+ if (result == null) {
+ return PaymentUriParser.ParseResult.RecognizedError(
ClassifiedQrContent.Error.UnsupportedNetwork(
raw = qrCode,
blockchain = matchingCoins.firstOrNull()?.network?.name,
),
)
}
+
+ val unsupportedParams = findUnsupportedParams(parsed.params, parsed.functionName)
+ return if (unsupportedParams.isEmpty()) {
+ PaymentUriParser.ParseResult.Success(result)
+ } else {
+ PaymentUriParser.ParseResult.SuccessWithWarning(result, unsupportedParams)
+ }
+ }
+
+ private fun findUnsupportedParams(params: Map, functionName: String?): Map {
+ val supportedKeys = if (functionName == FUNCTION_TRANSFER) {
+ Param.transferParams()
+ } else {
+ Param.nativeParams()
+ }
+ return params.filterKeys { key -> key !in supportedKeys }
}
private fun resolveNativeTransfer(
@@ -76,7 +90,7 @@ internal class Eip681PaymentUriParser(
matchingCoins: List,
allCurrencies: List,
): ClassifiedQrContent.PaymentUri? {
- val valueWei = parsed.params[PARAM_VALUE]?.toBigDecimalOrNull()
+ val valueWei = parsed.params[Param.VALUE.key]?.toBigDecimalOrNull()
if (matchingCoins.isEmpty()) return null
@@ -105,7 +119,7 @@ internal class Eip681PaymentUriParser(
matchingCoins: List,
allCurrencies: List,
): ClassifiedQrContent.PaymentUri? {
- val recipient = parsed.params[PARAM_ADDRESS] ?: return null
+ val recipient = parsed.params[Param.ADDRESS.key] ?: return null
val contractAddress = parsed.targetAddress
val matchingNetworkIds = matchingCoins.map { it.network.id }.toSet()
@@ -118,7 +132,7 @@ internal class Eip681PaymentUriParser(
if (matchingTokens.isEmpty()) return null
- val rawAmount = parsed.params[PARAM_UINT256]?.toBigDecimalOrNull()
+ val rawAmount = parsed.params[Param.UINT256.key]?.toBigDecimalOrNull()
val amount = rawAmount?.fromSmallestUnit(matchingTokens.first().decimals)
return ClassifiedQrContent.PaymentUri(
@@ -149,7 +163,7 @@ internal class Eip681PaymentUriParser(
val queryString = match.groupValues[GROUP_QUERY]
val params = parseQueryParams(queryString)
- val chainId = pathChainId ?: params[PARAM_CHAIN_ID]?.toLongOrNull()
+ val chainId = pathChainId ?: params[Param.CHAIN_ID.key]?.toLongOrNull()
return Eip681Result(
targetAddress = targetAddress,
@@ -179,15 +193,27 @@ internal class Eip681PaymentUriParser(
val params: Map,
)
+ private enum class Param(val key: String) {
+ VALUE("value"),
+ ADDRESS("address"),
+ UINT256("uint256"),
+ CHAIN_ID("chainId"),
+ ;
+
+ companion object {
+ /** Params supported for native transfers (no function or unknown function). */
+ fun nativeParams(): Set = setOf(VALUE.key, CHAIN_ID.key)
+
+ /** Params supported for ERC-20 transfer() calls. */
+ fun transferParams(): Set = setOf(ADDRESS.key, UINT256.key, CHAIN_ID.key)
+ }
+ }
+
private companion object {
// ethereum:[@][/][?]
val URI_REGEX = Regex("""^([^@/?]+)(?:@(\d+))?(?:/([^?]+))?(?:\?(.+))?$""")
val SCHEMES = Blockchain.Ethereum.getShareScheme()
const val FUNCTION_TRANSFER = "transfer"
- const val PARAM_VALUE = "value"
- const val PARAM_ADDRESS = "address"
- const val PARAM_UINT256 = "uint256"
- const val PARAM_CHAIN_ID = "chainId"
const val GROUP_ADDRESS = 1
const val GROUP_CHAIN_ID = 2
const val GROUP_FUNCTION = 3
diff --git a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/PaymentUriParser.kt b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/PaymentUriParser.kt
index e7a64b8740..a74168e99b 100644
--- a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/PaymentUriParser.kt
+++ b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/PaymentUriParser.kt
@@ -16,5 +16,11 @@ internal interface PaymentUriParser {
/** Successfully parsed with matching currencies. */
data class Success(val content: ClassifiedQrContent.PaymentUri) : ParseResult()
+
+ /** Successfully parsed but QR contains unsupported parameters. */
+ data class SuccessWithWarning(
+ val content: ClassifiedQrContent.PaymentUri,
+ val unsupportedParams: Map,
+ ) : ParseResult()
}
}
\ No newline at end of file
diff --git a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/PaymentUriResolveHelper.kt b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/PaymentUriResolveHelper.kt
index c28f09b726..3df547b4b1 100644
--- a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/PaymentUriResolveHelper.kt
+++ b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/PaymentUriResolveHelper.kt
@@ -40,7 +40,7 @@ internal class PaymentUriResolveHelper {
ClassifiedQrContent.PaymentUri(
address = context.parsed.address,
amount = amount,
- memo = context.parsed.memo,
+ memo = context.parsed.memo?.second,
matchingCurrencies = matchingTokens,
),
)
@@ -67,12 +67,40 @@ internal class PaymentUriResolveHelper {
ClassifiedQrContent.PaymentUri(
address = context.parsed.address,
amount = amount,
- memo = context.parsed.memo,
+ memo = context.parsed.memo?.second,
matchingCurrencies = matchingCurrencies,
),
)
}
+ fun validateParams(
+ result: PaymentUriParser.ParseResult,
+ unconsumedParams: Map,
+ memo: Pair?,
+ matchingCoins: List,
+ ): PaymentUriParser.ParseResult {
+ if (result !is PaymentUriParser.ParseResult.Success) return result
+
+ val unsupported = buildMap {
+ putAll(unconsumedParams)
+ if (memo != null && !isMemoSupported(matchingCoins)) {
+ put(memo.first, memo.second)
+ }
+ }
+
+ return if (unsupported.isEmpty()) {
+ result
+ } else {
+ PaymentUriParser.ParseResult.SuccessWithWarning(result.content, unsupported)
+ }
+ }
+
+ private fun isMemoSupported(matchingCoins: List): Boolean {
+ return matchingCoins.any {
+ it.network.transactionExtrasType != Network.TransactionExtrasType.NONE
+ }
+ }
+
data class ResolveContext(
val parsed: QrSentUriParser.Result,
val matchingCoins: List,
diff --git a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/QrContentClassifierParser.kt b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/QrContentClassifierParser.kt
index 3f8817247d..ca6fed1fc2 100644
--- a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/QrContentClassifierParser.kt
+++ b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/QrContentClassifierParser.kt
@@ -26,6 +26,12 @@ internal class QrContentClassifierParser(
when (val paymentUriResult = tryParsePaymentUri(qrCode, uniqueCoins, userCurrencies)) {
is PaymentUriParser.ParseResult.Success -> return paymentUriResult.content
+ is PaymentUriParser.ParseResult.SuccessWithWarning -> {
+ return ClassifiedQrContent.PaymentUriWarning(
+ paymentUri = paymentUriResult.content,
+ unsupportedParams = paymentUriResult.unsupportedParams,
+ )
+ }
is PaymentUriParser.ParseResult.RecognizedError -> return paymentUriResult.error
is PaymentUriParser.ParseResult.NotRecognized -> Unit
}
diff --git a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/QrSentUriParser.kt b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/QrSentUriParser.kt
index af64a3c485..2655f9ee3b 100644
--- a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/QrSentUriParser.kt
+++ b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/QrSentUriParser.kt
@@ -8,8 +8,8 @@ internal class QrSentUriParser {
data class Result(
val address: String,
val amount: BigDecimal?,
- val memo: String?,
- val params: Map,
+ val memo: Pair?,
+ val remainingParams: Map,
)
fun parse(withoutScheme: String): Result? {
@@ -20,15 +20,24 @@ internal class QrSentUriParser {
val params = extractParameters(withoutScheme)
val amount = params[PARAM_AMOUNT]?.toBigDecimalOrNull()
- val memo = (params[PARAM_MEMO] ?: params[PARAM_MESSAGE] ?: params[PARAM_DT])?.let {
- runCatching { URLDecoder.decode(it, CHARSET_UTF8) }.getOrDefault(it)
+
+ val memoKey = MemoParam.keys.firstOrNull { it in params }
+ val memo = memoKey?.let { key ->
+ val raw = params[key] ?: return@let null
+ val decoded = runCatching { URLDecoder.decode(raw, CHARSET_UTF8) }.getOrDefault(raw)
+ key to decoded
+ }
+
+ val consumedKeys = buildSet {
+ add(PARAM_AMOUNT)
+ addAll(MemoParam.keys)
}
return Result(
address = address,
amount = amount,
memo = memo,
- params = params,
+ remainingParams = params - consumedKeys,
)
}
@@ -44,6 +53,17 @@ internal class QrSentUriParser {
.toMap()
}
+ enum class MemoParam(val key: String) {
+ MEMO("memo"),
+ MESSAGE("message"),
+ DESTINATION_TAG("dt"),
+ ;
+
+ companion object {
+ val keys = entries.map { it.key }.toSet()
+ }
+ }
+
companion object {
const val CHAIN_DELIMITER = '@'
const val FUNCTION_DELIMITER = '/'
@@ -51,9 +71,6 @@ internal class QrSentUriParser {
const val PARAMS_DELIMITER = '&'
const val PARAM_VALUE_DELIMITER = '='
const val PARAM_AMOUNT = "amount"
- const val PARAM_MEMO = "memo"
- const val PARAM_MESSAGE = "message"
- const val PARAM_DT = "dt"
const val PARAM_ADDRESS = "address"
const val PARAM_VALUE = "value"
const val PARAM_UINT256 = "uint256"
diff --git a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/SolanaPaymentUriParser.kt b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/SolanaPaymentUriParser.kt
index e3a85ded0a..cd9cee1f8c 100644
--- a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/SolanaPaymentUriParser.kt
+++ b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/SolanaPaymentUriParser.kt
@@ -49,12 +49,20 @@ internal class SolanaPaymentUriParser(
qrCode = qrCode,
)
- val splTokenMint = parsed.params[PARAM_SPL_TOKEN]
- return if (splTokenMint != null) {
+ val splTokenMint = parsed.remainingParams[PARAM_SPL_TOKEN]
+ val result = if (splTokenMint != null) {
helper.resolveTokenTransfer(context, splTokenMint)
} else {
helper.resolveNativeOrAll(context)
}
+
+ val unconsumed = parsed.remainingParams - PARAM_SPL_TOKEN
+ return helper.validateParams(
+ result = result,
+ unconsumedParams = unconsumed,
+ memo = parsed.memo,
+ matchingCoins = matchingCoins,
+ )
}
private companion object {
diff --git a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/TronPaymentUriParser.kt b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/TronPaymentUriParser.kt
index bbf95cf345..985e94855d 100644
--- a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/TronPaymentUriParser.kt
+++ b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/parser/TronPaymentUriParser.kt
@@ -51,12 +51,20 @@ internal class TronPaymentUriParser(
qrCode = qrCode,
)
- val tokenContractAddress = parsed.params[PARAM_TOKEN]
- return if (tokenContractAddress != null) {
+ val tokenContractAddress = parsed.remainingParams[PARAM_TOKEN]
+ val result = if (tokenContractAddress != null) {
helper.resolveTokenTransfer(context, tokenContractAddress, ::interpretAmount)
} else {
helper.resolveNativeOrAll(context, ::interpretAmount)
}
+
+ val unconsumed = parsed.remainingParams - PARAM_TOKEN
+ return helper.validateParams(
+ result = result,
+ unconsumedParams = unconsumed,
+ memo = parsed.memo,
+ matchingCoins = matchingCoins,
+ )
}
/**
diff --git a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt
index 33d6158669..8b71bc156a 100644
--- a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt
+++ b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt
@@ -47,11 +47,11 @@ internal class DefaultQrScanningEventsRepository(
val result = QrResult(address = parsed.address)
result.amount = parsed.amount
- result.memo = parsed.memo
+ result.memo = parsed.memo?.second
// ERC-681: if 'address' parameter exists, currency must be a token,
// and the URI address must match the token's contract address.
- parsed.params[QrSentUriParser.PARAM_ADDRESS]?.let { addressValue ->
+ parsed.remainingParams[QrSentUriParser.PARAM_ADDRESS]?.let { addressValue ->
val tokenCurrency = cryptoCurrency as? CryptoCurrency.Token ?: return QrResult()
if (tokenCurrency.contractAddress.equals(parsed.address, ignoreCase = true)) {
result.address = addressValue
@@ -61,8 +61,8 @@ internal class DefaultQrScanningEventsRepository(
}
// ERC-681: value/uint256 is in the smallest unit, needs conversion
- val valueStr = parsed.params[QrSentUriParser.PARAM_VALUE]
- ?: parsed.params[QrSentUriParser.PARAM_UINT256]
+ val valueStr = parsed.remainingParams[QrSentUriParser.PARAM_VALUE]
+ ?: parsed.remainingParams[QrSentUriParser.PARAM_UINT256]
if (valueStr != null) {
result.amount = valueStr.parseBigDecimalOrNull()
?.toPlainString()?.toBigDecimalOrNull()
diff --git a/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/Bip321PaymentUriParserTest.kt b/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/Bip321PaymentUriParserTest.kt
index 4d3c46bdec..cac610a9e7 100644
--- a/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/Bip321PaymentUriParserTest.kt
+++ b/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/Bip321PaymentUriParserTest.kt
@@ -215,15 +215,76 @@ internal class Bip321PaymentUriParserTest {
}
@Test
- fun `bitcoin URI with memo param`() {
+ fun `bitcoin URI with memo param returns warning for unsupported memo`() {
val result = parser.parse(
qrCode = "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?amount=1&memo=TestMemo",
coins = listOf(bitcoinCoin),
allCurrencies = listOf(bitcoinCoin),
+ )
+
+ assertThat(result).isInstanceOf(PaymentUriParser.ParseResult.SuccessWithWarning::class.java)
+ val warning = result as PaymentUriParser.ParseResult.SuccessWithWarning
+ assertThat(warning.content.memo).isEqualTo("TestMemo")
+ assertThat(warning.unsupportedParams).containsEntry("memo", "TestMemo")
+ }
+
+ // endregion
+
+ // region Unsupported params
+
+ @Test
+ fun `unknown parameter returns SuccessWithWarning`() {
+ val result = parser.parse(
+ qrCode = "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?amount=0.5&req-unknownparam=bar",
+ coins = listOf(bitcoinCoin),
+ allCurrencies = listOf(bitcoinCoin),
+ )
+
+ assertThat(result).isInstanceOf(PaymentUriParser.ParseResult.SuccessWithWarning::class.java)
+ val warning = result as PaymentUriParser.ParseResult.SuccessWithWarning
+ assertThat(warning.content.address).isEqualTo("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa")
+ assertThat(warning.unsupportedParams).containsEntry("req-unknownparam", "bar")
+ }
+
+ @Test
+ fun `multiple unknown parameters all reported`() {
+ val result = parser.parse(
+ qrCode = "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?amount=1&foo=1&bar=2",
+ coins = listOf(bitcoinCoin),
+ allCurrencies = listOf(bitcoinCoin),
+ )
+
+ assertThat(result).isInstanceOf(PaymentUriParser.ParseResult.SuccessWithWarning::class.java)
+ val warning = result as PaymentUriParser.ParseResult.SuccessWithWarning
+ assertThat(warning.unsupportedParams).hasSize(2)
+ assertThat(warning.unsupportedParams).containsEntry("foo", "1")
+ assertThat(warning.unsupportedParams).containsEntry("bar", "2")
+ }
+
+ @Test
+ fun `memo on network with memo support is not unsupported`() {
+ val xrpCoin = buildCoin("XRP", "XRP", "XRP", decimals = 6, extrasType = Network.TransactionExtrasType.DESTINATION_TAG)
+
+ val result = parser.parse(
+ qrCode = "ripple:rAddress?dt=12345",
+ coins = listOf(xrpCoin),
+ allCurrencies = listOf(xrpCoin),
+ ).asSuccess()
+
+ assertThat(result).isNotNull()
+ assertThat(result!!.memo).isEqualTo("12345")
+ }
+
+ @Test
+ fun `only known params returns Success`() {
+ val result = parser.parse(
+ qrCode = "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?amount=0.5&label=Satoshi",
+ coins = listOf(bitcoinCoin),
+ allCurrencies = listOf(bitcoinCoin),
).asSuccess()
assertThat(result).isNotNull()
- assertThat(result!!.memo).isEqualTo("TestMemo")
+ assertThat(result!!.address).isEqualTo("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa")
}
// endregion
@@ -231,7 +292,11 @@ internal class Bip321PaymentUriParserTest {
// region Helpers
private fun PaymentUriParser.ParseResult.asSuccess(): ClassifiedQrContent.PaymentUri? {
- return (this as? PaymentUriParser.ParseResult.Success)?.content
+ return when (this) {
+ is PaymentUriParser.ParseResult.Success -> content
+ is PaymentUriParser.ParseResult.SuccessWithWarning -> content
+ else -> null
+ }
}
private val bitcoinCoin = buildCoin("BTC", "Bitcoin", "BTC", decimals = 8)
@@ -243,6 +308,7 @@ internal class Bip321PaymentUriParserTest {
name: String,
symbol: String,
decimals: Int,
+ extrasType: Network.TransactionExtrasType = Network.TransactionExtrasType.NONE,
): CryptoCurrency.Coin {
return CryptoCurrency.Coin(
id = CryptoCurrency.ID(
@@ -250,7 +316,7 @@ internal class Bip321PaymentUriParserTest {
body = CryptoCurrency.ID.Body.NetworkId(rawNetworkId),
suffix = CryptoCurrency.ID.Suffix.RawID(rawNetworkId),
),
- network = buildNetwork(rawNetworkId, name, symbol),
+ network = buildNetwork(rawNetworkId, name, symbol, extrasType),
name = name,
symbol = symbol,
decimals = decimals,
@@ -276,7 +342,12 @@ internal class Bip321PaymentUriParserTest {
)
}
- private fun buildNetwork(rawNetworkId: String, name: String, symbol: String): Network {
+ private fun buildNetwork(
+ rawNetworkId: String,
+ name: String,
+ symbol: String,
+ extrasType: Network.TransactionExtrasType = Network.TransactionExtrasType.NONE,
+ ): Network {
return Network(
id = Network.ID(Network.RawID(rawNetworkId), Network.DerivationPath.None),
backendId = rawNetworkId,
@@ -287,7 +358,7 @@ internal class Bip321PaymentUriParserTest {
standardType = Network.StandardType.Unspecified("UNSPECIFIED"),
hasFiatFeeRate = false,
canHandleTokens = false,
- transactionExtrasType = Network.TransactionExtrasType.NONE,
+ transactionExtrasType = extrasType,
nameResolvingType = Network.NameResolvingType.NONE,
)
}
diff --git a/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/Eip681PaymentUriParserTest.kt b/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/Eip681PaymentUriParserTest.kt
index e76d3c6910..a98cbe0026 100644
--- a/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/Eip681PaymentUriParserTest.kt
+++ b/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/Eip681PaymentUriParserTest.kt
@@ -283,10 +283,64 @@ internal class Eip681PaymentUriParserTest {
// endregion
+ // region Unsupported params
+
+ @Test
+ fun `native transfer with unknown param returns SuccessWithWarning`() {
+ every { blockchainDataProvider.getChainId(ethereumCoin.network) } returns 1L
+
+ val result = parser.parse(
+ qrCode = "ethereum:0xRecipient@1?value=1000000000000000000&gasLimit=21000",
+ coins = listOf(ethereumCoin),
+ allCurrencies = listOf(ethereumCoin),
+ )
+
+ assertThat(result).isInstanceOf(PaymentUriParser.ParseResult.SuccessWithWarning::class.java)
+ val warning = result as PaymentUriParser.ParseResult.SuccessWithWarning
+ assertThat(warning.content.address).isEqualTo("0xRecipient")
+ assertThat(warning.unsupportedParams).containsEntry("gasLimit", "21000")
+ }
+
+ @Test
+ fun `ERC-20 transfer with unknown param returns SuccessWithWarning`() {
+ every { blockchainDataProvider.getChainId(ethereumCoin.network) } returns 1L
+
+ val usdcToken = buildToken("ethereum", "USDC", "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
+
+ val result = parser.parse(
+ qrCode = "ethereum:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48@1/transfer?address=0xRecipient&uint256=1000000&gasPrice=20000000000",
+ coins = listOf(ethereumCoin),
+ allCurrencies = listOf(ethereumCoin, usdcToken),
+ )
+
+ assertThat(result).isInstanceOf(PaymentUriParser.ParseResult.SuccessWithWarning::class.java)
+ val warning = result as PaymentUriParser.ParseResult.SuccessWithWarning
+ assertThat(warning.unsupportedParams).containsEntry("gasPrice", "20000000000")
+ }
+
+ @Test
+ fun `native transfer with only known params returns Success`() {
+ every { blockchainDataProvider.getChainId(ethereumCoin.network) } returns 1L
+
+ val result = parser.parse(
+ qrCode = "ethereum:0xRecipient@1?value=1000000000000000000",
+ coins = listOf(ethereumCoin),
+ allCurrencies = listOf(ethereumCoin),
+ )
+
+ assertThat(result).isInstanceOf(PaymentUriParser.ParseResult.Success::class.java)
+ }
+
+ // endregion
+
// region Helpers
private fun PaymentUriParser.ParseResult.asSuccess(): ClassifiedQrContent.PaymentUri? {
- return (this as? PaymentUriParser.ParseResult.Success)?.content
+ return when (this) {
+ is PaymentUriParser.ParseResult.Success -> content
+ is PaymentUriParser.ParseResult.SuccessWithWarning -> content
+ else -> null
+ }
}
private val bitcoinCoin = buildCoin("bitcoin", decimals = 8)
diff --git a/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/SolanaPaymentUriParserTest.kt b/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/SolanaPaymentUriParserTest.kt
index 22d0e53a40..068663caed 100644
--- a/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/SolanaPaymentUriParserTest.kt
+++ b/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/SolanaPaymentUriParserTest.kt
@@ -146,10 +146,56 @@ internal class SolanaPaymentUriParserTest {
// endregion
+ // region Unsupported params
+
+ @Test
+ fun `unknown parameter returns SuccessWithWarning`() {
+ val result = parser.parse(
+ qrCode = "solana:SolAddress?amount=1&reference=abc123",
+ coins = listOf(solanaCoin),
+ allCurrencies = listOf(solanaCoin),
+ )
+
+ assertThat(result).isInstanceOf(PaymentUriParser.ParseResult.SuccessWithWarning::class.java)
+ val warning = result as PaymentUriParser.ParseResult.SuccessWithWarning
+ assertThat(warning.content.address).isEqualTo("SolAddress")
+ assertThat(warning.unsupportedParams).containsEntry("reference", "abc123")
+ }
+
+ @Test
+ fun `memo on non-memo network returns SuccessWithWarning`() {
+ val result = parser.parse(
+ qrCode = "solana:SolAddress?amount=1&memo=hello",
+ coins = listOf(solanaCoin),
+ allCurrencies = listOf(solanaCoin),
+ )
+
+ assertThat(result).isInstanceOf(PaymentUriParser.ParseResult.SuccessWithWarning::class.java)
+ val warning = result as PaymentUriParser.ParseResult.SuccessWithWarning
+ assertThat(warning.unsupportedParams).containsEntry("memo", "hello")
+ }
+
+ @Test
+ fun `only known params returns Success`() {
+ val result = parser.parse(
+ qrCode = "solana:SolAddress?amount=1",
+ coins = listOf(solanaCoin),
+ allCurrencies = listOf(solanaCoin),
+ )
+
+ assertThat(result).isInstanceOf(PaymentUriParser.ParseResult.Success::class.java)
+ }
+
+ // endregion
+
// region Helpers
private fun PaymentUriParser.ParseResult.asSuccess(): ClassifiedQrContent.PaymentUri? {
- return (this as? PaymentUriParser.ParseResult.Success)?.content
+ return when (this) {
+ is PaymentUriParser.ParseResult.Success -> content
+ is PaymentUriParser.ParseResult.SuccessWithWarning -> content
+ else -> null
+ }
}
private val solanaNetwork = buildNetwork("SOLANA", "Solana", "SOL")
diff --git a/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/TronPaymentUriParserTest.kt b/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/TronPaymentUriParserTest.kt
index 2525689756..8c07742046 100644
--- a/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/TronPaymentUriParserTest.kt
+++ b/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/TronPaymentUriParserTest.kt
@@ -186,10 +186,56 @@ internal class TronPaymentUriParserTest {
// endregion
+ // region Unsupported params
+
+ @Test
+ fun `unknown parameter returns SuccessWithWarning`() {
+ val result = parser.parse(
+ qrCode = "tron:TAddress?amount=100&gasLimit=21000",
+ coins = listOf(tronCoin),
+ allCurrencies = listOf(tronCoin),
+ )
+
+ assertThat(result).isInstanceOf(PaymentUriParser.ParseResult.SuccessWithWarning::class.java)
+ val warning = result as PaymentUriParser.ParseResult.SuccessWithWarning
+ assertThat(warning.content.address).isEqualTo("TAddress")
+ assertThat(warning.unsupportedParams).containsEntry("gaslimit", "21000")
+ }
+
+ @Test
+ fun `memo on non-memo network returns SuccessWithWarning`() {
+ val result = parser.parse(
+ qrCode = "tron:TAddress?amount=100&memo=hello",
+ coins = listOf(tronCoin),
+ allCurrencies = listOf(tronCoin),
+ )
+
+ assertThat(result).isInstanceOf(PaymentUriParser.ParseResult.SuccessWithWarning::class.java)
+ val warning = result as PaymentUriParser.ParseResult.SuccessWithWarning
+ assertThat(warning.unsupportedParams).containsEntry("memo", "hello")
+ }
+
+ @Test
+ fun `only known params returns Success`() {
+ val result = parser.parse(
+ qrCode = "tron:TAddress?amount=100",
+ coins = listOf(tronCoin),
+ allCurrencies = listOf(tronCoin),
+ )
+
+ assertThat(result).isInstanceOf(PaymentUriParser.ParseResult.Success::class.java)
+ }
+
+ // endregion
+
// region Helpers
private fun PaymentUriParser.ParseResult.asSuccess(): ClassifiedQrContent.PaymentUri? {
- return (this as? PaymentUriParser.ParseResult.Success)?.content
+ return when (this) {
+ is PaymentUriParser.ParseResult.Success -> content
+ is PaymentUriParser.ParseResult.SuccessWithWarning -> content
+ else -> null
+ }
}
private val tronNetwork = buildNetwork("TRON", "Tron", "TRX")
diff --git a/domain/qr-scanning/models/src/main/java/com/tangem/domain/qrscanning/models/ClassifiedQrContent.kt b/domain/qr-scanning/models/src/main/java/com/tangem/domain/qrscanning/models/ClassifiedQrContent.kt
index 1fefc8a555..b9b7b63ff6 100644
--- a/domain/qr-scanning/models/src/main/java/com/tangem/domain/qrscanning/models/ClassifiedQrContent.kt
+++ b/domain/qr-scanning/models/src/main/java/com/tangem/domain/qrscanning/models/ClassifiedQrContent.kt
@@ -19,6 +19,11 @@ sealed interface ClassifiedQrContent {
val matchingCurrencies: List,
) : ClassifiedQrContent
+ data class PaymentUriWarning(
+ val paymentUri: PaymentUri,
+ val unsupportedParams: Map,
+ ) : ClassifiedQrContent
+
sealed interface Error : ClassifiedQrContent {
/** QR code not recognized by any parser */
diff --git a/domain/qr-scanning/models/src/main/java/com/tangem/domain/qrscanning/models/QrSendTarget.kt b/domain/qr-scanning/models/src/main/java/com/tangem/domain/qrscanning/models/QrSendTarget.kt
index dcb02f58d4..986824f436 100644
--- a/domain/qr-scanning/models/src/main/java/com/tangem/domain/qrscanning/models/QrSendTarget.kt
+++ b/domain/qr-scanning/models/src/main/java/com/tangem/domain/qrscanning/models/QrSendTarget.kt
@@ -41,6 +41,11 @@ sealed class QrSendTarget {
data object AddressSameAsWallet : QrSendTarget()
+ data class Warning(
+ val target: QrSendTarget,
+ val unsupportedParams: Map,
+ ) : QrSendTarget()
+
data class WalletConnect(val uri: String) : QrSendTarget()
data class Error(val error: ClassifiedQrContent.Error) : QrSendTarget()
diff --git a/domain/qr-scanning/src/main/java/com/tangem/domain/qrscanning/usecases/ResolveQrSendTargetsUseCase.kt b/domain/qr-scanning/src/main/java/com/tangem/domain/qrscanning/usecases/ResolveQrSendTargetsUseCase.kt
index 9353df0a2a..4156a83e1f 100644
--- a/domain/qr-scanning/src/main/java/com/tangem/domain/qrscanning/usecases/ResolveQrSendTargetsUseCase.kt
+++ b/domain/qr-scanning/src/main/java/com/tangem/domain/qrscanning/usecases/ResolveQrSendTargetsUseCase.kt
@@ -73,6 +73,13 @@ class ResolveQrSendTargetsUseCase(
matchingCurrencies = classified.matchingCurrencies,
portfolioIndex = portfolioIndex,
)
+ is ClassifiedQrContent.PaymentUriWarning -> {
+ val inner = resolve(classified.paymentUri, portfolioIndex)
+ QrSendTarget.Warning(
+ target = inner,
+ unsupportedParams = classified.unsupportedParams,
+ )
+ }
}
}
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt
index a65941d7ed..280c099e2d 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt
@@ -755,7 +755,12 @@ internal class WalletModel @Inject constructor(
}
private suspend fun handleQrResult(qrCode: String, resultSource: QrResultSource) {
- when (val target = resolveQrSendTargetsUseCase(qrCode)) {
+ val target = resolveQrSendTargetsUseCase(qrCode)
+ handleQrTarget(target, resultSource)
+ }
+
+ private fun handleQrTarget(target: QrSendTarget, resultSource: QrResultSource) {
+ when (target) {
is QrSendTarget.WalletConnect -> {
val source = when (resultSource) {
QrResultSource.CLIPBOARD -> WcPairRequest.Source.CLIPBOARD
@@ -788,6 +793,14 @@ internal class WalletModel @Inject constructor(
is QrSendTarget.AddressSameAsWallet -> {
uiMessageSender.send(WalletAlertUM.qrCodeAddressSameAsWallet())
}
+ is QrSendTarget.Warning -> {
+ uiMessageSender.send(
+ WalletAlertUM.qrCodeUnsupportedParams(
+ unsupportedParams = target.unsupportedParams,
+ onContinue = { handleQrTarget(target.target, resultSource) },
+ ),
+ )
+ }
is QrSendTarget.Error -> handleQrError(target.error)
}
}
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAlertUM.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAlertUM.kt
index 9b07b7f428..8aa4bdf4a1 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAlertUM.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAlertUM.kt
@@ -94,6 +94,24 @@ internal object WalletAlertUM {
)
}
+ fun qrCodeUnsupportedParams(unsupportedParams: Map, onContinue: () -> Unit): DialogMessage {
+ val paramsList = unsupportedParams.entries.joinToString { "${it.key} = ${it.value}" }
+ return DialogMessage(
+ title = resourceReference(R.string.qr_scanner_warning_unknown_parameters_title),
+ message = resourceReference(
+ id = R.string.qr_scanner_warning_unknown_parameters_message,
+ formatArgs = WrappedList(listOf(paramsList)),
+ ),
+ firstActionBuilder = {
+ EventMessageAction(
+ title = resourceReference(R.string.common_continue),
+ onClick = onContinue,
+ )
+ },
+ secondActionBuilder = { cancelAction() },
+ )
+ }
+
fun qrCodeAddressSameAsWallet(): DialogMessage {
return DialogMessage(
message = resourceReference(R.string.send_error_address_same_as_wallet),