Skip to content

Commit 63b8986

Browse files
committed
feat: start to replace BalanceController with multi-token controller
- Handle multiple token balances and a record of Token's (MintMetadata) for querying - Update balance/wallet amount to show sum of tokens in header Signed-off-by: Brandon McAnsh <[email protected]>
1 parent 532572c commit 63b8986

File tree

26 files changed

+527
-129
lines changed

26 files changed

+527
-129
lines changed

apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/internal/updater/BalanceUpdater.kt renamed to apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/internal/updater/TokenUpdater.kt

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
package com.flipcash.app.core.internal.updater
22

33
import com.flipcash.app.core.updater.NetworkUpdater
4-
import com.getcode.opencode.controllers.BalanceController
4+
import com.getcode.opencode.controllers.TokenController
55
import kotlinx.coroutines.CoroutineScope
66
import kotlinx.coroutines.launch
7-
import java.util.Timer
87
import javax.inject.Inject
98
import kotlin.concurrent.fixedRateTimer
109
import kotlin.time.Duration
11-
import kotlin.time.Duration.Companion.seconds
1210

13-
class BalanceUpdater @Inject constructor(
14-
private val balanceController: BalanceController,
11+
class TokenUpdater @Inject constructor(
12+
private val tokenController: TokenController,
1513
) : NetworkUpdater() {
1614
override fun poll(
1715
key: Any?,
@@ -21,12 +19,12 @@ class BalanceUpdater @Inject constructor(
2119
) {
2220
stop()
2321
updater = fixedRateTimer(
24-
name = "update balance",
22+
name = "update tokens",
2523
initialDelay = startIn.inWholeMilliseconds,
2624
period = frequency.inWholeMilliseconds
2725
) {
2826
scope.launch {
29-
balanceController.fetchBalance()
27+
tokenController.update()
3028
}
3129
}
3230
}

apps/flipcash/core/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
<string name="error_description_linkExpired">The cash was automatically returned to the sender because it wasn\'t collected within 7 days. Please ask them to send the cash again</string>
5656

5757
<string name="subtitle_balanceIsHeldInUsdStablecoins">Your balance is held in US dollar stablecoins</string>
58+
<string name="subtitle_currentValueOfAllCurrencies">The current value of your currencies</string>
5859
<string name="subtitle_ofUsdStablecoins">of US dollar stablecoins</string>
5960
<string name="subtitle_balanceEmptyState">Ask a friend to give you some cash with Flipcash, or deposit USDC from your crypto exchange or another crypto wallet</string>
6061
<string name="action_depositFunds">Deposit Funds</string>

apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/BalanceScreenContent.kt

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ private fun BalanceScreenContent(
6969
BalanceHeader(
7070
modifier = Modifier
7171
.fillMaxWidth(),
72-
balance = state.balance
72+
balance = state.totalBalance
7373
) {
7474
dispatchEvent(BalanceViewModel.Event.OpenCurrencySelection)
7575
}
@@ -175,27 +175,12 @@ private fun Preview_BalanceScreen_Empty() {
175175
Box(modifier = Modifier.background(CodeTheme.colors.background)) {
176176
BalanceScreenContent(
177177
state = BalanceViewModel.State(
178-
balance = LocalFiat(0.toFiat())
178+
balances = emptyList()
179179
),
180180
feed = flowOf(PagingData.empty<ActivityFeedMessage>()).collectAsLazyPagingItems(),
181181
dispatchEvent = {}
182182
)
183183
}
184184
}
185185
}
186-
}
187-
188-
@Preview
189-
@Composable
190-
private fun Preview_FeedList_Empty() {
191-
FlipcashDesignSystem {
192-
Box(modifier = Modifier.background(CodeTheme.colors.background)) {
193-
FeedList(
194-
state = BalanceViewModel.State(
195-
balance = LocalFiat(0.toFiat())
196-
),
197-
feed = flowOf(PagingData.empty<ActivityFeedMessage>()).collectAsLazyPagingItems()
198-
) { }
199-
}
200-
}
201186
}

apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/BalanceViewModel.kt

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ import com.flipcash.services.user.AuthState
2121
import com.flipcash.services.user.UserManager
2222
import com.getcode.manager.BottomBarAction
2323
import com.getcode.manager.BottomBarManager
24-
import com.getcode.opencode.controllers.BalanceController
24+
import com.getcode.opencode.controllers.TokenController
2525
import com.getcode.opencode.controllers.TransactionController
2626
import com.getcode.opencode.exchange.Exchange
2727
import com.getcode.opencode.model.core.ID
2828
import com.getcode.opencode.model.financial.LocalFiat
29+
import com.getcode.opencode.model.financial.TokenWithLocalizedBalance
30+
import com.getcode.opencode.model.financial.sum
2931
import com.getcode.solana.keys.PublicKey
3032
import com.getcode.util.resources.ResourceHelper
3133
import com.getcode.view.BaseViewModel2
@@ -44,27 +46,30 @@ import javax.inject.Inject
4446

4547
@HiltViewModel
4648
internal class BalanceViewModel @Inject constructor(
47-
balanceController: BalanceController,
49+
tokenController: TokenController,
4850
feedCoordinator: ActivityFeedCoordinator,
4951
transactionController: TransactionController,
5052
featureFlags: FeatureFlagController,
5153
userManager: UserManager,
5254
resources: ResourceHelper,
53-
private val exchange: Exchange,
55+
exchange: Exchange,
5456
onrampController: OnRampAmountController,
5557
) : BaseViewModel2<BalanceViewModel.State, BalanceViewModel.Event>(
5658
initialState = State(),
5759
updateStateForEvent = updateStateForEvent
5860
) {
5961
data class State(
60-
val balance: LocalFiat? = null,
62+
val balances: List<TokenWithLocalizedBalance>? = null,
6163
val canViewDetails: Boolean = false,
6264
val preferredOnRampProvider: OnRampProvider? = null,
6365
val expandedItem: ID? = null,
64-
)
66+
) {
67+
val totalBalance: LocalFiat?
68+
get() = balances.orEmpty().map { it.balance }.sum()
69+
}
6570

6671
sealed interface Event {
67-
data class OnBalanceUpdated(val balance: LocalFiat) : Event
72+
data class OnBalancesUpdated(val balances: List<TokenWithLocalizedBalance>) : Event
6873
data class OnTransactionDetailsEnabled(val enabled: Boolean) : Event
6974
data class OnPreferredOnRampProviderChanged(val provider: OnRampProvider?) : Event
7075
data class ViewDetails(val id: ID?) : Event
@@ -82,16 +87,22 @@ internal class BalanceViewModel @Inject constructor(
8287

8388
init {
8489
combine(
85-
balanceController.rawBalance,
90+
tokenController.tokenBalances,
8691
exchange.observeBalanceRate(),
87-
) { balance, rate ->
88-
LocalFiat(
89-
usdc = balance,
90-
converted = balance.convertingTo(rate),
91-
rate = rate
92-
)
92+
) { balances, rate ->
93+
balances.map {
94+
TokenWithLocalizedBalance(
95+
token = it.token,
96+
balance = LocalFiat(
97+
usdc = it.balance,
98+
converted = it.balance.convertingTo(rate),
99+
rate = rate
100+
101+
)
102+
)
103+
}.sortedByDescending { it.balance.converted }
93104
}.onEach {
94-
dispatchEvent(Event.OnBalanceUpdated(it))
105+
dispatchEvent(Event.OnBalancesUpdated(it))
95106
}.launchIn(viewModelScope)
96107

97108
featureFlags.observe(FeatureFlag.TransactionDetails)
@@ -159,7 +170,7 @@ internal class BalanceViewModel @Inject constructor(
159170
onSuccess = {
160171
viewModelScope.launch {
161172
feedCoordinator.checkPendingMessagesForUpdates()
162-
balanceController.fetchBalance()
173+
tokenController.update()
163174
}
164175
}
165176
).launchIn(viewModelScope)
@@ -232,7 +243,7 @@ internal class BalanceViewModel @Inject constructor(
232243
Event.ResetSelections -> { state -> state }
233244
is Event.OnCancelRequested -> { state -> state }
234245
is Event.CancelTransfer -> { state -> state }
235-
is Event.OnBalanceUpdated -> { state -> state.copy(balance = event.balance) }
246+
is Event.OnBalancesUpdated -> { state -> state.copy(balances = event.balances) }
236247
is Event.OnTransactionDetailsEnabled -> { state -> state.copy(canViewDetails = event.enabled) }
237248
is Event.ViewDetails -> { state ->
238249
val currentlyExpanded = state.expandedItem

apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/components/BalanceHeader.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ internal fun BalanceHeader(
3535
}
3636
} else {
3737
Crossfade(balance.converted) { amount ->
38-
val captionText = stringResource(R.string.subtitle_balanceIsHeldInUsdStablecoins)
38+
val captionText = stringResource(R.string.subtitle_currentValueOfAllCurrencies)
3939
AmountArea(
4040
amountText = amount.formatted(),
4141
isAltCaption = false,

apps/flipcash/shared/payments/src/main/kotlin/com/flipcash/app/payments/internal/InternalPoolResolveDelegate.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import com.getcode.opencode.model.financial.Distribution
2121
import com.getcode.opencode.model.financial.Fiat
2222
import com.getcode.opencode.model.financial.LocalFiat
2323
import com.getcode.solana.keys.base58
24-
import com.getcode.utils.getPublicKeyBase58
2524
import com.getcode.utils.trace
2625
import javax.inject.Inject
2726

@@ -162,8 +161,8 @@ class InternalPoolResolveDelegate @Inject constructor(
162161
val winnerCount = matchingBets.count()
163162

164163
// Calculate base amount per winner and remainder
165-
val baseAmountPerWinner = poolBalance.quarks / winnerCount
166-
val remainderQuarks = poolBalance.quarks % winnerCount
164+
val baseAmountPerWinner = poolBalance / winnerCount
165+
val remainderQuarks = poolBalance % winnerCount
167166

168167
// 2. otherwise, pay out all winning (matching bets)
169168
// unequal remainder is added to the 'remainderQuarks' winners

apps/flipcash/shared/pools/src/main/kotlin/com/flipcash/app/pools/PoolsCoordinator.kt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,7 @@ import com.getcode.opencode.controllers.AccountController
3131
import com.getcode.opencode.model.accounts.AccountFilter
3232
import com.getcode.opencode.model.core.ID
3333
import com.getcode.opencode.model.financial.Fiat
34-
import kotlinx.coroutines.CoroutineScope
35-
import kotlinx.coroutines.Dispatchers
3634
import kotlinx.coroutines.ExperimentalCoroutinesApi
37-
import kotlinx.coroutines.SupervisorJob
3835
import kotlinx.coroutines.async
3936
import kotlinx.coroutines.coroutineScope
4037
import kotlinx.coroutines.flow.Flow
@@ -177,7 +174,7 @@ class PoolsCoordinator @Inject constructor(
177174
requestingOwner = owner,
178175
filter = AccountFilter.TokenAddress(pool.fundingDestination)
179176
).map {
180-
it.balance == Fiat.Zero
177+
it.balance == 0L
181178
}.onFailure {
182179
return Result.failure(it)
183180
}

apps/flipcash/shared/session/src/main/kotlin/com/flipcash/app/session/internal/RealSessionController.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import com.flipcash.app.core.bill.BillState
1010
import com.flipcash.app.core.bill.PaymentValuation
1111
import com.flipcash.app.core.internal.bill.BillController
1212
import com.flipcash.app.core.internal.errors.showNetworkError
13-
import com.flipcash.app.core.internal.updater.BalanceUpdater
1413
import com.flipcash.app.core.internal.updater.ExchangeUpdater
1514
import com.flipcash.app.core.internal.updater.ProfileUpdater
15+
import com.flipcash.app.core.internal.updater.TokenUpdater
1616
import com.flipcash.app.featureflags.FeatureFlag
1717
import com.flipcash.app.featureflags.FeatureFlagController
1818
import com.flipcash.app.pools.PoolUpdater
@@ -37,7 +37,7 @@ import com.flipcash.services.user.AuthState
3737
import com.flipcash.services.user.UserManager
3838
import com.getcode.manager.BottomBarAction
3939
import com.getcode.manager.BottomBarManager
40-
import com.getcode.opencode.controllers.BalanceController
40+
import com.getcode.opencode.controllers.TokenController
4141
import com.getcode.opencode.controllers.TransactionController
4242
import com.getcode.opencode.internal.transactors.ReceiveGiftTransactorError
4343
import com.getcode.opencode.model.accounts.AccountCluster
@@ -91,7 +91,7 @@ class RealSessionController @Inject constructor(
9191
private val networkObserver: NetworkConnectivityListener,
9292
private val resources: ResourceHelper,
9393
private val vibrator: Vibrator,
94-
private val balanceUpdater: BalanceUpdater,
94+
private val tokenUpdater: TokenUpdater,
9595
private val exchangeUpdater: ExchangeUpdater,
9696
private val activityFeedUpdater: ActivityFeedUpdater,
9797
private val poolsUpdater: PoolsUpdater,
@@ -101,7 +101,7 @@ class RealSessionController @Inject constructor(
101101
private val shareConfirmationController: ShareableConfirmationController,
102102
private val toastController: ToastController,
103103
private val billingClient: BillingClient,
104-
private val balanceController: BalanceController,
104+
private val tokenController: TokenController,
105105
private val featureFlagController: FeatureFlagController,
106106
private val analytics: FlipcashAnalyticsService,
107107
appSettingsCoordinator: AppSettingsCoordinator,
@@ -237,7 +237,7 @@ class RealSessionController @Inject constructor(
237237
private fun startPolling() {
238238
if (userManager.authState.canAccessAuthenticatedApis) {
239239
exchangeUpdater.poll(scope = scope, frequency = 10.seconds, startIn = 10.seconds)
240-
balanceUpdater.poll(scope = scope, frequency = 60.seconds, startIn = 0.seconds)
240+
tokenUpdater.poll(scope = scope, frequency = 60.seconds, startIn = 0.seconds)
241241
activityFeedUpdater.poll(scope = scope, frequency = 60.seconds, startIn = 60.seconds)
242242
// TODO: once we have streams setup for pool this can be removed
243243
poolsUpdater.poll(scope = scope, frequency = 60.seconds, startIn = 45.seconds)
@@ -252,7 +252,7 @@ class RealSessionController @Inject constructor(
252252

253253
private fun stopPolling() {
254254
exchangeUpdater.stop()
255-
balanceUpdater.stop()
255+
tokenUpdater.stop()
256256
activityFeedUpdater.stop()
257257
// TODO: once we have streams setup for pool this can be removed
258258
poolsUpdater.stop()
@@ -627,7 +627,7 @@ class RealSessionController @Inject constructor(
627627
).onFailure {
628628
dismissBill(PutInWallet)
629629
}.onSuccess {
630-
balanceController.fetchBalance()
630+
tokenController.update()
631631
checkPendingItemsInFeed()
632632
dismissBill(PutInWallet)
633633
}

libs/currency-math/src/main/kotlin/com/flipcash/libs/currency/math/Estimator.kt

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package com.flipcash.libs.currency.math
22

33
import com.flipcash.libs.currency.math.internal.DefaultMintDecimals
4-
import jakarta.inject.Inject
54
import java.math.BigDecimal
65
import java.math.BigInteger
76

8-
class Estimator @Inject constructor() {
9-
fun currentPriceFor(quarks: Long): Result<BigDecimal> {
7+
object Estimator {
8+
fun currentPriceFor(currentSupplyInQuarks: Long): Result<BigDecimal> {
109
return runCatching {
1110
val scale = BigDecimal.TEN.pow(DefaultMintDecimals, mc)
12-
val unscaledCurrentSupply = BigDecimal(quarks, mc)
11+
val unscaledCurrentSupply = BigDecimal(currentSupplyInQuarks, mc)
1312
val scaledCurrentSupply = unscaledCurrentSupply.divide(scale, mc)
1413
scaledCurrentSupply
1514
}.fold(
@@ -74,8 +73,8 @@ class Estimator @Inject constructor() {
7473
val feesQuarks = unscaledFees
7574

7675
BuyEstimation(
77-
netTokensToReceive = tokensQuarks.toBigInteger(),
78-
fees = feesQuarks.toBigInteger(),
76+
netTokensToReceive = tokensQuarks,
77+
fees = feesQuarks,
7978
)
8079
}
8180
}
@@ -110,19 +109,19 @@ class Estimator @Inject constructor() {
110109
val feesQuarks = unscaledFees
111110

112111
SellEstimation(
113-
netAmountToReceive = amountQuarks.toBigInteger(),
114-
fees = feesQuarks.toBigInteger(),
112+
netAmountToReceive = amountQuarks,
113+
fees = feesQuarks,
115114
)
116115
}
117116
}
118117
}
119118

120119
data class BuyEstimation(
121-
val netTokensToReceive: BigInteger,
122-
val fees: BigInteger,
120+
val netTokensToReceive: BigDecimal,
121+
val fees: BigDecimal,
123122
)
124123

125124
data class SellEstimation(
126-
val netAmountToReceive: BigInteger,
127-
val fees: BigInteger,
125+
val netAmountToReceive: BigDecimal,
126+
val fees: BigDecimal,
128127
)

0 commit comments

Comments
 (0)