Skip to content

Commit ce70322

Browse files
committed
chore: move reserve streaming to Controller; fix reserve and rate association in proto store
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 4a1cadf commit ce70322

3 files changed

Lines changed: 85 additions & 52 deletions

File tree

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

Lines changed: 40 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,8 @@ import androidx.lifecycle.DefaultLifecycleObserver
1111
import androidx.lifecycle.LifecycleOwner
1212
import androidx.lifecycle.ProcessLifecycleOwner
1313
import com.flipcash.app.persistence.sources.TokenDataSource
14-
import com.getcode.opencode.controllers.CurrencyController
1514
import com.getcode.opencode.controllers.TokenController
1615
import com.getcode.opencode.exchange.Exchange
17-
import com.getcode.opencode.internal.model.LiveMintDataResponse
1816
import com.getcode.opencode.internal.model.WindowedRange
1917
import com.getcode.opencode.model.accounts.AccountCluster
2018
import com.getcode.opencode.model.financial.CurrencyCode
@@ -48,7 +46,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
4846
import kotlinx.coroutines.flow.debounce
4947
import kotlinx.coroutines.flow.distinctUntilChanged
5048
import kotlinx.coroutines.flow.filter
51-
import kotlinx.coroutines.flow.filterIsInstance
5249
import kotlinx.coroutines.flow.filterNotNull
5350
import kotlinx.coroutines.flow.firstOrNull
5451
import kotlinx.coroutines.flow.flatMapLatest
@@ -80,7 +77,6 @@ import javax.inject.Singleton
8077
class TokenCoordinator @Inject constructor(
8178
@param:ApplicationContext private val context: Context,
8279
private val tokenController: TokenController,
83-
private val currencyController: CurrencyController,
8480
private val networkObserver: NetworkConnectivityListener,
8581
private val exchange: Exchange,
8682
private val dataSource: TokenDataSource,
@@ -401,56 +397,54 @@ class TokenCoordinator @Inject constructor(
401397
delay(100)
402398
trace(tag = TAG, message = "Reserve state stream started", type = TraceType.Process)
403399

404-
currencyController.streamLiveMintData(
400+
tokenController.streamReserveStates(
405401
scope = this,
406402
mints = _state.map { it.tokens.keys.toList() }
407403
.distinctUntilChanged().debounce(300),
408-
tag = "token-reserves"
409-
).filterIsInstance<LiveMintDataResponse.LaunchpadReserveState>()
410-
.collect { response ->
411-
trace(tag = TAG, message = "Received ${response.reserveStates.size} reserve state updates", type = TraceType.Process)
412-
413-
_state.update { state ->
414-
var updatedTokens = state.tokens
415-
var updatedBalances = state.balances
416-
417-
response.reserveStates.forEach { update ->
418-
val mint = update.reserveState.mint
419-
val token = state.tokens[mint] ?: return@forEach
420-
val launchpad = token.launchpadMetadata ?: return@forEach
421-
422-
val updatedToken = token.copy(
423-
launchpadMetadata = launchpad.copy(
424-
currentCirculatingSupplyQuarks = update.reserveState.currentSupply
425-
)
404+
).collect { response ->
405+
trace(tag = TAG, message = "Received ${response.reserveStates.size} reserve state updates", type = TraceType.Process)
406+
407+
_state.update { state ->
408+
var updatedTokens = state.tokens
409+
var updatedBalances = state.balances
410+
411+
response.reserveStates.forEach { update ->
412+
val mint = update.reserveState.mint
413+
val token = state.tokens[mint] ?: return@forEach
414+
val launchpad = token.launchpadMetadata ?: return@forEach
415+
416+
val updatedToken = token.copy(
417+
launchpadMetadata = launchpad.copy(
418+
currentCirculatingSupplyQuarks = update.reserveState.currentSupply
426419
)
427-
updatedTokens = updatedTokens + (mint to updatedToken)
428-
429-
state.balances[mint]?.let { balance ->
430-
val exchangedValue = runCatching {
431-
LocalFiat.valueExchangeIn(
432-
amount = balance,
433-
token = token,
434-
balance = balance,
435-
rate = Rate.oneToOne,
436-
debug = false,
437-
trace = false,
438-
).underlyingTokenAmount
439-
}.getOrNull()
440-
441-
if (exchangedValue != null) {
442-
val newBalance = Fiat.tokenBalance(
443-
quarks = exchangedValue.quarks,
444-
token = token
445-
)
446-
updatedBalances = updatedBalances + (mint to newBalance)
447-
}
420+
)
421+
updatedTokens = updatedTokens + (mint to updatedToken)
422+
423+
state.balances[mint]?.let { balance ->
424+
val exchangedValue = runCatching {
425+
LocalFiat.valueExchangeIn(
426+
amount = balance,
427+
token = token,
428+
balance = balance,
429+
rate = Rate.oneToOne,
430+
debug = false,
431+
trace = false,
432+
).underlyingTokenAmount
433+
}.getOrNull()
434+
435+
if (exchangedValue != null) {
436+
val newBalance = Fiat.tokenBalance(
437+
quarks = exchangedValue.quarks,
438+
token = token
439+
)
440+
updatedBalances = updatedBalances + (mint to newBalance)
448441
}
449442
}
450-
451-
state.copy(tokens = updatedTokens, balances = updatedBalances)
452443
}
444+
445+
state.copy(tokens = updatedTokens, balances = updatedBalances)
453446
}
447+
}
454448
}
455449
}
456450

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

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.getcode.opencode.controllers
22

3+
import com.getcode.opencode.internal.model.LiveMintDataResponse
34
import com.getcode.opencode.internal.model.WindowedRange
45
import com.getcode.opencode.model.accounts.AccountCluster
56
import com.getcode.opencode.model.accounts.AccountFilter
@@ -17,6 +18,9 @@ import com.getcode.solana.keys.base58
1718
import com.getcode.utils.TraceType
1819
import com.getcode.utils.network.retryable
1920
import com.getcode.utils.trace
21+
import kotlinx.coroutines.CoroutineScope
22+
import kotlinx.coroutines.flow.Flow
23+
import kotlinx.coroutines.flow.filterIsInstance
2024
import javax.inject.Inject
2125
import javax.inject.Singleton
2226

@@ -25,8 +29,8 @@ import javax.inject.Singleton
2529
*
2630
* This controller provides direct access to token-related network APIs
2731
* without any caching, persistence, or state management. It is designed
28-
* to be usable both within the Flipcash app and as part of a standalone public SDK.
29-
* (Have that need ever arise)
32+
* to be usable both within the Flipcash app (wrapped by [TokenCoordinator])
33+
* and as part of a standalone public SDK.
3034
*
3135
* All state management (caching, persistence, lifecycle, balance tracking)
3236
* is the responsibility of the consumer.
@@ -180,6 +184,34 @@ class TokenController @Inject constructor(
180184

181185
// endregion
182186

187+
// region Reserve state streaming
188+
189+
/**
190+
* Creates a flow of reserve state updates for the given mints.
191+
*
192+
* This is a stateless factory — the caller owns the [CoroutineScope] and
193+
* manages the collection lifecycle. The underlying stream also feeds
194+
* [VerifiedProtoManager] as a side effect inside [CurrencyService], ensuring
195+
* verified protos are available at intent submission time.
196+
*
197+
* @param scope The [CoroutineScope] that controls the stream's lifetime.
198+
* @param mints A reactive flow of mint lists to subscribe to. The stream
199+
* restarts when the mint list changes.
200+
* @return A [Flow] of [LiveMintDataResponse.LaunchpadReserveState] updates.
201+
*/
202+
fun streamReserveStates(
203+
scope: CoroutineScope,
204+
mints: Flow<List<Mint>>,
205+
): Flow<LiveMintDataResponse.LaunchpadReserveState> {
206+
return currencyController.streamLiveMintData(
207+
scope = scope,
208+
mints = mints,
209+
tag = "token-reserves"
210+
).filterIsInstance<LiveMintDataResponse.LaunchpadReserveState>()
211+
}
212+
213+
// endregion
214+
183215
// region Internal
184216

185217
private suspend fun buildTokenWithBalance(

services/opencode/src/main/kotlin/com/getcode/opencode/internal/manager/VerifiedProtoManager.kt

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package com.getcode.opencode.internal.manager
22

33
import com.codeinc.opencode.gen.currency.v1.CurrencyService
4-
import com.getcode.opencode.internal.model.VerifiedResponseData
54
import com.getcode.opencode.internal.network.extensions.toMint
65
import com.getcode.opencode.model.financial.CurrencyCode
76
import com.getcode.solana.keys.Mint
87
import kotlinx.coroutines.flow.MutableStateFlow
8+
import kotlinx.coroutines.flow.update
99
import javax.inject.Inject
1010
import javax.inject.Singleton
1111

@@ -24,7 +24,7 @@ class VerifiedProtoManager @Inject constructor() {
2424
/**
2525
* A [MutableStateFlow] holding the latest cached exchange rate data.
2626
* The data is stored in a map where the key is the [CurrencyCode] (e.g., "USD", "EUR")
27-
* and the value is the corresponding [VerifiedResponseData.ExchangeRate] object,
27+
* and the value is the corresponding [com.getcode.opencode.internal.model.VerifiedResponseData.ExchangeRate] object,
2828
* which includes the rate and the timestamp of when it was fetched.
2929
*/
3030
private val exchangeData = MutableStateFlow<Map<CurrencyCode, CurrencyService.VerifiedCoreMintFiatExchangeRate>>(emptyMap())
@@ -38,13 +38,20 @@ class VerifiedProtoManager @Inject constructor() {
3838
private val reserveStates = MutableStateFlow<Map<Mint, CurrencyService.VerifiedLaunchpadCurrencyReserveState>>(emptyMap())
3939

4040
fun saveRates(exchangeData: List<CurrencyService.VerifiedCoreMintFiatExchangeRate>) {
41-
this.exchangeData.value = exchangeData.mapNotNull { data ->
41+
val incoming = exchangeData.mapNotNull { data ->
4242
CurrencyCode.tryValueOf(data.exchangeRate.currencyCode)?.let { it to data }
4343
}.toMap()
44+
this.exchangeData.update { it + incoming }
4445
}
4546

4647
fun saveReserveStates(reserveStates: List<CurrencyService.VerifiedLaunchpadCurrencyReserveState>) {
47-
this.reserveStates.value = reserveStates.associateBy { it.reserveState.mint.toMint() }
48+
val incoming = reserveStates.associateBy { it.reserveState.mint.toMint() }
49+
this.reserveStates.update { it + incoming }
50+
}
51+
52+
fun reset() {
53+
exchangeData.value = emptyMap()
54+
reserveStates.value = emptyMap()
4855
}
4956

5057
private fun get(currencyCode: CurrencyCode): CurrencyService.VerifiedCoreMintFiatExchangeRate? {

0 commit comments

Comments
 (0)