Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import com.flipcash.app.bill.customization.LocalBillPlaygroundController
import com.flipcash.app.core.AppRoute
import com.flipcash.app.core.bill.Bill
import com.flipcash.app.core.tokens.CurrencyCreatorResult
import com.flipcash.app.core.tokens.CurrencyCreatorStep
import com.flipcash.app.currencycreator.internal.screens.BillCustomizationContent
Expand Down Expand Up @@ -62,7 +61,6 @@ import com.getcode.navigation.flow.deliverFlowResult
import com.getcode.navigation.results.NavResultOrCanceled
import com.getcode.navigation.results.NavResultStateRegistry
import com.getcode.opencode.model.financial.Fiat
import com.getcode.opencode.model.financial.LocalFiat
import com.getcode.opencode.model.financial.toFiat
import com.getcode.theme.CodeTheme
import com.getcode.theme.rememberDynamicAccent
Expand Down Expand Up @@ -181,17 +179,13 @@ fun CurrencyCreatorFlowScreen(
)

if (result is CurrencyCreatorResult.Success) {
if (state.purchaseAmount > Fiat.Zero) {
val token = state.launchedToken
if (token != null) {
val bill = Bill.Cash(
token = token,
amount = LocalFiat.fromUsd(usdf = state.purchaseAmount),
didReceive = true,
)
outerNavigator.hide()
session?.showBill(bill)
}
// The bill is priced through the new token's bonding curve in the
// ViewModel (see buildLaunchBill); presenting it lets someone grab the
// newly created currency. It's null if pricing failed — just close then.
val bill = state.launchBill
if (state.purchaseAmount > Fiat.Zero && bill != null) {
outerNavigator.hide()
session?.showBill(bill)
} else {
outerNavigator.pop()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ internal class CurrencyCreatorViewModel @Inject constructor(
moderationController: ModerationController,
currencyController: CurrencyController,
private val verifiedFiatCalculator: VerifiedFiatCalculator,
tokenCoordinator: TokenCoordinator,
private val tokenCoordinator: TokenCoordinator,
balancePoller: BalancePoller,
private val resources: ResourceHelper,
val contentReader: ContentReader,
Expand All @@ -113,6 +113,9 @@ internal class CurrencyCreatorViewModel @Inject constructor(
val bill: Bill? = null,
val createdMint: Mint? = null,
val launchedToken: Token? = null,
// The celebratory give-bill for the freshly launched currency, priced through the
// token's bonding curve (not USDF 1:1) and carrying the verified state a grab needs.
val launchBill: Bill.Cash? = null,
val purchaseAmount: Fiat = 5.toFiat(),
val feeAmount: Fiat? = null,
val processingState: LoadingSuccessState = LoadingSuccessState(),
Expand Down Expand Up @@ -189,7 +192,7 @@ internal class CurrencyCreatorViewModel @Inject constructor(
data class ConfirmPurchase(val fundedWith: Mint): Event

data class PurchaseSubmitted(val swapId: SwapId, val mint: Mint) : Event
data class PurchaseCompleted(val token: Token): Event
data class PurchaseCompleted(val token: Token, val bill: Bill.Cash?): Event

data object OnIntroContinue : Event
data object AdvanceFromInfo : Event
Expand Down Expand Up @@ -608,9 +611,14 @@ internal class CurrencyCreatorViewModel @Inject constructor(
).map { event.mint }
}.flatMapResult { mint ->
tokenCoordinator.getTokenMetadata(mint)
}.flatMapResult { result ->
// Build the celebratory give-bill for the new currency. A failure to price it
// (e.g. stale rates) must not fail the launch — the currency was created — so
// carry a null bill and let the flow simply close instead.
Result.success(result.token to buildLaunchBill(result.token))
}.onResult(
onSuccess = { result ->
dispatchEvent(Event.PurchaseCompleted(result.token))
onSuccess = { (token, bill) ->
dispatchEvent(Event.PurchaseCompleted(token, bill))
dispatchEvent(Event.UpdateProcessingState(success = true))
},
onError = {
Expand All @@ -619,6 +627,37 @@ internal class CurrencyCreatorViewModel @Inject constructor(
).launchIn(viewModelScope)
}

/**
* Builds the give-bill presented after a currency launch. The bill is for the newly
* minted [token], valued at the USD [State.purchaseAmount] but priced through the token's
* bonding curve via [VerifiedFiatCalculator.compute] — the same path the normal give flow
* uses. This yields a [LocalFiat] carrying the token's real mint and exchange rate plus the
* [VerifiedState] the grab intent submits, so the server's "expected sell value" check passes.
*
* Using `LocalFiat.fromUsd()` here instead would hard-code the USDF mint and a 1:1 rate,
* causing the grab to fail with `native amount does not match expected sell value`.
*
* Returns null if the verified fiat can't be computed; the launch still succeeds.
*/
private suspend fun buildLaunchBill(token: Token): Bill.Cash? {
val balance = tokenCoordinator.tokenBalances.firstOrNull()
?.firstOrNull { it.token.address == token.address }
?.balance
val verifiedFiat = verifiedFiatCalculator.compute(
amount = stateFlow.value.purchaseAmount,
token = token,
balance = balance,
rate = exchange.preferredRate,
).getOrNull() ?: return null

return Bill.Cash(
token = token,
amount = verifiedFiat.localFiat,
verifiedState = verifiedFiat.verifiedState,
didReceive = true,
)
}

/**
* Connect the ViewModel to the top bar controller. The ViewModel pushes
* [State.progress] to the controller whenever the current step changes.
Expand Down Expand Up @@ -706,7 +745,7 @@ internal class CurrencyCreatorViewModel @Inject constructor(
is Event.OpenScreen -> { state -> state }
is Event.PurchaseSubmitted -> { state -> state }
is Event.PurchaseCompleted -> { state ->
state.copy(launchedToken = event.token)
state.copy(launchedToken = event.token, launchBill = event.bill)
}
}
}
Expand Down
Loading