diff --git a/apps/flipcash/core/src/main/res/values/strings.xml b/apps/flipcash/core/src/main/res/values/strings.xml
index 8599d00d3..57ee06a11 100644
--- a/apps/flipcash/core/src/main/res/values/strings.xml
+++ b/apps/flipcash/core/src/main/res/values/strings.xml
@@ -679,6 +679,9 @@
Rate Unavailable
Couldn\'t get a fresh rate. Please try again.
+ Amount Too Small
+ The amount you entered is too small to transfer\nPlease enter a larger amount
+
Settings
Withdraw as USDC
diff --git a/apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenViewModel.kt b/apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenViewModel.kt
index 8c7e7b8ba..508626051 100644
--- a/apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenViewModel.kt
+++ b/apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenViewModel.kt
@@ -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
}
diff --git a/services/opencode/src/main/kotlin/com/getcode/opencode/internal/exchange/RealVerifiedFiatCalculator.kt b/services/opencode/src/main/kotlin/com/getcode/opencode/internal/exchange/RealVerifiedFiatCalculator.kt
index d8f93955a..3227bef7d 100644
--- a/services/opencode/src/main/kotlin/com/getcode/opencode/internal/exchange/RealVerifiedFiatCalculator.kt
+++ b/services/opencode/src/main/kotlin/com/getcode/opencode/internal/exchange/RealVerifiedFiatCalculator.kt
@@ -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) {
diff --git a/services/opencode/src/main/kotlin/com/getcode/opencode/model/core/errors/Errors.kt b/services/opencode/src/main/kotlin/com/getcode/opencode/model/core/errors/Errors.kt
index 358f44ff6..7abef714a 100644
--- a/services/opencode/src/main/kotlin/com/getcode/opencode/model/core/errors/Errors.kt
+++ b/services/opencode/src/main/kotlin/com/getcode/opencode/model/core/errors/Errors.kt
@@ -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")
}
diff --git a/services/opencode/src/test/kotlin/com/getcode/opencode/internal/exchange/RealVerifiedFiatCalculatorTest.kt b/services/opencode/src/test/kotlin/com/getcode/opencode/internal/exchange/RealVerifiedFiatCalculatorTest.kt
index 6c977efee..5abf381bf 100644
--- a/services/opencode/src/test/kotlin/com/getcode/opencode/internal/exchange/RealVerifiedFiatCalculatorTest.kt
+++ b/services/opencode/src/test/kotlin/com/getcode/opencode/internal/exchange/RealVerifiedFiatCalculatorTest.kt
@@ -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(result.exceptionOrNull())
+ }
+
+ // endregion
+
// region helpers
private fun usdfToken(): Token = MintMetadata(