Skip to content

Commit ef9ac1f

Browse files
committed
feat: add support to give/grab non-USDC mints
Signed-off-by: Brandon McAnsh <[email protected]>
1 parent 07f1e92 commit ef9ac1f

File tree

17 files changed

+311
-152
lines changed

17 files changed

+311
-152
lines changed

apps/flipcash/features/cash/src/main/kotlin/com/flipcash/app/cash/internal/CashScreenViewModel.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ internal class CashScreenViewModel @Inject constructor(
161161
LocalFiat.valueExchangeIn(
162162
localizedAmount,
163163
token = token,
164-
currencyCode = rate.currency
164+
rate = rate,
165165
)
166166
}
167167

@@ -313,17 +313,17 @@ internal class CashScreenViewModel @Inject constructor(
313313
}
314314

315315
val localizedAmount = Fiat(data.amountData.amount, rate.currency)
316-
316+
// TOD is there a way to consolidate all of this into valueExchangeIn?
317317
val amountFiat = if (token.address == Mint.usdc) {
318318
LocalFiat(
319319
usdc = localizedAmount.convertingTo(exchange.rateToUsd(rate.currency)!!),
320320
nativeAmount = localizedAmount,
321321
)
322322
} else {
323323
LocalFiat.valueExchangeIn(
324-
localizedAmount,
324+
amount = localizedAmount,
325325
token = token,
326-
currencyCode = rate.currency
326+
rate = rate,
327327
)
328328
}
329329

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

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

33
import com.getcode.crypt.MnemonicPhrase
4-
import com.getcode.ed25519.Ed25519
54
import com.getcode.opencode.internal.network.api.intents.IntentCreateAccount
65
import com.getcode.opencode.model.accounts.AccountCluster
76
import com.getcode.opencode.model.accounts.AccountFilter
@@ -31,7 +30,6 @@ import javax.inject.Inject
3130
import javax.inject.Singleton
3231
import kotlin.concurrent.atomics.AtomicBoolean
3332
import kotlin.concurrent.atomics.ExperimentalAtomicApi
34-
import kotlin.math.min
3533

3634
@OptIn(ExperimentalAtomicApi::class)
3735
@Singleton
@@ -75,11 +73,21 @@ class AccountController @Inject constructor(
7573
}.launchIn(scope)
7674
}
7775

