Skip to content

Commit 23a370d

Browse files
committed
feat(flipcash/pools): store rendezvous keys for all pools and persist unbet pools from deeplinks
Signed-off-by: Brandon McAnsh <[email protected]>
1 parent 6e7b425 commit 23a370d

File tree

10 files changed

+118
-50
lines changed

10 files changed

+118
-50
lines changed

apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/pools/PoolWithBets.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package com.flipcash.app.core.pools
22

3+
import com.getcode.ed25519.Ed25519
34
import com.getcode.opencode.model.financial.Fiat
45

56
data class PoolWithBets(
67
val pool: Pool,
78
val isHost: Boolean,
9+
val rendezvousSeed: String?,
810
val bets: List<PoolBet>
911
) {
1012
val totalPoolAmount: Fiat
@@ -22,4 +24,7 @@ data class PoolWithBets(
2224
fun winningAmountForResolution(resolution: PoolResolution.DecisionMade): Fiat {
2325
return pool.winningAmountForResolution(resolution)
2426
}
27+
28+
val rendezvous: Ed25519.KeyPair?
29+
get() = rendezvousSeed?.let { Ed25519.createKeyPair(it) }
2530
}

apps/flipcash/features/pools/src/main/kotlin/com/flipcash/app/pools/internal/betting/PoolBettingViewModel.kt

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ internal class PoolBettingViewModel @Inject constructor(
7171
get() = PoolWithBets(
7272
pool = metadata,
7373
isHost = metadata.creator == userId,
74-
bets = bets
74+
bets = bets,
75+
rendezvousSeed = rendezvous?.seed
7576
)
7677

7778
val isHost: Boolean
@@ -236,17 +237,6 @@ internal class PoolBettingViewModel @Inject constructor(
236237
}
237238
}.launchIn(viewModelScope)
238239

239-
eventFlow
240-
.filterIsInstance<Event.OnPoolLoaded>()
241-
.filter { it.data.isHost }
242-
.map { it.data.pool }
243-
.filter { stateFlow.value.rendezvous == null }
244-
.map { userManager.poolAccountAt(it.derivationIndex) }
245-
.onEach {
246-
dispatchEvent(Event.OnPoolRendezvousChanged(it.rendezvous, fromUser = false))
247-
}
248-
.launchIn(viewModelScope)
249-
250240
eventFlow
251241
.filterIsInstance<Event.OnPoolLoaded>()
252242
.map { it.data }
@@ -501,10 +491,10 @@ internal class PoolBettingViewModel @Inject constructor(
501491
}
502492

503493
is Event.OnPoolLoaded -> { state ->
504-
val existingRendezvous = state.rendezvous
505494
state.copy(
506495
metadata = event.data.pool,
507496
bets = event.data.bets,
497+
rendezvous = event.data.rendezvous,
508498
)
509499
}
510500

apps/flipcash/shared/persistence/db/schemas/com.flipcash.app.persistence.FlipcashDatabase/6.json

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"formatVersion": 1,
33
"database": {
44
"version": 6,
5-
"identityHash": "1a108f47c87c6befc8853fe8d6027a60",
5+
"identityHash": "0a01c71b9099ef20120ac28b9e3bed39",
66
"entities": [
77
{
88
"tableName": "messages",
@@ -219,11 +219,35 @@
219219
"idBase58"
220220
]
221221
}
222+
},
223+
{
224+
"tableName": "pool_rendezvous_keys",
225+
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`poolIdBase58` TEXT NOT NULL, `rendezvousSeedBase58` TEXT NOT NULL, PRIMARY KEY(`poolIdBase58`))",
226+
"fields": [
227+
{
228+
"fieldPath": "poolIdBase58",
229+
"columnName": "poolIdBase58",
230+
"affinity": "TEXT",
231+
"notNull": true
232+
},
233+
{
234+
"fieldPath": "rendezvousSeedBase58",
235+
"columnName": "rendezvousSeedBase58",
236+
"affinity": "TEXT",
237+
"notNull": true
238+
}
239+
],
240+
"primaryKey": {
241+
"autoGenerate": false,
242+
"columnNames": [
243+
"poolIdBase58"
244+
]
245+
}
222246
}
223247
],
224248
"setupQueries": [
225249
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
226-
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1a108f47c87c6befc8853fe8d6027a60')"
250+
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0a01c71b9099ef20120ac28b9e3bed39')"
227251
]
228252
}
229253
}

