Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-13 13:58:42 +04:00
parent c21ef23449
commit 0ad1913175
8 changed files with 275 additions and 34 deletions

View file

@ -8,7 +8,9 @@ import java.util.Locale
* exchange rate for a swap pair ([REDACTED_TASK_KEY]).
*
* Categories used by the rules:
* - **Stable** a [CryptoCurrency.Token] whose symbol is in [STABLECOIN_RANKS].
* - **Stable** a [CryptoCurrency.Token] whose normalized symbol is in [STABLECOIN_RANKS].
* The symbol is normalized to uppercase and the suffix after `.` is stripped, so bridged
* variants (`USDC.E`, `USDT.e`, etc.) are matched against their underlying asset.
* - **Coin** a [CryptoCurrency.Coin] (any native coin: BTC, ETH, SOL, TRX, ...).
* - Anything else (a [CryptoCurrency.Token] outside the stable list) falls into the default
* branch and is treated as a regular token.
@ -43,8 +45,8 @@ internal object SwapRateDirectionResolver {
}
private fun resolveStableToStable(from: CryptoCurrency, to: CryptoCurrency): SwapRateDirection {
val fromRank = stableRank(from.symbol.uppercaseRoot())
val toRank = stableRank(to.symbol.uppercaseRoot())
val fromRank = stableRank(from.symbol.normalizeStableSymbol())
val toRank = stableRank(to.symbol.normalizeStableSymbol())
return if (fromRank <= toRank) {
SwapRateDirection(base = from, quote = to)
} else {
@ -77,7 +79,7 @@ internal object SwapRateDirectionResolver {
private fun stableRank(symbol: String): Int = STABLECOIN_RANKS[symbol] ?: Int.MAX_VALUE
private fun CryptoCurrency.isStable(): Boolean {
return this is CryptoCurrency.Token && STABLECOIN_RANKS.containsKey(symbol.uppercaseRoot())
return this is CryptoCurrency.Token && STABLECOIN_RANKS.containsKey(symbol.normalizeStableSymbol())
}
private fun CryptoCurrency.isCoin(): Boolean = this is CryptoCurrency.Coin
@ -85,6 +87,12 @@ internal object SwapRateDirectionResolver {
private fun String.isBtcOrEth(): Boolean = this == BTC_SYMBOL || this == ETH_SYMBOL
private fun String.uppercaseRoot(): String = uppercase(Locale.ROOT)
/**
* Drops bridge/wrapped suffix (e.g. `USDC.E` `USDC`, `USDT.e` `USDT`) before stable lookup.
* Bridged variants share the underlying asset's rank.
*/
private fun String.normalizeStableSymbol(): String = substringBefore('.').uppercaseRoot()
}
internal data class SwapRateDirection(val base: CryptoCurrency, val quote: CryptoCurrency)

View file

@ -172,6 +172,66 @@ internal class SwapRateDirectionResolverTest {
assertThat(result).isEqualTo(SwapRateDirection(base = usdt, quote = usdc))
}
@Test
fun `GIVEN stable usdt and bridged usdc_e WHEN resolve THEN base is usdt`() {
val usdt = stable(symbol = "USDT")
val usdcE = stable(symbol = "USDC.E")
val result = SwapRateDirectionResolver.resolve(from = usdt, to = usdcE)
assertThat(result).isEqualTo(SwapRateDirection(base = usdt, quote = usdcE))
}
@Test
fun `GIVEN bridged usdc_e and stable usdt WHEN resolve THEN base is usdt`() {
val usdcE = stable(symbol = "USDC.E")
val usdt = stable(symbol = "USDT")
val result = SwapRateDirectionResolver.resolve(from = usdcE, to = usdt)
assertThat(result).isEqualTo(SwapRateDirection(base = usdt, quote = usdcE))
}
@Test
fun `GIVEN coin sol and bridged usdc_e WHEN resolve THEN base is coin`() {
val sol = coin(symbol = "SOL")
val usdcE = stable(symbol = "USDC.E")
val result = SwapRateDirectionResolver.resolve(from = sol, to = usdcE)
assertThat(result).isEqualTo(SwapRateDirection(base = sol, quote = usdcE))
}
@Test
fun `GIVEN bridged usdc_e and coin sol WHEN resolve THEN base is coin`() {
val usdcE = stable(symbol = "USDC.E")
val sol = coin(symbol = "SOL")
val result = SwapRateDirectionResolver.resolve(from = usdcE, to = sol)
assertThat(result).isEqualTo(SwapRateDirection(base = sol, quote = usdcE))
}
@Test
fun `GIVEN lowercase bridged usdt_e and stable usdc WHEN resolve THEN base is usdt`() {
val usdtE = stable(symbol = "usdt.e")
val usdc = stable(symbol = "USDC")
val result = SwapRateDirectionResolver.resolve(from = usdtE, to = usdc)
assertThat(result).isEqualTo(SwapRateDirection(base = usdtE, quote = usdc))
}
@Test
fun `GIVEN bridged dai_e and bridged usdc_e WHEN resolve THEN base is usdc`() {
val daiE = stable(symbol = "DAI.E")
val usdcE = stable(symbol = "USDC.E")
val result = SwapRateDirectionResolver.resolve(from = daiE, to = usdcE)
assertThat(result).isEqualTo(SwapRateDirection(base = usdcE, quote = daiE))
}
private fun coin(symbol: String): CryptoCurrency = mockk<CryptoCurrency.Coin> {
every { this@mockk.symbol } returns symbol
}