@@ -30,6 +30,7 @@ import com.getcode.opencode.model.financial.Limits
3030import com.getcode.opencode.model.financial.LocalFiat
3131import com.getcode.opencode.model.financial.SendLimit
3232import com.getcode.opencode.model.financial.Token
33+ import com.getcode.opencode.model.financial.toFiat
3334import com.getcode.opencode.model.transactions.SwapFundingSource
3435import com.getcode.solana.keys.Mint
3536import com.getcode.ui.components.text.AmountAnimatedInputUiModel
@@ -113,32 +114,37 @@ internal class OnRampViewModel @Inject constructor(
113114 val mint : Mint ? = null ,
114115 val token : Token ? = null ,
115116 val providers : List <OnRampProviderItem > = DefaultOnRampOptions ,
117+ val canChangeCurrency : Boolean = false ,
116118 val hasVerifiedPhone : Boolean = false ,
117119 val hasVerifiedEmail : Boolean = false ,
118120 val selectedProvider : OnRampProvider .ThirdParty ? = null ,
119121 val amountEntryState : AmountEntryState = AmountEntryState (),
120- )
122+ ) {
123+ val minimumPurchaseAmount = 5 .toFiat()
124+ }
121125
122126 sealed interface Event {
123- data class OnMintChanged (val mint : Mint ): Event
124- data class OnTokenChanged (val token : Token ): Event
127+ data class OnMintChanged (val mint : Mint ) : Event
128+ data class OnTokenChanged (val token : Token ) : Event
125129 data class OnProvidersUpdated (val providers : List <OnRampProviderItem >) : Event
126130
127131 data class OnPhoneVerificationChanged (val verified : Boolean ) : Event
128132 data class OnEmailVerificationChanged (val verified : Boolean ) : Event
129133
130134 data class OnProviderSelected (val item : OnRampProvider ) : Event
131135
132- data class OnVerificationNeeded (val phone : Boolean = false , val email : Boolean = false ): Event
136+ data class OnVerificationNeeded (val phone : Boolean = false , val email : Boolean = false ) :
137+ Event
133138
134- data class OnOrderCreated (val order : OnrampOrder ): Event {
135- constructor (orderId: String , url: String ): this (OnrampOrder (orderId, url))
139+ data class OnOrderCreated (val order : OnrampOrder ) : Event {
140+ constructor (orderId: String , url: String ) : this (OnrampOrder (orderId, url))
136141 }
142+
137143 data class OnBuyUrlGenerated (val url : String ) : Event
138144
139145 data class OnPaymentSuccess (val orderId : String ) : Event
140146 data class OnPaymentError (val error : CoinbaseOnRampWebError ) : Event
141- data object OnPaymentCancel : Event
147+ data object OnPaymentCancel : Event
142148
143149 // region amount entry events
144150 data class OnMaxDetermined (val max : Double , val currencyCode : CurrencyCode ) : Event
@@ -161,7 +167,7 @@ internal class OnRampViewModel @Inject constructor(
161167 data class OnAmountAccepted (val amount : LocalFiat ) : Event
162168
163169 data class CreateAndSendTransactionToWallet (val amount : LocalFiat ) : Event
164- data class OnBuySubmitted (val swapId : SwapId ): Event
170+ data class OnBuySubmitted (val swapId : SwapId ) : Event
165171 // endregion
166172 }
167173
@@ -295,11 +301,23 @@ internal class OnRampViewModel @Inject constructor(
295301 .filter { ! (checkFundingAmount()) }
296302 .onEach { data ->
297303 dispatchEvent(Event .UpdateConfirmingAmountState (loading = true ))
298- val rate = exchange.rateFor(stateFlow.value.amountEntryState.currencyModel.code ? : CurrencyCode .USD )
299- ? : exchange.entryRate
304+ val rate = exchange.rateFor(
305+ stateFlow.value.amountEntryState.currencyModel.code ? : CurrencyCode .USD
306+ ) ? : exchange.entryRate
300307
301308 val localizedAmount = Fiat (data.amountData.amount, rate.currency)
302309
310+ if (stateFlow.value.selectedProvider is OnRampProvider .Coinbase ) {
311+ if (localizedAmount < stateFlow.value.minimumPurchaseAmount) {
312+ BottomBarManager .showAlert(
313+ title = resources.getString(R .string.error_title_onrampAmountTooLow),
314+ message = resources.getString(R .string.error_description_onrampAmountTooLow)
315+ )
316+ dispatchEvent(Event .UpdateConfirmingAmountState ())
317+ return @onEach
318+ }
319+ }
320+
303321 val amountFiat = LocalFiat (
304322 usdf = localizedAmount.convertingTo(exchange.rateToUsd(rate.currency)!! ),
305323 nativeAmount = localizedAmount,
@@ -401,23 +419,32 @@ internal class OnRampViewModel @Inject constructor(
401419 else -> return @mapNotNull null
402420 }
403421
404- val destination = if (! (hasVerifiedPhone && hasVerifiedEmail)) {
405- AppRoute .Verification (
406- origin = AppRoute .OnRamp .ProviderList (
407- from = OnRampFlowTracker .source!!
408- ),
409- target = AppRoute .OnRamp .AmountEntry (mint),
410- includePhone = ! hasVerifiedPhone,
411- includeEmail = ! hasVerifiedEmail,
412- )
413- } else {
414- AppRoute .OnRamp .AmountEntry (mint)
415- }
422+ val destination =
423+ if (! (hasVerifiedPhone && hasVerifiedEmail)) {
424+ AppRoute .Verification (
425+ origin = AppRoute .OnRamp .ProviderList (
426+ from = OnRampFlowTracker .source!!
427+ ),
428+ target = AppRoute .OnRamp .AmountEntry (mint),
429+ includePhone = ! hasVerifiedPhone,
430+ includeEmail = ! hasVerifiedEmail,
431+ )
432+ } else {
433+ AppRoute .OnRamp .AmountEntry (mint)
434+ }
416435
417436 when (provider.type) {
418- OnRampType .Virtual -> OnRampProviderDestination .Screen (destination)
419- OnRampType .PhysicalDebit -> OnRampProviderDestination .Screen (destination)
420- OnRampType .PhysicalCredit -> OnRampProviderDestination .Screen (destination)
437+ OnRampType .Virtual -> OnRampProviderDestination .Screen (
438+ destination
439+ )
440+
441+ OnRampType .PhysicalDebit -> OnRampProviderDestination .Screen (
442+ destination
443+ )
444+
445+ OnRampType .PhysicalCredit -> OnRampProviderDestination .Screen (
446+ destination
447+ )
421448 }
422449 }
423450
@@ -433,8 +460,8 @@ internal class OnRampViewModel @Inject constructor(
433460 eventFlow
434461 .filterIsInstance<Event .OnProviderSelected >()
435462 .map { it.item }
436- // we are locking deeplink transfers to USD
437- .filterIsInstance< OnRampProvider .UsesDeeplinks >()
463+ // we are locking deeplink transfers and onramp buys to USD
464+ .filter { it is OnRampProvider .UsesDeeplinks || it is OnRampProvider . Coinbase }
438465 .mapNotNull { exchange.getCurrency(CurrencyCode .USD .name) }
439466 .onEach { dispatchEvent(Event .OnCurrencyChanged (it)) }
440467 .launchIn(viewModelScope)
@@ -454,14 +481,24 @@ internal class OnRampViewModel @Inject constructor(
454481 .onSuccess {
455482 dispatchEvent(Event .OnOrderCreated (it.first, it.second.url))
456483 }.onFailure { error ->
457- dispatchEvent(Event .UpdateConfirmingAmountState (loading = false , success = false ))
484+ dispatchEvent(
485+ Event .UpdateConfirmingAmountState (
486+ loading = false ,
487+ success = false
488+ )
489+ )
458490 when (error) {
459491 is OnRampAuthError .CoinbasePhoneVerificationRequired -> {
460492 dispatchEvent(Event .OnVerificationNeeded (phone = true ))
461493 }
462494
463495 is OnRampAuthError .VerificationRequired -> {
464- dispatchEvent(Event .OnVerificationNeeded (phone = error.phone, email = error.email))
496+ dispatchEvent(
497+ Event .OnVerificationNeeded (
498+ phone = error.phone,
499+ email = error.email
500+ )
501+ )
465502 }
466503
467504 else -> {
@@ -473,6 +510,7 @@ internal class OnRampViewModel @Inject constructor(
473510 }
474511 }
475512 }
513+
476514 else -> Unit
477515 }
478516 }
@@ -492,54 +530,63 @@ internal class OnRampViewModel @Inject constructor(
492530 message = resources.getString(R .string.error_description_onrampUnknownFailure)
493531 )
494532 }
533+
495534 is CoinbaseOnRampWebError .MissingTransactionUuid -> { // TODO:
496535 BottomBarManager .showError(
497536 title = resources.getString(R .string.error_title_onrampUnknownFailure),
498537 message = resources.getString(R .string.error_description_onrampUnknownFailure)
499538 )
500539 }
540+
501541 is CoinbaseOnRampWebError .GuestCardNotDebit -> {
502542 BottomBarManager .showError(
503543 title = resources.getString(R .string.error_title_onrampInvalidCard),
504544 message = resources.getString(R .string.error_description_onrampInvalidCard)
505545 )
506546 }
547+
507548 is CoinbaseOnRampWebError .GuestGooglePayError -> {
508549 BottomBarManager .showError(
509550 title = resources.getString(R .string.error_title_onrampTransactionFailed),
510551 message = resources.getString(R .string.error_description_onrampTransactionFailed)
511552 )
512553 }
554+
513555 is CoinbaseOnRampWebError .GuestTransactionBuyFailed -> {
514556 BottomBarManager .showError(
515557 title = resources.getString(R .string.error_title_onrampTransactionBuyFailed),
516558 message = resources.getString(R .string.error_description_onrampTransactionBuyFailed)
517559 )
518560 }
561+
519562 is CoinbaseOnRampWebError .GuestTransactionSendFailed -> {
520563 BottomBarManager .showError(
521564 title = resources.getString(R .string.error_title_onrampTransactionSendFailed),
522565 message = resources.getString(R .string.error_description_onrampTransactionSendFailed)
523566 )
524567 }
568+
525569 is CoinbaseOnRampWebError .GuestTransactionAvsValidationFailed -> {
526570 BottomBarManager .showError(
527571 title = resources.getString(R .string.error_title_onrampTransactionAvsValidationFailed),
528572 message = resources.getString(R .string.error_description_onrampTransactionAvsValidationFailed)
529573 )
530574 }
575+
531576 is CoinbaseOnRampWebError .GuestTransactionTransactionFailed -> {
532577 BottomBarManager .showError(
533578 title = resources.getString(R .string.error_title_onrampTransactionFailed),
534579 message = resources.getString(R .string.error_description_onrampTransactionFailed)
535580 )
536581 }
582+
537583 is CoinbaseOnRampWebError .Internal -> {
538584 BottomBarManager .showError(
539585 title = resources.getString(R .string.error_title_onrampInternal),
540586 message = resources.getString(R .string.error_description_onrampInternal)
541587 )
542588 }
589+
543590 is CoinbaseOnRampWebError .GooglePayButtonNotFound -> {
544591 BottomBarManager .showError(
545592 title = resources.getString(R .string.error_title_onrampInternal),
@@ -554,7 +601,13 @@ internal class OnRampViewModel @Inject constructor(
554601 when (event) {
555602 is Event .OnMintChanged -> { state -> state.copy(mint = event.mint) }
556603 is Event .OnTokenChanged -> { state -> state.copy(token = event.token) }
557- is Event .OnProviderSelected -> { state -> state.copy(selectedProvider = event.item as ? OnRampProvider .ThirdParty ) }
604+ is Event .OnProviderSelected -> { state ->
605+ state.copy(
606+ canChangeCurrency = event.item !is OnRampProvider .Phantom && event.item !is OnRampProvider .Coinbase ,
607+ selectedProvider = event.item as ? OnRampProvider .ThirdParty
608+ )
609+ }
610+
558611 is Event .OnProvidersUpdated -> { state -> state.copy(providers = event.providers) }
559612
560613 is Event .OnPhoneVerificationChanged -> { state -> state.copy(hasVerifiedPhone = event.verified) }
0 commit comments