apps/flipcash/shared/persistence/db/src/main/kotlin/com/flipcash/app/persistence/FlipcashDatabase.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import com.flipcash.app.persistence.dao.PoolDao
1717
import com.flipcash.app.persistence.entities.MessageEntity
1818
import com.flipcash.app.persistence.entities.PoolBetEntity
1919
import com.flipcash.app.persistence.entities.PoolEntity
20+
import com.flipcash.app.persistence.entities.PoolRendezvousKeyEntity
2021
import com.getcode.utils.TraceType
2122
import com.getcode.utils.trace
2223
import com.getcode.vendor.Base58
@@ -27,6 +28,7 @@ import org.kin.sdk.base.tools.subByteArray
2728
MessageEntity::class,
2829
PoolEntity::class,
2930
PoolBetEntity::class,
31+
PoolRendezvousKeyEntity::class,
3032
],
3133
autoMigrations = [
3234
AutoMigration(from = 1, to = 2, spec = FlipcashDatabase.Migration1To2::class),

apps/flipcash/shared/persistence/db/src/main/kotlin/com/flipcash/app/persistence/dao/PoolDao.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import androidx.room.Transaction
99
import com.flipcash.app.core.pools.PoolResolution
1010
import com.flipcash.app.persistence.entities.PoolBetEntity
1111
import com.flipcash.app.persistence.entities.PoolEntity
12+
import com.flipcash.app.persistence.entities.PoolRendezvousKeyEntity
1213
import com.flipcash.app.persistence.entities.PoolWithBetsEntity
14+
import com.getcode.ed25519.Ed25519
1315
import com.getcode.opencode.model.core.ID
1416
import com.getcode.utils.base58
1517
import kotlinx.coroutines.flow.Flow
@@ -23,6 +25,12 @@ interface PoolDao {
2325
@Insert(onConflict = OnConflictStrategy.REPLACE)
2426
suspend fun upsert(vararg bet: PoolBetEntity)
2527

28+
@Insert(onConflict = OnConflictStrategy.REPLACE)
29+
suspend fun upsertRendezvous(pair: PoolRendezvousKeyEntity)
30+
suspend fun upsertRendezvous(poolId: ID, rendezvous: Ed25519.KeyPair) {
31+
upsertRendezvous(PoolRendezvousKeyEntity(poolId.base58, rendezvous.seed))
32+
}
33+
2634
@Query("SELECT * FROM pool_bet_metadata WHERE idBase58 = :id")
2735
suspend fun getBet(id: String): PoolBetEntity?
2836
suspend fun getBet(id: ID): PoolBetEntity? {

apps/flipcash/shared/persistence/db/src/main/kotlin/com/flipcash/app/persistence/entities/PoolEntity.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import com.getcode.solana.keys.PublicKey
1111
import com.getcode.vendor.Base58
1212
import kotlinx.datetime.Instant
1313
import kotlinx.serialization.Serializable
14-
import java.io.Serial
1514

1615
@Serializable
1716
@Entity(tableName = "pool_metadata")
@@ -60,5 +59,13 @@ data class PoolWithBetsEntity(
6059
parentColumn = "idBase58",
6160
entityColumn = "poolIdBase58"
6261
)
63-
val bets: List<PoolBetEntity>
62+
val bets: List<PoolBetEntity>,
63+
@Relation(
64+
entity = PoolRendezvousKeyEntity::class,
65+
parentColumn = "idBase58",
66+
entityColumn = "poolIdBase58",
67+
projection = ["rendezvousSeedBase58"]
68+
69+
)
70+
val rendezvous: String?,
6471
)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.flipcash.app.persistence.entities
2+
3+
import androidx.room.Entity
4+
import androidx.room.PrimaryKey
5+
6+
@Entity(tableName = "pool_rendezvous_keys")
7+
data class PoolRendezvousKeyEntity(
8+
@PrimaryKey
9+
val poolIdBase58: String,
10+
val rendezvousSeedBase58: String,
11+
)

apps/flipcash/shared/persistence/sources/src/main/kotlin/com/flipcash/app/persistence/sources/PoolDataSource.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ class PoolDataSource @Inject constructor(
5454
PoolWithBets(
5555
pool = pool,
5656
isHost = isHost,
57-
bets = bets
57+
bets = bets,
58+
rendezvousSeed = entity.rendezvous
5859
)
5960
} ?: flowOf(null)
6061
}
@@ -67,7 +68,8 @@ class PoolDataSource @Inject constructor(
6768
return PoolWithBets(
6869
pool = pool,
6970
isHost = isHost,
70-
bets = bets
71+
bets = bets,
72+
rendezvousSeed = result.rendezvous
7173
)
7274
}
7375

@@ -80,7 +82,8 @@ class PoolDataSource @Inject constructor(
8082
PoolWithBets(
8183
pool = pool,
8284
isHost = isHost,
83-
bets = bets
85+
bets = bets,
86+
rendezvousSeed = entity.rendezvous
8487
)
8588
}
8689
}
@@ -95,6 +98,10 @@ class PoolDataSource @Inject constructor(
9598
}
9699
}
97100

