Skip to content

Commit 8f38e59

Browse files
committed
refactor(ocp): introduce IntentStatelessSwap and rename swap request/response types
Add IntentStatelessSwap mirroring IntentSwap pattern — encapsulates initiate and submit-signatures protobuf request building. Update StatelessSwapExecutor to take the intent instead of raw params + lambda. Create StatelessSwapRequest model and rename stateful counterparts: SwapRequest -> StatefulSwapRequest, SwapResponseServerParameters -> StatefulSwapResponseServerParameters, SwapResult -> StatefulSwapResult. Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 5ddf72c commit 8f38e59

25 files changed

Lines changed: 155 additions & 162 deletions

‎services/opencode/src/androidTest/kotlin/com/getcode/opencode/solana/swap/SwapTests.kt‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import com.getcode.opencode.model.financial.LaunchpadMetadata
2323
import com.getcode.opencode.model.financial.MintMetadata
2424
import com.getcode.opencode.model.financial.VmMetadata
2525
import com.getcode.opencode.model.financial.usdf
26-
import com.getcode.opencode.model.transactions.SwapResponseServerParameters
26+
import com.getcode.opencode.model.transactions.StatefulSwapResponseServerParameters
2727
import com.getcode.opencode.tests.generateRandomPublicKeyForTest
2828
import com.getcode.opencode.utils.generate
2929
import com.getcode.solana.keys.Mint
@@ -89,7 +89,7 @@ class SwapInstructionsTest {
8989
)
9090