78-
suspend fun createUserAccount(owner: AccountCluster, mint: Mint): Result<ID> {
76+
/**
77+
* Creates a new user account for the given owner and mint.
78+
*
79+
* @param ownerForMint The AccountCluster to create the account for.
80+
* @param mint The Mint to create the account for.
81+
*
82+
* NOTE: The cluster should be updated for the corresponding Token associated with the Mint to ensure the vault is correct.
83+
*
84+
* @return The ID of the created account.
85+
*/
86+
suspend fun createUserAccount(ownerForMint: AccountCluster, mint: Mint): Result<ID> {
7987
// Authority is the owner of the account
80-
val intent = IntentCreateAccount.createUserAccount(owner, mint)
88+
val intent = IntentCreateAccount.createUserAccount(ownerForMint, mint)
8189

82-
return transactionController.submitIntent(scope, intent, owner.authority.keyPair)
90+
return transactionController.submitIntent(scope, intent, ownerForMint.authority.keyPair)
8391
.map { it.id.bytes }
8492
}
8593

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

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,37 @@ package com.getcode.opencode.controllers
22

33
import com.codeinc.opencode.gen.messaging.v1.MessagingService
44
import com.getcode.ed25519.Ed25519.KeyPair
5+
import com.getcode.opencode.internal.extensions.toPublicKey
56
import com.getcode.opencode.internal.network.extensions.asSolanaAccountId
7+
import com.getcode.opencode.internal.network.extensions.toPublicKey
68
import com.getcode.opencode.internal.network.services.OcpMessageStreamReference
79
import com.getcode.opencode.model.core.OpenCodePayload
8-
import com.getcode.opencode.model.transactions.TransferRequest
10+
import com.getcode.opencode.model.financial.Token
11+
import com.getcode.opencode.model.transactions.GiveRequest
12+
import com.getcode.opencode.model.transactions.GrabRequest
913
import com.getcode.opencode.repositories.MessagingRepository
14+
import com.getcode.opencode.utils.flowInterval
1015
import com.getcode.solana.keys.Mint
1116
import com.getcode.solana.keys.PublicKey
1217
import com.getcode.utils.TraceType
1318
import com.getcode.utils.trace
1419
import kotlinx.coroutines.CoroutineScope
1520
import kotlinx.coroutines.Dispatchers
1621
import kotlinx.coroutines.delay
22+
import kotlinx.coroutines.flow.catch
23+
import kotlinx.coroutines.flow.filter
24+
import kotlinx.coroutines.flow.firstOrNull
25+
import kotlinx.coroutines.flow.fold
26+
import kotlinx.coroutines.flow.map
27+
import kotlinx.coroutines.flow.mapNotNull
28+
import kotlinx.coroutines.flow.takeWhile
1729
import kotlinx.coroutines.launch
1830
import kotlinx.coroutines.suspendCancellableCoroutine
1931
import kotlinx.coroutines.sync.Mutex
2032
import kotlinx.coroutines.sync.withLock
2133
import kotlinx.coroutines.withContext
34+
import java.util.concurrent.atomic.AtomicBoolean
35+
import java.util.concurrent.atomic.AtomicInteger
2236
import javax.inject.Inject
2337
import javax.inject.Singleton
2438
import kotlin.coroutines.resume
@@ -28,22 +42,44 @@ class MessagingController @Inject constructor(
2842
private val repository: MessagingRepository,
2943
) {
3044
private var streamReference: OcpMessageStreamReference? = null
45+
3146
// Thread-safe streamReference management
3247
private val streamReferenceMutex = Mutex()
3348

3449
suspend fun awaitRequestToGrabBill(
3550
scope: CoroutineScope,
3651
rendezvous: KeyPair,
37-
): TransferRequest? {
52+
): GrabRequest? {
3853
cancelAwaitForBillGrab()
3954
delay(500)
4055

56+
fun extractRequestToGrabBill(messages: List<MessagingService.Message>): GrabRequest? {
57+
val message = messages.firstOrNull { it.kindCase == MessagingService.Message.KindCase.REQUEST_TO_GRAB_BILL } ?: return null
58+
val account =
59+
message.requestToGrabBill.requestorAccount.value.toByteArray().toPublicKey()
60+
val signature =
61+
com.getcode.solana.keys.Signature(
62+
message.sendMessageRequestSignature.value.toByteArray().toList()
63+
)
64+
65+
return GrabRequest(account, signature)
66+
}
67+
4168
return suspendCancellableCoroutine { cont ->
4269
try {
4370
scope.launch {
4471
streamReferenceMutex.withLock {
4572
streamReference =
46-
repository.openMessageStreamWithKeepAlive(scope, rendezvous) { result ->
73+
repository.openMessageStreamWithKeepAlive(
74+
scope = scope,
75+
rendezvous = rendezvous,
76+
ackFilter = {
77+
// do NOT ack the give requests as the sender
78+
// doing so will delete the messages before the recipient can get them
79+
it.kindCase != MessagingService.Message.KindCase.REQUEST_TO_GIVE_BILL
80+
},
81+
transformer = { extractRequestToGrabBill(it) }
82+
) { result ->
4783
result.onSuccess {
4884
cont.resume(it)
4985
}.onFailure { throwable ->
@@ -120,9 +156,26 @@ class MessagingController @Inject constructor(
120156
)
121157
}
122158

159+
suspend fun pollForGiveRequest(
160+
rendezvous: KeyPair,
161+
): Result<Pair<PublicKey, Mint>>{
162+
163+
return repository.pollMessages(rendezvous)
164+
.map { messages ->
165+
messages.filter {
166+
it.kindCase == MessagingService.Message.KindCase.REQUEST_TO_GIVE_BILL
167+
}
168+
}.mapCatching { messages ->
169+
val message = messages.firstOrNull() ?: throw IllegalStateException("No message found")
170+
val mint = message.requestToGiveBill.mint.toPublicKey()
171+
172+
(message.id.toPublicKey() to mint)
173+
}
174+
}
175+
123176
suspend fun sendRequestToGiveBill(
124177
tokenMint: Mint,
125-
payload: OpenCodePayload,
178+
rendezvous: KeyPair,
126179
): Result<PublicKey> {
127180
val paymentRequest = MessagingService.RequestToGiveBill.newBuilder()
128181
.setMint(tokenMint.asSolanaAccountId())
@@ -132,7 +185,15 @@ class MessagingController @Inject constructor(
132185

133186
return repository.sendMessage(
134187
message = message,
135-
rendezvous = payload.rendezvous
188+
rendezvous = rendezvous
136189
)
137190
}
191+
192+
suspend fun ackMessages(
193+
rendezvous: KeyPair,
194+
messageIds: List<PublicKey>,
195+
): Result<Unit> = repository.ackMessages(
196+
rendezvous = rendezvous,
197+
messageIds = messageIds
198+
)
138199
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,15 @@ class TransactionController @Inject constructor(
276276
Result.failure(e)
277277
}
278278
}
279+
.onEach {
280+
if (debugLogs) {
281+
trace(
282+
tag = "CodeScan",
283+
message = "pollIntentMetadata: [$it] received metadata",
284+
type = TraceType.Process
285+
)
286+
}
287+
}
279288
.filter { !stopped.get() }
280289
.mapNotNull { result ->
281290
result.fold(

services/opencode/src/main/kotlin/com/getcode/opencode/internal/domain/repositories/InternalMessagingRepository.kt

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package com.getcode.opencode.internal.domain.repositories
22

33
import com.getcode.ed25519.Ed25519
4-
import com.getcode.opencode.internal.extensions.toPublicKey
4+
import com.getcode.opencode.internal.network.extensions.asMessageId
5+
import com.getcode.opencode.internal.network.extensions.asSolanaAccountId
56
import com.getcode.opencode.internal.network.services.MessagingService
67
import com.getcode.opencode.internal.network.services.OcpMessageStreamReference
78
import com.getcode.opencode.model.transactions.TransferRequest
@@ -18,57 +19,61 @@ import com.codeinc.opencode.gen.messaging.v1.MessagingService as RpcMessagingSer
1819
internal class InternalMessagingRepository @Inject constructor(
1920
private val service: MessagingService,
2021
): MessagingRepository {
21-
override fun openMessageStreamWithKeepAlive(
22+
override fun <R: TransferRequest> openMessageStreamWithKeepAlive(
2223
scope: CoroutineScope,
2324
rendezvous: Ed25519.KeyPair,
24-
onEvent: (Result<TransferRequest>) -> Unit
25+
ackFilter: (RpcMessagingService.Message) -> Boolean,
26+
transformer: (List<RpcMessagingService.Message>) -> R?,
27+
onEvent: (Result<R>) -> Unit
2528
): OcpMessageStreamReference {
26-
return service.openMessageStreamWithKeepAlive(scope, rendezvous) { result ->
29+
return service.openMessageStreamWithKeepAlive(
30+
scope = scope,
31+
rendezvous = rendezvous,
32+
messageFilter = { messages ->
33+
messages.any { it.kindCase == RpcMessagingService.Message.KindCase.REQUEST_TO_GRAB_BILL }
34+
}
35+
) { result ->
2736
result.onSuccess { messages ->
2837
scope.launch {
29-
// ack them back to server
30-
service.ackMessages(
31-
rendezvous,
32-
messages.map { it.id }
33-
).onSuccess {
34-
trace(
35-
tag = "MessagingService",
36-
message = "acked",
37-
type = TraceType.Silent
38-
)
39-
// extract request to grab bill
40-
val request = extractRequestToGrabBill(messages)
38+
val acks = messages.filter { ackFilter(it) }.map { it.id }
39+
val ackResult = if (acks.isNotEmpty()) {
40+
// ack them back to server
41+
service.ackMessages(
42+
rendezvous,
43+
acks,
44+
).onSuccess {
45+
trace(
46+
tag = "MessagingService",
47+
message = "acked ${acks.size} messages",
48+
type = TraceType.Silent
49+
)
50+
}.onFailure { ErrorUtils.handleError(it) }
51+
} else {
52+
Result.success(Unit)
53+
}
54+
55+
ackResult.onSuccess {
56+
// extract request
57+
val request = transformer(messages)
4158
if (request != null) {
4259
// send back to transactor
4360
onEvent(Result.success(request))
4461
}
45-
}.onFailure { ErrorUtils.handleError(it) }
62+
}
4663
}
4764
}.onFailure { onEvent(Result.failure(it)) }
4865
}
4966
}
5067

51-
private fun extractRequestToGrabBill(messages: List<RpcMessagingService.Message>): TransferRequest? {
52-
val message = messages.firstOrNull { it.kindCase == RpcMessagingService.Message.KindCase.REQUEST_TO_GRAB_BILL } ?: return null
53-
val account =
54-
message.requestToGrabBill.requestorAccount.value.toByteArray().toPublicKey()
55-
val signature =
56-
com.getcode.solana.keys.Signature(
57-
message.sendMessageRequestSignature.value.toByteArray().toList()
58-
)
59-
60-
return TransferRequest(account, signature)
61-
}
62-
6368
override suspend fun pollMessages(rendezvous: Ed25519.KeyPair): Result<List<RpcMessagingService.Message>> {
6469
return service.pollMessages(rendezvous)
6570
}
6671

6772
override suspend fun ackMessages(
6873
rendezvous: Ed25519.KeyPair,
69-
messageIds: List<RpcMessagingService.MessageId>
74+
messageIds: List<PublicKey>
7075
): Result<Unit> {
71-
return service.ackMessages(rendezvous, messageIds)
76+
return service.ackMessages(rendezvous, messageIds.map { it.asMessageId() })
7277
}
7378

7479
override suspend fun sendMessage(rendezvous: Ed25519.KeyPair, message: RpcMessagingService.Message.Builder): Result<PublicKey> {

services/opencode/src/main/kotlin/com/getcode/opencode/internal/network/api/intents/actions/ActionPublicTransfer.kt

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,15 @@
11
package com.getcode.opencode.internal.network.api.intents.actions
22

33
import com.codeinc.opencode.gen.transaction.v2.TransactionService
4-
import com.flipcash.libs.currency.math.Estimator
5-
import com.getcode.crypt.DerivedKey
6-
import com.getcode.ed25519.Ed25519
74
import com.getcode.ed25519.Ed25519.KeyPair
8-
import com.getcode.opencode.model.accounts.AccountCluster
9-
import com.getcode.opencode.solana.intents.CompactMessageArgs
10-
import com.getcode.opencode.solana.intents.ServerParameter
115
import com.getcode.opencode.internal.network.extensions.asSolanaAccountId
12-
import com.getcode.opencode.solana.SolanaTransaction
136
import com.getcode.opencode.model.financial.Fiat
7+
import com.getcode.opencode.solana.SolanaTransaction
8+
import com.getcode.opencode.solana.intents.CompactMessageArgs
9+
import com.getcode.opencode.solana.intents.ServerParameter
1410
import com.getcode.opencode.solana.intents.actions.ActionType
1511
import com.getcode.solana.keys.Mint
1612
import com.getcode.solana.keys.PublicKey
17-
import kotlin.math.min
18-
import kotlin.math.sign
1913

2014
internal class ActionPublicTransfer(
2115
override var id: Int,

0 commit comments

Comments
 (0)