101+
suspend fun persistRendezvous(poolId: ID, rendezvous: Ed25519.KeyPair) {
102+
db?.poolDao()?.upsertRendezvous(poolId, rendezvous)
103+
}
104+
98105
suspend fun resolvePool(id: ID, resolution: PoolResolution.DecisionMade) {
99106
db?.poolDao()?.resolvePool(id, resolution)
100107
}
@@ -142,7 +149,8 @@ class PoolDataSource @Inject constructor(
142149
return PoolWithBets(
143150
pool = pool,
144151
isHost = isHost,
145-
bets = bets
152+
bets = bets,
153+
rendezvousSeed = result.rendezvous
146154
)
147155
}
148156

apps/flipcash/shared/persistence/sources/src/main/kotlin/com/flipcash/app/persistence/sources/mapper/pools/NetworkPoolToDomainMapper.kt

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,43 @@ import com.flipcash.app.persistence.converters.BetOutcomeConverter
77
import com.flipcash.app.persistence.converters.PoolBetSummaryConverter
88
import com.flipcash.app.persistence.converters.PoolResolutionConverter
99
import com.flipcash.services.models.NetworkPool
10+
import com.flipcash.services.models.PoolBetMetadata
1011
import com.flipcash.services.user.UserManager
1112
import com.getcode.ed25519.Ed25519
1213
import com.getcode.opencode.mapper.Mapper
14+
import com.getcode.opencode.model.core.ID
1315
import javax.inject.Inject
1416

