Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-20 15:27:00 +03:00
parent 2a1369fce6
commit 5a99de87fb
29 changed files with 194 additions and 47 deletions

View file

@ -25,7 +25,10 @@ internal class Bip321PaymentUriParser(
val matchingCurrencies = allCurrencies.filter { it.network.id in matchingNetworkIds }
if (matchingCurrencies.isEmpty()) {
return PaymentUriParser.ParseResult.RecognizedError(
ClassifiedQrContent.Error.UnsupportedNetwork,
ClassifiedQrContent.Error.UnsupportedNetwork(
raw = qrCode,
blockchain = matchingCoins.firstOrNull()?.network?.name,
),
)
}

View file

@ -24,7 +24,10 @@ internal class Eip681PaymentUriParser(
val matchingCoins = findMatchingCoins(parsed.chainId, coins)
if (matchingCoins.isEmpty()) {
return PaymentUriParser.ParseResult.RecognizedError(
ClassifiedQrContent.Error.UnsupportedNetwork,
ClassifiedQrContent.Error.UnsupportedNetwork(
raw = qrCode,
blockchain = parsed.chainId?.let { blockchainDataProvider.getBlockchainNameByChainId(it) },
),
)
}
@ -42,7 +45,10 @@ internal class Eip681PaymentUriParser(
PaymentUriParser.ParseResult.Success(result)
} else {
PaymentUriParser.ParseResult.RecognizedError(
ClassifiedQrContent.Error.UnsupportedNetwork,
ClassifiedQrContent.Error.UnsupportedNetwork(
raw = qrCode,
blockchain = matchingCoins.firstOrNull()?.network?.name,
),
)
}
}

View file

@ -43,8 +43,12 @@ internal class QrContentClassifierParser(
)
}
if (blockchainDataProvider.isSupportedAddress(qrCode)) {
return ClassifiedQrContent.Error.UnsupportedNetwork
val supportedBlockchain = blockchainDataProvider.findSupportedBlockchainName(qrCode)
if (supportedBlockchain != null) {
return ClassifiedQrContent.Error.UnsupportedNetwork(
raw = qrCode,
blockchain = supportedBlockchain,
)
}
return ClassifiedQrContent.Error.Unrecognized(qrCode)
@ -76,7 +80,8 @@ internal class QrContentClassifierParser(
fun getShareSchemes(network: Network): List<String>
fun validateAddress(network: Network, address: String): Boolean
fun getChainId(network: Network): Long?
fun isSupportedAddress(address: String): Boolean
fun findSupportedBlockchainName(address: String): String?
fun getBlockchainNameByChainId(chainId: Long): String?
}
internal class DefaultBlockchainDataProvider : BlockchainDataProvider {
@ -92,12 +97,20 @@ internal class QrContentClassifierParser(
return runCatching { network.toBlockchain().getChainId()?.toLong() }.getOrNull()
}
override fun isSupportedAddress(address: String): Boolean {
override fun findSupportedBlockchainName(address: String): String? {
return Blockchain.entries
.filter { !it.isTestnet() }
.any { blockchain ->
.firstOrNull { blockchain ->
runCatching { blockchain.validateAddress(address) }.getOrDefault(false)
}
}?.fullName
}
override fun getBlockchainNameByChainId(chainId: Long): String? {
return Blockchain.entries
.filter { !it.isTestnet() }
.firstOrNull { blockchain ->
runCatching { blockchain.getChainId()?.toLong() == chainId }.getOrDefault(false)
}?.fullName
}
}

View file

@ -17,6 +17,7 @@ internal class Eip681PaymentUriParserTest {
private val blockchainDataProvider = mockk<QrContentClassifierParser.BlockchainDataProvider> {
every { getShareSchemes(any()) } returns emptyList()
every { getChainId(any()) } returns null
every { getBlockchainNameByChainId(any()) } returns null
}
private val parser = Eip681PaymentUriParser(blockchainDataProvider)

View file

@ -17,7 +17,8 @@ internal class QrContentClassifierTest {
every { getShareSchemes(any()) } returns emptyList()
every { validateAddress(any(), any()) } returns false
every { getChainId(any()) } returns null
every { isSupportedAddress(any()) } returns false
every { findSupportedBlockchainName(any()) } returns null
every { getBlockchainNameByChainId(any()) } returns null
}
private val paymentUriParser = mockk<PaymentUriParser> {
every { parse(any(), any(), any()) } returns PaymentUriParser.ParseResult.NotRecognized

View file

@ -41,13 +41,17 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
@Suppress("LongMethod")
override operator fun invoke(): Flow<WcPairState> {
val (uri: String, source: WcPairRequest.Source) = pairRequest
return flow {
Timber.tag(WC_TAG).i("start pair flow $pairRequest")
analytics.send(WcAnalyticEvents.NewPairInitiated(source))
analytics.send(
WcAnalyticEvents.NewPairInitiated(
source = pairRequest.source,
screen = pairRequest.screen,
),
)
emit(WcPairState.Loading)
val pairResult = sdkDelegate.pair(uri)
val pairResult = sdkDelegate.pair(pairRequest.uri)
.onLeft {
Timber.tag(WC_TAG).e(it, "Failed to call pair $pairRequest")
analytics.send(