Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/flipcash/core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,9 @@
<string name="error_title_staleRates">Rate Unavailable</string>
<string name="error_description_staleRates">Couldn\'t get a fresh rate. Please try again.</string>

<string name="error_title_amountTooSmall">Amount Too Small</string>
<string name="error_description_amountTooSmall">The amount you entered is too small to transfer\nPlease enter a larger amount</string>

<string name="title_settings">Settings</string>

<string name="title_withdrawUsdfAsUsdc">Withdraw as USDC</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,11 +316,19 @@ internal class CashScreenViewModel @Inject constructor(
token = token,
balance = balance.underlyingTokenAmount,
rate = rate,
).getOrElse {
).getOrElse { error ->
dispatchEvent(Event.UpdateLoadingState(loading = false))
val (title, message) = when (error) {
is ComputeVerifiedFiatError.AmountBelowMinimum -> {
R.string.error_title_amountTooSmall to R.string.error_description_amountTooSmall
}
else -> {
R.string.error_title_staleRates to R.string.error_description_staleRates
}
}
BottomBarManager.showAlert(
title = resources.getString(R.string.error_title_staleRates),
message = resources.getString(R.string.error_description_staleRates),
title = resources.getString(title),
message = resources.getString(message),
)
return@onEach
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ internal class RealVerifiedFiatCalculator @Inject constructor(
val underlyingTokenAmount = Fiat(quarks = quarks.toLong(), currencyCode = CurrencyCode.USD)

val sellEstimate = Fiat.tokenBalance(quarks.toLong(), token, supply).convertingTo(rate)

if (!sellEstimate.hasDisplayableValue) {
return Result.failure(ComputeVerifiedFiatError.AmountBelowMinimum())
}

val fx = sellEstimate.decimalValue.toBigDecimal().divideWithHighPrecision(units).toDouble()

if (trace) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,4 +382,5 @@ sealed class ComputeVerifiedFiatError(
class StaleRate : ComputeVerifiedFiatError("Reserve state unavailable or stale")
data class ComputationFailed(override val cause: Throwable? = null) :
ComputeVerifiedFiatError(message = cause?.message, cause = cause)
class AmountBelowMinimum : ComputeVerifiedFiatError("Amount is too small to transfer")
}
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,32 @@ class RealVerifiedFiatCalculatorTest {

// endregion

// region sub-minimum amount

@Test
fun `returns AmountBelowMinimum when sell estimate has no displayable value`() = runTest {
// Simulates the Bugsnag scenario: a low-supply custom token where the
// bonding curve produces a native amount below the currency's smallest
// displayable unit (e.g. ₦0.003 rounds to ₦0.00 for NGN).
val supply = 1_000_000_000_000L
val token = bondingCurveToken(supply = supply)
stubVerifiedState(CurrencyCode.USD, testMint, supply)

// Use a sub-cent amount ($0.000001 = 1 quark) so the curve succeeds
// but the sell estimate is below the smallest displayable USD unit ($0.01).
val result = calculator.compute(
amount = Fiat(quarks = 1, currencyCode = CurrencyCode.USD),
token = token,
rate = Rate.oneToOne,
trace = false,
)

assertTrue(result.isFailure)
assertIs<ComputeVerifiedFiatError.AmountBelowMinimum>(result.exceptionOrNull())
}

// endregion

// region helpers

private fun usdfToken(): Token = MintMetadata(
Expand Down
Loading