17+
data class NetworkPoolMapperParameters(
18+
val networkPool: NetworkPool,
19+
val rendezvous: Ed25519.KeyPair?,
20+
)
21+
1522
class NetworkPoolToDomainMapper @Inject constructor(
1623
private val userManager: UserManager,
17-
): Mapper<NetworkPool, PoolWithBets> {
18-
override fun map(from: NetworkPool): PoolWithBets {
19-
val selectedOutcome = from.bets.find { it.metadata.userId == userManager.accountId }?.metadata?.selectedOutcome
24+
): Mapper<NetworkPoolMapperParameters, PoolWithBets> {
25+
override fun map(from: NetworkPoolMapperParameters): PoolWithBets {
26+
val (response, rendezvous) = from
27+
val selectedOutcome = response.bets.find { it.metadata.userId == userManager.accountId }?.metadata?.selectedOutcome
2028

2129
return PoolWithBets(
2230
pool = Pool(
23-
id = from.metadata.id,
24-
creator = from.metadata.creator,
25-
name = from.metadata.name,
26-
buyIn = from.metadata.buyIn,
27-
fundingDestination = from.metadata.fundingDestination,
28-
isOpen = from.metadata.isOpen,
29-
resolution = PoolResolutionConverter.toPoolResolution(from.metadata.resolution),
30-
createdAt = from.metadata.createdAt,
31-
closedAt = from.metadata.closedAt,
32-
didWin = from.metadata.resolution.didWin(selectedOutcome),
33-
derivationIndex = from.derivationIndex,
34-
betSummary = PoolBetSummaryConverter.toPoolBetSummary(from.betSummary),
31+
id = response.metadata.id,
32+
creator = response.metadata.creator,
33+
name = response.metadata.name,
34+
buyIn = response.metadata.buyIn,
35+
fundingDestination = response.metadata.fundingDestination,
36+
isOpen = response.metadata.isOpen,
37+
resolution = PoolResolutionConverter.toPoolResolution(response.metadata.resolution),
38+
createdAt = response.metadata.createdAt,
39+
closedAt = response.metadata.closedAt,
40+
didWin = response.metadata.resolution.didWin(selectedOutcome),
41+
derivationIndex = response.derivationIndex,
42+
betSummary = PoolBetSummaryConverter.toPoolBetSummary(response.betSummary),
3543
),
36-
isHost = userManager.accountId == from.metadata.creator,
37-
bets = from.bets.map {
44+
isHost = userManager.accountId == response.metadata.creator,
45+
rendezvousSeed = rendezvous?.seed,
46+
bets = response.bets.map {
3847
PoolBet(
3948
id = it.metadata.id,
4049
userId = it.metadata.userId,

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

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import com.flipcash.app.core.pools.PoolWithHostStatus
1313
import com.flipcash.app.persistence.converters.BetOutcomeConverter
1414
import com.flipcash.app.persistence.converters.PoolResolutionConverter
1515
import com.flipcash.app.persistence.sources.PoolDataSource
16+
import com.flipcash.app.persistence.sources.mapper.pools.NetworkPoolMapperParameters
1617
import com.flipcash.app.persistence.sources.mapper.pools.NetworkPoolToDomainMapper
1718
import com.flipcash.app.persistence.sources.mapper.pools.PoolEntityToPoolMapper
1819
import com.flipcash.app.persistence.sources.mediator.PoolRemoteMediator
@@ -75,7 +76,7 @@ class PoolsCoordinator @Inject constructor(
7576

7677
suspend fun updatePools() = coroutineScope {
7778
val pools = dataSource.get()
78-
pools.map { (pool, _, _) ->
79+
pools.map { (pool, _, _, _) ->
7980
async { getPool(pool.id) }
8081
}.forEach { it.await() }
8182
}
@@ -97,33 +98,36 @@ class PoolsCoordinator @Inject constructor(
9798
.getOrElse { return Result.failure(it) }
9899

99100
val isHost = networkPool.metadata.creator == userManager.accountId
100-
101-
// store the pool if we are the host or if we have bet already
102-
if (isHost || networkPool.bets.find { it.metadata.userId == userManager.accountId }?.hasIntentBeenSubmitted == true) {
103-
dataSource.upsert(listOf(networkPool))
101+
if (isHost) {
102+
val rendezvous = userManager.poolAccountAt(networkPool.derivationIndex).rendezvous
103+
dataSource.persistRendezvous(networkPool.metadata.id, rendezvous)
104104
}
105105

106+
dataSource.upsert(listOf(networkPool))
107+
108+
106109
return runCatching {
107110
dataSource.getById(id)!!
108111
}.recoverCatching {
109-
networkToDomainMapper.map(networkPool)
112+
val params = NetworkPoolMapperParameters(networkPool, null)
113+
networkToDomainMapper.map(params)
110114
}
111115
}
112116

113117
suspend fun getPool(rendezvous: KeyPair): Result<PoolWithBets> {
114118
val networkPool = controller.getPool(rendezvous)
115119
.getOrElse { return Result.failure(it) }
116120

117-
val (_, isHost, bets) = networkToDomainMapper.map(networkPool)
118-
119121
// store the pool if we are the host or if we have bet already (and paid for it)
120-
if (isHost || bets.find { it.userId == userManager.accountId }?.hasPaidForBet == true) {
121-
dataSource.upsert(listOf(networkPool))
122-
}
122+
dataSource.persistRendezvous(networkPool.metadata.id, rendezvous)
123+
dataSource.upsert(listOf(networkPool))
123124

124125
return runCatching {
125126
dataSource.getById(rendezvous.publicKeyBytes.toList())!!
126-
}.recoverCatching { networkToDomainMapper.map(networkPool) }
127+
}.recoverCatching {
128+
val params = NetworkPoolMapperParameters(networkPool, rendezvous)
129+
networkToDomainMapper.map(params)
130+
}
127131
}
128132

129133
fun observePool(id: ID): Flow<PoolWithBets?> {

0 commit comments

Comments
 (0)