Updated on 2026-08-14
This commit is contained in:
parent
76d77bd83c
commit
c44508b705
9 changed files with 137 additions and 7 deletions
|
|
@ -26,9 +26,9 @@ internal object QrScanningDataModule {
|
|||
blockchainDataProvider = blockchainDataProvider,
|
||||
paymentUriParsers = setOf(
|
||||
Eip681PaymentUriParser(blockchainDataProvider),
|
||||
TronPaymentUriParser(),
|
||||
SolanaPaymentUriParser(),
|
||||
Bip321PaymentUriParser(),
|
||||
TronPaymentUriParser(blockchainDataProvider),
|
||||
SolanaPaymentUriParser(blockchainDataProvider),
|
||||
Bip321PaymentUriParser(blockchainDataProvider),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
|||
import com.tangem.domain.qrscanning.models.ClassifiedQrContent
|
||||
|
||||
internal class Bip321PaymentUriParser(
|
||||
private val blockchainDataProvider: QrContentClassifierParser.BlockchainDataProvider,
|
||||
private val helper: PaymentUriResolveHelper = PaymentUriResolveHelper(),
|
||||
) : PaymentUriParser {
|
||||
|
||||
|
|
@ -36,6 +37,15 @@ internal class Bip321PaymentUriParser(
|
|||
)
|
||||
}
|
||||
|
||||
val isAddressValid = matchingCoins.any {
|
||||
blockchainDataProvider.validateAddress(it.network, parsed.address)
|
||||
}
|
||||
if (!isAddressValid) {
|
||||
return PaymentUriParser.ParseResult.RecognizedError(
|
||||
ClassifiedQrContent.Error.Unrecognized(qrCode),
|
||||
)
|
||||
}
|
||||
|
||||
return PaymentUriParser.ParseResult.Success(
|
||||
ClassifiedQrContent.PaymentUri(
|
||||
address = parsed.address,
|
||||
|
|
|
|||
|
|
@ -31,8 +31,26 @@ internal class Eip681PaymentUriParser(
|
|||
)
|
||||
}
|
||||
|
||||
val isAddressValid = matchingCoins.any { coin ->
|
||||
blockchainDataProvider.validateAddress(coin.network, parsed.targetAddress)
|
||||
}
|
||||
if (!isAddressValid) {
|
||||
return PaymentUriParser.ParseResult.RecognizedError(
|
||||
ClassifiedQrContent.Error.Unrecognized(qrCode),
|
||||
)
|
||||
}
|
||||
|
||||
val result = if (parsed.functionName == FUNCTION_TRANSFER) {
|
||||
if (PARAM_ADDRESS !in parsed.params) {
|
||||
val recipient = parsed.params[PARAM_ADDRESS]
|
||||
if (recipient == null) {
|
||||
return PaymentUriParser.ParseResult.RecognizedError(
|
||||
ClassifiedQrContent.Error.Unrecognized(qrCode),
|
||||
)
|
||||
}
|
||||
val isRecipientValid = matchingCoins.any { coin ->
|
||||
blockchainDataProvider.validateAddress(coin.network, recipient)
|
||||
}
|
||||
if (!isRecipientValid) {
|
||||
return PaymentUriParser.ParseResult.RecognizedError(
|
||||
ClassifiedQrContent.Error.Unrecognized(qrCode),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
|||
import com.tangem.domain.qrscanning.models.ClassifiedQrContent
|
||||
|
||||
internal class SolanaPaymentUriParser(
|
||||
private val blockchainDataProvider: QrContentClassifierParser.BlockchainDataProvider,
|
||||
private val helper: PaymentUriResolveHelper = PaymentUriResolveHelper(),
|
||||
) : PaymentUriParser {
|
||||
|
||||
|
|
@ -29,6 +30,15 @@ internal class SolanaPaymentUriParser(
|
|||
)
|
||||
}
|
||||
|
||||
val isAddressValid = matchingCoins.any {
|
||||
blockchainDataProvider.validateAddress(it.network, parsed.address)
|
||||
}
|
||||
if (!isAddressValid) {
|
||||
return PaymentUriParser.ParseResult.RecognizedError(
|
||||
ClassifiedQrContent.Error.Unrecognized(qrCode),
|
||||
)
|
||||
}
|
||||
|
||||
val matchingNetworkIds = matchingCoins.map { it.network.id }.toSet()
|
||||
val context = PaymentUriResolveHelper.ResolveContext(
|
||||
parsed = parsed,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import java.math.BigDecimal
|
|||
import java.math.MathContext
|
||||
|
||||
internal class TronPaymentUriParser(
|
||||
private val blockchainDataProvider: QrContentClassifierParser.BlockchainDataProvider,
|
||||
private val helper: PaymentUriResolveHelper = PaymentUriResolveHelper(),
|
||||
) : PaymentUriParser {
|
||||
|
||||
|
|
@ -31,6 +32,15 @@ internal class TronPaymentUriParser(
|
|||
)
|
||||
}
|
||||
|
||||
val isAddressValid = matchingCoins.any {
|
||||
blockchainDataProvider.validateAddress(it.network, parsed.address)
|
||||
}
|
||||
if (!isAddressValid) {
|
||||
return PaymentUriParser.ParseResult.RecognizedError(
|
||||
ClassifiedQrContent.Error.Unrecognized(qrCode),
|
||||
)
|
||||
}
|
||||
|
||||
val matchingNetworkIds = matchingCoins.map { it.network.id }.toSet()
|
||||
val context = PaymentUriResolveHelper.ResolveContext(
|
||||
parsed = parsed,
|
||||
|
|
|
|||
|
|
@ -3,15 +3,21 @@ package com.tangem.data.qrscanning
|
|||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.data.qrscanning.parser.Bip321PaymentUriParser
|
||||
import com.tangem.data.qrscanning.parser.PaymentUriParser
|
||||
import com.tangem.data.qrscanning.parser.QrContentClassifierParser
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.qrscanning.models.ClassifiedQrContent
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class Bip321PaymentUriParserTest {
|
||||
|
||||
private val parser = Bip321PaymentUriParser()
|
||||
private val blockchainDataProvider = mockk<QrContentClassifierParser.BlockchainDataProvider> {
|
||||
every { validateAddress(any(), any()) } returns true
|
||||
}
|
||||
private val parser = Bip321PaymentUriParser(blockchainDataProvider)
|
||||
|
||||
// region Basic parsing
|
||||
|
||||
|
|
@ -138,6 +144,21 @@ internal class Bip321PaymentUriParserTest {
|
|||
|
||||
// region Unsupported network
|
||||
|
||||
@Test
|
||||
fun `invalid address returns Unrecognized error`() {
|
||||
every { blockchainDataProvider.validateAddress(any(), eq("InvalidBtcAddress")) } returns false
|
||||
|
||||
val result = parser.parse(
|
||||
qrCode = "bitcoin:InvalidBtcAddress?amount=0.5",
|
||||
coins = listOf(bitcoinCoin),
|
||||
allCurrencies = listOf(bitcoinCoin),
|
||||
)
|
||||
|
||||
assertThat(result).isInstanceOf(PaymentUriParser.ParseResult.RecognizedError::class.java)
|
||||
val error = (result as PaymentUriParser.ParseResult.RecognizedError).error
|
||||
assertThat(error).isInstanceOf(ClassifiedQrContent.Error.Unrecognized::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `bitcoin URI with no matching coin returns UnsupportedNetwork`() {
|
||||
val result = parser.parse(
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ internal class Eip681PaymentUriParserTest {
|
|||
private val blockchainDataProvider = mockk<QrContentClassifierParser.BlockchainDataProvider> {
|
||||
every { getChainId(any()) } returns null
|
||||
every { getBlockchainNameByChainId(any()) } returns null
|
||||
every { validateAddress(any(), any()) } returns true
|
||||
}
|
||||
private val parser = Eip681PaymentUriParser(blockchainDataProvider)
|
||||
|
||||
|
|
@ -173,6 +174,24 @@ internal class Eip681PaymentUriParserTest {
|
|||
assertThat(error).isInstanceOf(ClassifiedQrContent.Error.Unrecognized::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ERC-20 transfer with invalid recipient address returns Unrecognized error`() {
|
||||
every { blockchainDataProvider.getChainId(ethereumCoin.network) } returns 1L
|
||||
every { blockchainDataProvider.validateAddress(any(), eq("0xInvalidRecipient")) } returns false
|
||||
|
||||
val usdcToken = buildToken("ethereum", "USDC", "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
|
||||
|
||||
val result = parser.parse(
|
||||
qrCode = "ethereum:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48@1/transfer?address=0xInvalidRecipient&uint256=1000000",
|
||||
coins = listOf(ethereumCoin),
|
||||
allCurrencies = listOf(ethereumCoin, usdcToken),
|
||||
)
|
||||
|
||||
assertThat(result).isInstanceOf(PaymentUriParser.ParseResult.RecognizedError::class.java)
|
||||
val error = (result as PaymentUriParser.ParseResult.RecognizedError).error
|
||||
assertThat(error).isInstanceOf(ClassifiedQrContent.Error.Unrecognized::class.java)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -2,16 +2,22 @@ package com.tangem.data.qrscanning
|
|||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.data.qrscanning.parser.PaymentUriParser
|
||||
import com.tangem.data.qrscanning.parser.QrContentClassifierParser
|
||||
import com.tangem.data.qrscanning.parser.SolanaPaymentUriParser
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.qrscanning.models.ClassifiedQrContent
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class SolanaPaymentUriParserTest {
|
||||
|
||||
private val parser = SolanaPaymentUriParser()
|
||||
private val blockchainDataProvider = mockk<QrContentClassifierParser.BlockchainDataProvider> {
|
||||
every { validateAddress(any(), any()) } returns true
|
||||
}
|
||||
private val parser = SolanaPaymentUriParser(blockchainDataProvider)
|
||||
|
||||
// region Scheme matching
|
||||
|
||||
|
|
@ -112,6 +118,21 @@ internal class SolanaPaymentUriParserTest {
|
|||
|
||||
// region Unsupported network
|
||||
|
||||
@Test
|
||||
fun `invalid address returns Unrecognized error`() {
|
||||
every { blockchainDataProvider.validateAddress(any(), eq("InvalidSolAddress")) } returns false
|
||||
|
||||
val result = parser.parse(
|
||||
qrCode = "solana:InvalidSolAddress?amount=1",
|
||||
coins = listOf(solanaCoin),
|
||||
allCurrencies = listOf(solanaCoin),
|
||||
)
|
||||
|
||||
assertThat(result).isInstanceOf(PaymentUriParser.ParseResult.RecognizedError::class.java)
|
||||
val error = (result as PaymentUriParser.ParseResult.RecognizedError).error
|
||||
assertThat(error).isInstanceOf(ClassifiedQrContent.Error.Unrecognized::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no matching solana coin returns UnsupportedNetwork`() {
|
||||
val result = parser.parse(
|
||||
|
|
|
|||
|
|
@ -2,16 +2,22 @@ package com.tangem.data.qrscanning
|
|||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.data.qrscanning.parser.PaymentUriParser
|
||||
import com.tangem.data.qrscanning.parser.QrContentClassifierParser
|
||||
import com.tangem.data.qrscanning.parser.TronPaymentUriParser
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.qrscanning.models.ClassifiedQrContent
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class TronPaymentUriParserTest {
|
||||
|
||||
private val parser = TronPaymentUriParser()
|
||||
private val blockchainDataProvider = mockk<QrContentClassifierParser.BlockchainDataProvider> {
|
||||
every { validateAddress(any(), any()) } returns true
|
||||
}
|
||||
private val parser = TronPaymentUriParser(blockchainDataProvider)
|
||||
|
||||
// region Scheme matching
|
||||
|
||||
|
|
@ -152,6 +158,21 @@ internal class TronPaymentUriParserTest {
|
|||
|
||||
// region Unsupported network
|
||||
|
||||
@Test
|
||||
fun `invalid address returns Unrecognized error`() {
|
||||
every { blockchainDataProvider.validateAddress(any(), eq("InvalidTronAddress")) } returns false
|
||||
|
||||
val result = parser.parse(
|
||||
qrCode = "tron:InvalidTronAddress?amount=1",
|
||||
coins = listOf(tronCoin),
|
||||
allCurrencies = listOf(tronCoin),
|
||||
)
|
||||
|
||||
assertThat(result).isInstanceOf(PaymentUriParser.ParseResult.RecognizedError::class.java)
|
||||
val error = (result as PaymentUriParser.ParseResult.RecognizedError).error
|
||||
assertThat(error).isInstanceOf(ClassifiedQrContent.Error.Unrecognized::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no matching tron coin returns UnsupportedNetwork`() {
|
||||
val result = parser.parse(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue