Skip to content

Commit 0538464

Browse files
authored
Initial migration to KFSM v2 (#104)
* Initial migration to KFSM v2 * Address review comments
1 parent 411c471 commit 0538464

File tree

74 files changed

+1828
-1251
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+1828
-1251
lines changed

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ plugins {
99
alias(libs.plugins.versionsGradlePlugin)
1010
alias(libs.plugins.versionCatalogUpdateGradlePlugin)
1111
alias(libs.plugins.kotlinBinaryCompatibilityPlugin) apply false
12-
id("com.vanniktech.maven.publish") version "0.36.0" apply false
12+
alias(libs.plugins.mavenPublish) apply false
1313
}
1414

1515
subprojects {

outie/src/main/kotlin/xyz/block/bittycity/outie/client/BitcoinAccountClient.kt renamed to common/src/main/kotlin/xyz/block/bittycity/common/client/BitcoinAccountClient.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
package xyz.block.bittycity.outie.client
1+
package xyz.block.bittycity.common.client
22

3-
import xyz.block.bittycity.outie.models.BitcoinAccount
3+
import xyz.block.bittycity.common.models.BitcoinAccount
44
import xyz.block.bittycity.common.models.CustomerId
55

66
interface BitcoinAccountClient {

outie/src/main/kotlin/xyz/block/bittycity/outie/models/BalanceId.kt renamed to common/src/main/kotlin/xyz/block/bittycity/common/models/BalanceId.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package xyz.block.bittycity.outie.models
1+
package xyz.block.bittycity.common.models
22

33
data class BalanceId(val id: String) {
44
override fun toString() = id
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package xyz.block.bittycity.common.models
2+
3+
data class BitcoinAccount(
4+
val customerId: CustomerId,
5+
val balanceId: BalanceId,
6+
val currencyDisplayPreference: CurrencyDisplayPreference
7+
)

gradle/libs.versions.toml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
[versions]
22
arrow = "2.2.1"
33
bitcoinj = "0.17"
4-
domainApi = "0.4.0"
4+
domainApi = "0.5.3"
5+
domainApiKfsm = "0.5.3"
6+
domainApiKfsmv2 = "0.1.0"
57
guice = "7.0.0"
68
jodaMoney = "1.0.7"
79
kfsm = "0.12.0"
10+
kfsmv2 = "2.0.0"
811
kotest = "5.9.1"
12+
kotlinxCoroutines = "1.10.2"
913
kotestArrow = "2.0.0"
1014
# @pin
1115
kotlin = "2.2.21"
1216
kotlinBinaryCompatibilityPlugin = "0.18.1"
1317
kotlinLogging = "7.0.14"
14-
mavenPublish = "0.36.0"
18+
mavenPublish = "0.35.0"
1519
mockk = "1.14.7"
1620
moshi = "1.15.2"
1721
quiver = "1.0.3"
@@ -24,18 +28,22 @@ versionsGradlePlugin = "0.53.0"
2428
arrowCore = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" }
2529
bitcoinj = { module = "org.bitcoinj:bitcoinj-core", version.ref = "bitcoinj" }
2630
domainApi = { module = "xyz.block.domainapi:domain-api", version.ref = "domainApi" }
31+
domainApiKfsm = { module = "xyz.block.domainapi:domain-api-kfsm", version.ref = "domainApiKfsm" }
32+
domainApiKfsmv2 = { module = "xyz.block.domainapi:domain-api-kfsm-v2", version.ref = "domainApiKfsmv2" }
2733
guice = { module = "com.google.inject:guice", version.ref = "guice" }
2834
guiceAssistedInject = { module = "com.google.inject.extensions:guice-assistedinject", version.ref = "guice" }
2935
jodaMoney = { module = "org.joda:joda-money", version.ref = "jodaMoney" }
3036
junitJupiter = { module = "org.junit.jupiter:junit-jupiter", version = "5.14.2" }
3137
kfsm = { module = "app.cash.kfsm:kfsm", version.ref = "kfsm" }
38+
kfsmv2 = { module = "app.cash.kfsm:kfsm-v2", version.ref = "kfsmv2" }
3239
kotestAssertions = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }
3340
kotestAssertionsArrow = { module = "io.kotest.extensions:kotest-assertions-arrow", version.ref = "kotestArrow" }
3441
kotestAssertionsArrowJvm = { module = "io.kotest.extensions:kotest-assertions-arrow-jvm", version.ref = "kotestArrow" }
3542
kotestJunitRunnerJvm = { module = "io.kotest:kotest-runner-junit5-jvm", version.ref = "kotest" }
3643
kotestProperty = { module = "io.kotest:kotest-property", version.ref = "kotest" }
3744
kotlinLogging = { module = "io.github.oshai:kotlin-logging", version.ref = "kotlinLogging" }
3845
kotlinReflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
46+
kotlinxCoroutinesCore = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
3947
mavenPublishGradlePlugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "mavenPublish" }
4048
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
4149
moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" }

innie/build.gradle.kts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ dependencies {
2424
implementation(libs.arrowCore)
2525
implementation(libs.bitcoinj)
2626
implementation(libs.domainApi)
27+
implementation(libs.domainApiKfsmv2)
2728
implementation(libs.guice)
2829
implementation(libs.guiceAssistedInject)
2930
implementation(libs.jodaMoney)
30-
implementation(libs.kfsm)
31+
implementation(libs.kfsmv2)
3132
implementation(libs.kotlinLogging)
3233
implementation(libs.kotlinReflect)
3334
implementation(libs.moshi)
@@ -49,7 +50,7 @@ tasks.register<JavaExec>("generateStateMachineDiagram") {
4950
description = "Generate state machine diagram in Mermaid format"
5051

5152
classpath = sourceSets.test.get().runtimeClasspath
52-
mainClass.set("xyz.block.bittycity.innie.fsm.StateMachineDiagramGeneratorKt")
53+
mainClass.set("xyz.block.bittycity.innie.fsm.DepositDiagramGeneratorKt")
5354

5455
val outputFile = project.file("docs/state-machine.md")
5556
args(outputFile.absolutePath)
@@ -58,7 +59,6 @@ tasks.register<JavaExec>("generateStateMachineDiagram") {
5859
inputs.files(fileTree("src/main/kotlin/xyz/block/bittycity/innie/fsm") {
5960
include("**/*.kt")
6061
})
61-
inputs.file("src/test/kotlin/xyz/block/bittycity/innie/fsm/StateMachineDiagramGenerator.kt")
6262

6363
// Declare output for incremental builds
6464
outputs.file(outputFile)

innie/docs/state-machine.md

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,32 @@
11
```mermaid
22
stateDiagram-v2
3-
[*] --> WaitingForDepositConfirmedOnChainStatus
4-
CheckingDepositRisk --> Settled
5-
CheckingDepositRisk --> WaitingForReversal
6-
CheckingEligibility --> CheckingDepositRisk
7-
CheckingEligibility --> WaitingForReversal
8-
CheckingReversalRisk --> WaitingForReversal
9-
CheckingReversalRisk --> WaitingForReversalPendingConfirmationStatus
10-
CheckingSanctions --> CheckingReversalRisk
11-
CheckingSanctions --> CollectingSanctionsInfo
12-
CheckingSanctions --> WaitingForReversal
13-
CollectingInfo --> CheckingSanctions
14-
CollectingInfo --> WaitingForReversal
15-
CollectingSanctionsInfo --> Sanctioned
16-
CollectingSanctionsInfo --> WaitingForReversal
17-
CollectingSanctionsInfo --> WaitingForSanctionsHeldDecision
18-
ExpiredPending --> CheckingEligibility
19-
ExpiredPending --> Voided
20-
WaitingForDepositConfirmedOnChainStatus --> CheckingEligibility
21-
WaitingForDepositConfirmedOnChainStatus --> ExpiredPending
22-
WaitingForDepositConfirmedOnChainStatus --> Voided
23-
WaitingForReversal --> CollectingInfo
24-
WaitingForReversalConfirmedOnChainStatus --> ReversalConfirmedComplete
25-
WaitingForReversalConfirmedOnChainStatus --> WaitingForReversal
26-
WaitingForReversalPendingConfirmationStatus --> WaitingForReversal
27-
WaitingForReversalPendingConfirmationStatus --> WaitingForReversalConfirmedOnChainStatus
28-
WaitingForSanctionsHeldDecision --> Sanctioned
29-
WaitingForSanctionsHeldDecision --> WaitingForReversal
30-
WaitingForSanctionsHeldDecision --> WaitingForReversalPendingConfirmationStatus
3+
[*] --> New
4+
CheckingDepositEligibility --> CheckingDepositRisk
5+
CheckingDepositEligibility --> CollectingReversalInfo
6+
CheckingDepositRisk --> CollectingReversalInfo
7+
CheckingDepositRisk --> DepositSettled
8+
CheckingReversalRisk --> CollectingReversalInfo
9+
CheckingReversalRisk --> WaitingForReversalPendingConfirmationStatus
10+
CheckingReversalSanctions --> CheckingReversalRisk
11+
CheckingReversalSanctions --> CollectingReversalInfo
12+
CheckingReversalSanctions --> CollectingReversalSanctionsInfo
13+
CollectingReversalInfo --> CheckingReversalSanctions
14+
CollectingReversalSanctionsInfo --> CollectingReversalInfo
15+
CollectingReversalSanctionsInfo --> ReversalSanctioned
16+
CollectingReversalSanctionsInfo --> WaitingForReversalPendingConfirmationStatus
17+
CollectingReversalSanctionsInfo --> WaitingForReversalSanctionsHeldDecision
18+
CreatingDepositTransaction --> WaitingForDepositConfirmedOnChainStatus
19+
DepositExpiredPending --> CheckingDepositEligibility
20+
DepositExpiredPending --> DepositVoided
21+
New --> CreatingDepositTransaction
22+
WaitingForDepositConfirmedOnChainStatus --> CheckingDepositEligibility
23+
WaitingForDepositConfirmedOnChainStatus --> DepositExpiredPending
24+
WaitingForDepositConfirmedOnChainStatus --> DepositVoided
25+
WaitingForReversalConfirmedOnChainStatus --> CollectingReversalInfo
26+
WaitingForReversalConfirmedOnChainStatus --> ReversalConfirmedComplete
27+
WaitingForReversalPendingConfirmationStatus --> CollectingReversalInfo
28+
WaitingForReversalPendingConfirmationStatus --> WaitingForReversalConfirmedOnChainStatus
29+
WaitingForReversalSanctionsHeldDecision --> CheckingReversalRisk
30+
WaitingForReversalSanctionsHeldDecision --> CollectingReversalInfo
31+
WaitingForReversalSanctionsHeldDecision --> ReversalSanctioned
3132
```

innie/src/main/kotlin/xyz/block/bittycity/innie/InnieModule.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@ package xyz.block.bittycity.innie
33
import com.google.inject.AbstractModule
44
import xyz.block.bittycity.common.models.Bitcoins
55
import xyz.block.bittycity.innie.controllers.DomainControllerModule
6-
import xyz.block.bittycity.innie.fsm.StateMachineModule
76

87
open class InnieModule : AbstractModule() {
98

109
override fun configure() {
1110
installValidationModule()
12-
install(StateMachineModule)
1311
install(DomainControllerModule)
1412
Bitcoins.currency
1513
}

innie/src/main/kotlin/xyz/block/bittycity/innie/api/OnChainDepositDomainApi.kt

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,21 @@ import app.cash.quiver.extensions.success
55
import arrow.core.getOrElse
66
import arrow.core.raise.result
77
import jakarta.inject.Inject
8+
import java.util.UUID
89
import org.bitcoinj.base.Address
910
import org.joda.money.CurrencyUnit
11+
import xyz.block.bittycity.common.client.BitcoinAccountClient
1012
import xyz.block.bittycity.common.client.ExchangeRateClient
13+
import xyz.block.bittycity.common.models.BalanceId
1114
import xyz.block.bittycity.common.models.Bitcoins
15+
import xyz.block.bittycity.common.models.CustomerId
1216
import xyz.block.bittycity.common.store.Transactor
1317
import xyz.block.bittycity.innie.client.WalletClient
1418
import xyz.block.bittycity.innie.controllers.DomainController
1519
import xyz.block.bittycity.innie.controllers.IdempotencyHandler
1620
import xyz.block.bittycity.innie.models.Deposit
1721
import xyz.block.bittycity.innie.models.DepositState
18-
import xyz.block.bittycity.innie.models.CheckingEligibility
22+
import xyz.block.bittycity.innie.models.CheckingDepositEligibility
1923
import xyz.block.bittycity.innie.models.DepositToken
2024
import xyz.block.bittycity.innie.models.RequirementId
2125
import xyz.block.bittycity.innie.models.WaitingForDepositConfirmedOnChainStatus
@@ -28,15 +32,15 @@ import xyz.block.domainapi.ProcessInfo
2832
import xyz.block.domainapi.SearchParameter
2933
import xyz.block.domainapi.SearchResult
3034
import xyz.block.domainapi.UpdateResponse
31-
import xyz.block.domainapi.util.Operation
32-
import java.util.UUID
35+
import xyz.block.domainapi.kfsm.v2.util.Operation
3336

3437
typealias DepositDomainController =
3538
DomainController<DepositToken, DepositState, Deposit, RequirementId>
3639

3740
class OnChainDepositDomainApi @Inject constructor(
3841
private val transactor: Transactor<DepositOperations>,
3942
private val domainController: DepositDomainController,
43+
private val bitcoinAccountClient: BitcoinAccountClient,
4044
private val exchangeRateClient: ExchangeRateClient,
4145
private val walletClient: WalletClient,
4246
private val idempotencyHandler: IdempotencyHandler
@@ -64,7 +68,8 @@ class OnChainDepositDomainApi @Inject constructor(
6468
targetWalletAddress = initialRequest.targetWalletAddress,
6569
blockchainTransactionId = initialRequest.blockchainTransactionId,
6670
blockchainTransactionOutputIndex = initialRequest.blockchainTransactionOutputIndex,
67-
paymentToken = initialRequest.paymentToken
71+
paymentToken = initialRequest.paymentToken,
72+
targetBalanceToken = getSourceBalanceToken(customerId).bind()
6873
)
6974
)
7075
}.bind()
@@ -151,10 +156,19 @@ class OnChainDepositDomainApi @Inject constructor(
151156
limit: Int
152157
): Result<SearchResult<Unit, Deposit>> = UnsupportedOperationException().failure()
153158

159+
private fun getSourceBalanceToken(
160+
customerId: CustomerId
161+
): Result<BalanceId> = result {
162+
val customerSourceBalanceTokens = bitcoinAccountClient.getBitcoinAccounts(customerId).bind()
163+
customerSourceBalanceTokens.firstOrNull()?.balanceId ?: raise(
164+
IllegalStateException("No default balance token found")
165+
)
166+
}
167+
154168
companion object {
155169
fun mapPaymentState(state: PaymentState): DepositState = when (state) {
156170
PaymentState.AWAITING_CONFIRMATION -> WaitingForDepositConfirmedOnChainStatus
157-
PaymentState.COMPLETED_CONFIRMED -> CheckingEligibility
171+
PaymentState.COMPLETED_CONFIRMED -> CheckingDepositEligibility
158172
}
159173
}
160174
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package xyz.block.bittycity.innie.client
2+
3+
import org.bitcoinj.base.Address
4+
import org.joda.money.Money
5+
import xyz.block.bittycity.common.models.BalanceId
6+
import xyz.block.bittycity.common.models.Bitcoins
7+
import xyz.block.bittycity.common.models.CustomerId
8+
import xyz.block.bittycity.common.models.LedgerTransactionId
9+
import xyz.block.bittycity.innie.models.DepositReversalToken
10+
import xyz.block.bittycity.innie.models.DepositToken
11+
import java.time.Instant
12+
13+
interface DepositLedgerClient {
14+
/**
15+
* Confirms a ledger transaction after the blockchain confirms a deposit.
16+
*
17+
* @param customerId The ID of the user receiving the deposit.
18+
* @param balanceId The ID representing the user's stored balance.
19+
* @param ledgerTransactionId The ID identifying the ledger transaction.
20+
* @return a [Result] indicating whether the confirmation was successful.
21+
*/
22+
fun confirmDepositTransaction(
23+
customerId: CustomerId,
24+
balanceId: BalanceId,
25+
ledgerTransactionId: LedgerTransactionId
26+
): Result<Unit>
27+
28+
/**
29+
* Confirms a ledger transaction after the blockchain confirms a deposit reversal.
30+
*
31+
* @param customerId The ID of the user initiating the reversal.
32+
* @param balanceId The ID representing the user's stored balance.
33+
* @param ledgerTransactionId The ID identifying the ledger transaction.
34+
* @return a [Result] indicating whether the confirmation was successful.
35+
*/
36+
fun confirmReversalTransaction(
37+
customerId: CustomerId,
38+
balanceId: BalanceId,
39+
ledgerTransactionId: LedgerTransactionId
40+
): Result<Unit>
41+
42+
/**
43+
* Creates a ledger transaction for an on-chain deposit.
44+
*
45+
* @param depositId The ID of the deposit.
46+
* @param customerId The ID of the user receiving the deposit.
47+
* @param balanceId The ID representing the user's stored balance.
48+
* @param createdAt The date when the deposit was created.
49+
* @param amount The deposit amount in bitcoin.
50+
* @param fiatEquivalent The equivalent amount in fiat.
51+
* @return a [Result] containing the ledger [LedgerTransactionId] for the transaction if successful.
52+
*/
53+
fun createDepositTransaction(
54+
depositId: DepositToken,
55+
customerId: CustomerId,
56+
balanceId: BalanceId,
57+
createdAt: Instant,
58+
amount: Bitcoins,
59+
fiatEquivalent: Money
60+
): Result<LedgerTransactionId>
61+
62+
/**
63+
* Voids a ledger transaction if the deposit fails on-chain.
64+
*
65+
* @param customerId The ID of the user receiving the deposit.
66+
* @param balanceId The ID representing the user's stored balance.
67+
* @param ledgerTransactionId The ID identifying the ledger transaction.
68+
* @return a [Result] indicating whether the voiding operation was successful.
69+
*/
70+
fun voidDepositTransaction(
71+
customerId: CustomerId,
72+
balanceId: BalanceId,
73+
ledgerTransactionId: LedgerTransactionId
74+
): Result<Unit>
75+
76+
/**
77+
* Freezes the funds in the reversal. This typically involves debiting the deposit funds that were previously reserved
78+
* and crediting them to a sanctions account.
79+
*
80+
* @param depositReversalId The ID of the reversal.
81+
* @param customerId The ID of the user receiving the deposit.
82+
* @param balanceId The ID representing the user's stored balance.
83+
* @param createdAt The date when the deposit was created.
84+
* @param amount The deposit amount in bitcoin.
85+
* @param fiatEquivalent The equivalent amount in fiat.
86+
* @param targetWalletAddress The target wallet address of the reversal.
87+
* @return a [Result] indicating whether the funds were frozen successfully.
88+
*/
89+
fun freezeFunds(
90+
depositReversalId: DepositReversalToken,
91+
customerId: CustomerId,
92+
balanceId: BalanceId,
93+
createdAt: Instant,
94+
amount: Bitcoins,
95+
fiatEquivalent: Money,
96+
targetWalletAddress: Address
97+
): Result<Unit>
98+
}

0 commit comments

Comments
 (0)