9191
// Mock Server Response (Stateless for simplicity)
92-
private val mockServerParams = SwapResponseServerParameters.ExistingCurrency(
92+
private val mockServerParams = StatefulSwapResponseServerParameters.ExistingCurrency(
9393
payer = mockPayer,
9494
blockhash = mockRecentBlockhash,
9595
nonce = mockNonce,
@@ -337,7 +337,7 @@ class SwapInstructionsTest {
337337
private val mockNewCurrencyAuthority = generateRandomPublicKeyForTest()
338338
private val mockSeed = generateRandomPublicKeyForTest()
339339

340-
private val mockNewCurrencyServerParams = SwapResponseServerParameters.NewCurrency(
340+
private val mockNewCurrencyServerParams = StatefulSwapResponseServerParameters.NewCurrency(
341341
payer = mockPayer,
342342
blockhash = mockRecentBlockhash,
343343
nonce = mockNonce,

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import com.getcode.opencode.model.financial.Token
2424
import com.getcode.opencode.model.transactions.ExchangeData
2525
import com.getcode.opencode.model.transactions.SwapFundingSource
2626
import com.getcode.opencode.model.transactions.SwapMetadata
27-
import com.getcode.opencode.model.transactions.SwapRequest
27+
import com.getcode.opencode.model.transactions.StatefulSwapRequest
2828
import com.getcode.opencode.model.transactions.SwapState
2929
import com.getcode.opencode.model.transactions.TransactionMetadata
3030
import com.getcode.opencode.model.transactions.WithdrawalAvailability
@@ -269,7 +269,7 @@ class TransactionController @Inject constructor(
269269
swapId: SwapId?,
270270
of: Token,
271271
source: SwapFundingSource,
272-
fund: (suspend (SwapRequest) -> Result<Unit>)?,
272+
fund: (suspend (StatefulSwapRequest) -> Result<Unit>)?,
273273
): Result<SwapId> {
274274
trace("Starting ${amount.localFiat.nativeAmount.formatted()} buy of ${of.symbol}")
275275
val tokenizedOwner = owner.withTimelockForToken(of)

‎services/opencode/src/main/kotlin/com/getcode/opencode/controllers/TransactionOperations.kt‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import com.getcode.opencode.model.financial.LocalFiat
99
import com.getcode.opencode.model.financial.Token
1010
import com.getcode.opencode.model.transactions.SwapFundingSource
1111
import com.getcode.opencode.model.transactions.SwapMetadata
12-
import com.getcode.opencode.model.transactions.SwapRequest
12+
import com.getcode.opencode.model.transactions.StatefulSwapRequest
1313
import com.getcode.opencode.model.transactions.SwapState
1414
import com.getcode.opencode.model.transactions.WithdrawalAvailability
1515
import com.getcode.opencode.solana.intents.IntentType
@@ -35,7 +35,7 @@ interface TransactionOperations {
3535
swapId: SwapId? = null,
3636
of: Token,
3737
source: SwapFundingSource = SwapFundingSource.SubmitIntent(),
38-
fund: (suspend (SwapRequest) -> Result<Unit>)? = null,
38+
fund: (suspend (StatefulSwapRequest) -> Result<Unit>)? = null,
3939
): Result<SwapId>
4040

4141
suspend fun sell(

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import com.getcode.opencode.model.financial.Limits
99
import com.getcode.opencode.model.financial.LocalFiat
1010
import com.getcode.opencode.model.financial.Token
1111
import com.getcode.opencode.model.transactions.SwapFundingSource
12-
import com.getcode.opencode.model.transactions.SwapRequest
12+
import com.getcode.opencode.model.transactions.StatefulSwapRequest
1313
import com.getcode.opencode.model.transactions.TransactionMetadata
1414
import com.getcode.opencode.model.transactions.WithdrawalAvailability
1515
import com.getcode.opencode.model.core.errors.SubmitIntentError
@@ -73,7 +73,7 @@ internal class InternalTransactionRepository @Inject constructor(
7373
swapId: SwapId?,
7474
verifiedState: VerifiedState,
7575
source: SwapFundingSource,
76-
fund: (suspend (SwapRequest) -> Result<Unit>)?
76+
fund: (suspend (StatefulSwapRequest) -> Result<Unit>)?
7777
): Result<SwapId> = service.buy(
7878
scope = scope,
7979
swapId = swapId,
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.getcode.opencode.internal.network.api.intents
2+
3+
import com.codeinc.opencode.gen.transaction.v1.TransactionService
4+
import com.getcode.opencode.internal.network.extensions.asSignature
5+
import com.getcode.opencode.internal.network.extensions.asSolanaAccountId
6+
import com.getcode.opencode.internal.network.extensions.sign
7+
import com.getcode.opencode.model.transactions.StatelessSwapRequest
8+
import com.getcode.opencode.model.transactions.StatelessSwapServerParameters
9+
import com.getcode.opencode.solana.SolanaTransaction
10+
import com.getcode.solana.keys.Signature
11+
12+
internal class IntentStatelessSwap(
13+
val request: StatelessSwapRequest,
14+
) {
15+
var parameters: StatelessSwapServerParameters? = null
16+
17+
fun sign(parameters: StatelessSwapServerParameters): List<Signature> {
18+
val transaction = transaction(parameters)
19+
return transaction.signatures(request.owner.authority.keyPair)
20+
}
21+
22+
fun transaction(parameters: StatelessSwapServerParameters): SolanaTransaction {
23+
TODO("Stateless swap transaction building not yet implemented")
24+
}
25+
26+
fun initiate(): TransactionService.StatelessSwapRequest {
27+
val owner = request.owner.authority.keyPair
28+
val initiate = TransactionService.StatelessSwapRequest.Initiate.newBuilder()
29+
.setOwner(owner.asSolanaAccountId())
30+
.setStablecoin(
31+
TransactionService.StatelessSwapRequest.Initiate.CoinbaseStableSwapperClientParameters.newBuilder()
32+
.setFromMint(request.fromMint.asSolanaAccountId())
33+
.setToMint(request.toMint.asSolanaAccountId())
34+
.setSwapAmount(request.amount)
35+
.build()
36+
)
37+
.setWaitForFinalization(request.waitForFinalization)
38+
.apply { setSignature(sign(owner)) }
39+
.build()
40+
41+
return TransactionService.StatelessSwapRequest.newBuilder()
42+
.setInitiate(initiate)
43+
.build()
44+
}
45+
46+
fun requestToSubmitSignatures(): TransactionService.StatelessSwapRequest {
47+
val params = parameters ?: throw IllegalStateException("parameters not set")
48+
49+
return TransactionService.StatelessSwapRequest.newBuilder()
50+
.setSubmitSignatures(
51+
TransactionService.StatelessSwapRequest.SubmitSignatures.newBuilder()
52+
.addAllTransactionSignatures(sign(params).map { it.asSignature() })
53+
.build()
54+
)
55+
.build()
56+
}
57+
}

‎services/opencode/src/main/kotlin/com/getcode/opencode/internal/network/api/intents/IntentSwap.kt‎

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,55 +10,55 @@ import com.getcode.opencode.internal.network.extensions.stablecoinParams
1010
import com.getcode.opencode.internal.network.extensions.verifiedMetadata
1111
import com.getcode.opencode.model.financial.Token
1212
import com.getcode.opencode.model.financial.usdf
13-
import com.getcode.opencode.model.transactions.SwapRequest
14-
import com.getcode.opencode.model.transactions.SwapResponseServerParameters
13+
import com.getcode.opencode.model.transactions.StatefulSwapRequest
14+
import com.getcode.opencode.model.transactions.StatefulSwapResponseServerParameters
1515
import com.getcode.opencode.model.transactions.SwapStartKind
1616
import com.getcode.opencode.model.transactions.VerifiedSwapMetadata
1717
import com.getcode.opencode.solana.SolanaTransaction
1818
import com.getcode.opencode.solana.TransactionBuilder
1919
import com.getcode.solana.keys.Signature
2020

2121
internal class IntentSwap(
22-
val request: SwapRequest,
22+
val request: StatefulSwapRequest,
2323
val metadata: VerifiedSwapMetadata,
2424
){
25-
var parameters: SwapResponseServerParameters? = null
25+
var parameters: StatefulSwapResponseServerParameters? = null
2626

27-
fun sign(parameters: SwapResponseServerParameters): List<Signature> {
27+
fun sign(parameters: StatefulSwapResponseServerParameters): List<Signature> {
2828
val transaction = transaction(parameters)
2929
return when (parameters) {
30-
is SwapResponseServerParameters.ExistingCurrency -> {
30+
is StatefulSwapResponseServerParameters.ExistingCurrency -> {
3131
transaction.signatures(request.owner.authority.keyPair, request.swapAuthority)
3232
}
33-
is SwapResponseServerParameters.NewCurrency -> {
33+
is StatefulSwapResponseServerParameters.NewCurrency -> {
3434
// For new currency, owner == swapAuthority, so only 1 unique signature needed
3535
transaction.signatures(request.owner.authority.keyPair)
3636
}
3737

38-
is SwapResponseServerParameters.Stablecoin -> {
38+
is StatefulSwapResponseServerParameters.Stablecoin -> {
3939
transaction.signatures(request.owner.authority.keyPair, request.swapAuthority)
4040
}
4141
}
4242
}
4343

44-
fun transaction(parameters: SwapResponseServerParameters): SolanaTransaction {
44+
fun transaction(parameters: StatefulSwapResponseServerParameters): SolanaTransaction {
4545
return when (parameters) {
46-
is SwapResponseServerParameters.ExistingCurrency -> TransactionBuilder.swap(
46+
is StatefulSwapResponseServerParameters.ExistingCurrency -> TransactionBuilder.swap(
4747
response = parameters,
4848
authority = request.owner.authorityPublicKey,
4949
swapAuthority = request.swapAuthority.toPublicKey(),
5050
direction = request.direction,
5151
amount = request.swapAmount.underlyingTokenAmount.quarks,
5252
)
53-
is SwapResponseServerParameters.NewCurrency -> TransactionBuilder.buyNewCurrency(
53+
is StatefulSwapResponseServerParameters.NewCurrency -> TransactionBuilder.buyNewCurrency(
5454
response = parameters,
5555
authority = request.owner.authorityPublicKey,
5656
coreMintMetadata = Token.usdf,
5757
amount = request.swapAmount.underlyingTokenAmount.quarks,
5858
feeAmount = request.feeAmount?.underlyingTokenAmount?.quarks,
5959
)
6060

61-
is SwapResponseServerParameters.Stablecoin -> TransactionBuilder.stablecoinSwap(
61+
is StatefulSwapResponseServerParameters.Stablecoin -> TransactionBuilder.stablecoinSwap(
6262
response = parameters,
6363
authority = request.owner.authorityPublicKey,
6464
swapAuthority = request.swapAuthority.toPublicKey(),

‎services/opencode/src/main/kotlin/com/getcode/opencode/internal/network/executors/StatelessSwapExecutor.kt‎

Lines changed: 13 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,19 @@
11
package com.getcode.opencode.internal.network.executors
22

33
import com.codeinc.opencode.gen.transaction.v1.TransactionService
4-
import com.getcode.ed25519.Ed25519.KeyPair
54
import com.getcode.opencode.internal.bidi.BidirectionalStreamReference
65
import com.getcode.opencode.internal.bidi.openBidirectionalStreamForResult
76
import com.getcode.opencode.internal.network.api.TransactionApi
8-
import com.getcode.opencode.internal.network.extensions.asSignature
9-
import com.getcode.opencode.internal.network.extensions.asSolanaAccountId
10-
import com.getcode.opencode.internal.network.extensions.sign
7+
import com.getcode.opencode.internal.network.api.intents.IntentStatelessSwap
118
import com.getcode.opencode.internal.network.extensions.toCode
129
import com.getcode.opencode.internal.network.extensions.toProps
1310
import com.getcode.opencode.internal.network.extensions.toSignature
14-
import com.getcode.opencode.model.core.errors.SwapError
1511
import com.getcode.opencode.model.core.errors.SubmitIntentError
12+
import com.getcode.opencode.model.core.errors.SwapError
13+
import com.getcode.opencode.model.transactions.StatelessSwapRequest
1614
import com.getcode.opencode.model.transactions.StatelessSwapResult
17-
import com.getcode.opencode.model.transactions.StatelessSwapServerParameters
1815
import com.getcode.opencode.model.transactions.StatelessSwapSuccess
19-
import com.getcode.opencode.solana.SolanaTransaction
2016
import com.getcode.services.opencode.BuildConfig
21-
import com.getcode.solana.keys.Mint
22-
import com.getcode.solana.keys.Signature
23-
import com.getcode.solana.keys.base58
2417
import com.getcode.utils.TraceType
2518
import com.getcode.utils.trace
2619
import kotlinx.coroutines.CoroutineScope
@@ -40,29 +33,18 @@ internal class StatelessSwapExecutor(
4033

4134
suspend fun execute(
4235
scope: CoroutineScope,
43-
owner: KeyPair,
44-
fromMint: Mint,
45-
toMint: Mint,
46-
amount: Long,
47-
waitForFinalization: Boolean = false,
48-
buildAndSign: (StatelessSwapServerParameters) -> Pair<SolanaTransaction, List<Signature>>,
36+
request: StatelessSwapRequest,
4937
): StatelessSwapResult = suspendCancellableCoroutine { cont ->
5038
trace(tag = TAG, message = "Opening stateless swap stream")
5139

5240
val streamReference = StatelessSwapStreamReference(scope, "stateless-swap")
5341
streamReference.retain()
5442

43+
val intent = IntentStatelessSwap(request = request)
44+
5545
scope.launch {
5646
try {
57-
val result = openStream(
58-
streamRef = streamReference,
59-
owner = owner,
60-
fromMint = fromMint,
61-
toMint = toMint,
62-
amount = amount,
63-
waitForFinalization = waitForFinalization,
64-
buildAndSign = buildAndSign,
65-
)
47+
val result = openStream(streamReference, intent)
6648
cont.resume(result)
6749
} catch (e: Exception) {
6850
trace(tag = TAG, message = "Failed to open stateless swap stream.", error = e)
@@ -89,24 +71,17 @@ internal class StatelessSwapExecutor(
8971

9072
private suspend fun openStream(
9173
streamRef: StatelessSwapStreamReference,
92-
owner: KeyPair,
93-
fromMint: Mint,
94-
toMint: Mint,
95-
amount: Long,
96-
waitForFinalization: Boolean,
97-
buildAndSign: (StatelessSwapServerParameters) -> Pair<SolanaTransaction, List<Signature>>,
74+
intent: IntentStatelessSwap,
9875
): StatelessSwapResult = openBidirectionalStreamForResult(
9976
streamRef = streamRef,
10077
apiCall = api::statelessSwap,
101-
initialRequest = {
102-
buildInitiateRequest(owner, fromMint, toMint, amount, waitForFinalization)
103-
},
78+
initialRequest = { intent.initiate() },
10479
responseHandler = { response, onResult, requestChannel ->
10580
when (response.responseCase) {
10681
TransactionService.StatelessSwapResponse.ResponseCase.SERVER_PARAMETERS -> {
10782
handleServerParameters(
83+
intent = intent,
10884
serverParameters = response.serverParameters,
109-
buildAndSign = buildAndSign,
11085
onResult = onResult,
11186
requestChannel = requestChannel,
11287
)
@@ -142,34 +117,9 @@ internal class StatelessSwapExecutor(
142117
)
143118
}
144119

145-
private fun buildInitiateRequest(
146-
owner: KeyPair,
147-
fromMint: Mint,
148-
toMint: Mint,
149-
amount: Long,
150-
waitForFinalization: Boolean,
151-
): TransactionService.StatelessSwapRequest {
152-
val initiate = TransactionService.StatelessSwapRequest.Initiate.newBuilder()
153-
.setOwner(owner.asSolanaAccountId())
154-
.setStablecoin(
155-
TransactionService.StatelessSwapRequest.Initiate.CoinbaseStableSwapperClientParameters.newBuilder()
156-
.setFromMint(fromMint.asSolanaAccountId())
157-
.setToMint(toMint.asSolanaAccountId())
158-
.setSwapAmount(amount)
159-
.build()
160-
)
161-
.setWaitForFinalization(waitForFinalization)
162-
.apply { setSignature(sign(owner)) }
163-
.build()
164-
165-
return TransactionService.StatelessSwapRequest.newBuilder()
166-
.setInitiate(initiate)
167-
.build()
168-
}
169-
170120
private fun handleServerParameters(
121+
intent: IntentStatelessSwap,
171122
serverParameters: TransactionService.StatelessSwapResponse.ServerParameters?,
172-
buildAndSign: (StatelessSwapServerParameters) -> Pair<SolanaTransaction, List<Signature>>,
173123
requestChannel: (TransactionService.StatelessSwapRequest) -> Unit,
174124
onResult: (StatelessSwapResult) -> Unit,
175125
) {
@@ -188,17 +138,8 @@ private fun handleServerParameters(
188138

189139
trace(tag = TAG, message = "Received server parameters. Building transaction and submitting signatures...")
190140

191-
val (_, signatures) = buildAndSign(params)
192-
193-
val submitSignatures = TransactionService.StatelessSwapRequest.SubmitSignatures.newBuilder()
194-
.addAllTransactionSignatures(signatures.map { it.asSignature() })
195-
.build()
196-
197-
requestChannel(
198-
TransactionService.StatelessSwapRequest.newBuilder()
199-
.setSubmitSignatures(submitSignatures)
200-
.build()
201-
)
141+
intent.parameters = params
142+
requestChannel(intent.requestToSubmitSignatures())
202143
} catch (e: Exception) {
203144
if (BuildConfig.DEBUG) e.printStackTrace()
204145
onResult(Result.failure(SwapError.Other(cause = e)))

‎services/opencode/src/main/kotlin/com/getcode/opencode/internal/network/executors/SwapExecutor.kt‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import com.getcode.opencode.internal.network.extensions.toCode
99
import com.getcode.opencode.internal.network.extensions.toProps
1010
import com.getcode.opencode.model.core.errors.SubmitIntentError
1111
import com.getcode.opencode.model.core.errors.SwapError
12-
import com.getcode.opencode.model.transactions.SwapRequest
12+
import com.getcode.opencode.model.transactions.StatefulSwapRequest
1313
import com.getcode.opencode.model.transactions.SwapResult
1414
import com.getcode.opencode.model.transactions.SwapStartKind
1515
import com.getcode.opencode.model.transactions.VerifiedSwapMetadata
@@ -27,7 +27,7 @@ import kotlinx.coroutines.sync.Mutex
2727
import kotlinx.coroutines.sync.withLock
2828
import kotlin.coroutines.resume
2929

30-
typealias OcpSwapStreamReference = BidirectionalStreamReference<SwapRequest, TransactionService.StatefulSwapResponse>
30+
typealias OcpSwapStreamReference = BidirectionalStreamReference<StatefulSwapRequest, TransactionService.StatefulSwapResponse>
3131

3232

3333
/**
@@ -49,7 +49,7 @@ internal class SwapExecutor(
4949

5050
suspend fun execute(
5151
scope: CoroutineScope,
52-
request: SwapRequest,
52+
request: StatefulSwapRequest,
5353
): SwapResult = suspendCancellableCoroutine { cont ->
5454
trace(
5555
tag = "Swap",

0 commit comments

Comments
 (0)