From e39b400471b73ecb5252e99cb9c64181b41bb309 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Wed, 3 Jun 2026 12:04:40 -0400 Subject: [PATCH] fix(exchange): round Fiat.hasDisplayableValue to currency precision The buy-sell round-trip through the bonding curve introduces sub-quark truncation, causing 0.01 USD to fall just below smallestUnit (9987 vs 10000 quarks). Round to the currency display digits before comparing so the property matches formatted() semantics. Also removes a debug println from RealVerifiedFiatCalculator. --- .../exchange/RealVerifiedFiatCalculator.kt | 1 - .../getcode/opencode/model/financial/Fiat.kt | 2 +- .../RealVerifiedFiatCalculatorTest.kt | 21 +++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) 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 3227bef7d..2eda845e6 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,7 +114,6 @@ 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()) } diff --git a/services/opencode/src/main/kotlin/com/getcode/opencode/model/financial/Fiat.kt b/services/opencode/src/main/kotlin/com/getcode/opencode/model/financial/Fiat.kt index 482dd0b1a..184d288fc 100644 --- a/services/opencode/src/main/kotlin/com/getcode/opencode/model/financial/Fiat.kt +++ b/services/opencode/src/main/kotlin/com/getcode/opencode/model/financial/Fiat.kt @@ -163,7 +163,7 @@ data class Fiat( /** Whether this value would format as non-zero in its currency. */ val hasDisplayableValue: Boolean - get() = this >= smallestUnit + get() = rounded(currencyCode.fractionDigits) >= smallestUnit fun valueLessThan(other: Fiat): Boolean = toDouble() < other.toDouble() fun valueGreaterThan(other: Fiat): Boolean = toDouble() > other.toDouble() 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 5abf381bf..88c4aff5a 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 @@ -420,6 +420,27 @@ class RealVerifiedFiatCalculatorTest { assertIs(result.exceptionOrNull()) } + @Test + fun `one cent USD succeeds for established token`() = runTest { + // Regression: $0.01 USD on a high-supply (established) bonding-curve token + // would fail with AmountBelowMinimum due to round-trip precision loss through + // the bonding curve. The sell estimate quarks (e.g. 9987) fell just below the + // smallestUnit (10000) before rounding, even though formatted() shows "$0.01". + val supply = 50_000_000_000_000L // established token with high supply + val token = bondingCurveToken(supply = supply) + stubVerifiedState(CurrencyCode.USD, testMint, supply) + + val result = calculator.compute( + amount = Fiat(fiat = 0.01, currencyCode = CurrencyCode.USD), + token = token, + rate = Rate.oneToOne, + trace = false, + ) + + assertTrue(result.isSuccess, "Expected success but got: ${result.exceptionOrNull()}") + assertTrue(result.getOrThrow().localFiat.underlyingTokenAmount.quarks > 0) + } + // endregion // region helpers