Skip to content

Commit 2ede53f

Browse files
committed
feat(opencode): add VerifiedFiatCalculator for supply-consistent exchange
Introduce VerifiedFiatCalculator that computes LocalFiat using the verified supply from VerifiedProtoManager, ensuring underlyingTokenAmount, nativeAmount, and fx are all consistent with the VerifiedState sent to the server. - Move valueExchangeIn logic from LocalFiat into RealVerifiedFiatCalculator - Add supplyOverride parameter to Fiat.tokenBalance - Bind via Hilt in OpenCodeModule - Remove unused imports from TransactionController Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 3f73f3f commit 2ede53f

15 files changed

Lines changed: 560 additions & 141 deletions

File tree

apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenViewModel.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.getcode.manager.BottomBarAction
1111
import com.getcode.manager.BottomBarManager
1212
import com.getcode.opencode.controllers.TransactionOperations
1313
import com.getcode.opencode.exchange.Exchange
14+
import com.getcode.opencode.exchange.VerifiedFiatCalculator
1415
import com.getcode.opencode.model.financial.Currency
1516
import com.getcode.opencode.model.financial.CurrencyCode
1617
import com.getcode.opencode.model.financial.Fiat
@@ -47,6 +48,7 @@ import kotlin.math.min
4748
internal class CashScreenViewModel @Inject constructor(
4849
private val resources: ResourceHelper,
4950
private val exchange: Exchange,
51+
private val verifiedFiatCalculator: VerifiedFiatCalculator,
5052
tokenCoordinator: TokenCoordinator,
5153
transactionController: TransactionOperations,
5254
dispatchers: DispatcherProvider,
@@ -140,7 +142,7 @@ internal class CashScreenViewModel @Inject constructor(
140142
viewModelScope.launch {
141143
val rate = exchange.entryRate
142144
val (token, balance) = stateFlow.value.token!!
143-
val amountFiat = LocalFiat.valueExchangeIn(
145+
val amountFiat = verifiedFiatCalculator.compute(
144146
amount = Fiat(amount, rate.currency),
145147
token = token,
146148
rate = rate,
@@ -302,7 +304,7 @@ internal class CashScreenViewModel @Inject constructor(
302304
val (token, balance) = stateFlow.value.token!!
303305
val rate = exchange.entryRate
304306

305-
val amountFiat = LocalFiat.valueExchangeIn(
307+
val amountFiat = verifiedFiatCalculator.compute(
306308
amount = Fiat(data.amountData.amount, rate.currency),
307309
token = token,
308310
balance = balance.underlyingTokenAmount,

apps/flipcash/features/cash/src/test/kotlin/com/flipcash/app/cash/internal/CashScreenViewModelTest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.flipcash.libs.coroutines.DispatcherProvider
1010
import com.getcode.manager.BottomBarManager
1111
import com.getcode.opencode.controllers.TransactionOperations
1212
import com.getcode.opencode.exchange.Exchange
13+
import com.getcode.opencode.exchange.VerifiedFiatCalculator
1314
import com.getcode.opencode.model.financial.Currency
1415
import com.getcode.opencode.model.financial.CurrencyCode
1516
import com.getcode.opencode.model.financial.Fiat
@@ -54,6 +55,7 @@ class CashScreenViewModelTest {
5455

5556
private val resources: ResourceHelper = mockk(relaxed = true)
5657
private val exchange: Exchange = mockk(relaxed = true)
58+
private val verifiedFiatCalculator: VerifiedFiatCalculator = mockk(relaxed = true)
5759
private val tokenCoordinator: TokenCoordinator = mockk(relaxed = true)
5860
private val transactionController: TransactionOperations = mockk(relaxed = true)
5961

@@ -90,6 +92,7 @@ class CashScreenViewModelTest {
9092
return CashScreenViewModel(
9193
resources = resources,
9294
exchange = exchange,
95+
verifiedFiatCalculator = verifiedFiatCalculator,
9396
tokenCoordinator = tokenCoordinator,
9497
transactionController = transactionController,
9598
dispatchers = dispatchers,

apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/WithdrawalViewModel.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import com.getcode.manager.BottomBarAction
1717
import com.getcode.manager.BottomBarManager
1818
import com.getcode.opencode.controllers.TransactionOperations
1919
import com.getcode.opencode.exchange.Exchange
20+
import com.getcode.opencode.exchange.VerifiedFiatCalculator
2021
import com.getcode.opencode.model.financial.Currency
2122
import com.getcode.opencode.model.financial.CurrencyCode
2223
import com.getcode.opencode.model.financial.Fiat
@@ -68,6 +69,7 @@ internal data class DestinationState(
6869
internal class WithdrawalViewModel @Inject constructor(
6970
private val resources: ResourceHelper,
7071
private val exchange: Exchange,
72+
private val verifiedFiatCalculator: VerifiedFiatCalculator,
7173
private val userManager: UserManager,
7274
transactionController: TransactionOperations,
7375
clipboardManager: ClipboardManager,
@@ -267,7 +269,7 @@ internal class WithdrawalViewModel @Inject constructor(
267269
dispatchEvent(Event.UpdateConfirmingAmountState(loading = true))
268270
val rate = exchange.rateForUsd()
269271
val token = stateFlow.value.token!!.token
270-
val amountFiat = LocalFiat.valueExchangeIn(
272+
val amountFiat = verifiedFiatCalculator.compute(
271273
amount = Fiat(data.amountData.amount, rate.currency),
272274
token = token,
273275
balance = stateFlow.value.token!!.balance,
@@ -394,8 +396,8 @@ internal class WithdrawalViewModel @Inject constructor(
394396
val sendingVault = owner.withTimelockForToken(token)
395397

396398
val feeInMint = feeInUsd?.let { fee ->
397-
LocalFiat.valueExchangeIn(
398-
fee,
399+
verifiedFiatCalculator.compute(
400+
amount = fee,
399401
token = token,
400402
balance = stateFlow.value.token!!.balance,
401403
rate = exchange.rateToUsd(CurrencyCode.USD)!!,

apps/flipcash/features/withdrawal/src/test/kotlin/com/flipcash/app/withdrawal/WithdrawalViewModelErrorTest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.flipcash.services.user.UserManager
99
import com.getcode.manager.BottomBarManager
1010
import com.getcode.opencode.controllers.TransactionOperations
1111
import com.getcode.opencode.exchange.Exchange
12+
import com.getcode.opencode.exchange.VerifiedFiatCalculator
1213
import com.getcode.util.resources.ResourceHelper
1314
import com.flipcash.app.core.MainCoroutineRule
1415
import com.flipcash.app.core.dispatchers.TestDispatchers
@@ -32,6 +33,7 @@ class WithdrawalViewModelErrorTest {
3233

3334
private val resources = mockk<ResourceHelper>(relaxed = true)
3435
private val exchange = mockk<Exchange>(relaxed = true)
36+
private val verifiedFiatCalculator = mockk<VerifiedFiatCalculator>(relaxed = true)
3537
private val userManager = mockk<UserManager>(relaxed = true)
3638
private val transactionController = mockk<TransactionOperations>(relaxed = true)
3739
private val clipboardManager = mockk<ClipboardManager>(relaxed = true)
@@ -58,6 +60,7 @@ class WithdrawalViewModelErrorTest {
5860
return WithdrawalViewModel(
5961
resources = resources,
6062
exchange = exchange,
63+
verifiedFiatCalculator = verifiedFiatCalculator,
6164
userManager = userManager,
6265
transactionController = transactionController,
6366
clipboardManager = clipboardManager,

apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenCoordinator.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import com.flipcash.app.tokens.core.ReservesBalanceProvider
1515
import com.getcode.opencode.controllers.AccountController
1616
import com.getcode.opencode.controllers.TokenController
1717
import com.getcode.opencode.exchange.Exchange
18+
import com.getcode.opencode.exchange.VerifiedFiatCalculator
1819
import com.getcode.opencode.model.ui.WindowedRange
1920
import com.getcode.opencode.model.accounts.AccountCluster
2021
import com.getcode.opencode.model.financial.CurrencyCode
@@ -82,6 +83,7 @@ class TokenCoordinator @Inject constructor(
8283
private val accountController: AccountController,
8384
private val networkObserver: NetworkConnectivityListener,
8485
private val exchange: Exchange,
86+
private val verifiedFiatCalculator: VerifiedFiatCalculator,
8587
private val dataSource: TokenDataSource,
8688
) : TokenMetadataProvider, SessionListener, DefaultLifecycleObserver, ReservesBalanceProvider {
8789

@@ -476,12 +478,11 @@ class TokenCoordinator @Inject constructor(
476478

477479
state.balances[mint]?.let { balance ->
478480
val exchangedValue = runCatching {
479-
LocalFiat.valueExchangeIn(
481+
verifiedFiatCalculator.compute(
480482
amount = balance,
481483
token = updatedToken,
482484
balance = balance,
483485
rate = Rate.oneToOne,
484-
debug = false,
485486
trace = false,
486487
).underlyingTokenAmount
487488
}.getOrNull()

apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SwapViewModel.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import com.flipcash.shared.tokens.R
1515
import com.getcode.manager.BottomBarManager
1616
import com.getcode.opencode.controllers.TransactionOperations
1717
import com.getcode.opencode.exchange.Exchange
18+
import com.getcode.opencode.exchange.VerifiedFiatCalculator
1819
import com.getcode.opencode.internal.solana.model.SwapId
1920
import com.getcode.opencode.model.core.errors.SwapError
2021
import com.getcode.opencode.model.financial.Currency
@@ -67,6 +68,7 @@ data class AmountEntryState(
6768
class SwapViewModel @Inject constructor(
6869
userManager: UserManager,
6970
private val exchange: Exchange,
71+
private val verifiedFiatCalculator: VerifiedFiatCalculator,
7072
transactionController: TransactionOperations,
7173
resources: ResourceHelper,
7274
tokenCoordinator: TokenCoordinator,
@@ -475,7 +477,7 @@ class SwapViewModel @Inject constructor(
475477
is SwapPurpose.Buy -> {
476478
val rate = exchange.entryRate
477479
// buy with reserves
478-
val amountFiat = LocalFiat.valueExchangeIn(
480+
val amountFiat = verifiedFiatCalculator.compute(
479481
amount = Fiat(data.amountData.amount, rate.currency),
480482
token = Token.usdf,
481483
balance = stateFlow.value.reservesBalance.convertingToUsdIfNeeded(rate),
@@ -511,7 +513,7 @@ class SwapViewModel @Inject constructor(
511513
is SwapPurpose.Sell -> {
512514
val rate = exchange.entryRate
513515
val tokenWithBalance = stateFlow.value.tokenWithBalance!!
514-
val amountFiat = LocalFiat.valueExchangeIn(
516+
val amountFiat = verifiedFiatCalculator.compute(
515517
amount = Fiat(data.amountData.amount, rate.currency),
516518
token = tokenWithBalance.token,
517519
balance = tokenWithBalance.balance,

apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/SwapViewModelErrorTest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.flipcash.shared.tokens.R
99
import com.getcode.manager.BottomBarManager
1010
import com.getcode.opencode.controllers.TransactionOperations
1111
import com.getcode.opencode.exchange.Exchange
12+
import com.getcode.opencode.exchange.VerifiedFiatCalculator
1213
import com.getcode.opencode.model.accounts.AccountCluster
1314
import com.getcode.opencode.model.financial.LocalFiat
1415
import com.getcode.opencode.model.financial.Token
@@ -47,6 +48,7 @@ class SwapViewModelErrorTest {
4748

4849
private val userManager = mockk<UserManager>(relaxed = true)
4950
private val exchange = mockk<Exchange>(relaxed = true)
51+
private val verifiedFiatCalculator = mockk<VerifiedFiatCalculator>(relaxed = true)
5052
// Mockito for Result-returning methods (MockK double-boxes Result inline class)
5153
private val transactionController: TransactionOperations = mock()
5254
private val resources = mockk<ResourceHelper>(relaxed = true)
@@ -85,6 +87,7 @@ class SwapViewModelErrorTest {
8587
return SwapViewModel(
8688
userManager = userManager,
8789
exchange = exchange,
90+
verifiedFiatCalculator = verifiedFiatCalculator,
8891
transactionController = transactionController,
8992
resources = resources,
9093
tokenCoordinator = tokenCoordinator,

services/opencode/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,5 @@ dependencies {
9494

9595
testImplementation(kotlin("test"))
9696
testImplementation(libs.bundles.unit.testing)
97+
testImplementation(testFixtures(project(":libs:currency-math")))
9798
}

services/opencode/src/main/kotlin/com/getcode/opencode/controllers/TransactionController.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import com.getcode.opencode.internal.solana.model.SwapId
1212
import com.getcode.opencode.model.accounts.AccountCluster
1313
import com.getcode.opencode.model.accounts.GiftCardAccount
1414
import com.getcode.opencode.model.core.errors.GetIntentMetadataError
15-
import com.getcode.opencode.model.core.errors.GetLimitsError
1615
import com.getcode.opencode.model.core.errors.SwapError
1716
import com.getcode.opencode.model.financial.Distribution
1817
import com.getcode.opencode.model.financial.Fee
@@ -46,13 +45,11 @@ import kotlinx.coroutines.flow.MutableStateFlow
4645
import kotlinx.coroutines.flow.StateFlow
4746
import kotlinx.coroutines.flow.asStateFlow
4847
import kotlinx.coroutines.flow.catch
49-
import kotlinx.coroutines.flow.filter
5048
import kotlinx.coroutines.flow.firstOrNull
5149
import kotlinx.coroutines.flow.map
5250
import kotlinx.coroutines.flow.mapNotNull
5351
import kotlinx.coroutines.flow.onEach
5452
import kotlinx.coroutines.flow.takeWhile
55-
import java.util.concurrent.atomic.AtomicBoolean
5653
import java.util.concurrent.atomic.AtomicInteger
5754
import javax.inject.Inject
5855
import javax.inject.Singleton
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.getcode.opencode.exchange
2+
3+
import com.getcode.opencode.model.financial.Fiat
4+
import com.getcode.opencode.model.financial.LocalFiat
5+
import com.getcode.opencode.model.financial.Rate
6+
import com.getcode.opencode.model.financial.Token
7+
import com.getcode.services.opencode.BuildConfig
8+
9+
interface VerifiedFiatCalculator {
10+
fun compute(
11+
amount: Fiat,
12+
token: Token,
13+
balance: Fiat? = null,
14+
rate: Rate,
15+
trace: Boolean = true,
16+
): LocalFiat
17+
}

0 commit comments

Comments
 (0)