Skip to content

Commit 72b4782

Browse files
authored
feat(oc): add directTransfer for peer-to-peer sends (#822)
New `directTransfer()` suspend function on `TransactionController` and new `IntentTransfer.create()` overload accepting `VerifiedState` + `destinationOwner`. Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 252326a commit 72b4782

3 files changed

Lines changed: 79 additions & 3 deletions

File tree

services/opencode/src/main/kotlin/com/getcode/opencode/ControllerFactory.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ object ControllerFactory {
3535
repository = RepositoryFactory.createTransactionRepository(context, config),
3636
swapRepository = RepositoryFactory.createSwapRepository(context, config),
3737
accountController = createAccountController(context, config),
38-
eventBus = module.providesEventBus(),
3938
)
4039
}
4140
fun createCurrencyController(context: Context, config: ProtocolConfig): CurrencyController {

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

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.getcode.opencode.internal.network.api.intents.IntentDistribution
77
import com.getcode.opencode.internal.network.api.intents.IntentRemoteReceive
88
import com.getcode.opencode.internal.network.api.intents.IntentRemoteSend
99
import com.getcode.opencode.internal.network.api.intents.IntentTransfer
10+
import com.getcode.opencode.internal.solana.extensions.newInstance
1011
import com.getcode.opencode.internal.network.api.intents.IntentWithdraw
1112
import com.getcode.opencode.internal.solana.model.SwapId
1213
import com.getcode.opencode.model.accounts.AccountCluster
@@ -20,6 +21,7 @@ import com.getcode.opencode.model.financial.Fiat
2021
import com.getcode.opencode.model.financial.Limits
2122
import com.getcode.opencode.model.financial.LocalFiat
2223
import com.getcode.opencode.model.financial.Token
24+
import com.getcode.opencode.solana.keys.TimelockDerivedAccounts
2325
import com.getcode.opencode.model.transactions.ExchangeData
2426
import com.getcode.opencode.model.transactions.SwapFundingSource
2527
import com.getcode.opencode.model.transactions.SwapMetadata
@@ -38,7 +40,6 @@ import com.getcode.utils.TraceType
3840
import com.getcode.utils.base64
3941
import com.getcode.utils.trace
4042
import com.getcode.vendor.Base58
41-
import com.hoc081098.channeleventbus.ChannelEventBus
4243
import kotlinx.coroutines.CoroutineScope
4344
import kotlinx.coroutines.Dispatchers
4445
import kotlinx.coroutines.SupervisorJob
@@ -63,7 +64,6 @@ class TransactionController @Inject constructor(
6364
private val repository: TransactionRepository,
6465
private val swapRepository: SwapRepository,
6566
private val accountController: AccountController,
66-
private val eventBus: ChannelEventBus,
6767
) : TransactionOperations {
6868
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
6969

@@ -122,6 +122,35 @@ class TransactionController @Inject constructor(
122122
return submitIntent(scope, intent, source.authority.keyPair)
123123
}
124124

125+
/**
126+
* Sends funds directly to another user's vault by resolving their
127+
* [destinationOwner] into a timelock-derived vault address.
128+
*/
129+
suspend fun directTransfer(
130+
amount: VerifiedFiat,
131+
token: Token,
132+
source: AccountCluster,
133+
destinationOwner: PublicKey,
134+
scope: CoroutineScope = this.scope,
135+
): Result<IntentType> {
136+
val verifiedState = amount.verifiedState
137+
?: return Result.failure(IllegalStateException("No verified state"))
138+
139+
val timelock = TimelockDerivedAccounts.newInstance(owner = destinationOwner, token = token)
140+
val destinationVault = timelock.vault.publicKey
141+
142+
val intent = IntentTransfer.create(
143+
amount = amount.localFiat,
144+
mint = token.address,
145+
sourceCluster = source,
146+
destination = destinationVault,
147+
destinationOwner = destinationOwner,
148+
verifiedState = verifiedState,
149+
)
150+
151+
return submitIntent(scope, intent, source.authority.keyPair)
152+
}
153+
125154
override suspend fun withdraw(
126155
amount: VerifiedFiat,
127156
mint: Mint,

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.getcode.opencode.internal.network.api.intents
22

33
import com.codeinc.opencode.gen.transaction.v1.TransactionService
4+
import com.getcode.opencode.internal.manager.VerifiedState
45
import com.getcode.opencode.internal.network.api.intents.actions.ActionPublicTransfer
56
import com.getcode.opencode.model.accounts.AccountCluster
67
import com.getcode.opencode.internal.network.extensions.asProtobufMetadata
@@ -9,9 +10,20 @@ import com.getcode.opencode.model.transactions.ExchangeData
910
import com.getcode.opencode.model.transactions.TransactionMetadata
1011
import com.getcode.opencode.solana.intents.ActionGroup
1112
import com.getcode.opencode.solana.intents.IntentType
13+
import com.getcode.opencode.utils.generate
1214
import com.getcode.solana.keys.Mint
1315
import com.getcode.solana.keys.PublicKey
1416

17+
/**
18+
* A SendPublicPayment intent for transferring funds to another account.
19+
*
20+
* Two creation paths are supported:
21+
* - **Rendezvous-based** ([create] with [ExchangeData.Verified]): used for
22+
* peer-to-peer bill gives where the rendezvous key serves as the intent id.
23+
* - **Direct** ([create] with [VerifiedState]): used for direct sends to a
24+
* known recipient (e.g. Flipcash contact). A random intent id is generated
25+
* and the [destinationOwner] is included in the metadata.
26+
*/
1527
internal class IntentTransfer(
1628
override val id: PublicKey,
1729
override val metadata: TransactionMetadata,
@@ -22,6 +34,7 @@ internal class IntentTransfer(
2234
}
2335

2436
companion object {
37+
/** Creates a rendezvous-based transfer for peer-to-peer bill gives. */
2538
fun create(
2639
amount: LocalFiat,
2740
mint: Mint,
@@ -54,5 +67,40 @@ internal class IntentTransfer(
5467
}
5568
)
5669
}
70+
71+
/** Creates a direct transfer to a known recipient's vault. */
72+
fun create(
73+
amount: LocalFiat,
74+
mint: Mint,
75+
sourceCluster: AccountCluster,
76+
destination: PublicKey,
77+
destinationOwner: PublicKey,
78+
verifiedState: VerifiedState,
79+
): IntentTransfer {
80+
val transfer = ActionPublicTransfer.newInstance(
81+
owner = sourceCluster.authority.keyPair,
82+
source = sourceCluster.vaultPublicKey,
83+
destination = destination,
84+
amount = amount.underlyingTokenAmount,
85+
mint = mint,
86+
)
87+
88+
return IntentTransfer(
89+
id = PublicKey.generate(),
90+
metadata = TransactionMetadata.SendPublicPayment(
91+
source = sourceCluster.vaultPublicKey,
92+
destination = destination,
93+
destinationOwner = destinationOwner,
94+
amount = amount,
95+
verifiedState = verifiedState,
96+
mint = mint,
97+
isRemoteSend = false,
98+
isWithdrawal = false,
99+
),
100+
actionGroup = ActionGroup().apply {
101+
actions = listOf(transfer)
102+
},
103+
)
104+
}
57105
}
58106
}

0 commit comments

Comments
 (0)