Skip to content

Commit fe987d5

Browse files
authored
Merge pull request #645 from code-payments/refacor/localfiat-verifiable-calculations
feat(opencode): add VerifiedFiatCalculator for supply-consistent exchange
2 parents f8a0ed9 + 009282a commit fe987d5

44 files changed

Lines changed: 879 additions & 389 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/internal/bill/BillController.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,10 @@ class BillController @Inject constructor(
8787
amount: LocalFiat,
8888
token: Token,
8989
owner: AccountCluster,
90+
verifiedState: VerifiedState,
9091
onFunded: suspend (LocalFiat) -> Unit,
9192
onError: (Throwable) -> Unit,
92-
) = transactionManager.fundGiftCard(giftCard, amount, owner, token, onFunded, onError)
93+
) = transactionManager.fundGiftCard(giftCard, amount, owner, token, verifiedState, onFunded, onError)
9394

9495
/** Initiates the **receive cash link** flow — claims a gift card by entropy. */
9596
fun receiveGiftCard(

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

Lines changed: 7 additions & 4 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,11 +142,11 @@ 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,
147-
)
149+
).localFiat
148150

149151
val neededAmount = amountFiat.nativeAmount - tokenBalance
150152
println("entered amount ${amountFiat.nativeAmount}, tokenbalace=$tokenBalance, needed=$neededAmount")
@@ -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 result = verifiedFiatCalculator.compute(
306308
amount = Fiat(data.amountData.amount, rate.currency),
307309
token = token,
308310
balance = balance.underlyingTokenAmount,
@@ -311,7 +313,8 @@ internal class CashScreenViewModel @Inject constructor(
311313

312314
val bill = Bill.Cash(
313315
token = stateFlow.value.token!!.token,
314-
amount = amountFiat
316+
amount = result.localFiat,
317+
verifiedState = result.verifiedState,
315318
)
316319

317320
dispatchEvent(Event.UpdateLoadingState(loading = false, success = true))

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/currency-creator/src/main/kotlin/com/flipcash/app/currencycreator/internal/CurrencyCreatorViewModel.kt

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,22 @@ import com.flipcash.services.user.UserManager
3737
import com.getcode.manager.BottomBarManager
3838
import com.getcode.opencode.controllers.CurrencyController
3939
import com.getcode.opencode.controllers.TransactionController
40+
import com.getcode.opencode.exchange.VerifiedFiat
41+
import com.getcode.opencode.exchange.VerifiedFiatCalculator
4042
import com.getcode.opencode.internal.solana.model.SwapId
4143
import com.getcode.opencode.model.core.errors.CheckTokenAvailabilityError
4244
import com.getcode.opencode.model.core.errors.GetMintsError
4345
import com.getcode.opencode.model.core.errors.LaunchTokenError
4446
import com.getcode.opencode.model.core.errors.ValidationException
4547
import com.getcode.opencode.model.financial.MintMetadata
48+
import com.getcode.opencode.model.financial.Rate
4649
import com.getcode.opencode.model.financial.Token
4750
import com.getcode.opencode.model.financial.TokenCreateRequest
4851
import com.getcode.opencode.model.financial.fromLaunch
4952
import com.getcode.opencode.model.financial.minus
5053
import com.getcode.opencode.model.financial.orZero
5154
import com.getcode.opencode.model.financial.plus
55+
import com.getcode.opencode.model.financial.usdf
5256
import com.getcode.opencode.model.moderation.ModerationAttestation
5357
import com.getcode.opencode.model.transactions.SwapFundingSource
5458
import com.getcode.solana.keys.Mint
@@ -87,6 +91,7 @@ internal class CurrencyCreatorViewModel @Inject constructor(
8791
moderationController: ModerationController,
8892
currencyController: CurrencyController,
8993
transactionController: TransactionController,
94+
private val verifiedFiatCalculator: VerifiedFiatCalculator,
9095
externalWalletController: ExternalWalletOnRampController,
9196
tokenCoordinator: TokenCoordinator,
9297
balancePoller: BalancePoller,
@@ -513,8 +518,11 @@ internal class CurrencyCreatorViewModel @Inject constructor(
513518
AppRoute.Token.CurrencyCreator,
514519
OnRampProvider.Phantom
515520
)
516-
val totalAmount = LocalFiat(usdf = event.context.amount)
517-
println("total amount ${totalAmount.underlyingTokenAmount}")
521+
val totalAmount = verifiedFiatCalculator.compute(
522+
amount = event.context.amount,
523+
token = Token.usdf,
524+
rate = Rate.oneToOne,
525+
)
518526
val feeAmount = event.context.feeAmount?.let { LocalFiat(usdf = it) }
519527
externalWalletController.setAmount(amount = totalAmount, feeAmount = feeAmount)
520528
externalWalletController.setTokenToPurchase(event.context.token)
@@ -529,10 +537,16 @@ internal class CurrencyCreatorViewModel @Inject constructor(
529537
}
530538
.onEach { dispatchEvent(Event.UpdateProcessingState(loading = true)) }
531539
.map { (owner, context) ->
540+
val totalAmount = verifiedFiatCalculator.compute(
541+
amount = context.amount,
542+
token = Token.usdf,
543+
rate = Rate.oneToOne,
544+
)
545+
val feeAmount = context.feeAmount?.let { LocalFiat(usdf = it) }
532546
transactionController.buy(
533547
owner = owner,
534-
amount = LocalFiat(usdf = context.amount),
535-
feeAmount = context.feeAmount?.let { LocalFiat(usdf = it) },
548+
amount = totalAmount,
549+
feeAmount = feeAmount,
536550
of = context.token,
537551
source = SwapFundingSource.SubmitIntent(),
538552
fund = null,

apps/flipcash/features/onramp/src/main/kotlin/com/flipcash/app/onramp/OnRampCustomAmountScreen.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import androidx.compose.ui.Modifier
99
import androidx.compose.ui.res.stringResource
1010
import com.flipcash.app.core.AppRoute
1111
import com.getcode.solana.keys.Mint
12+
1213
import com.flipcash.app.onramp.internal.OnRampViewModel
1314
import com.flipcash.app.onramp.internal.screens.OnRampAmountScreen
1415
import com.flipcash.features.onramp.R

apps/flipcash/features/onramp/src/main/kotlin/com/flipcash/app/onramp/internal/OnRampViewModel.kt

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import com.getcode.manager.BottomBarManager
1616
import com.getcode.opencode.controllers.TokenController
1717
import com.getcode.opencode.controllers.TransactionOperations
1818
import com.getcode.opencode.exchange.Exchange
19+
import com.getcode.opencode.exchange.VerifiedFiat
20+
import com.getcode.opencode.exchange.VerifiedFiatCalculator
1921
import com.getcode.opencode.model.financial.Currency
2022
import com.getcode.opencode.model.financial.CurrencyCode
2123
import com.getcode.opencode.model.financial.Fiat
@@ -24,6 +26,7 @@ import com.getcode.opencode.model.financial.LocalFiat
2426
import com.getcode.opencode.model.financial.SendLimit
2527
import com.getcode.opencode.model.financial.Token
2628
import com.getcode.opencode.model.financial.toFiat
29+
import com.getcode.opencode.model.financial.usdf
2730
import com.getcode.solana.keys.Mint
2831
import com.getcode.ui.components.text.AmountAnimatedInputUiModel
2932
import com.getcode.ui.components.text.NumberInputHelper
@@ -45,7 +48,7 @@ internal data class AmountEntryState(
4548
val currencyModel: CurrencyHolder = CurrencyHolder(),
4649
val amountAnimatedModel: AmountAnimatedInputUiModel = AmountAnimatedInputUiModel(),
4750
val confirmingAmount: LoadingSuccessState = LoadingSuccessState(),
48-
val selectedAmount: LocalFiat = LocalFiat.Zero,
51+
val selectedAmount: VerifiedFiat = VerifiedFiat(LocalFiat.Zero, null),
4952
) {
5053
val canAdd: Boolean
5154
get() = (amountAnimatedModel.amountData.amount) > 0.00
@@ -71,6 +74,7 @@ internal data class AmountEntryState(
7174
@HiltViewModel
7275
internal class OnRampViewModel @Inject constructor(
7376
private val exchange: Exchange,
77+
private val verifiedFiatCalculator: VerifiedFiatCalculator,
7478
private val resources: ResourceHelper,
7579
private val onRampController: CoinbaseOnRampController,
7680
tokenController: TokenController,
@@ -127,9 +131,9 @@ internal class OnRampViewModel @Inject constructor(
127131
val success: Boolean = false
128132
) : Event
129133

130-
data class OnAmountAccepted(val amount: LocalFiat) : Event
134+
data class OnAmountAccepted(val amount: VerifiedFiat) : Event
131135

132-
data class CreateAndSendTransactionToWallet(val amount: LocalFiat) : Event
136+
data class CreateAndSendTransactionToWallet(val amount: VerifiedFiat) : Event
133137
// endregion
134138
}
135139

@@ -279,9 +283,10 @@ internal class OnRampViewModel @Inject constructor(
279283
}
280284
}
281285

282-
val amountFiat = LocalFiat(
283-
usdf = localizedAmount.convertingTo(exchange.rateToUsd(rate.currency)!!),
284-
nativeAmount = localizedAmount,
286+
val amountFiat = verifiedFiatCalculator.compute(
287+
amount = localizedAmount,
288+
token = Token.usdf,
289+
rate = rate,
285290
)
286291

287292
dispatchEvent(Event.OnAmountAccepted(amountFiat))
@@ -314,9 +319,9 @@ internal class OnRampViewModel @Inject constructor(
314319
}
315320

316321
onRampController.placeOrderAndStartPayment(
317-
amount = selectedAmount.underlyingTokenAmount,
322+
amount = selectedAmount.localFiat.underlyingTokenAmount,
318323
token = token,
319-
localFiat = selectedAmount,
324+
verifiedFiat = selectedAmount,
320325
).onFailure { error ->
321326
dispatchEvent(Event.UpdateConfirmingAmountState())
322327
when (error) {

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

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ 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.VerifiedFiat
21+
import com.getcode.opencode.exchange.VerifiedFiatCalculator
2022
import com.getcode.opencode.model.financial.Currency
2123
import com.getcode.opencode.model.financial.CurrencyCode
2224
import com.getcode.opencode.model.financial.Fiat
@@ -55,7 +57,7 @@ internal data class AmountEntryState(
5557
val currencyModel: CurrencyHolder = CurrencyHolder(),
5658
val amountAnimatedModel: AmountAnimatedInputUiModel = AmountAnimatedInputUiModel(),
5759
val confirmingAmount: LoadingSuccessState = LoadingSuccessState(),
58-
val selectedAmount: LocalFiat = LocalFiat.Zero,
60+
val selectedAmount: VerifiedFiat = VerifiedFiat(LocalFiat.Zero, null),
5961
)
6062

6163
internal data class DestinationState(
@@ -68,6 +70,7 @@ internal data class DestinationState(
6870
internal class WithdrawalViewModel @Inject constructor(
6971
private val resources: ResourceHelper,
7072
private val exchange: Exchange,
73+
private val verifiedFiatCalculator: VerifiedFiatCalculator,
7174
private val userManager: UserManager,
7275
transactionController: TransactionOperations,
7376
clipboardManager: ClipboardManager,
@@ -121,7 +124,7 @@ internal class WithdrawalViewModel @Inject constructor(
121124
data class OnAmountChanged(val amountAnimatedModel: AmountAnimatedInputUiModel) : Event
122125
data class OnCurrencyChanged(val currency: Currency) : Event
123126
data object OnAmountConfirmed : Event
124-
data class OnAmountAccepted(val amount: LocalFiat) : Event
127+
data class OnAmountAccepted(val amount: VerifiedFiat) : Event
125128
data object OnDestinationConfirmed : Event
126129
data class UpdateConfirmingAmountState(
127130
val loading: Boolean = false,
@@ -267,15 +270,15 @@ internal class WithdrawalViewModel @Inject constructor(
267270
dispatchEvent(Event.UpdateConfirmingAmountState(loading = true))
268271
val rate = exchange.rateForUsd()
269272
val token = stateFlow.value.token!!.token
270-
val amountFiat = LocalFiat.valueExchangeIn(
273+
val amountVerified = verifiedFiatCalculator.compute(
271274
amount = Fiat(data.amountData.amount, rate.currency),
272275
token = token,
273276
balance = stateFlow.value.token!!.balance,
274277
rate = rate,
275278
)
276279

277280
dispatchEvent(Event.UpdateConfirmingAmountState(loading = false, success = true))
278-
dispatchEvent(Event.OnAmountAccepted(amountFiat))
281+
dispatchEvent(Event.OnAmountAccepted(amountVerified))
279282
}.launchIn(viewModelScope)
280283

281284
eventFlow
@@ -339,7 +342,7 @@ internal class WithdrawalViewModel @Inject constructor(
339342
eventFlow
340343
.filterIsInstance<Event.OnWithdraw>()
341344
.onEach {
342-
val amount = stateFlow.value.amountEntryState.selectedAmount
345+
val amount = stateFlow.value.amountEntryState.selectedAmount.localFiat
343346
val withdrawalChecks = stateFlow.value.destinationState.availability
344347
val fee = withdrawalChecks?.feeAmount
345348
if (amount.nativeAmount - (fee ?: Fiat.Zero) < Fiat.Zero) {
@@ -394,12 +397,12 @@ internal class WithdrawalViewModel @Inject constructor(
394397
val sendingVault = owner.withTimelockForToken(token)
395398

396399
val feeInMint = feeInUsd?.let { fee ->
397-
LocalFiat.valueExchangeIn(
398-
fee,
400+
verifiedFiatCalculator.compute(
401+
amount = fee,
399402
token = token,
400403
balance = stateFlow.value.token!!.balance,
401404
rate = exchange.rateToUsd(CurrencyCode.USD)!!,
402-
).underlyingTokenAmount
405+
).localFiat.underlyingTokenAmount
403406
}
404407

405408
transactionController.withdraw(
@@ -415,7 +418,7 @@ internal class WithdrawalViewModel @Inject constructor(
415418
onError = {
416419
analytics.transfer(
417420
event = Analytics.Transfer.Withdrawal,
418-
amount = stateFlow.value.amountEntryState.selectedAmount,
421+
amount = stateFlow.value.amountEntryState.selectedAmount.localFiat,
419422
successful = false,
420423
error = it,
421424
)
@@ -428,7 +431,7 @@ internal class WithdrawalViewModel @Inject constructor(
428431
onSuccess = {
429432
analytics.transfer(
430433
event = Analytics.Transfer.Withdrawal,
431-
amount = stateFlow.value.amountEntryState.selectedAmount,
434+
amount = stateFlow.value.amountEntryState.selectedAmount.localFiat,
432435
)
433436
viewModelScope.launch {
434437
coroutineScope {

apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/internal/confirmation/WithdrawalConfirmationScreen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ private fun WithdrawalConfirmationScreenContent(
7373
TransferInfo(
7474
tokenWithBalance = TokenWithBalance(
7575
state.token!!.token,
76-
balance = state.amountEntryState.selectedAmount.nativeAmount,
76+
balance = state.amountEntryState.selectedAmount.localFiat.nativeAmount,
7777
),
7878
destination = state.destinationState.textFieldState.text.toString(),
7979
fee = state.destinationState.availability?.feeAmount,

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,

0 commit comments

Comments
 (0)