diff --git a/.gitignore b/.gitignore
index a1c2a238a..4fde0c05c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,9 @@
# BlueJ files
*.ctxt
+# Idea folders
+.idea
+
# Mobile Tools for Java (J2ME)
.mtj.tmp/
diff --git a/Accountant/.gitignore b/Accountant/.gitignore
new file mode 100644
index 000000000..797b705f6
--- /dev/null
+++ b/Accountant/.gitignore
@@ -0,0 +1 @@
+*.iml
\ No newline at end of file
diff --git a/Accountant/accountant-app/.gitignore b/Accountant/accountant-app/.gitignore
new file mode 100644
index 000000000..e7c460346
--- /dev/null
+++ b/Accountant/accountant-app/.gitignore
@@ -0,0 +1,80 @@
+# Created by .ignore support plugin (hsz.mobi)
+### Kotlin template
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+.idea/artifacts
+.idea/compiler.xml
+.idea/jarRepositories.xml
+.idea/modules.xml
+.idea/*.iml
+.idea/modules
+*.iml
+*.ipr
+.mvn/
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+target/
+
+.DS_Store
+
+
+
+
diff --git a/Accountant/accountant-app/Dockerfile b/Accountant/accountant-app/Dockerfile
new file mode 100644
index 000000000..f2cbd4c26
--- /dev/null
+++ b/Accountant/accountant-app/Dockerfile
@@ -0,0 +1,5 @@
+FROM openjdk:8-jdk-alpine
+VOLUME /tmp
+ARG JAR_FILE=target/*.jar
+COPY ${JAR_FILE} app.jar
+ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"]
\ No newline at end of file
diff --git a/Accountant/accountant-app/pom.xml b/Accountant/accountant-app/pom.xml
new file mode 100644
index 000000000..ade675037
--- /dev/null
+++ b/Accountant/accountant-app/pom.xml
@@ -0,0 +1,200 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.4.4
+
+
+ co.nilin.opex
+ accountant-app
+ 1.0-SNAPSHOT
+ accountant-app
+ Accountant app Opex
+
+
+ 1.8
+ 1.4.31
+ ${version}
+ ${version}
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+
+
+ com.fasterxml.jackson.module
+ jackson-module-kotlin
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ co.nilin.opex
+ accountant-core
+ ${accountant.version}
+
+
+ co.nilin.opex
+ accountant-eventlistener-kafka
+ ${accountant.version}
+
+
+ co.nilin.opex
+ accountant-submitter-kafka
+ ${accountant.version}
+
+
+ co.nilin.opex
+ accountant-persister-postgres
+ ${accountant.version}
+
+
+ co.nilin.opex
+ accountant-wallet-proxy
+ ${accountant.version}
+
+
+ co.nilin.opex
+ error-handler
+ ${utility.version}
+
+
+ co.nilin.opex
+ logging-handler
+ ${utility.version}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+
+
+
+
+
+
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/test/kotlin
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.18
+
+
+ ${skip.unit.tests}
+
+
+ **/*IntegrationTest.java
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ add-test-source
+ generate-test-sources
+
+ add-test-source
+
+
+
+ src/test/java
+
+
+
+
+ compile
+
+ add-source
+
+
+
+ src/main/java
+
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+
+
+ -Xjsr305=strict
+
+
+ spring
+
+ 1.8
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-allopen
+ ${kotlin.version}
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ testCompile
+ test-compile
+
+ testCompile
+
+
+
+
+
+ opex-accountant
+
+
diff --git a/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/AccountantApp.kt b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/AccountantApp.kt
new file mode 100644
index 000000000..8b5c89f25
--- /dev/null
+++ b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/AccountantApp.kt
@@ -0,0 +1,15 @@
+package co.nilin.opex.app
+
+import co.nilin.opex.utility.error.EnableOpexErrorHandler
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.runApplication
+import org.springframework.context.annotation.ComponentScan
+
+@SpringBootApplication
+@ComponentScan("co.nilin.opex")
+@EnableOpexErrorHandler
+class AccountantApp
+
+fun main(args: Array) {
+ runApplication(*args)
+}
\ No newline at end of file
diff --git a/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/config/AppConfig.kt b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/config/AppConfig.kt
new file mode 100644
index 000000000..1ddca4bdd
--- /dev/null
+++ b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/config/AppConfig.kt
@@ -0,0 +1,238 @@
+package co.nilin.opex.app.config
+
+import co.nilin.opex.accountant.core.api.FinancialActionJobManager
+import co.nilin.opex.accountant.core.api.OrderManager
+import co.nilin.opex.accountant.core.api.TradeManager
+import co.nilin.opex.accountant.core.service.FinancialActionJobManagerImpl
+import co.nilin.opex.accountant.core.service.OrderManagerImpl
+import co.nilin.opex.accountant.core.service.TradeManagerImpl
+import co.nilin.opex.accountant.core.spi.*
+import co.nilin.opex.matching.core.eventh.events.*
+import co.nilin.opex.port.accountant.kafka.consumer.OrderKafkaListener
+import co.nilin.opex.port.accountant.kafka.spi.OrderSubmitRequestListener
+import co.nilin.opex.port.order.kafka.inout.OrderSubmitRequest
+import co.nilin.opex.port.accountant.kafka.consumer.EventKafkaListener
+import co.nilin.opex.port.accountant.kafka.consumer.TempEventKafkaListener
+import co.nilin.opex.port.accountant.kafka.consumer.TradeKafkaListener
+import co.nilin.opex.port.accountant.kafka.spi.EventListener
+import co.nilin.opex.port.accountant.kafka.spi.TempEventListener
+import co.nilin.opex.port.accountant.kafka.spi.TradeListener
+import kotlinx.coroutines.runBlocking
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.scheduling.annotation.EnableScheduling
+
+@Configuration
+@EnableScheduling
+class AppConfig {
+
+ @Bean
+ fun getFinancialActionJobManager(
+ financialActionLoader: FinancialActionLoader,
+ financialActionPersister: FinancialActionPersister,
+ walletProxy: WalletProxy
+ ): FinancialActionJobManager {
+ return FinancialActionJobManagerImpl(
+ financialActionLoader,
+ financialActionPersister,
+ walletProxy
+ )
+ }
+
+ @Bean
+ fun orderManager(
+ pairConfigLoader: PairConfigLoader,
+ financialActionPersister: FinancialActionPersister,
+ financeActionLoader: FinancialActionLoader,
+ orderPersister: OrderPersister,
+ tempEventPersister: TempEventPersister,
+ tempEventRepublisher: TempEventRepublisher,
+ richOrderPublisher: RichOrderPublisher
+ ): OrderManager {
+ return OrderManagerImpl(
+ pairConfigLoader,
+ financialActionPersister,
+ financeActionLoader,
+ orderPersister,
+ tempEventPersister,
+ tempEventRepublisher,
+ richOrderPublisher
+ )
+ }
+
+ @Bean
+ fun tradeManager(
+ pairStaticRateLoader: PairStaticRateLoader,
+ financeActionPersister: FinancialActionPersister,
+ financeActionLoader: FinancialActionLoader,
+ orderPersister: OrderPersister,
+ tempEventPersister: TempEventPersister,
+ richTradePublisher: RichTradePublisher,
+ walletProxy: WalletProxy,
+ @Value("\${app.coin}") platformCoin: String,
+ @Value("\${app.address}") platformAddress: String
+ ): TradeManager {
+ return TradeManagerImpl(
+ pairStaticRateLoader,
+ financeActionPersister,
+ financeActionLoader,
+ orderPersister,
+ tempEventPersister,
+ richTradePublisher,
+ walletProxy,
+ platformCoin,
+ platformAddress
+ )
+ }
+
+ @Bean
+ fun orderListener(orderManager: OrderManager): OrderListener {
+ return OrderListener(orderManager)
+ }
+
+ @Bean
+ fun accountantTradeListener(tradeManager: TradeManager): AccountantTradeListener {
+ return AccountantTradeListener(tradeManager)
+ }
+
+ @Bean
+ fun accountantEventListener(
+ orderManager: OrderManager
+ ): AccountantEventListener {
+ return AccountantEventListener(orderManager)
+ }
+
+ @Bean
+ fun accountantTempEventListener(
+ orderManager: OrderManager,
+ tradeManager: TradeManager
+ ): AccountantTempEventListener {
+ return AccountantTempEventListener(orderManager, tradeManager)
+ }
+
+ @Autowired
+ fun configureOrderListener(orderKafkaListener: OrderKafkaListener, orderListener: OrderListener) {
+ orderKafkaListener.addOrderListener(orderListener)
+ }
+
+ @Autowired
+ fun configureTradeListener(
+ tradeKafkaListener: TradeKafkaListener,
+ accountantTradeListener: AccountantTradeListener
+ ) {
+ tradeKafkaListener.addTradeListener(accountantTradeListener)
+ }
+
+ @Autowired
+ fun configureEventListener(
+ eventKafkaListener: EventKafkaListener,
+ accountantEventListener: AccountantEventListener
+ ) {
+ eventKafkaListener.addEventListener(accountantEventListener)
+ }
+
+ @Autowired
+ fun configureTempEventListener(
+ tempEventKafkaListener: TempEventKafkaListener,
+ accountantTempEventListener: AccountantTempEventListener
+ ) {
+ tempEventKafkaListener.addEventListener(accountantTempEventListener)
+ }
+
+ class OrderListener(val orderManager: OrderManager) : OrderSubmitRequestListener {
+
+ override fun id(): String {
+ return "OrderListener"
+ }
+
+ override fun onOrder(order: OrderSubmitRequest, partition: Int, offset: Long, timestamp: Long) {
+ runBlocking(AppDispatchers.kafkaExecutor) {
+ orderManager.handleRequestOrder(
+ SubmitOrderEvent(
+ order.ouid,
+ order.uuid,
+ order.orderId,
+ order.pair,
+ order.price,
+ order.quantity,
+ order.quantity,
+ order.direction,
+ order.matchConstraint,
+ order.orderType
+ )
+ )
+ }
+ }
+ }
+
+ class AccountantTradeListener(val tradeManager: TradeManager) : TradeListener {
+
+ override fun id(): String {
+ return "TradeListener"
+ }
+
+ override fun onTrade(tradeEvent: TradeEvent, partition: Int, offset: Long, timestamp: Long) {
+ runBlocking(AppDispatchers.kafkaExecutor) {
+ tradeManager.handleTrade(tradeEvent)
+ }
+ }
+ }
+
+ class AccountantEventListener(
+ val orderManager: OrderManager
+ ) : EventListener {
+
+ override fun id(): String {
+ return "EventListener"
+ }
+
+ override fun onEvent(coreEvent: CoreEvent, partition: Int, offset: Long, timestamp: Long) {
+ runBlocking(AppDispatchers.kafkaExecutor) {
+ if (coreEvent is CreateOrderEvent)
+ orderManager.handleNewOrder(coreEvent)
+ else if (coreEvent is RejectOrderEvent)
+ orderManager.handleRejectOrder(coreEvent)
+ else if (coreEvent is UpdatedOrderEvent)
+ orderManager.handleUpdateOrder(coreEvent)
+ else if (coreEvent is CancelOrderEvent)
+ orderManager.handleCancelOrder(coreEvent)
+ else {
+ println("Event is not accepted ${coreEvent::class.java}")
+ }
+ }
+ println("onEvent")
+ }
+ }
+
+ class AccountantTempEventListener(
+ val orderManager: OrderManager,
+ val tradeManager: TradeManager
+ ) : TempEventListener {
+
+ override fun id(): String {
+ return "TempEventListener"
+ }
+
+ override fun onEvent(coreEvent: CoreEvent, partition: Int, offset: Long, timestamp: Long) {
+ println("TempEvent " + coreEvent)
+ runBlocking(AppDispatchers.kafkaExecutor) {
+ if (coreEvent is CreateOrderEvent)
+ orderManager.handleNewOrder(coreEvent)
+ else if (coreEvent is RejectOrderEvent)
+ orderManager.handleRejectOrder(coreEvent)
+ else if (coreEvent is UpdatedOrderEvent)
+ orderManager.handleUpdateOrder(coreEvent)
+ else if (coreEvent is CancelOrderEvent)
+ orderManager.handleCancelOrder(coreEvent)
+ else if (coreEvent is TradeEvent)
+ tradeManager.handleTrade(coreEvent)
+ else {
+ throw IllegalArgumentException("Event is not accepted ${coreEvent::class.java}")
+ }
+ }
+ println("onEvent")
+ }
+ }
+}
\ No newline at end of file
diff --git a/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/config/AppDispatchers.kt b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/config/AppDispatchers.kt
new file mode 100644
index 000000000..a94b76fb9
--- /dev/null
+++ b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/config/AppDispatchers.kt
@@ -0,0 +1,8 @@
+package co.nilin.opex.app.config
+
+import kotlinx.coroutines.asCoroutineDispatcher
+import java.util.concurrent.Executors
+
+object AppDispatchers {
+ val kafkaExecutor = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
+}
\ No newline at end of file
diff --git a/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/controller/AccountantController.kt b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/controller/AccountantController.kt
new file mode 100644
index 000000000..7cb4ce65b
--- /dev/null
+++ b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/controller/AccountantController.kt
@@ -0,0 +1,52 @@
+package co.nilin.opex.app.controller
+
+import co.nilin.opex.accountant.core.model.PairConfig
+import co.nilin.opex.accountant.core.model.PairFeeConfig
+import co.nilin.opex.accountant.core.spi.FinancialActionLoader
+import co.nilin.opex.accountant.core.spi.PairConfigLoader
+import co.nilin.opex.accountant.core.spi.WalletProxy
+import co.nilin.opex.matching.core.eventh.events.SubmitOrderEvent
+import co.nilin.opex.matching.core.model.OrderDirection
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.PathVariable
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+import java.math.BigDecimal
+
+@RestController
+class AccountantController(
+ val walletProxy: WalletProxy,
+ val financialActionLoader: FinancialActionLoader,
+ val pairConfigLoader: PairConfigLoader
+) {
+ data class BooleanResponse(val result: Boolean)
+
+ @GetMapping("{uuid}/create_order/{amount}_{currency}/allowed")
+ suspend fun canCreateOrder(
+ @PathVariable("uuid") uuid: String,
+ @PathVariable("currency") currency: String,
+ @PathVariable("amount") amount: BigDecimal
+ ): BooleanResponse {
+ return BooleanResponse(
+ financialActionLoader.countUnprocessed(uuid, currency, SubmitOrderEvent::class.simpleName!!) <= 0
+ && walletProxy.canFulfil(currency, "main", uuid, amount)
+ )
+ }
+
+ @GetMapping(
+ value = ["/config/{pair}/fee/{direction}-{userLevel}", "/config/{pair}/fee/{direction}"]
+ )
+ suspend fun fetchPairFeeConfig(
+ @PathVariable("pair") pair: String,
+ @PathVariable("direction") direction: OrderDirection,
+ @PathVariable("userLevel") level: String?
+ ): PairFeeConfig {
+ return pairConfigLoader.load(pair, direction, level ?: "")
+ }
+
+ @GetMapping("/config/all")
+ suspend fun fetchPairConfigs():List{
+ return pairConfigLoader.loadPairConfigs()
+ }
+
+}
\ No newline at end of file
diff --git a/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/scheduler/FinancialActionsJob.kt b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/scheduler/FinancialActionsJob.kt
new file mode 100644
index 000000000..d4e02be81
--- /dev/null
+++ b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/scheduler/FinancialActionsJob.kt
@@ -0,0 +1,28 @@
+package co.nilin.opex.app.scheduler
+
+import co.nilin.opex.accountant.core.api.FinancialActionJobManager
+import kotlinx.coroutines.runBlocking
+import org.slf4j.LoggerFactory
+import org.springframework.scheduling.annotation.Scheduled
+import org.springframework.stereotype.Service
+
+@Service
+class FinancialActionsJob(
+ val financialActionJobManager: FinancialActionJobManager
+) {
+
+ private val log = LoggerFactory.getLogger(FinancialActionsJob::class.java)
+
+ @Scheduled(fixedDelay = 10000)
+ fun processFinancialActions() {
+ runBlocking {
+ try {
+ //read unprocessed fa records and call transfer
+ financialActionJobManager.processFinancialActions(0, 100)
+ } catch (e: Exception) {
+ log.error("Job error!", e)
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/scheduler/TempEventsJob.kt b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/scheduler/TempEventsJob.kt
new file mode 100644
index 000000000..6956089d2
--- /dev/null
+++ b/Accountant/accountant-app/src/main/kotlin/co/nilin/opex/app/scheduler/TempEventsJob.kt
@@ -0,0 +1,34 @@
+package co.nilin.opex.app.scheduler
+
+import co.nilin.opex.accountant.core.spi.TempEventPersister
+import co.nilin.opex.accountant.core.spi.TempEventRepublisher
+import kotlinx.coroutines.runBlocking
+import org.slf4j.LoggerFactory
+import org.springframework.scheduling.annotation.Scheduled
+import org.springframework.stereotype.Service
+
+@Service
+class TempEventsJob(
+ val tempEventPersister: TempEventPersister,
+ val tempEventRepublisher: TempEventRepublisher,
+) {
+
+ private val log = LoggerFactory.getLogger(TempEventsJob::class.java)
+
+ @Scheduled(fixedDelay = 1000)
+ fun processTempEventJobs() {
+ runBlocking {
+ try {
+ //read unprocessed temp event and call republish
+ val tempEvents = tempEventPersister.fetchTempEvents(0, 100);
+ if (tempEvents.isNotEmpty()) {
+ tempEventRepublisher.republish(tempEvents.map { event -> event.eventBody })
+ tempEventPersister.removeTempEvents(tempEvents)
+ }
+ } catch (e: Exception) {
+ log.error("Job error!", e)
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/Accountant/accountant-app/src/main/resources/application-docker.yml b/Accountant/accountant-app/src/main/resources/application-docker.yml
new file mode 100644
index 000000000..238e144fa
--- /dev/null
+++ b/Accountant/accountant-app/src/main/resources/application-docker.yml
@@ -0,0 +1,12 @@
+spring:
+ kafka:
+ bootstrap-servers: ${KAFKA_IP_PORT}
+ redis:
+ host: ${REDIS_HOST}
+ r2dbc:
+ url: r2dbc:postgresql://${DB_IP_PORT}/opex_accountant
+ username: opex
+ password: hiopex
+ cloud:
+ consul:
+ host: ${CONSUL_HOST}
diff --git a/Accountant/accountant-app/src/main/resources/application.yml b/Accountant/accountant-app/src/main/resources/application.yml
new file mode 100644
index 000000000..aa238fa2f
--- /dev/null
+++ b/Accountant/accountant-app/src/main/resources/application.yml
@@ -0,0 +1,37 @@
+server.port: 8089
+logging:
+ level:
+ co.nilin: DEBUG
+ reactor.netty.http.client: DEBUG
+spring:
+ application:
+ name: opex-accountant
+ main:
+ allow-bean-definition-overriding: false
+ kafka:
+ bootstrap-servers: 192.168.178.29:9092
+ consumer:
+ group-id: accountant
+ redis:
+ hostname: 127.0.0.1
+ port: 6379
+ r2dbc:
+ url: r2dbc:postgresql://localhost/opex_accountant
+ username: opex
+ password: hiopex
+ initialization-mode: always
+ cloud:
+ bootstrap:
+ enabled: true
+ consul:
+ port: 8500
+ discovery:
+ #healthCheckPath: ${management.context-path}/health
+ instance-id: ${spring.application.name}:${server.port}
+ healthCheckInterval: 20s
+ prefer-ip-address: true
+app:
+ coin: nln
+ address: 1
+ wallet:
+ url: lb://opex-wallet/
diff --git a/Accountant/accountant-core/.gitignore b/Accountant/accountant-core/.gitignore
new file mode 100644
index 000000000..f3a8317d6
--- /dev/null
+++ b/Accountant/accountant-core/.gitignore
@@ -0,0 +1,4 @@
+*.iml
+target/
+.mvn/
+.idea/
\ No newline at end of file
diff --git a/Accountant/accountant-core/mvnw b/Accountant/accountant-core/mvnw
new file mode 100644
index 000000000..a16b5431b
--- /dev/null
+++ b/Accountant/accountant-core/mvnw
@@ -0,0 +1,310 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/Accountant/accountant-core/mvnw.cmd b/Accountant/accountant-core/mvnw.cmd
new file mode 100644
index 000000000..c8d43372c
--- /dev/null
+++ b/Accountant/accountant-core/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/Accountant/accountant-core/pom.xml b/Accountant/accountant-core/pom.xml
new file mode 100644
index 000000000..3f7cc6f20
--- /dev/null
+++ b/Accountant/accountant-core/pom.xml
@@ -0,0 +1,105 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.4.4
+
+
+ co.nilin.opex
+ accountant-core
+ 1.0-SNAPSHOT
+ accountant-core
+ Accountant logic of Opex
+
+
+ 1.8
+ 1.4.31
+ ${version}
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+
+
+ exchange.core2
+ collections
+ 0.5.1
+
+
+
+ io.projectreactor.kotlin
+ reactor-kotlin-extensions
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-reactor
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-core
+
+
+
+ co.nilin.opex
+ matching-core
+ ${matching.version}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+
+
+
+
+ org.springframework
+ spring-tx
+ provided
+
+
+
+
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/test/kotlin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ -Xjsr305=strict
+
+
+ spring
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-allopen
+ ${kotlin.version}
+
+
+
+
+
+
+
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/FinancialActionJobManager.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/FinancialActionJobManager.kt
new file mode 100644
index 000000000..bb9c26062
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/FinancialActionJobManager.kt
@@ -0,0 +1,5 @@
+package co.nilin.opex.accountant.core.api
+
+interface FinancialActionJobManager {
+ suspend fun processFinancialActions(offset: Long, size: Long)
+}
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/OrderManager.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/OrderManager.kt
new file mode 100644
index 000000000..fb947ea8d
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/OrderManager.kt
@@ -0,0 +1,12 @@
+package co.nilin.opex.accountant.core.api
+
+import co.nilin.opex.accountant.core.model.FinancialAction
+import co.nilin.opex.matching.core.eventh.events.*
+
+interface OrderManager {
+ suspend fun handleRequestOrder(submitOrderEvent: SubmitOrderEvent): List
+ suspend fun handleNewOrder(createOrderEvent: CreateOrderEvent): List
+ suspend fun handleUpdateOrder(updatedOrderEvent: UpdatedOrderEvent): List
+ suspend fun handleRejectOrder(rejectOrderEvent: RejectOrderEvent): List
+ suspend fun handleCancelOrder(cancelOrderEvent: CancelOrderEvent): List
+}
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/TradeManager.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/TradeManager.kt
new file mode 100644
index 000000000..003e4fca2
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/TradeManager.kt
@@ -0,0 +1,8 @@
+package co.nilin.opex.accountant.core.api
+
+import co.nilin.opex.accountant.core.model.FinancialAction
+import co.nilin.opex.matching.core.eventh.events.TradeEvent
+
+interface TradeManager {
+ suspend fun handleTrade(trade:TradeEvent): List
+}
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/OrderStatus.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/OrderStatus.kt
new file mode 100644
index 000000000..b554691b6
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/OrderStatus.kt
@@ -0,0 +1,40 @@
+package co.nilin.opex.accountant.core.inout
+
+enum class OrderStatus(val code: Int, private val orderOfAppearance: Int) {
+
+ REQUESTED(0, 0),
+ NEW(1, 1), //The order has been accepted by the engine.
+ PARTIALLY_FILLED(4, 2), //A part of the order has been filled.
+ FILLED(5, 3), //The order has been completed.
+ CANCELED(2, 3), //The order has been canceled by the user.
+ REJECTED(3, 3), //The order was not accepted by the engine and not processed.
+ EXPIRED(6, 3); //The order was canceled according to the order type's rules (e.g. LIMIT FOK orders with no fill, LIMIT IOC or MARKET orders that partially fill) or by the exchange, (e.g. orders canceled during liquidation, orders canceled during maintenance)
+
+ fun comesBefore(status: OrderStatus?): Boolean {
+ if (status == null)
+ return false
+ return orderOfAppearance < status.orderOfAppearance
+ }
+
+ fun comesAfter(status: OrderStatus?): Boolean {
+ if (status == null)
+ return false
+ return orderOfAppearance > status.orderOfAppearance
+ }
+
+ companion object {
+ fun fromCode(code: Int?): OrderStatus? {
+ if (code == null)
+ return null
+ return values().find { it.code == code }
+ }
+ }
+}
+
+fun Int?.comesBefore(code: Int?): Boolean {
+ return OrderStatus.fromCode(this)?.comesBefore(OrderStatus.fromCode(code)) == true
+}
+
+fun Int?.comesAfter(code: Int?): Boolean {
+ return OrderStatus.fromCode(this)?.comesAfter(OrderStatus.fromCode(code)) == true
+}
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/RichOrder.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/RichOrder.kt
new file mode 100644
index 000000000..dd5a98893
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/RichOrder.kt
@@ -0,0 +1,67 @@
+package co.nilin.opex.accountant.core.inout
+
+import co.nilin.opex.matching.core.model.MatchConstraint
+import co.nilin.opex.matching.core.model.OrderDirection
+import co.nilin.opex.matching.core.model.OrderType
+import java.math.BigDecimal
+
+class RichOrder() {
+ var orderId: Long? = 0
+ lateinit var pair: String
+ lateinit var ouid: String
+ lateinit var uuid: String
+ lateinit var userLevel: String
+ lateinit var makerFee: BigDecimal
+ lateinit var takerFee: BigDecimal
+ lateinit var leftSideFraction: BigDecimal
+ lateinit var rightSideFraction: BigDecimal
+ lateinit var direction: OrderDirection
+ lateinit var constraint: MatchConstraint
+ lateinit var type: OrderType
+ lateinit var price: BigDecimal;
+ lateinit var quantity: BigDecimal;
+ lateinit var executedQuantity: BigDecimal;
+ lateinit var accumulativeQuoteQty: BigDecimal;
+ lateinit var quoteQuantity: BigDecimal;
+ var status: Int = 0;
+
+ constructor(
+ orderId: Long?,
+ pair: String,
+ ouid: String,
+ uuid: String,
+ userLevel: String,
+ makerFee: BigDecimal,
+ takerFee: BigDecimal,
+ leftSideFraction: BigDecimal,
+ rightSideFraction: BigDecimal,
+ direction: OrderDirection,
+ constraint: MatchConstraint,
+ type: OrderType,
+ price: BigDecimal,
+ quantity: BigDecimal,
+ quoteQuantity: BigDecimal,
+ executedQuantity: BigDecimal,
+ accumulativeQuoteQty: BigDecimal,
+ status: Int
+ ) : this() {
+ this.orderId = orderId
+ this.pair = pair
+ this.ouid = ouid
+ this.uuid = uuid
+ this.userLevel = userLevel
+ this.makerFee = makerFee
+ this.takerFee = takerFee
+ this.leftSideFraction = leftSideFraction
+ this.rightSideFraction = rightSideFraction
+ this.direction = direction
+ this.constraint = constraint
+ this.type = type
+ this.price = price
+ this.quantity = quantity
+ this.executedQuantity = executedQuantity
+ this.accumulativeQuoteQty = accumulativeQuoteQty
+ this.quoteQuantity = quoteQuantity
+ this.status = status
+ }
+}
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/RichTrade.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/RichTrade.kt
new file mode 100644
index 000000000..964b5b5e1
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/RichTrade.kt
@@ -0,0 +1,85 @@
+package co.nilin.opex.accountant.core.inout
+
+import co.nilin.opex.matching.core.model.OrderDirection
+import java.math.BigDecimal
+import java.time.LocalDateTime
+
+class RichTrade() {
+ var id: Long = 0
+ lateinit var pair: String
+ lateinit var takerOuid: String
+ lateinit var takerUuid: String
+ var takerOrderId: Long = 0
+ lateinit var takerDirection: OrderDirection
+ lateinit var takerPrice: BigDecimal
+ lateinit var takerQuantity: BigDecimal
+ lateinit var takerQuoteQuantity: BigDecimal
+ lateinit var takerRemainedQuantity: BigDecimal
+ lateinit var takerCommision: BigDecimal
+ lateinit var takerCommisionAsset: String
+ lateinit var makerOuid: String
+ lateinit var makerUuid: String
+ var makerOrderId: Long = 0
+ lateinit var makerDirection: OrderDirection
+ lateinit var makerPrice: BigDecimal
+ lateinit var makerQuantity: BigDecimal
+ lateinit var makerQuoteQuantity: BigDecimal
+ lateinit var makerRemainedQuantity: BigDecimal
+ lateinit var matchedQuantity: BigDecimal
+ lateinit var makerCommision: BigDecimal
+ lateinit var makerCommisionAsset: String
+
+ lateinit var tradeDateTime: LocalDateTime
+
+ constructor(
+ id: Long,
+ pair: String,
+ takerOuid: String,
+ takerUuid: String,
+ takerOrderId: Long,
+ takerDirection: OrderDirection,
+ takerPrice: BigDecimal,
+ takerQuantity: BigDecimal,
+ takerQuoteQuantity: BigDecimal,
+ takerRemainedQuantity: BigDecimal,
+ takerCommision: BigDecimal,
+ takerCommisionAsset: String,
+ makerOuid: String,
+ makerUuid: String,
+ makerOrderId: Long,
+ makerDirection: OrderDirection,
+ makerPrice: BigDecimal,
+ makerQuantity: BigDecimal,
+ makerQuoteQuantity: BigDecimal,
+ makerRemainedQuantity: BigDecimal,
+ makerCommision: BigDecimal,
+ makerCommisionAsset: String,
+ matchedQuantity: BigDecimal,
+ tradeDateTime: LocalDateTime
+ ) : this() {
+ this.id = id
+ this.pair = pair
+ this.takerOuid = takerOuid
+ this.takerUuid = takerUuid
+ this.takerOrderId = takerOrderId
+ this.takerDirection = takerDirection
+ this.takerPrice = takerPrice
+ this.takerQuantity = takerQuantity
+ this.takerQuoteQuantity = takerQuoteQuantity
+ this.takerRemainedQuantity = takerRemainedQuantity
+ this.takerCommision = takerCommision
+ this.takerCommisionAsset = takerCommisionAsset
+ this.makerOuid = makerOuid
+ this.makerUuid = makerUuid
+ this.makerOrderId = makerOrderId
+ this.makerDirection = makerDirection
+ this.makerPrice = makerPrice
+ this.makerQuantity = makerQuantity
+ this.makerQuoteQuantity = makerQuoteQuantity
+ this.makerRemainedQuantity = makerRemainedQuantity
+ this.matchedQuantity = matchedQuantity
+ this.makerCommision = makerCommision
+ this.makerCommisionAsset = makerCommisionAsset
+ this.tradeDateTime = tradeDateTime
+ }
+}
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/FinancialAction.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/FinancialAction.kt
new file mode 100644
index 000000000..e6f57e4d7
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/FinancialAction.kt
@@ -0,0 +1,41 @@
+package co.nilin.opex.accountant.core.model
+
+import java.math.BigDecimal
+import java.time.LocalDateTime
+
+class FinancialAction(
+ val id: Long? = null,
+ val parent: FinancialAction?,
+ val eventType: String,
+ val pointer: String,
+ val symbol: String,
+ val amount: BigDecimal,
+ val sender: String,
+ val senderWalletType: String,
+ val receiver: String,
+ val receiverWalletType: String,
+ val createDate: LocalDateTime
+) {
+ constructor(
+ parent: FinancialAction?,
+ eventType: String,
+ pointer: String,
+ symbol: String,
+ amount: BigDecimal,
+ sender: String,
+ senderWalletType: String,
+ receiver: String,
+ receiverWalletType: String,
+ createDate: LocalDateTime
+ ) : this(null, parent, eventType, pointer, symbol, amount, sender, senderWalletType, receiver, receiverWalletType, createDate)
+
+ override fun toString(): String {
+ return "FinancialAction(id=$id, parent=$parent, eventType='$eventType', pointer='$pointer', symbol='$symbol', amount=$amount, sender='$sender', senderWalletType='$senderWalletType', receiver='$receiver', receiverWalletType='$receiverWalletType', createDate=$createDate)"
+ }
+
+
+}
+
+enum class FinancialActionStatus {
+ CREATED, PROCESSED, ERROR
+}
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/Order.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/Order.kt
new file mode 100644
index 000000000..ca311f1a6
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/Order.kt
@@ -0,0 +1,31 @@
+package co.nilin.opex.accountant.core.model
+
+import co.nilin.opex.matching.core.model.MatchConstraint
+import co.nilin.opex.matching.core.model.OrderDirection
+import co.nilin.opex.matching.core.model.OrderType
+import java.math.BigDecimal
+
+data class Order(
+ val pair: String,
+ val ouid: String,
+ var matchingEngineId: Long?,
+ val makerFee: Double,
+ val takerFee: Double,
+ val leftSideFraction: Double,
+ val rightSideFraction: Double,
+ val uuid: String,
+ val userLevel: String,
+ val direction: OrderDirection,
+ val matchConstraint: MatchConstraint,
+ val orderType: OrderType,
+ val price: Long,
+ val quantity: Long,
+ val filledQuantity: Long,
+ val origPrice: BigDecimal,
+ val origQuantity: BigDecimal,
+ val filledOrigQuantity: BigDecimal,
+ val firstTransferAmount: BigDecimal,
+ var remainedTransferAmount: BigDecimal,
+ var status: Int,
+ val id: Long? = null
+)
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/PairConfig.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/PairConfig.kt
new file mode 100644
index 000000000..6df4d4e82
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/PairConfig.kt
@@ -0,0 +1,9 @@
+package co.nilin.opex.accountant.core.model
+
+class PairConfig(
+ val pair: String,
+ val leftSideWalletSymbol: String, //can be same as pair left side
+ val rightSideWalletSymbol: String, //can be same as pair right side
+ val leftSideFraction: Double,
+ val rightSideFraction: Double
+)
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/PairFeeConfig.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/PairFeeConfig.kt
new file mode 100644
index 000000000..ba8e3fbf8
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/PairFeeConfig.kt
@@ -0,0 +1,9 @@
+package co.nilin.opex.accountant.core.model
+
+class PairFeeConfig(
+ val pairConfig: PairConfig,
+ val direction: String?,
+ val userLevel: String?,
+ val makerFee: Double,
+ val takerFee: Double
+)
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/TempEvent.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/TempEvent.kt
new file mode 100644
index 000000000..144cddf19
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/TempEvent.kt
@@ -0,0 +1,6 @@
+package co.nilin.opex.accountant.core.model
+
+import co.nilin.opex.matching.core.eventh.events.CoreEvent
+import java.time.LocalDateTime
+
+data class TempEvent(val id: Long, val ouid: String, val eventBody: CoreEvent, val eventDate: LocalDateTime)
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/FinancialActionJobManagerImpl.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/FinancialActionJobManagerImpl.kt
new file mode 100644
index 000000000..b2c9faf6c
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/FinancialActionJobManagerImpl.kt
@@ -0,0 +1,39 @@
+package co.nilin.opex.accountant.core.service
+
+import co.nilin.opex.accountant.core.api.FinancialActionJobManager
+import co.nilin.opex.accountant.core.model.FinancialActionStatus
+import co.nilin.opex.accountant.core.spi.FinancialActionLoader
+import co.nilin.opex.accountant.core.spi.FinancialActionPersister
+import co.nilin.opex.accountant.core.spi.WalletProxy
+import org.slf4j.LoggerFactory
+
+class FinancialActionJobManagerImpl(
+ val financialActionLoader: FinancialActionLoader,
+ val financialActionPersister: FinancialActionPersister,
+ val walletProxy: WalletProxy
+) : FinancialActionJobManager {
+
+ private val log = LoggerFactory.getLogger(FinancialActionJobManagerImpl::class.java)
+
+ override suspend fun processFinancialActions(offset: Long, size: Long) {
+ val factions = financialActionLoader.loadUnprocessed(offset, size)
+ factions.forEach { faction ->
+ try {
+ walletProxy.transfer(
+ faction.symbol,
+ faction.senderWalletType,
+ faction.sender,
+ faction.receiverWalletType,
+ faction.receiver,
+ faction.amount,
+ faction.eventType + faction.pointer,
+ null
+ )
+ financialActionPersister.updateStatus(faction, FinancialActionStatus.PROCESSED)
+ } catch (e: Exception) {
+ log.error("financial job error", e)
+ financialActionPersister.updateStatus(faction, FinancialActionStatus.ERROR)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/OrderManagerImpl.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/OrderManagerImpl.kt
new file mode 100644
index 000000000..06638663f
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/OrderManagerImpl.kt
@@ -0,0 +1,220 @@
+package co.nilin.opex.accountant.core.service
+
+import co.nilin.opex.accountant.core.api.OrderManager
+import co.nilin.opex.accountant.core.inout.OrderStatus
+import co.nilin.opex.accountant.core.inout.RichOrder
+import co.nilin.opex.accountant.core.model.FinancialAction
+import co.nilin.opex.accountant.core.model.Order
+import co.nilin.opex.accountant.core.spi.*
+import co.nilin.opex.matching.core.eventh.events.*
+import co.nilin.opex.matching.core.model.OrderDirection
+import org.springframework.transaction.annotation.Transactional
+import java.math.BigDecimal
+import java.time.LocalDateTime
+
+open class OrderManagerImpl(
+ val pairConfigLoader: PairConfigLoader,
+ val financialActionPersister: FinancialActionPersister,
+ val financeActionLoader: FinancialActionLoader,
+ val orderPersister: OrderPersister,
+ val tempEventPersister: TempEventPersister,
+ val tempEventRepublisher: TempEventRepublisher,
+ val richOrderPublisher: RichOrderPublisher
+) : OrderManager {
+
+ @Transactional
+ override suspend fun handleRequestOrder(submitOrderEvent: SubmitOrderEvent): List {
+ //pair + dir -> symbol
+ //user level?
+ //pair config.makerFee and takerFee
+ val symbol = if (submitOrderEvent.direction == OrderDirection.ASK) {
+ submitOrderEvent.pair.leftSideName
+ } else {
+ submitOrderEvent.pair.rightSideName
+ }
+ val pairFeeConfig = pairConfigLoader.load(submitOrderEvent.pair.toString(), submitOrderEvent.direction, "")
+ val makerFee = pairFeeConfig.makerFee * 1 //user level formula
+ val takerFee = pairFeeConfig.takerFee * 1 //user level formula
+
+ //create fa for transfer uuid symbol main wallet to uuid symbol exchange wallet
+ /*
+ amount for sell (ask): quantity
+ amount for buy (bid): quantity * price
+ */
+ val financialAction =
+ FinancialAction(
+ null,
+ SubmitOrderEvent::class.simpleName!!,
+ submitOrderEvent.ouid,
+ symbol,
+ if (submitOrderEvent.direction == OrderDirection.ASK) {
+ BigDecimal(submitOrderEvent.quantity).multiply(pairFeeConfig.pairConfig.leftSideFraction.toBigDecimal())
+ } else {
+ BigDecimal(submitOrderEvent.quantity).multiply(pairFeeConfig.pairConfig.leftSideFraction.toBigDecimal())
+ .multiply(submitOrderEvent.price.toBigDecimal())
+ .multiply(pairFeeConfig.pairConfig.rightSideFraction.toBigDecimal())
+ },
+ submitOrderEvent.uuid,
+ "main",
+ submitOrderEvent.uuid,
+ "exchange",
+ LocalDateTime.now()
+ )
+ //store order (ouid, uuid, fees, userlevel, pair, direction, price, quantity, filledQ, status, transfered)
+ orderPersister.save(
+ Order(
+ submitOrderEvent.pair.toString(),
+ submitOrderEvent.ouid,
+ null,
+ makerFee,
+ takerFee,
+ pairFeeConfig.pairConfig.leftSideFraction,
+ pairFeeConfig.pairConfig.rightSideFraction,
+ submitOrderEvent.uuid,
+ "",
+ submitOrderEvent.direction,
+ submitOrderEvent.matchConstraint,
+ submitOrderEvent.orderType,
+ submitOrderEvent.price,
+ submitOrderEvent.quantity,
+ submitOrderEvent.quantity - submitOrderEvent.remainedQuantity,
+ submitOrderEvent.price.toBigDecimal()
+ .multiply(pairFeeConfig.pairConfig.rightSideFraction.toBigDecimal()),
+ submitOrderEvent.quantity.toBigDecimal()
+ .multiply(pairFeeConfig.pairConfig.leftSideFraction.toBigDecimal()),
+ BigDecimal(submitOrderEvent.quantity - submitOrderEvent.remainedQuantity).multiply(pairFeeConfig.pairConfig.leftSideFraction.toBigDecimal()),
+ financialAction.amount,
+ financialAction.amount,
+ OrderStatus.REQUESTED.code
+ )
+ )
+ return financialActionPersister.persist(listOf(financialAction))
+ }
+
+ @Transactional
+ override suspend fun handleNewOrder(createOrderEvent: CreateOrderEvent): List {
+ //update order add id to other fields
+ val order = orderPersister.load(createOrderEvent.ouid)
+ if (order != null) {
+ order.matchingEngineId = createOrderEvent.orderId
+ orderPersister.save(order)
+ //new order accepted by engine
+ publishRichOrder(order, createOrderEvent.remainedQuantity.toBigDecimal())
+ } else {
+ tempEventPersister.saveTempEvent(createOrderEvent.ouid, createOrderEvent)
+ }
+ return emptyList()
+ }
+
+ private suspend fun publishRichOrder(
+ order: Order, remainedQuantity: BigDecimal, status: OrderStatus? = null
+ ) {
+ richOrderPublisher.publish(
+ RichOrder(
+ order.id,
+ order.pair,
+ order.ouid,
+ order.uuid,
+ order.userLevel,
+ order.makerFee.toBigDecimal(),
+ order.takerFee.toBigDecimal(),
+ order.leftSideFraction.toBigDecimal(),
+ order.rightSideFraction.toBigDecimal(),
+ order.direction,
+ order.matchConstraint,
+ order.orderType,
+ order.origPrice,
+ order.origQuantity,
+ order.origPrice.multiply(order.origQuantity),
+ order.quantity.toBigDecimal().subtract(remainedQuantity)
+ .multiply(order.leftSideFraction.toBigDecimal()),
+ order.origPrice.multiply(
+ order.quantity.toBigDecimal().subtract(remainedQuantity)
+ ),
+ status?.code ?: if (remainedQuantity.compareTo(BigDecimal.ZERO) == 0) {
+ OrderStatus.FILLED.code
+ } else if (remainedQuantity.compareTo(order.quantity.toBigDecimal()) == 0) {
+ OrderStatus.NEW.code
+ } else {
+ OrderStatus.PARTIALLY_FILLED.code
+ }
+ )
+ )
+ }
+
+ override suspend fun handleUpdateOrder(updatedOrderEvent: UpdatedOrderEvent): List {
+ TODO("Not yet implemented")
+ }
+
+ @Transactional
+ override suspend fun handleRejectOrder(rejectOrderEvent: RejectOrderEvent): List {
+ //order by ouid
+ val order = orderPersister.load(rejectOrderEvent.ouid)
+ if (order == null) {
+ tempEventPersister.saveTempEvent(rejectOrderEvent.ouid, rejectOrderEvent)
+ return emptyList()
+ }
+ val symbol = if (rejectOrderEvent.direction == OrderDirection.ASK) {
+ rejectOrderEvent.pair.leftSideName
+ } else {
+ rejectOrderEvent.pair.rightSideName
+ }
+ //check uuid
+ //lookup for parent fa
+ val parentFinancialAction = financeActionLoader.findLast(rejectOrderEvent.uuid, rejectOrderEvent.ouid)
+ //create fa for transfer remaining transfered uuid symbol exchange wallet to uuid main exchange wallet
+ val financialAction = FinancialAction(
+ parentFinancialAction,
+ RejectOrderEvent::class.simpleName!!,
+ rejectOrderEvent.ouid,
+ symbol,
+ order.remainedTransferAmount,
+ rejectOrderEvent.uuid,
+ "exchange",
+ rejectOrderEvent.uuid,
+ "main",
+ LocalDateTime.now()
+ )
+ //update order status
+ order.status = OrderStatus.REJECTED.code
+ orderPersister.save(order)
+ publishRichOrder(order, order.quantity.toBigDecimal(), OrderStatus.REJECTED)
+ return financialActionPersister.persist(listOf(financialAction))
+ }
+
+ @Transactional
+ override suspend fun handleCancelOrder(cancelOrderEvent: CancelOrderEvent): List {
+ //order by ouid
+ val order = orderPersister.load(cancelOrderEvent.ouid)
+ if (order == null) {
+ tempEventPersister.saveTempEvent(cancelOrderEvent.ouid, cancelOrderEvent)
+ return emptyList()
+ }
+ val symbol = if (cancelOrderEvent.direction == OrderDirection.ASK) {
+ cancelOrderEvent.pair.leftSideName
+ } else {
+ cancelOrderEvent.pair.rightSideName
+ }
+ //check uuid
+ //lookup for parent fa
+ val parentFinancialAction = financeActionLoader.findLast(cancelOrderEvent.uuid, cancelOrderEvent.ouid)
+ //create fa for transfer remaining transfered uuid symbol exchange wallet to uuid main exchange wallet
+ val financialAction = FinancialAction(
+ parentFinancialAction,
+ RejectOrderEvent::class.simpleName!!,
+ cancelOrderEvent.ouid,
+ symbol,
+ order.remainedTransferAmount,
+ cancelOrderEvent.uuid,
+ "exchange",
+ cancelOrderEvent.uuid,
+ "main",
+ LocalDateTime.now()
+ )
+ //update order status
+ order.status = OrderStatus.CANCELED.code
+ orderPersister.save(order)
+ publishRichOrder(order, cancelOrderEvent.quantity.toBigDecimal(), OrderStatus.CANCELED)
+ return financialActionPersister.persist(listOf(financialAction))
+ }
+}
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/TradeManagerImpl.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/TradeManagerImpl.kt
new file mode 100644
index 000000000..bf24dfa7d
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/TradeManagerImpl.kt
@@ -0,0 +1,266 @@
+package co.nilin.opex.accountant.core.service
+
+import co.nilin.opex.accountant.core.api.TradeManager
+import co.nilin.opex.accountant.core.inout.RichTrade
+import co.nilin.opex.accountant.core.model.FinancialAction
+import co.nilin.opex.accountant.core.spi.*
+import co.nilin.opex.matching.core.eventh.events.TradeEvent
+import co.nilin.opex.matching.core.model.OrderDirection
+import org.slf4j.LoggerFactory
+import org.springframework.transaction.annotation.Transactional
+import java.math.BigDecimal
+import java.time.LocalDateTime
+
+open class TradeManagerImpl(
+ val pairStaticRateLoader: PairStaticRateLoader,
+ val financeActionPersister: FinancialActionPersister,
+ val financeActionLoader: FinancialActionLoader,
+ val orderPersister: OrderPersister,
+ val tempEventPersister: TempEventPersister,
+ val richTradePublisher: RichTradePublisher,
+ val walletProxy: WalletProxy,
+ val platformCoin: String,
+ val platformAddress: String
+) : TradeManager {
+
+ private val log = LoggerFactory.getLogger(TradeManagerImpl::class.java)
+
+ @Transactional
+ override suspend fun handleTrade(trade: TradeEvent): List {
+ log.info("trade event started {}", trade)
+ val financialActions = mutableListOf()
+ //taker order by ouid
+ val takerOrder = orderPersister.load(trade.takerOuid)
+ //maker order by ouid
+ val makerOrder = orderPersister.load(trade.makerOuid)
+ if (takerOrder == null || makerOrder == null) {
+ if (takerOrder == null) {
+ tempEventPersister.saveTempEvent(trade.takerOuid, trade)
+ }
+ if (makerOrder == null) {
+ tempEventPersister.saveTempEvent(trade.makerOuid, trade)
+ }
+ return emptyList()
+ }
+ //check taker uuid
+ //
+ //check maker uuid
+ //
+ val leftSidePCRate = pairStaticRateLoader.calculateStaticRate(platformCoin, trade.pair.leftSideName)
+ val rightSidePCRate = pairStaticRateLoader.calculateStaticRate(platformCoin, trade.pair.rightSideName)
+
+ val takerMatchedAmount = if (takerOrder.direction == OrderDirection.ASK) {
+ trade.matchedQuantity.toBigDecimal().multiply(takerOrder.leftSideFraction.toBigDecimal())
+ } else {
+ trade.matchedQuantity.toBigDecimal().multiply(takerOrder.leftSideFraction.toBigDecimal())
+ .multiply(trade.makerPrice.toBigDecimal()).multiply(takerOrder.rightSideFraction.toBigDecimal())
+ }
+
+ val takerPCFeeCoefficient: Double = if (takerOrder.direction == OrderDirection.ASK) {
+ leftSidePCRate ?: 0.0
+ } else {
+ rightSidePCRate ?: 0.0
+ }
+
+ val makerMatchedAmount = if (makerOrder.direction == OrderDirection.ASK) {
+ trade.matchedQuantity.toBigDecimal().multiply(makerOrder.leftSideFraction.toBigDecimal())
+ } else {
+ trade.matchedQuantity.toBigDecimal().multiply(makerOrder.leftSideFraction.toBigDecimal())
+ .multiply(trade.takerPrice.toBigDecimal()).multiply(makerOrder.rightSideFraction.toBigDecimal())
+ }
+
+ val makerPCFeeCoefficient: Double = if (makerOrder.direction == OrderDirection.ASK) {
+ leftSidePCRate ?: 0.0
+ } else {
+ rightSidePCRate ?: 0.0
+ }
+ log.info("trade event configs loaded ")
+ //lookup for taker parent fa
+ val takerParentFinancialAction = financeActionLoader.findLast(trade.takerUuid, trade.takerOuid)
+ log.info("trade event takerParentFinancialAction {} ", takerParentFinancialAction)
+ //lookup for maker parent fa
+ val makerParentFinancialAction = financeActionLoader.findLast(trade.makerUuid, trade.makerOuid)
+ log.info("trade event makerParentFinancialAction {} ", makerParentFinancialAction)
+
+
+ //calculate maker fee
+ val makerFee = makerOrder.makerFee
+ val makerTotalFeeWithPlatformCoin = takerMatchedAmount
+ .multiply(makerFee.toBigDecimal())
+ .multiply(makerPCFeeCoefficient.toBigDecimal())
+ //check if maker uuid can pay the fee with platform coin
+ //create fa for transfer taker uuid symbol exchange wallet to maker symbol main wallet
+ /*
+ amount for sell (ask): match_quantity (if not pay by platform coin then - maker fee)
+ amount for buy (bid): match_quantity * maker price (if not pay by platform coin then - maker fee)
+ */
+ val takerTransferAction = FinancialAction(
+ takerParentFinancialAction,
+ TradeEvent::class.simpleName!!,
+ trade.takerOuid,
+ if (takerOrder.direction == OrderDirection.ASK) {
+ trade.pair.leftSideName
+ } else {
+ trade.pair.rightSideName
+ },
+ takerMatchedAmount,
+ trade.takerUuid,
+ "exchange",
+ trade.makerUuid,
+ "main",
+ LocalDateTime.now()
+ )
+ log.info("trade event takerTransferAction {}", takerTransferAction)
+ financialActions.add(takerTransferAction)
+ val makerFeeAction = if (makerTotalFeeWithPlatformCoin > BigDecimal.ZERO &&
+ walletProxy.canFulfil(platformCoin, "main", trade.makerUuid, makerTotalFeeWithPlatformCoin)
+ ) {
+ FinancialAction(
+ makerParentFinancialAction,
+ TradeEvent::class.simpleName!!,
+ trade.takerOuid,
+ platformCoin,
+ makerTotalFeeWithPlatformCoin,
+ trade.makerUuid,
+ "main",
+ platformAddress,
+ "exchange",
+ LocalDateTime.now()
+ )
+ } else {
+ FinancialAction(
+ makerParentFinancialAction,
+ TradeEvent::class.simpleName!!,
+ trade.takerOuid,
+ if (takerOrder.direction == OrderDirection.ASK) {
+ trade.pair.leftSideName
+ } else {
+ trade.pair.rightSideName
+ },
+ takerMatchedAmount.multiply(makerFee.toBigDecimal()),
+ trade.makerUuid,
+ "main",
+ platformAddress,
+ "exchange",
+ LocalDateTime.now()
+ )
+ }
+ log.info("trade event makerFeeAction {}", makerFeeAction)
+ financialActions.add(makerFeeAction)
+ //update taker order status
+ takerOrder.remainedTransferAmount -= takerMatchedAmount
+ if (takerOrder.filledQuantity == takerOrder.quantity) {
+ takerOrder.status = 1
+ }
+ orderPersister.save(takerOrder)
+ log.info("taker order saved {}", takerOrder)
+
+ //calculate taker fee
+ val takerFee = takerOrder.takerFee
+ val takerTotalFeeWithPlatformCoin = takerMatchedAmount
+ .multiply(takerFee.toBigDecimal())
+ .multiply(takerPCFeeCoefficient.toBigDecimal())
+
+ //create fa for transfer makeruuid symbol exchange wallet to taker symbol main wallet
+ /*
+ amount for sell (ask): match_quantity (if not pay by platform coin then - taker fee)
+ amount for buy (bid): match_quantity * maker price (if not pay by platform coin then - taker fee)
+ */
+ val makerTransferAction = FinancialAction(
+ makerParentFinancialAction,
+ TradeEvent::class.simpleName!!,
+ trade.makerOuid,
+ if (makerOrder.direction == OrderDirection.ASK) {
+ trade.pair.leftSideName
+ } else {
+ trade.pair.rightSideName
+ },
+ makerMatchedAmount,
+ trade.makerUuid,
+ "exchange",
+ trade.takerUuid,
+ "main",
+ LocalDateTime.now()
+ )
+ log.info("trade event makerTransferAction {}", makerTransferAction)
+ financialActions.add(makerTransferAction)
+ //check if taker uuid can pay the fee with platform coin
+ val takerFeeAction = if (takerTotalFeeWithPlatformCoin > BigDecimal.ZERO &&
+ walletProxy.canFulfil(platformCoin, "main", trade.takerUuid, takerTotalFeeWithPlatformCoin)
+ ) {
+ FinancialAction(
+ takerParentFinancialAction,
+ TradeEvent::class.simpleName!!,
+ trade.makerOuid,
+ if (makerOrder.direction == OrderDirection.ASK) {
+ trade.pair.leftSideName
+ } else {
+ trade.pair.rightSideName
+ },
+ takerTotalFeeWithPlatformCoin,
+ trade.takerUuid,
+ "main",
+ platformAddress,
+ "",
+ LocalDateTime.now()
+ )
+ } else {
+ FinancialAction(
+ takerParentFinancialAction,
+ TradeEvent::class.simpleName!!,
+ trade.makerOuid,
+ if (makerOrder.direction == OrderDirection.ASK) {
+ trade.pair.leftSideName
+ } else {
+ trade.pair.rightSideName
+ },
+ makerMatchedAmount.multiply(takerFee.toBigDecimal()),
+ trade.takerUuid,
+ "main",
+ platformAddress,
+ "exchange",
+ LocalDateTime.now()
+ )
+
+ }
+ log.info("trade event takerFeeAction {}", takerFeeAction)
+ financialActions.add(takerFeeAction)
+ //update maker order status
+ makerOrder.remainedTransferAmount -= makerMatchedAmount
+ if (makerOrder.filledQuantity == makerOrder.quantity) {
+ makerOrder.status = 1
+ }
+ orderPersister.save(makerOrder)
+ log.info("maker order saved {}", makerOrder)
+ richTradePublisher.publish(
+ co.nilin.opex.accountant.core.inout.RichTrade(
+ trade.tradeId,
+ trade.pair.toString(),
+ trade.takerOuid,
+ trade.takerUuid,
+ trade.takerOrderId,
+ trade.takerDirection,
+ trade.takerPrice.toBigDecimal().multiply(takerOrder.rightSideFraction.toBigDecimal()),
+ takerOrder.origQuantity,
+ takerOrder.origPrice.multiply(takerOrder.origQuantity),
+ trade.takerRemainedQuantity.toBigDecimal().multiply(takerOrder.leftSideFraction.toBigDecimal()),
+ takerFeeAction.amount,
+ takerFeeAction.symbol,
+ trade.makerOuid,
+ trade.makerUuid,
+ trade.makerOrderId,
+ trade.makerDirection,
+ trade.makerPrice.toBigDecimal().multiply(makerOrder.rightSideFraction.toBigDecimal()),
+ makerOrder.origQuantity,
+ makerOrder.origPrice.multiply(makerOrder.origQuantity),
+ trade.makerRemainedQuantity.toBigDecimal().multiply(makerOrder.leftSideFraction.toBigDecimal()),
+ makerFeeAction.amount,
+ makerFeeAction.symbol,
+ trade.matchedQuantity.toBigDecimal().multiply(makerOrder.leftSideFraction.toBigDecimal()),
+ trade.eventDate
+ )
+
+ )
+ return financeActionPersister.persist(financialActions)
+ }
+}
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/FinancialActionLoader.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/FinancialActionLoader.kt
new file mode 100644
index 000000000..98721879d
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/FinancialActionLoader.kt
@@ -0,0 +1,10 @@
+package co.nilin.opex.accountant.core.spi
+
+import co.nilin.opex.accountant.core.model.FinancialAction
+import kotlinx.coroutines.flow.Flow
+
+interface FinancialActionLoader {
+ suspend fun findLast(uuid: String, ouid: String): FinancialAction?
+ suspend fun loadUnprocessed(offset: Long, size: Long): List
+ suspend fun countUnprocessed(uuid: String, symbol: String, eventType: String): Long
+}
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/FinancialActionPersister.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/FinancialActionPersister.kt
new file mode 100644
index 000000000..f51e4eef9
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/FinancialActionPersister.kt
@@ -0,0 +1,9 @@
+package co.nilin.opex.accountant.core.spi
+
+import co.nilin.opex.accountant.core.model.FinancialAction
+import co.nilin.opex.accountant.core.model.FinancialActionStatus
+
+interface FinancialActionPersister {
+ suspend fun persist(financialActions: List): List
+ suspend fun updateStatus(financialAction: FinancialAction, status: FinancialActionStatus)
+}
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/OrderPersister.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/OrderPersister.kt
new file mode 100644
index 000000000..a3ae68637
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/OrderPersister.kt
@@ -0,0 +1,8 @@
+package co.nilin.opex.accountant.core.spi
+
+import co.nilin.opex.accountant.core.model.Order
+
+interface OrderPersister {
+ suspend fun load(ouid: String): Order?
+ suspend fun save(order: Order): Order
+}
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/PairConfigLoader.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/PairConfigLoader.kt
new file mode 100644
index 000000000..22a9c2518
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/PairConfigLoader.kt
@@ -0,0 +1,12 @@
+package co.nilin.opex.accountant.core.spi
+
+import co.nilin.opex.accountant.core.model.PairConfig
+import co.nilin.opex.accountant.core.model.PairFeeConfig
+import co.nilin.opex.matching.core.model.OrderDirection
+
+interface PairConfigLoader {
+
+ suspend fun loadPairConfigs(): List
+
+ suspend fun load(pair: String, direction: OrderDirection, userLevel: String): PairFeeConfig
+}
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/PairStaticRateLoader.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/PairStaticRateLoader.kt
new file mode 100644
index 000000000..3708a0e18
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/PairStaticRateLoader.kt
@@ -0,0 +1,5 @@
+package co.nilin.opex.accountant.core.spi
+
+interface PairStaticRateLoader {
+ suspend fun calculateStaticRate(leftSide:String, rightSide: String): Double?
+}
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/RichOrderPublisher.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/RichOrderPublisher.kt
new file mode 100644
index 000000000..ae6866675
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/RichOrderPublisher.kt
@@ -0,0 +1,7 @@
+package co.nilin.opex.accountant.core.spi
+
+import co.nilin.opex.accountant.core.inout.RichOrder
+
+interface RichOrderPublisher {
+ suspend fun publish(order: RichOrder)
+}
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/RichTradePublisher.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/RichTradePublisher.kt
new file mode 100644
index 000000000..da9f860d3
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/RichTradePublisher.kt
@@ -0,0 +1,7 @@
+package co.nilin.opex.accountant.core.spi
+
+import co.nilin.opex.accountant.core.inout.RichTrade
+
+interface RichTradePublisher {
+ suspend fun publish(trade: RichTrade)
+}
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/TempEventPersister.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/TempEventPersister.kt
new file mode 100644
index 000000000..a949f1c95
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/TempEventPersister.kt
@@ -0,0 +1,12 @@
+package co.nilin.opex.accountant.core.spi
+
+import co.nilin.opex.accountant.core.model.TempEvent
+import co.nilin.opex.matching.core.eventh.events.CoreEvent
+
+interface TempEventPersister {
+ suspend fun saveTempEvent(ouid: String, event: CoreEvent)
+ suspend fun loadTempEvents(ouid: String): List
+ suspend fun removeTempEvents(ouid: String)
+ suspend fun removeTempEvents(tempEvents: List)
+ suspend fun fetchTempEvents(offset: Long, size: Long): List
+}
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/TempEventRepublisher.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/TempEventRepublisher.kt
new file mode 100644
index 000000000..f86f23901
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/TempEventRepublisher.kt
@@ -0,0 +1,7 @@
+package co.nilin.opex.accountant.core.spi
+
+import co.nilin.opex.matching.core.eventh.events.CoreEvent
+
+interface TempEventRepublisher {
+ suspend fun republish(events: List)
+}
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/WalletProxy.kt b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/WalletProxy.kt
new file mode 100644
index 000000000..ed27996ca
--- /dev/null
+++ b/Accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/WalletProxy.kt
@@ -0,0 +1,19 @@
+package co.nilin.opex.accountant.core.spi
+
+import java.math.BigDecimal
+
+interface WalletProxy {
+
+ suspend fun transfer(
+ symbol: String,
+ senderWalletType: String,
+ senderUuid: String,
+ receiverWalletType: String,
+ receiverUuid: String,
+ amount: BigDecimal,
+ description: String?,
+ transferRef: String?
+ )
+
+ suspend fun canFulfil(symbol: String, walletType: String, uuid: String, amount: BigDecimal): Boolean
+}
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/MockitoHelper.kt b/Accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/MockitoHelper.kt
new file mode 100644
index 000000000..d05650e71
--- /dev/null
+++ b/Accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/MockitoHelper.kt
@@ -0,0 +1,12 @@
+package co.nilin.opex.accountant.core.service
+
+import org.mockito.Mockito
+
+object MockitoHelper {
+ fun anyObject(): T {
+ Mockito.any()
+ return uninitialized()
+ }
+ @Suppress("UNCHECKED_CAST")
+ fun uninitialized(): T = null as T
+}
\ No newline at end of file
diff --git a/Accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/OrderManagerImplTest.kt b/Accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/OrderManagerImplTest.kt
new file mode 100644
index 000000000..15f54a86c
--- /dev/null
+++ b/Accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/OrderManagerImplTest.kt
@@ -0,0 +1,180 @@
+package co.nilin.opex.accountant.core.service
+
+import co.nilin.opex.accountant.core.api.OrderManager
+import co.nilin.opex.accountant.core.model.FinancialAction
+import co.nilin.opex.accountant.core.model.PairConfig
+import co.nilin.opex.accountant.core.model.PairFeeConfig
+import co.nilin.opex.accountant.core.spi.*
+import co.nilin.opex.matching.core.eventh.events.SubmitOrderEvent
+import co.nilin.opex.matching.core.model.MatchConstraint
+import co.nilin.opex.matching.core.model.OrderDirection
+import co.nilin.opex.matching.core.model.OrderType
+import co.nilin.opex.matching.core.model.Pair
+import kotlinx.coroutines.runBlocking
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+import java.time.LocalDateTime
+
+internal class OrderManagerImplTest() {
+
+ @Mock
+ lateinit var financialActionPersister: FinancialActionPersister
+
+ @Mock
+ lateinit var financialActionLoader: FinancialActionLoader
+
+ @Mock
+ lateinit var orderPersister: OrderPersister
+
+ @Mock
+ lateinit var tempEventPersister: TempEventPersister
+
+ @Mock
+ lateinit var tempEventRepublisher: TempEventRepublisher
+
+ @Mock
+ lateinit var pairConfigLoader: PairConfigLoader
+
+ @Mock
+ lateinit var richOrderPublisher: RichOrderPublisher
+
+ val orderManager: OrderManager
+
+ init {
+ MockitoAnnotations.openMocks(this)
+ orderManager = OrderManagerImpl(
+ pairConfigLoader, financialActionPersister, financialActionLoader, orderPersister, tempEventPersister, tempEventRepublisher, richOrderPublisher
+ )
+ runBlocking {
+ Mockito.`when`(tempEventPersister.loadTempEvents(anyString())).thenReturn(emptyList())
+ }
+ }
+
+ @Test
+ fun givenAskOrder_whenHandleRequestOrder_thenFAMatch() {
+ runBlocking {
+ //given
+ val pair = Pair("eth", "btc")
+ val pairConfig = PairConfig(
+ pair.toString(), pair.leftSideName, pair.rightSideName, 1.0, 0.001
+ )
+ val submitOrderEvent = SubmitOrderEvent(
+ "ouid", "uuid", null, pair, 30, 60, 0, OrderDirection.ASK, MatchConstraint.GTC, OrderType.LIMIT_ORDER
+ )
+ Mockito.`when`(pairConfigLoader.load(pair.toString(), submitOrderEvent.direction, ""))
+ .thenReturn(
+ PairFeeConfig(
+ pairConfig,
+ submitOrderEvent.direction.toString(),
+ "",
+ 0.1,
+ 0.12
+ )
+ )
+ Mockito.`when`(financialActionPersister.persist(MockitoHelper.anyObject()))
+ .then {
+ return@then it.getArgument>(0)
+ }
+
+ //when
+ val financialActions = orderManager.handleRequestOrder(submitOrderEvent)
+
+ //then
+ assertEquals(1, financialActions.size)
+ val expectedFinancialAction = FinancialAction(
+ null,
+ SubmitOrderEvent::class.simpleName!!,
+ submitOrderEvent.ouid,
+ pair.leftSideName,
+ pairConfig.leftSideFraction.toBigDecimal().multiply(submitOrderEvent.quantity.toBigDecimal()),
+ submitOrderEvent.uuid,
+ "main",
+ submitOrderEvent.uuid,
+ "exchange",
+ LocalDateTime.now()
+ )
+ assertEquals(expectedFinancialAction.eventType, financialActions[0].eventType)
+ assertEquals(expectedFinancialAction.symbol, financialActions[0].symbol)
+ assertEquals(expectedFinancialAction.amount, financialActions[0].amount)
+ assertEquals(expectedFinancialAction.sender, financialActions[0].sender)
+ assertEquals(expectedFinancialAction.senderWalletType, financialActions[0].senderWalletType)
+ assertEquals(expectedFinancialAction.receiver, financialActions[0].receiver)
+ assertEquals(expectedFinancialAction.receiverWalletType, financialActions[0].receiverWalletType)
+ }
+ }
+
+ @Test
+ fun givenBidOrder_whenHandleRequestOrder_thenFAMatch() {
+ runBlocking {
+ //given
+ val pair = Pair("eth", "btc")
+ val pairConfig = PairConfig(
+ pair.toString(), pair.leftSideName, pair.rightSideName, 1.0, 0.001
+ )
+ val submitOrderEvent = SubmitOrderEvent(
+ "ouid", "uuid", null, pair, 35, 14, 0, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER
+ )
+ Mockito.`when`(pairConfigLoader.load(pair.toString(), submitOrderEvent.direction, ""))
+ .thenReturn(
+ PairFeeConfig(
+ pairConfig, submitOrderEvent.direction.toString(), "", 0.08, 0.1
+ )
+ )
+ Mockito.`when`(financialActionPersister.persist(MockitoHelper.anyObject()))
+ .then {
+ return@then it.getArgument>(0)
+ }
+
+ //when
+ val financialActions = runBlocking {
+ orderManager.handleRequestOrder(submitOrderEvent)
+ }
+
+ //then
+ assertEquals(1, financialActions.size)
+ val expectedFinancialAction = FinancialAction(
+ null,
+ SubmitOrderEvent::class.simpleName!!,
+ submitOrderEvent.ouid,
+ pair.rightSideName,
+ pairConfig.leftSideFraction.toBigDecimal().multiply(submitOrderEvent.quantity.toBigDecimal())
+ .multiply(pairConfig.rightSideFraction.toBigDecimal())
+ .multiply(submitOrderEvent.price.toBigDecimal()),
+ submitOrderEvent.uuid,
+ "main",
+ submitOrderEvent.uuid,
+ "exchange",
+ LocalDateTime.now()
+ )
+ assertEquals(expectedFinancialAction.eventType, financialActions[0].eventType)
+ assertEquals(expectedFinancialAction.symbol, financialActions[0].symbol)
+ assertEquals(expectedFinancialAction.amount, financialActions[0].amount)
+ assertEquals(expectedFinancialAction.sender, financialActions[0].sender)
+ assertEquals(expectedFinancialAction.senderWalletType, financialActions[0].senderWalletType)
+ assertEquals(expectedFinancialAction.receiver, financialActions[0].receiver)
+ assertEquals(expectedFinancialAction.receiverWalletType, financialActions[0].receiverWalletType)
+ }
+ }
+
+ @Test
+ fun handleNewOrder() {
+ }
+
+ @Test
+ fun handleUpdateOrder() {
+ }
+
+ @Test
+ fun handleRejectOrder() {
+ }
+
+ @Test
+ fun handleCancelOrder() {
+ }
+}
+
+
diff --git a/Accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/TradeManagerImplTest.kt b/Accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/TradeManagerImplTest.kt
new file mode 100644
index 000000000..2982d59aa
--- /dev/null
+++ b/Accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/TradeManagerImplTest.kt
@@ -0,0 +1,239 @@
+package co.nilin.opex.accountant.core.service
+
+import co.nilin.opex.accountant.core.api.OrderManager
+import co.nilin.opex.accountant.core.api.TradeManager
+import co.nilin.opex.accountant.core.model.FinancialAction
+import co.nilin.opex.accountant.core.model.Order
+import co.nilin.opex.accountant.core.model.PairConfig
+import co.nilin.opex.accountant.core.model.PairFeeConfig
+import co.nilin.opex.accountant.core.spi.*
+import co.nilin.opex.matching.core.eventh.events.SubmitOrderEvent
+import co.nilin.opex.matching.core.eventh.events.TradeEvent
+import co.nilin.opex.matching.core.model.MatchConstraint
+import co.nilin.opex.matching.core.model.OrderDirection
+import co.nilin.opex.matching.core.model.OrderType
+import co.nilin.opex.matching.core.model.Pair
+import kotlinx.coroutines.runBlocking
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.Test
+import org.mockito.ArgumentMatchers
+
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+internal class TradeManagerImplTest() {
+ @Mock
+ lateinit var financialActionPersister: FinancialActionPersister
+
+ @Mock
+ lateinit var financeActionLoader: FinancialActionLoader
+
+ @Mock
+ lateinit var orderPersister: OrderPersister
+
+ @Mock
+ lateinit var pairConfigLoader: PairConfigLoader
+
+ @Mock
+ lateinit var pairStaticRateLoader: PairStaticRateLoader
+
+ @Mock
+ lateinit var walletProxy: WalletProxy
+
+ @Mock
+ lateinit var tempEventPersister: TempEventPersister
+
+ @Mock
+ lateinit var tempEventRepublisher: TempEventRepublisher
+
+ @Mock
+ lateinit var richOrderPublisher: RichOrderPublisher
+
+ @Mock
+ lateinit var richTradePublisher: RichTradePublisher
+
+ val orderManager: OrderManager
+
+ val tradeManager: TradeManager
+
+
+ init {
+ MockitoAnnotations.openMocks(this)
+ orderManager = OrderManagerImpl(
+ pairConfigLoader,
+ financialActionPersister,
+ financeActionLoader,
+ orderPersister,
+ tempEventPersister,
+ tempEventRepublisher,
+ richOrderPublisher
+ )
+ tradeManager = TradeManagerImpl(
+ pairStaticRateLoader,
+ financialActionPersister,
+ financeActionLoader,
+ orderPersister,
+ tempEventPersister,
+ richTradePublisher,
+ walletProxy,
+ "pcoin",
+ "0x0"
+ )
+ runBlocking {
+ Mockito.`when`(tempEventPersister.loadTempEvents(ArgumentMatchers.anyString())).thenReturn(emptyList())
+ }
+ }
+
+ @Test
+ fun givenMatchOrders_whenTradeCreated_thenFAMatched() {
+ runBlocking {
+ //given
+ val pair = Pair("eth", "btc")
+ val pairConfig = co.nilin.opex.accountant.core.model.PairConfig(
+ pair.toString(), pair.leftSideName, pair.rightSideName, 1.0, 0.001
+ )
+ val makerSubmitOrderEvent = SubmitOrderEvent(
+ "mouid", "muuid", null, pair, 29, 60, 0, OrderDirection.ASK, MatchConstraint.GTC, OrderType.LIMIT_ORDER
+ )
+ prepareOrder(pair, pairConfig, makerSubmitOrderEvent, 0.1, 0.12)
+
+ val takerSubmitOrderEvent = SubmitOrderEvent(
+ "touid", "tuuid", null, pair, 30, 14, 0, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER
+ )
+
+ prepareOrder(pair, pairConfig, takerSubmitOrderEvent, 0.08, 0.1)
+
+ val tradeEvent = makeTradeEvent(pair, takerSubmitOrderEvent, makerSubmitOrderEvent)
+ //when
+ val tradeFinancialActions = tradeManager.handleTrade(tradeEvent)
+ Assertions.assertEquals(4, tradeFinancialActions.size)
+ }
+ }
+
+ @Test
+ fun givenMatchOrders2_whenTradeCreated_thenFAMatched() {
+ runBlocking {
+ //given
+ val pair = Pair("eth", "btc")
+ val pairConfig = co.nilin.opex.accountant.core.model.PairConfig(
+ pair.toString(), pair.leftSideName, pair.rightSideName, 0.000001, 0.000001
+ )
+ val makerSubmitOrderEvent = SubmitOrderEvent(
+ "mouid",
+ "muuid",
+ null,
+ pair,
+ 33333,
+ 5000000,
+ 0,
+ OrderDirection.ASK,
+ MatchConstraint.GTC,
+ OrderType.LIMIT_ORDER
+ )
+ prepareOrder(pair, pairConfig, makerSubmitOrderEvent, 0.8 * 0.001, 1.0 * 0.001)
+
+ val takerSubmitOrderEvent = SubmitOrderEvent(
+ "touid",
+ "tuuid",
+ null,
+ pair,
+ 34482,
+ 1000000,
+ 0,
+ OrderDirection.BID,
+ MatchConstraint.GTC,
+ OrderType.LIMIT_ORDER
+ )
+
+ prepareOrder(pair, pairConfig, takerSubmitOrderEvent, 0.8 * 0.001, 1.0 * 0.001)
+
+ val tradeEvent = makeTradeEvent(pair, takerSubmitOrderEvent, makerSubmitOrderEvent)
+ //when
+ val tradeFinancialActions = tradeManager.handleTrade(tradeEvent)
+ Assertions.assertEquals(4, tradeFinancialActions.size)
+ }
+ }
+
+ private fun makeTradeEvent(
+ pair: Pair,
+ takerSubmitOrderEvent: SubmitOrderEvent,
+ makerSubmitOrderEvent: SubmitOrderEvent
+ ): TradeEvent {
+ val tradeEvent = TradeEvent(
+ 0,
+ pair,
+ takerSubmitOrderEvent.ouid,
+ takerSubmitOrderEvent.uuid,
+ takerSubmitOrderEvent.orderId ?: -1,
+ takerSubmitOrderEvent.direction,
+ takerSubmitOrderEvent.price,
+ 0,
+ makerSubmitOrderEvent.ouid,
+ makerSubmitOrderEvent.uuid,
+ makerSubmitOrderEvent.orderId ?: 1,
+ makerSubmitOrderEvent.direction,
+ makerSubmitOrderEvent.price,
+ makerSubmitOrderEvent.quantity - takerSubmitOrderEvent.quantity,
+ takerSubmitOrderEvent.quantity
+ )
+ return tradeEvent
+ }
+
+ private fun prepareOrder(
+ pair: Pair,
+ pairConfig: co.nilin.opex.accountant.core.model.PairConfig,
+ submitOrderEvent: SubmitOrderEvent,
+ makerFee: Double,
+ takerFee: Double
+ ) {
+ runBlocking {
+ Mockito.`when`(pairConfigLoader.load(pair.toString(), submitOrderEvent.direction, ""))
+ .thenReturn(
+ co.nilin.opex.accountant.core.model.PairFeeConfig(
+ pairConfig,
+ submitOrderEvent.direction.toString(),
+ "",
+ makerFee,
+ takerFee
+ )
+ )
+ Mockito.`when`(financialActionPersister.persist(MockitoHelper.anyObject()))
+ .then {
+ return@then it.getArgument>(0)
+ }
+
+ val financialActions = orderManager.handleRequestOrder(submitOrderEvent)
+
+ val orderPairFeeConfig =
+ pairConfigLoader.load(submitOrderEvent.pair.toString(), submitOrderEvent.direction, "")
+ val orderMakerFee = orderPairFeeConfig.makerFee * 1 //user level formula
+ val orderTakerFee = orderPairFeeConfig.takerFee * 1 //user level formula
+ Mockito.`when`(orderPersister.load(submitOrderEvent.ouid)).thenReturn(
+ Order(
+ submitOrderEvent.pair.toString(),
+ submitOrderEvent.ouid,
+ null,
+ orderMakerFee,
+ orderTakerFee,
+ orderPairFeeConfig.pairConfig.leftSideFraction,
+ orderPairFeeConfig.pairConfig.rightSideFraction,
+ submitOrderEvent.uuid,
+ "",
+ submitOrderEvent.direction,
+ submitOrderEvent.matchConstraint,
+ submitOrderEvent.orderType,
+ submitOrderEvent.price,
+ submitOrderEvent.quantity,
+ submitOrderEvent.quantity - submitOrderEvent.remainedQuantity,
+ submitOrderEvent.price.toBigDecimal(),
+ submitOrderEvent.quantity.toBigDecimal(),
+ (submitOrderEvent.quantity - submitOrderEvent.remainedQuantity).toBigDecimal(),
+ financialActions[0].amount,
+ financialActions[0].amount,
+ 0
+ )
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/.gitignore b/Accountant/accountant-ports/.gitignore
new file mode 100644
index 000000000..f3a8317d6
--- /dev/null
+++ b/Accountant/accountant-ports/.gitignore
@@ -0,0 +1,4 @@
+*.iml
+target/
+.mvn/
+.idea/
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/.gitignore b/Accountant/accountant-ports/accountant-eventlistener-kafka/.gitignore
new file mode 100644
index 000000000..bb9840a17
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/.gitignore
@@ -0,0 +1,34 @@
+HELP.md
+target/
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+.mvn/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+
+
+### VS Code ###
+.vscode/
+
+.DS_Store
diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/mvnw b/Accountant/accountant-ports/accountant-eventlistener-kafka/mvnw
new file mode 100644
index 000000000..a16b5431b
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/mvnw
@@ -0,0 +1,310 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/mvnw.cmd b/Accountant/accountant-ports/accountant-eventlistener-kafka/mvnw.cmd
new file mode 100644
index 000000000..c8d43372c
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/pom.xml b/Accountant/accountant-ports/accountant-eventlistener-kafka/pom.xml
new file mode 100644
index 000000000..e85bab28d
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/pom.xml
@@ -0,0 +1,113 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.4.4
+
+
+ co.nilin.opex
+ accountant-eventlistener-kafka
+ 1.0-SNAPSHOT
+ accountant-eventlistener-kafka
+ Accountant kafka listener of Opex
+
+
+ 1.8
+ 1.4.31
+ ${version}
+ ${version}
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ co.nilin.opex
+ matching-core
+ ${matching.version}
+ provided
+
+
+ co.nilin.opex
+ accountant-core
+ ${accountant.version}
+ provided
+
+
+ org.springframework.kafka
+ spring-kafka
+
+
+ io.projectreactor.kotlin
+ reactor-kotlin-extensions
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-reactor
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-core
+
+
+ org.springframework.kafka
+ spring-kafka-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+
+
+
+
+
+
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/test/kotlin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ -Xjsr305=strict
+
+
+ spring
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-allopen
+ ${kotlin.version}
+
+
+
+
+
+
+
diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/config/AccountantKafkaConfig.kt b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/config/AccountantKafkaConfig.kt
new file mode 100644
index 000000000..6c4781a87
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/config/AccountantKafkaConfig.kt
@@ -0,0 +1,128 @@
+package co.nilin.opex.port.accountant.kafka.config
+
+
+import co.nilin.opex.matching.core.eventh.events.CoreEvent
+import co.nilin.opex.port.accountant.kafka.consumer.OrderKafkaListener
+import co.nilin.opex.port.accountant.kafka.consumer.EventKafkaListener
+import co.nilin.opex.port.accountant.kafka.consumer.TempEventKafkaListener
+import co.nilin.opex.port.accountant.kafka.consumer.TradeKafkaListener
+import org.apache.kafka.clients.admin.NewTopic
+import org.apache.kafka.clients.consumer.ConsumerConfig
+import org.apache.kafka.clients.producer.ProducerConfig
+import org.apache.kafka.common.serialization.StringDeserializer
+import org.apache.kafka.common.serialization.StringSerializer
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.beans.factory.annotation.Qualifier
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.context.support.GenericApplicationContext
+import org.springframework.kafka.core.ConsumerFactory
+import org.springframework.kafka.core.DefaultKafkaConsumerFactory
+import org.springframework.kafka.core.DefaultKafkaProducerFactory
+import org.springframework.kafka.core.KafkaTemplate
+import org.springframework.kafka.core.ProducerFactory
+import org.springframework.kafka.listener.ConcurrentMessageListenerContainer
+import org.springframework.kafka.listener.ContainerProperties
+import org.springframework.kafka.support.serializer.JsonDeserializer
+import org.springframework.kafka.support.serializer.JsonSerializer
+import java.util.*
+import java.util.regex.Pattern
+
+
+@Configuration
+class AccountantKafkaConfig {
+ @Value("\${spring.kafka.bootstrap-servers}")
+ private val bootstrapServers: String? = null
+
+ @Value("\${spring.kafka.consumer.group-id}")
+ private val groupId: String? = null
+
+ @Bean("accountantConsumerConfig")
+ fun consumerConfigs(): Map? {
+ val props: MutableMap = HashMap()
+ props[ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers
+ props[ConsumerConfig.GROUP_ID_CONFIG] = groupId
+ props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java
+ props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = JsonDeserializer::class.java
+ props[JsonDeserializer.TRUSTED_PACKAGES] = "co.nilin.opex.*"
+ return props
+ }
+
+ @Bean("accountantConsumerFactory")
+ fun consumerFactory(@Qualifier("accountantConsumerConfig")consumerConfigs: Map): ConsumerFactory {
+ return DefaultKafkaConsumerFactory(consumerConfigs)
+ }
+
+ @Bean("accountantProducerConfig")
+ fun producerConfigs(): Map {
+ val props: MutableMap = HashMap()
+ props[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers
+ props[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java
+ props[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = JsonSerializer::class.java
+ return props
+ }
+
+ @Bean("accountantProducerFactory")
+ fun producerFactory(@Qualifier("accountantProducerConfig") producerConfigs: Map): ProducerFactory {
+ return DefaultKafkaProducerFactory(producerConfigs)
+ }
+
+ @Bean("accountantKafkaTemplate")
+ fun kafkaTemplate(@Qualifier("accountantProducerFactory") producerFactory: ProducerFactory): KafkaTemplate {
+ return KafkaTemplate(producerFactory)
+ }
+
+
+ @Autowired
+ @ConditionalOnBean(TradeKafkaListener::class)
+ fun configureTradeListener(tradeListener: TradeKafkaListener
+ , @Qualifier("accountantConsumerFactory") consumerFactory: ConsumerFactory) {
+ val containerProps = ContainerProperties(Pattern.compile("trades_.*"))
+ containerProps.messageListener = tradeListener
+ val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps)
+ container.setBeanName("TradeKafkaListenerContainer")
+ container.start()
+ }
+
+ @Autowired
+ @ConditionalOnBean(EventKafkaListener::class)
+ fun configureEventListener(eventListener: EventKafkaListener
+ , @Qualifier("accountantConsumerFactory") consumerFactory: ConsumerFactory) {
+ val containerProps = ContainerProperties(Pattern.compile("events_.*"))
+ containerProps.messageListener = eventListener
+ val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps)
+ container.setBeanName("EventKafkaListenerContainer")
+ container.start()
+ }
+
+ @Autowired
+ @ConditionalOnBean(OrderKafkaListener::class)
+ fun configureOrderListener(orderListener: OrderKafkaListener
+ , @Qualifier("accountantConsumerFactory") consumerFactory: ConsumerFactory) {
+ val containerProps = ContainerProperties(Pattern.compile("orders_.*"))
+ containerProps.messageListener = orderListener
+ val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps)
+ container.setBeanName("OrderKafkaListenerContainer")
+ container.start()
+ }
+
+ @Autowired
+ fun createTempTopics(applicationContext: GenericApplicationContext){
+ applicationContext.registerBean("topic_tempevents", NewTopic::class.java, "tempevents", 1 ,1)
+ }
+
+ @Autowired
+ @ConditionalOnBean(TempEventKafkaListener::class)
+ fun configureTempEventListener(eventListener: TempEventKafkaListener
+ , @Qualifier("accountantConsumerFactory") consumerFactory: ConsumerFactory) {
+ val containerProps = ContainerProperties(Pattern.compile("tempevents"))
+ containerProps.messageListener = eventListener
+ val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps)
+ container.setBeanName("TempEventKafkaListenerContainer")
+ container.start()
+ }
+
+
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/EventKafkaListener.kt b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/EventKafkaListener.kt
new file mode 100644
index 000000000..2f86674d6
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/EventKafkaListener.kt
@@ -0,0 +1,29 @@
+package co.nilin.opex.port.accountant.kafka.consumer
+
+import co.nilin.opex.matching.core.eventh.events.CoreEvent
+import co.nilin.opex.port.accountant.kafka.spi.EventListener
+import org.apache.kafka.clients.consumer.ConsumerRecord
+import org.springframework.kafka.listener.MessageListener
+import org.springframework.stereotype.Component
+
+@Component
+class EventKafkaListener: MessageListener {
+
+ val eventListeners = arrayListOf()
+
+ override fun onMessage(data: ConsumerRecord) {
+ eventListeners.forEach{
+ tl -> tl.onEvent(data.value(), data.partition(), data.offset(), data.timestamp())
+ }
+ }
+
+ fun addEventListener(tl: EventListener){
+ eventListeners.add(tl)
+ }
+
+ fun removeEventListener(tl: EventListener){
+ eventListeners.removeIf {
+ item -> item.id() == tl.id()
+ }
+ }
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/OrderKafkaListener.kt b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/OrderKafkaListener.kt
new file mode 100644
index 000000000..208b7e039
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/OrderKafkaListener.kt
@@ -0,0 +1,30 @@
+package co.nilin.opex.port.accountant.kafka.consumer
+
+import co.nilin.opex.port.accountant.kafka.spi.OrderSubmitRequestListener
+import co.nilin.opex.port.order.kafka.inout.OrderSubmitRequest
+import org.apache.kafka.clients.consumer.ConsumerRecord
+import org.springframework.kafka.listener.MessageListener
+import org.springframework.stereotype.Component
+
+@Component
+class OrderKafkaListener : MessageListener {
+
+ val orderListeners = arrayListOf()
+
+ override fun onMessage(data: ConsumerRecord) {
+ orderListeners.forEach { tl ->
+ tl.onOrder(data.value(), data.partition(), data.offset(), data.timestamp())
+ }
+
+ }
+
+ fun addOrderListener(tl: OrderSubmitRequestListener) {
+ orderListeners.add(tl)
+ }
+
+ fun removeOrderListener(tl: OrderSubmitRequestListener) {
+ orderListeners.removeIf { item ->
+ item.id() == tl.id()
+ }
+ }
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/TempEventKafkaListener.kt b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/TempEventKafkaListener.kt
new file mode 100644
index 000000000..2e9ef8b6a
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/TempEventKafkaListener.kt
@@ -0,0 +1,31 @@
+package co.nilin.opex.port.accountant.kafka.consumer
+
+
+import co.nilin.opex.matching.core.eventh.events.CoreEvent
+import co.nilin.opex.port.accountant.kafka.spi.TempEventListener
+import org.apache.kafka.clients.consumer.ConsumerRecord
+import org.springframework.kafka.listener.MessageListener
+import org.springframework.stereotype.Component
+
+@Component
+class TempEventKafkaListener: MessageListener {
+
+ val eventListeners = arrayListOf()
+
+ override fun onMessage(data: ConsumerRecord) {
+ println("TempEvent onMessage")
+ eventListeners.forEach{
+ tl -> tl.onEvent(data.value(), data.partition(), data.offset(), data.timestamp())
+ }
+ }
+
+ fun addEventListener(tl: TempEventListener){
+ eventListeners.add(tl)
+ }
+
+ fun removeEventListener(tl: TempEventListener){
+ eventListeners.removeIf {
+ item -> item.id() == tl.id()
+ }
+ }
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/TradeKafkaListener.kt b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/TradeKafkaListener.kt
new file mode 100644
index 000000000..6d91a0c8a
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/consumer/TradeKafkaListener.kt
@@ -0,0 +1,29 @@
+package co.nilin.opex.port.accountant.kafka.consumer
+
+import co.nilin.opex.matching.core.eventh.events.TradeEvent
+import co.nilin.opex.port.accountant.kafka.spi.TradeListener
+import org.apache.kafka.clients.consumer.ConsumerRecord
+import org.springframework.kafka.listener.MessageListener
+import org.springframework.stereotype.Component
+
+@Component
+class TradeKafkaListener: MessageListener {
+
+ val tradeListeners = arrayListOf()
+
+ override fun onMessage(data: ConsumerRecord) {
+ tradeListeners.forEach{
+ tl -> tl.onTrade(data.value(), data.partition(), data.offset(), data.timestamp())
+ }
+ }
+
+ fun addTradeListener(tl: TradeListener){
+ tradeListeners.add(tl)
+ }
+
+ fun removeTradeListener(tl: TradeListener){
+ tradeListeners.removeIf {
+ item -> item.id() == tl.id()
+ }
+ }
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/EventListener.kt b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/EventListener.kt
new file mode 100644
index 000000000..4bc2c860f
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/EventListener.kt
@@ -0,0 +1,8 @@
+package co.nilin.opex.port.accountant.kafka.spi
+
+import co.nilin.opex.matching.core.eventh.events.CoreEvent
+
+interface EventListener {
+ fun id(): String
+ fun onEvent(coreEvent: CoreEvent, partition: Int, offset: Long, timestamp: Long)
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/OrderSubmitRequestListener.kt b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/OrderSubmitRequestListener.kt
new file mode 100644
index 000000000..a956db9fe
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/OrderSubmitRequestListener.kt
@@ -0,0 +1,8 @@
+package co.nilin.opex.port.accountant.kafka.spi
+
+import co.nilin.opex.port.order.kafka.inout.OrderSubmitRequest
+
+interface OrderSubmitRequestListener {
+ fun id(): String
+ fun onOrder(order: OrderSubmitRequest, partition: Int, offset: Long, timestamp: Long)
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/TempEventListener.kt b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/TempEventListener.kt
new file mode 100644
index 000000000..76e14cce2
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/TempEventListener.kt
@@ -0,0 +1,8 @@
+package co.nilin.opex.port.accountant.kafka.spi
+
+import co.nilin.opex.matching.core.eventh.events.CoreEvent
+
+interface TempEventListener {
+ fun id(): String
+ fun onEvent(coreEvent: CoreEvent, partition: Int, offset: Long, timestamp: Long)
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/TradeListener.kt b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/TradeListener.kt
new file mode 100644
index 000000000..f4f958b97
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/spi/TradeListener.kt
@@ -0,0 +1,8 @@
+package co.nilin.opex.port.accountant.kafka.spi
+
+import co.nilin.opex.matching.core.eventh.events.TradeEvent
+
+interface TradeListener {
+ fun id(): String
+ fun onTrade(tradeEvent: TradeEvent, partition: Int, offset: Long, timestamp: Long)
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/inout/OrderSubmitRequest.kt b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/inout/OrderSubmitRequest.kt
new file mode 100644
index 000000000..db9f6e74f
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/order/kafka/inout/OrderSubmitRequest.kt
@@ -0,0 +1,43 @@
+package co.nilin.opex.port.order.kafka.inout
+
+import co.nilin.opex.matching.core.model.MatchConstraint
+import co.nilin.opex.matching.core.model.OrderDirection
+import co.nilin.opex.matching.core.model.OrderType
+import co.nilin.opex.matching.core.model.Pair
+
+class OrderSubmitRequest() {
+
+ lateinit var ouid: String
+ lateinit var uuid: String
+ var orderId: Long? = null
+ lateinit var pair: Pair
+ var price: Long = 0
+ var quantity: Long = 0
+ var direction: OrderDirection = OrderDirection.BID
+ var matchConstraint: MatchConstraint = MatchConstraint.GTC
+ var orderType: OrderType = OrderType.LIMIT_ORDER
+
+ constructor(
+ ouid: String,
+ uuid: String,
+ orderId: Long?,
+ pair: Pair,
+ price: Long,
+ quantity: Long,
+ direction: OrderDirection,
+ matchConstraint: MatchConstraint,
+ orderType: OrderType
+ ) : this() {
+ this.ouid = ouid
+ this.uuid = uuid
+ this.orderId = orderId
+ this.pair = pair
+ this.price = price
+ this.quantity = quantity
+ this.direction = direction
+ this.matchConstraint = matchConstraint
+ this.orderType = orderType
+ }
+
+
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-persister-postgres/.gitignore b/Accountant/accountant-ports/accountant-persister-postgres/.gitignore
new file mode 100644
index 000000000..de5a9214d
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-persister-postgres/.gitignore
@@ -0,0 +1,34 @@
+HELP.md
+target/
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+.mvn/
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+.DS_Store
diff --git a/Accountant/accountant-ports/accountant-persister-postgres/mvnw b/Accountant/accountant-ports/accountant-persister-postgres/mvnw
new file mode 100644
index 000000000..a16b5431b
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-persister-postgres/mvnw
@@ -0,0 +1,310 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/Accountant/accountant-ports/accountant-persister-postgres/mvnw.cmd b/Accountant/accountant-ports/accountant-persister-postgres/mvnw.cmd
new file mode 100644
index 000000000..c8d43372c
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-persister-postgres/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/Accountant/accountant-ports/accountant-persister-postgres/pom.xml b/Accountant/accountant-ports/accountant-persister-postgres/pom.xml
new file mode 100644
index 000000000..db635b291
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-persister-postgres/pom.xml
@@ -0,0 +1,122 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.4.4
+
+
+ co.nilin.opex
+ accountant-persister-postgres
+ 1.0-SNAPSHOT
+ accountant-persister-postgres
+ Persist items of Opex accountant on Postgres
+
+
+ 1.8
+ 1.4.31
+ ${version}
+ ${version}
+
+
+
+
+ co.nilin.opex
+ matching-core
+ ${matching.version}
+ provided
+
+
+ co.nilin.opex
+ accountant-core
+ ${accountant.version}
+ provided
+
+
+ org.springframework.boot
+ spring-boot-starter-data-r2dbc
+
+
+ io.r2dbc
+ r2dbc-postgresql
+ runtime
+
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+
+ io.projectreactor.kotlin
+ reactor-kotlin-extensions
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-reactor
+
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-core
+
+
+ com.google.code.gson
+ gson
+
+
+ co.nilin.opex
+ error-handler
+ 1.0-SNAPSHOT
+
+
+ io.projectreactor
+ reactor-test
+ test
+
+
+
+
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/test/kotlin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ -Xjsr305=strict
+
+
+ spring
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-allopen
+ ${kotlin.version}
+
+
+
+
+
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+
+
diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/config/PostgresConfig.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/config/PostgresConfig.kt
new file mode 100644
index 000000000..cb04c037f
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/config/PostgresConfig.kt
@@ -0,0 +1,99 @@
+package co.nilin.opex.port.accountant.postgres.config
+
+import org.springframework.context.annotation.Configuration
+import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories
+import org.springframework.r2dbc.core.DatabaseClient
+
+@Configuration
+@EnableR2dbcRepositories(basePackages = ["co.nilin.opex"])
+class PostgresConfig(db: DatabaseClient) {
+
+ init {
+ val sql = """
+ CREATE TABLE IF NOT EXISTS orders (
+ id SERIAL PRIMARY KEY,
+ ouid VARCHAR(72) NOT NULL UNIQUE,
+ uuid VARCHAR(72) NOT NULL,
+ pair VARCHAR(20),
+ matching_engine_id numeric,
+ maker_fee decimal,
+ taker_fee decimal,
+ left_side_fraction decimal,
+ right_side_fraction decimal,
+ user_level VARCHAR(20),
+ direction VARCHAR(20),
+ match_constraint VARCHAR(30),
+ order_type VARCHAR(30),
+ price numeric,
+ quantity numeric,
+ filled_quantity numeric,
+ orig_price decimal,
+ orig_quantity decimal,
+ filled_orig_quantity decimal,
+ first_transfer_amount numeric,
+ remained_transfer_amount numeric,
+ status integer,
+ agent VARCHAR(20),
+ ip VARCHAR(11),
+ create_date TIMESTAMP
+ );
+
+ CREATE TABLE IF NOT EXISTS fi_actions (
+ id SERIAL PRIMARY KEY,
+ parent_id numeric,
+ event_type VARCHAR(72) NOT NULL,
+ pointer VARCHAR(72) NOT NULL,
+ symbol VARCHAR(36),
+ amount decimal,
+ sender VARCHAR(36),
+ sender_wallet_type VARCHAR(36),
+ receiver VARCHAR(36),
+ receiver_wallet_type VARCHAR(36),
+ agent VARCHAR(20),
+ ip VARCHAR(11),
+ create_date TIMESTAMP,
+ status VARCHAR(20),
+ retry_count numeric,
+ last_try_date TIMESTAMP
+ );
+ CREATE TABLE IF NOT EXISTS pair_config (
+ pair VARCHAR(72) PRIMARY KEY,
+ left_side_wallet_symbol VARCHAR(36) NOT NULL,
+ right_side_wallet_symbol VARCHAR(36) NOT NULL,
+ left_side_fraction decimal,
+ right_side_fraction decimal,
+ rate decimal
+ );
+ CREATE TABLE IF NOT EXISTS pair_fee_config (
+ id SERIAL PRIMARY KEY,
+ pair_config_id VARCHAR(72),
+ direction VARCHAR(36) NOT NULL,
+ user_level VARCHAR(36) NOT NULL,
+ maker_fee decimal,
+ taker_fee decimal
+ );
+
+ CREATE TABLE IF NOT EXISTS temp_events (
+ id SERIAL PRIMARY KEY,
+ ouid VARCHAR(72) NOT NULL,
+ event_type VARCHAR(72) NOT NULL,
+ event_body TEXT,
+ event_date TIMESTAMP
+ );
+ insert into pair_config values('btc_usdt', 'btc', 'usdt', 0.000001, 0.01, 55000)ON CONFLICT DO NOTHING;
+ insert into pair_fee_config values(1, 'btc_usdt', 'ASK', '*', 0.01, 0.01) ON CONFLICT DO NOTHING;
+ insert into pair_fee_config values(2, 'btc_usdt', 'BID', '*', 0.01, 0.01) ON CONFLICT DO NOTHING;
+ insert into pair_config values('nln_usdt', 'nln', 'usdt', 1.0, 0.01, 0.01) ON CONFLICT DO NOTHING;
+ insert into pair_fee_config values(3, 'nln_usdt', 'ASK', '*', 0.01, 0.01) ON CONFLICT DO NOTHING;
+ insert into pair_fee_config values(4, 'nln_usdt', 'BID', '*', 0.01, 0.01) ON CONFLICT DO NOTHING;
+ insert into pair_config values('nln_btc', 'nln', 'btc', 1.0, 0.000001, 1/5500000) ON CONFLICT DO NOTHING;
+ insert into pair_fee_config values(5, 'nln_btc', 'ASK', '*', 0.01, 0.01) ON CONFLICT DO NOTHING;
+ insert into pair_fee_config values(6, 'nln_btc', 'BID', '*', 0.01, 0.01) ON CONFLICT DO NOTHING;
+ commit;
+ """
+ val initDb = db.sql { sql }
+ initDb // initialize the database
+ .then()
+ .subscribe() // execute
+ }
+}
diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/FinancialActionRepository.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/FinancialActionRepository.kt
new file mode 100644
index 000000000..3113cdcde
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/FinancialActionRepository.kt
@@ -0,0 +1,32 @@
+package co.nilin.opex.port.accountant.postgres.dao
+
+import co.nilin.opex.accountant.core.model.FinancialActionStatus
+import co.nilin.opex.port.accountant.postgres.model.FinancialActionModel
+import kotlinx.coroutines.flow.Flow
+import org.springframework.data.domain.Pageable
+import org.springframework.data.r2dbc.repository.Query
+import org.springframework.data.repository.query.Param
+import org.springframework.data.repository.reactive.ReactiveCrudRepository
+import org.springframework.stereotype.Repository
+import reactor.core.publisher.Mono
+import java.math.BigDecimal
+
+@Repository
+interface FinancialActionRepository : ReactiveCrudRepository {
+
+ @Query("select * from fi_actions fi where pointer = :ouid and :uuid in (fi.sender, fi.receiver)")
+ fun findByOuidAndUuid(
+ @Param("ouid") ouid: String, @Param("uuid") uuid: String, paging: Pageable
+ ): Flow
+
+ @Query("select count(1) from fi_actions fi where fi.sender = :uuid and fi.symbol = :symbol and fi.event_type = :eventType and fi.status = :status")
+ fun findByUuidAndSymbolAndEventTypeAndStatus(
+ @Param("uuid") uuid: String,
+ @Param("symbol") symbol: String,
+ @Param("eventType") eventType: String,
+ @Param("status") financialActionStatus: FinancialActionStatus
+ ): Mono
+
+ @Query("select * from fi_actions fi where status = :status")
+ fun findByStatus(@Param("status") status: String, paging: Pageable): Flow
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/OrderRepository.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/OrderRepository.kt
new file mode 100644
index 000000000..adf817a93
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/OrderRepository.kt
@@ -0,0 +1,14 @@
+package co.nilin.opex.port.accountant.postgres.dao
+
+import co.nilin.opex.port.accountant.postgres.model.OrderModel
+import org.springframework.data.r2dbc.repository.Query
+import org.springframework.data.repository.query.Param
+import org.springframework.data.repository.reactive.ReactiveCrudRepository
+import org.springframework.stereotype.Repository
+import reactor.core.publisher.Mono
+
+@Repository
+interface OrderRepository: ReactiveCrudRepository {
+ @Query("select * from orders where ouid = :ouid")
+ fun findByOuid(@Param("ouid") ouid: String): Mono
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/PairConfigRepository.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/PairConfigRepository.kt
new file mode 100644
index 000000000..b43a4bb84
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/PairConfigRepository.kt
@@ -0,0 +1,9 @@
+package co.nilin.opex.port.accountant.postgres.dao
+
+import co.nilin.opex.port.accountant.postgres.model.PairConfigModel
+import org.springframework.data.repository.reactive.ReactiveCrudRepository
+import org.springframework.stereotype.Repository
+
+@Repository
+interface PairConfigRepository: ReactiveCrudRepository {
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/PairFeeConfigRepository.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/PairFeeConfigRepository.kt
new file mode 100644
index 000000000..60903c0c9
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/PairFeeConfigRepository.kt
@@ -0,0 +1,20 @@
+package co.nilin.opex.port.accountant.postgres.dao
+
+import co.nilin.opex.matching.core.model.OrderDirection
+import co.nilin.opex.port.accountant.postgres.model.PairConfigModel
+import co.nilin.opex.port.accountant.postgres.model.PairFeeConfigModel
+import org.springframework.data.r2dbc.repository.Query
+import org.springframework.data.repository.query.Param
+import org.springframework.data.repository.reactive.ReactiveCrudRepository
+import org.springframework.stereotype.Repository
+import reactor.core.publisher.Mono
+
+@Repository
+interface PairFeeConfigRepository : ReactiveCrudRepository {
+ @Query("select * from pair_fee_config where pair_config_id = :pair and direction = :direction and user_level = :userLevel")
+ fun findByPairAndDirectionAndUserLevel(
+ @Param("pair") pair: String,
+ @Param("direction") direction: OrderDirection,
+ @Param("userLevel") userLevel: String
+ ): Mono
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/TempEventRepository.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/TempEventRepository.kt
new file mode 100644
index 000000000..6f89925f2
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/dao/TempEventRepository.kt
@@ -0,0 +1,19 @@
+package co.nilin.opex.port.accountant.postgres.dao
+
+import co.nilin.opex.port.accountant.postgres.model.TempEventModel
+import kotlinx.coroutines.flow.Flow
+import org.springframework.data.domain.Pageable
+import org.springframework.data.r2dbc.repository.Query
+import org.springframework.data.repository.reactive.ReactiveCrudRepository
+import org.springframework.stereotype.Repository
+import reactor.core.publisher.Mono
+
+@Repository
+interface TempEventRepository : ReactiveCrudRepository {
+
+ fun findByOuid(ouid: String): Flow
+ fun deleteByOuid(ouid: String): Mono
+
+ @Query("select * from temp_events")
+ fun findAll(paging: Pageable): Flow
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/FinancialActionLoaderImpl.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/FinancialActionLoaderImpl.kt
new file mode 100644
index 000000000..e7cba4d18
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/FinancialActionLoaderImpl.kt
@@ -0,0 +1,66 @@
+package co.nilin.opex.port.accountant.postgres.impl
+
+import co.nilin.opex.accountant.core.model.FinancialAction
+import co.nilin.opex.accountant.core.model.FinancialActionStatus
+import co.nilin.opex.accountant.core.spi.FinancialActionLoader
+import co.nilin.opex.port.accountant.postgres.dao.FinancialActionRepository
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.reactive.awaitFirst
+import kotlinx.coroutines.reactive.awaitFirstOrElse
+import org.springframework.data.domain.PageRequest
+import org.springframework.data.domain.Sort
+import org.springframework.stereotype.Component
+import java.math.BigDecimal
+
+@Component
+class FinancialActionLoaderImpl(val financialActionRepository: FinancialActionRepository) : FinancialActionLoader {
+
+ override suspend fun loadUnprocessed(offset: Long, size: Long): List {
+ return financialActionRepository.findByStatus(
+ FinancialActionStatus.CREATED.name,
+ PageRequest.of(offset.toInt(), size.toInt(), Sort.by(Sort.Direction.ASC, "createDate"))
+ ).map { fim ->
+ loadFinancialAction(fim.id)!!
+ }.toList()
+ }
+
+ override suspend fun findLast(uuid: String, ouid: String): FinancialAction? {
+ return financialActionRepository.findByOuidAndUuid(
+ ouid, uuid, PageRequest.of(0, 1, Sort.by(Sort.Direction.DESC, "createDate"))
+ ).map { fim ->
+ loadFinancialAction(fim.id)
+ }.firstOrNull()
+ }
+
+ private suspend fun loadFinancialAction(id: Long?): FinancialAction? {
+ if (id != null) {
+ val fim = financialActionRepository.findById(id).awaitFirst()
+ return FinancialAction(
+ fim.id,
+ loadFinancialAction(fim.parentId),
+ fim.eventType,
+ fim.pointer,
+ fim.symbol,
+ BigDecimal.valueOf(fim.amount),
+ fim.sender,
+ fim.senderWalletType,
+ fim.receiver,
+ fim.receiverWalletType,
+ fim.createDate
+ )
+ }
+ return null
+ }
+
+ override suspend fun countUnprocessed(uuid: String, symbol: String, eventType: String): Long {
+ return financialActionRepository.findByUuidAndSymbolAndEventTypeAndStatus(
+ uuid,
+ symbol,
+ eventType,
+ FinancialActionStatus.CREATED
+ ).awaitFirstOrElse { BigDecimal.ZERO }
+ .toLong()
+ }
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/FinancialActionPersisterImpl.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/FinancialActionPersisterImpl.kt
new file mode 100644
index 000000000..6af5107d7
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/FinancialActionPersisterImpl.kt
@@ -0,0 +1,65 @@
+package co.nilin.opex.port.accountant.postgres.impl
+
+import co.nilin.opex.accountant.core.model.FinancialAction
+import co.nilin.opex.accountant.core.model.FinancialActionStatus
+import co.nilin.opex.accountant.core.spi.FinancialActionPersister
+import co.nilin.opex.port.accountant.postgres.dao.FinancialActionRepository
+import co.nilin.opex.port.accountant.postgres.model.FinancialActionModel
+import kotlinx.coroutines.reactive.awaitFirst
+import kotlinx.coroutines.reactive.awaitFirstOrElse
+import kotlinx.coroutines.reactive.awaitLast
+import org.springframework.stereotype.Component
+import java.lang.IllegalArgumentException
+import java.time.LocalDateTime
+
+@Component
+class FinancialActionPersisterImpl(val financialActionRepository: FinancialActionRepository) :
+ FinancialActionPersister {
+
+ override suspend fun persist(financialActions: List): List {
+ financialActionRepository.saveAll(financialActions.map { fa ->
+ FinancialActionModel(
+ null,
+ fa.parent?.id,
+ fa.eventType,
+ fa.pointer,
+ fa.symbol,
+ fa.amount.toDouble(),
+ fa.sender,
+ fa.senderWalletType,
+ fa.receiver,
+ fa.receiverWalletType,
+ "",
+ "",
+ fa.createDate
+ )
+ }).awaitLast()
+ return financialActions
+ }
+
+ override suspend fun updateStatus(financialAction: FinancialAction, status: FinancialActionStatus) {
+ val existing = financialActionRepository.findById(financialAction.id!!).awaitFirstOrElse {
+ throw IllegalArgumentException()
+ }
+ financialActionRepository.save(
+ FinancialActionModel(
+ existing.id,
+ existing.parentId,
+ existing.eventType,
+ existing.pointer,
+ existing.symbol,
+ existing.amount,
+ existing.sender,
+ existing.senderWalletType,
+ existing.receiver,
+ existing.receiverWalletType,
+ existing.agent,
+ existing.ip,
+ existing.createDate,
+ status,
+ 1 + (existing.retryCount ?: 0),
+ LocalDateTime.now()
+ )
+ ).awaitFirst()
+ }
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/OrderPersisterImpl.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/OrderPersisterImpl.kt
new file mode 100644
index 000000000..094d49e67
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/OrderPersisterImpl.kt
@@ -0,0 +1,74 @@
+package co.nilin.opex.port.accountant.postgres.impl
+
+import co.nilin.opex.accountant.core.model.Order
+import co.nilin.opex.accountant.core.spi.OrderPersister
+import co.nilin.opex.port.accountant.postgres.dao.OrderRepository
+import co.nilin.opex.port.accountant.postgres.model.OrderModel
+import kotlinx.coroutines.reactive.awaitFirstOrNull
+import org.springframework.stereotype.Component
+import java.time.LocalDateTime
+
+@Component
+class OrderPersisterImpl(val orderRepository: OrderRepository) : OrderPersister {
+
+ override suspend fun load(ouid: String): Order? {
+ val model = orderRepository.findByOuid(ouid).awaitFirstOrNull() ?: return null
+ return Order(
+ model.pair,
+ model.ouid,
+ model.matchingEngineId,
+ model.makerFee,
+ model.takerFee,
+ model.leftSideFraction,
+ model.rightSideFraction,
+ model.uuid,
+ model.userLevel,
+ model.direction,
+ model.matchConstraint,
+ model.orderType,
+ model.price,
+ model.quantity,
+ model.filledQuantity,
+ model.origPrice,
+ model.origQuantity,
+ model.filledOrigQuantity,
+ model.firstTransferAmount,
+ model.remainedTransferAmount,
+ model.status,
+ model.id
+ )
+ }
+
+ override suspend fun save(order: Order): Order {
+ orderRepository.save(
+ OrderModel(
+ order.id,
+ order.ouid,
+ order.uuid,
+ order.pair,
+ order.matchingEngineId,
+ order.makerFee,
+ order.takerFee,
+ order.leftSideFraction,
+ order.rightSideFraction,
+ order.userLevel,
+ order.direction,
+ order.matchConstraint,
+ order.orderType,
+ order.price,
+ order.quantity,
+ order.filledQuantity,
+ order.origPrice,
+ order.origQuantity,
+ order.filledOrigQuantity,
+ order.firstTransferAmount,
+ order.remainedTransferAmount,
+ order.status,
+ "",
+ "",
+ LocalDateTime.now()
+ )
+ ).awaitFirstOrNull()
+ return order
+ }
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/PairConfigLoaderImpl.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/PairConfigLoaderImpl.kt
new file mode 100644
index 000000000..5c499242c
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/PairConfigLoaderImpl.kt
@@ -0,0 +1,75 @@
+package co.nilin.opex.port.accountant.postgres.impl
+
+import co.nilin.opex.accountant.core.model.PairConfig
+import co.nilin.opex.accountant.core.model.PairFeeConfig
+import co.nilin.opex.accountant.core.spi.PairConfigLoader
+import co.nilin.opex.matching.core.model.OrderDirection
+import co.nilin.opex.port.accountant.postgres.dao.PairConfigRepository
+import co.nilin.opex.port.accountant.postgres.dao.PairFeeConfigRepository
+import co.nilin.opex.port.accountant.postgres.model.PairFeeConfigModel
+import co.nilin.opex.utility.error.data.OpexError
+import co.nilin.opex.utility.error.data.OpexException
+import kotlinx.coroutines.reactive.awaitFirstOrElse
+import kotlinx.coroutines.reactive.awaitFirstOrNull
+import org.springframework.stereotype.Component
+
+@Component
+class PairConfigLoaderImpl(
+ val pairConfigRepository: PairConfigRepository, val pairFeeConfigRepository: PairFeeConfigRepository
+) : PairConfigLoader {
+
+ override suspend fun loadPairConfigs(): List {
+ return pairConfigRepository.findAll()
+ .collectList()
+ .awaitFirstOrElse { emptyList() }
+ .map {
+ PairConfig(
+ it.pair,
+ it.leftSideWalletSymbol,
+ it.rightSideWalletSymbol,
+ it.leftSideFraction,
+ it.rightSideFraction
+ )
+ }
+ }
+
+ override suspend fun load(pair: String, direction: OrderDirection, userLevel: String): PairFeeConfig {
+ val pairConfig = pairConfigRepository
+ .findById(pair).awaitFirstOrElse {
+ val error = OpexError.InvalidPair
+ throw OpexException(error, String.format(error.message!!, pair))
+ }
+ var pairFeeConfig: PairFeeConfigModel?
+ if (userLevel.isEmpty()) {
+ pairFeeConfig = pairFeeConfigRepository
+ .findByPairAndDirectionAndUserLevel(pair, direction, "*")
+ .awaitFirstOrElse {
+ val error = OpexError.InvalidPair
+ throw OpexException(error, String.format(error.message!!, pair))
+ }
+ } else {
+ pairFeeConfig = pairFeeConfigRepository
+ .findByPairAndDirectionAndUserLevel(pair, direction, userLevel)
+ .awaitFirstOrNull()
+ if (pairFeeConfig == null) {
+ pairFeeConfig = pairFeeConfigRepository
+ .findByPairAndDirectionAndUserLevel(pair, direction, "*")
+ .awaitFirstOrElse {
+ val error = OpexError.InvalidPairFee
+ throw OpexException(error, String.format(error.message!!, pair))
+ }
+ }
+ }
+
+ return PairFeeConfig(
+ PairConfig(
+ pair,
+ pairConfig.leftSideWalletSymbol,
+ pairConfig.rightSideWalletSymbol,
+ pairConfig.leftSideFraction,
+ pairConfig.rightSideFraction
+ ), pairFeeConfig!!.direction, pairFeeConfig.userLevel, pairFeeConfig.makerFee, pairFeeConfig.takerFee
+ )
+
+ }
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/PairStaticRateLoaderImpl.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/PairStaticRateLoaderImpl.kt
new file mode 100644
index 000000000..ea4345ff9
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/PairStaticRateLoaderImpl.kt
@@ -0,0 +1,18 @@
+package co.nilin.opex.port.accountant.postgres.impl
+
+import co.nilin.opex.accountant.core.spi.PairStaticRateLoader
+import co.nilin.opex.port.accountant.postgres.dao.PairConfigRepository
+import kotlinx.coroutines.reactive.awaitFirstOrElse
+import org.springframework.stereotype.Component
+import java.lang.IllegalArgumentException
+
+@Component
+class PairStaticRateLoaderImpl(val pairConfigRepository: PairConfigRepository) : PairStaticRateLoader {
+
+ override suspend fun calculateStaticRate(leftSide: String, rightSide: String): Double? {
+ val pairConfig = pairConfigRepository
+ .findById("${leftSide}_$rightSide")
+ .awaitFirstOrElse { throw IllegalArgumentException("${leftSide}_$rightSide is not available") }
+ return pairConfig.rate
+ }
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/TempEventPersisterImpl.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/TempEventPersisterImpl.kt
new file mode 100644
index 000000000..7c723b392
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/impl/TempEventPersisterImpl.kt
@@ -0,0 +1,67 @@
+package co.nilin.opex.port.accountant.postgres.impl
+
+import co.nilin.opex.accountant.core.model.TempEvent
+import co.nilin.opex.accountant.core.spi.TempEventPersister
+import co.nilin.opex.matching.core.eventh.events.CoreEvent
+import co.nilin.opex.port.accountant.postgres.dao.TempEventRepository
+import co.nilin.opex.port.accountant.postgres.model.TempEventModel
+import com.google.gson.Gson
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.reactive.awaitFirstOrNull
+import kotlinx.coroutines.reactive.awaitSingle
+import org.springframework.data.domain.PageRequest
+import org.springframework.data.domain.Sort
+import org.springframework.stereotype.Component
+import java.time.LocalDateTime
+
+@Component
+class TempEventPersisterImpl(val tempEventRepository: TempEventRepository) : TempEventPersister {
+
+ override suspend fun saveTempEvent(ouid: String, event: CoreEvent) {
+ tempEventRepository.save(
+ TempEventModel(
+ null, ouid, event.javaClass.name,
+ Gson().toJson(event), LocalDateTime.now()
+ )
+ ).awaitSingle()
+ }
+
+ override suspend fun loadTempEvents(ouid: String): List {
+ return tempEventRepository
+ .findByOuid(ouid)
+ .map { value: TempEventModel ->
+ Gson().fromJson(value.eventBody, Class.forName(value.eventType)) as CoreEvent
+ }
+ .toList()
+ }
+
+ override suspend fun removeTempEvents(ouid: String) {
+ tempEventRepository.deleteByOuid(ouid).awaitFirstOrNull()
+ }
+
+ override suspend fun removeTempEvents(tempEvents: List) {
+ tempEventRepository.deleteAll(tempEvents.map { event ->
+ TempEventModel(
+ event.id, event.ouid, event.eventBody.javaClass.name,
+ "", event.eventDate
+ )
+ }).awaitFirstOrNull()
+ }
+
+ override suspend fun fetchTempEvents(
+ offset: Long,
+ size: Long
+ ): List {
+ return tempEventRepository
+ .findAll(PageRequest.of(offset.toInt(), size.toInt(), Sort.by(Sort.Direction.ASC, "eventDate")))
+ .map { value: TempEventModel ->
+ TempEvent(
+ value.id!!, value.ouid, Gson().fromJson(value.eventBody, Class.forName(value.eventType))
+ as
+ CoreEvent, value.eventDate
+ )
+ }
+ .toList()
+ }
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/FinancialActionModel.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/FinancialActionModel.kt
new file mode 100644
index 000000000..eefd9e7ad
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/FinancialActionModel.kt
@@ -0,0 +1,29 @@
+package co.nilin.opex.port.accountant.postgres.model
+
+import co.nilin.opex.accountant.core.model.FinancialActionStatus
+import org.springframework.data.annotation.Id
+import org.springframework.data.relational.core.mapping.Column
+import org.springframework.data.relational.core.mapping.Table
+import java.time.LocalDateTime
+
+@Table("fi_actions")
+class FinancialActionModel(
+ @Id var id: Long?,
+ @Column("parent_id") var parentId: Long?,
+ @Column("event_type") val eventType: String,
+ val pointer: String,
+ val symbol: String,
+ @Column("amount") val amount: Double,
+ val sender: String,
+ @Column("sender_wallet_type") val senderWalletType: String,
+ val receiver: String,
+ @Column("receiver_wallet_type") val receiverWalletType: String,
+ val agent: String,
+ val ip: String,
+ @Column("create_date") val createDate: LocalDateTime,
+ val status: FinancialActionStatus = FinancialActionStatus.CREATED,
+ @Column("retry_count") val retryCount: Int? = null,
+ @Column("last_try_date") val lastTryDate: LocalDateTime? = null
+)
+
+
diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/OrderModel.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/OrderModel.kt
new file mode 100644
index 000000000..8bf12cde4
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/OrderModel.kt
@@ -0,0 +1,39 @@
+package co.nilin.opex.port.accountant.postgres.model
+
+import co.nilin.opex.matching.core.model.MatchConstraint
+import co.nilin.opex.matching.core.model.OrderDirection
+import co.nilin.opex.matching.core.model.OrderType
+import org.springframework.data.annotation.Id
+import org.springframework.data.relational.core.mapping.Column
+import org.springframework.data.relational.core.mapping.Table
+import java.math.BigDecimal
+import java.time.LocalDateTime
+
+@Table("orders")
+class OrderModel(
+ @Id var id: Long?,
+ val ouid: String,
+ val uuid: String,
+ val pair: String,
+ @Column(value = "matching_engine_id") val matchingEngineId: Long?,
+ @Column("maker_fee") val makerFee: Double,
+ @Column("taker_fee") val takerFee: Double,
+ @Column("left_side_fraction") val leftSideFraction: Double,
+ @Column("right_side_fraction") val rightSideFraction: Double,
+ @Column("user_level") val userLevel: String,
+ @Column("direction") val direction: OrderDirection,
+ @Column("match_constraint") val matchConstraint: MatchConstraint,
+ @Column("order_type") val orderType: OrderType,
+ @Column("price") val price: Long,
+ @Column("quantity") val quantity: Long,
+ @Column("filled_quantity") val filledQuantity: Long,
+ @Column("orig_price") val origPrice: BigDecimal,
+ @Column("orig_quantity") val origQuantity: BigDecimal,
+ @Column("filled_orig_quantity") val filledOrigQuantity: BigDecimal,
+ @Column("first_transfer_amount") val firstTransferAmount: BigDecimal,
+ @Column("remained_transfer_amount") val remainedTransferAmount: BigDecimal,
+ @Column("status") val status: Int,
+ val agent: String,
+ val ip: String,
+ @Column("create_date") val createDate: LocalDateTime
+)
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/PairConfigModel.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/PairConfigModel.kt
new file mode 100644
index 000000000..c0c0726df
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/PairConfigModel.kt
@@ -0,0 +1,15 @@
+package co.nilin.opex.port.accountant.postgres.model
+
+import org.springframework.data.annotation.Id
+import org.springframework.data.relational.core.mapping.Column
+import org.springframework.data.relational.core.mapping.Table
+
+@Table("pair_config")
+data class PairConfigModel(
+ @Id val pair: String,
+ @Column("left_side_wallet_symbol") val leftSideWalletSymbol: String, //can be same as pair left side
+ @Column("right_side_wallet_symbol") val rightSideWalletSymbol: String, //can be same as pair right side
+ @Column("left_side_fraction") val leftSideFraction: Double,
+ @Column("right_side_fraction") val rightSideFraction: Double,
+ @Column("rate") val rate: Double
+)
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/PairFeeConfigModel.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/PairFeeConfigModel.kt
new file mode 100644
index 000000000..8426f4190
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/PairFeeConfigModel.kt
@@ -0,0 +1,14 @@
+package co.nilin.opex.port.accountant.postgres.model
+
+import org.springframework.data.relational.core.mapping.Column
+import org.springframework.data.relational.core.mapping.Table
+
+@Table("pair_fee_config")
+class PairFeeConfigModel(
+ val id: Long?,
+ @Column("pair_config_id") val pairConfigId: String,
+ @Column("direction") val direction: String?,
+ @Column("user_level") val userLevel: String?,
+ @Column("maker_fee") val makerFee: Double,
+ @Column("taker_fee") val takerFee: Double
+)
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/TempEventModel.kt b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/TempEventModel.kt
new file mode 100644
index 000000000..61ce47ec2
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/port/accountant/postgres/model/TempEventModel.kt
@@ -0,0 +1,16 @@
+package co.nilin.opex.port.accountant.postgres.model
+
+import org.springframework.data.annotation.Id
+import org.springframework.data.relational.core.mapping.Column
+import org.springframework.data.relational.core.mapping.Table
+import java.time.LocalDateTime
+
+@Table("temp_events")
+data class TempEventModel(
+ @Id val id: Long?,
+ val ouid: String,
+ @Column("event_type") val eventType: String,
+ @Column("event_body") val eventBody: String,
+ @Column("event_date") val eventDate: LocalDateTime
+) {
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-submitter-kafka/.gitignore b/Accountant/accountant-ports/accountant-submitter-kafka/.gitignore
new file mode 100644
index 000000000..d98fa6485
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-submitter-kafka/.gitignore
@@ -0,0 +1,50 @@
+# Created by .ignore support plugin (hsz.mobi)
+### Kotlin template
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+.idea
+*.iml
+*.ipr
+
+# File-based project format
+*.iws
+
+# IntelliJ
+target/
+
+.DS_Store
+
+
+
+
diff --git a/Accountant/accountant-ports/accountant-submitter-kafka/mvnw b/Accountant/accountant-ports/accountant-submitter-kafka/mvnw
new file mode 100644
index 000000000..a16b5431b
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-submitter-kafka/mvnw
@@ -0,0 +1,310 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/Accountant/accountant-ports/accountant-submitter-kafka/mvnw.cmd b/Accountant/accountant-ports/accountant-submitter-kafka/mvnw.cmd
new file mode 100644
index 000000000..c8d43372c
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-submitter-kafka/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/Accountant/accountant-ports/accountant-submitter-kafka/pom.xml b/Accountant/accountant-ports/accountant-submitter-kafka/pom.xml
new file mode 100644
index 000000000..f7d3e70ab
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-submitter-kafka/pom.xml
@@ -0,0 +1,120 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.4.4
+
+
+ co.nilin.opex
+ accountant-submitter-kafka
+ 1.0-SNAPSHOT
+ accountant-temp-submitter-kafka
+ Accountant kafka event submitter of Opex
+
+
+ 1.8
+ 1.4.31
+ ${version}
+ ${version}
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ co.nilin.opex
+ matching-core
+ ${matching.version}
+ provided
+
+
+ co.nilin.opex
+ accountant-core
+ ${accountant.version}
+ provided
+
+
+ org.springframework.kafka
+ spring-kafka
+
+
+ io.projectreactor.kotlin
+ reactor-kotlin-extensions
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-reactor
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-core
+
+
+ org.springframework.kafka
+ spring-kafka-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+
+
+
+
+
+
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/test/kotlin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ -Xjsr305=strict
+
+
+ spring
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-allopen
+ ${kotlin.version}
+
+
+
+
+
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+
+
diff --git a/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/config/SubmitterKafkaConfig.kt b/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/config/SubmitterKafkaConfig.kt
new file mode 100644
index 000000000..f643d6bc7
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/config/SubmitterKafkaConfig.kt
@@ -0,0 +1,76 @@
+package co.nilin.opex.port.accountant.kafka.config
+
+import co.nilin.opex.accountant.core.inout.RichOrder
+import co.nilin.opex.accountant.core.inout.RichTrade
+import co.nilin.opex.matching.core.eventh.events.CoreEvent
+import org.apache.kafka.clients.admin.NewTopic
+import org.apache.kafka.clients.producer.ProducerConfig
+import org.apache.kafka.common.serialization.StringSerializer
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.beans.factory.annotation.Qualifier
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.context.support.GenericApplicationContext
+import org.springframework.kafka.core.DefaultKafkaProducerFactory
+import org.springframework.kafka.core.KafkaTemplate
+import org.springframework.kafka.core.ProducerFactory
+import org.springframework.kafka.support.serializer.JsonSerializer
+
+@Configuration
+class SubmitterKafkaConfig() {
+ @Value("\${spring.kafka.bootstrap-servers}")
+ private lateinit var bootstrapServers: String
+
+ @Value("\${spring.kafka.consumer.group-id}")
+ private val groupId: String? = null
+
+ @Autowired
+ private val applicationContext: GenericApplicationContext? = null
+
+
+ @Bean("accountantProducerConfigs")
+ fun producerConfigs(): Map? {
+ val props: MutableMap = HashMap()
+ props[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers
+ props[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java
+ props[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = JsonSerializer::class.java
+ return props
+ }
+
+ @Bean("accountantEventProducerFactory")
+ fun producerFactory(@Qualifier("accountantProducerConfigs") producerConfigs: Map): ProducerFactory {
+ return DefaultKafkaProducerFactory(producerConfigs)
+ }
+
+ @Bean("accountantEventKafkaTemplate")
+ fun kafkaTemplate(@Qualifier("accountantEventProducerFactory") producerFactory: ProducerFactory): KafkaTemplate {
+ return KafkaTemplate(producerFactory)
+ }
+
+ @Bean("richTradeProducerFactory")
+ fun richTradeProducerFactory(@Qualifier("accountantProducerConfigs") producerConfigs: Map): ProducerFactory {
+ return DefaultKafkaProducerFactory(producerConfigs)
+ }
+
+ @Bean("richTradeKafkaTemplate")
+ fun richTradeKafkaTemplate(@Qualifier("richTradeProducerFactory") producerFactory: ProducerFactory): KafkaTemplate {
+ return KafkaTemplate(producerFactory)
+ }
+
+ @Bean("richOrderProducerFactory")
+ fun richOrderProducerFactory(@Qualifier("accountantProducerConfigs") producerConfigs: Map): ProducerFactory {
+ return DefaultKafkaProducerFactory(producerConfigs)
+ }
+
+ @Bean("richOrderKafkaTemplate")
+ fun richOrderKafkaTemplate(@Qualifier("richOrderProducerFactory") producerFactory: ProducerFactory): KafkaTemplate {
+ return KafkaTemplate(producerFactory)
+ }
+
+ @Autowired
+ fun createTopics(applicationContext: GenericApplicationContext) {
+ applicationContext.registerBean("topic_richOrder", NewTopic::class.java, "richOrder", 10, 1)
+ applicationContext.registerBean("topic_richTrade", NewTopic::class.java, "richTrade", 10, 1)
+ }
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/service/RichOrderSubmitter.kt b/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/service/RichOrderSubmitter.kt
new file mode 100644
index 000000000..52c0ecf22
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/service/RichOrderSubmitter.kt
@@ -0,0 +1,24 @@
+package co.nilin.opex.port.accountant.kafka.service
+
+import co.nilin.opex.accountant.core.inout.RichOrder
+import co.nilin.opex.accountant.core.spi.RichOrderPublisher
+import org.springframework.beans.factory.annotation.Qualifier
+import org.springframework.kafka.core.KafkaTemplate
+import org.springframework.stereotype.Component
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlin.coroutines.suspendCoroutine
+
+@Component
+class RichOrderSubmitter(@Qualifier("richOrderKafkaTemplate") val kafkaTemplate: KafkaTemplate) :
+ RichOrderPublisher {
+ override suspend fun publish(order: RichOrder): Unit = suspendCoroutine { cont ->
+ println("richOrderSubmit!")
+ val sendFuture = kafkaTemplate.send("richOrder", order)
+ sendFuture.addCallback({ sendResult ->
+ cont.resume(Unit)
+ }, { exception ->
+ cont.resumeWithException(exception)
+ })
+ }
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/service/RichTradeSubmitter.kt b/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/service/RichTradeSubmitter.kt
new file mode 100644
index 000000000..0336e91b7
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/service/RichTradeSubmitter.kt
@@ -0,0 +1,24 @@
+package co.nilin.opex.port.accountant.kafka.service
+
+import co.nilin.opex.accountant.core.inout.RichTrade
+import co.nilin.opex.accountant.core.spi.RichTradePublisher
+import org.springframework.beans.factory.annotation.Qualifier
+import org.springframework.kafka.core.KafkaTemplate
+import org.springframework.stereotype.Component
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlin.coroutines.suspendCoroutine
+
+@Component
+class RichTradeSubmitter(@Qualifier("richTradeKafkaTemplate") val kafkaTemplate: KafkaTemplate) :
+ RichTradePublisher {
+ override suspend fun publish(trade: RichTrade): Unit = suspendCoroutine { cont ->
+ println("richTradeSubmit!")
+ val sendFuture = kafkaTemplate.send("richTrade", trade)
+ sendFuture.addCallback({ sendResult ->
+ cont.resume(Unit)
+ }, { exception ->
+ cont.resumeWithException(exception)
+ })
+ }
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/service/TempEventSubmitter.kt b/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/service/TempEventSubmitter.kt
new file mode 100644
index 000000000..4c4c0a977
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-submitter-kafka/src/main/kotlin/co/nilin/opex/port/accountant/kafka/service/TempEventSubmitter.kt
@@ -0,0 +1,26 @@
+package co.nilin.opex.port.accountant.kafka.service
+
+import co.nilin.opex.accountant.core.spi.TempEventRepublisher
+import co.nilin.opex.matching.core.eventh.events.CoreEvent
+import org.springframework.beans.factory.annotation.Qualifier
+import org.springframework.kafka.core.KafkaTemplate
+import org.springframework.stereotype.Component
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlin.coroutines.suspendCoroutine
+
+@Component
+class TempEventSubmitter(@Qualifier("accountantEventKafkaTemplate") val kafkaTemplate: KafkaTemplate) :
+ TempEventRepublisher {
+ override suspend fun republish(events: List): Unit = suspendCoroutine { cont ->
+ println("accountantEventSubmit!")
+ events.forEach { event ->
+ val sendFuture = kafkaTemplate.send("tempevents", event)
+ sendFuture.addCallback({ sendResult ->
+ cont.resume(Unit)
+ }, { exception ->
+ cont.resumeWithException(exception)
+ })
+ }
+ }
+}
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-wallet-proxy/.gitignore b/Accountant/accountant-ports/accountant-wallet-proxy/.gitignore
new file mode 100644
index 000000000..f3a8317d6
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-wallet-proxy/.gitignore
@@ -0,0 +1,4 @@
+*.iml
+target/
+.mvn/
+.idea/
\ No newline at end of file
diff --git a/Accountant/accountant-ports/accountant-wallet-proxy/mvnw b/Accountant/accountant-ports/accountant-wallet-proxy/mvnw
new file mode 100644
index 000000000..a16b5431b
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-wallet-proxy/mvnw
@@ -0,0 +1,310 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/Accountant/accountant-ports/accountant-wallet-proxy/mvnw.cmd b/Accountant/accountant-ports/accountant-wallet-proxy/mvnw.cmd
new file mode 100644
index 000000000..c8d43372c
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-wallet-proxy/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/Accountant/accountant-ports/accountant-wallet-proxy/pom.xml b/Accountant/accountant-ports/accountant-wallet-proxy/pom.xml
new file mode 100644
index 000000000..d536d061c
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-wallet-proxy/pom.xml
@@ -0,0 +1,146 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.4.4
+
+
+ co.nilin.opex
+ accountant-wallet-proxy
+ 1.0-SNAPSHOT
+ accountant-wallet-proxy
+ Opex wallet proxy
+
+
+ 1.8
+ 1.4.31
+ 2020.0.2
+ ${version}
+ ${version}
+ ${version}
+
+
+
+
+ co.nilin.opex
+ matching-core
+ ${matching.version}
+ provided
+
+
+ co.nilin.opex
+ accountant-core
+ ${accountant.version}
+ provided
+
+
+ co.nilin.opex
+ logging-handler
+ ${utility.version}
+ provided
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.springframework.cloud
+ spring-cloud-starter-consul-all
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-r2dbc
+
+
+ io.r2dbc
+ r2dbc-postgresql
+ runtime
+
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+ io.projectreactor.kotlin
+ reactor-kotlin-extensions
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-reactor
+
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-core
+
+
+ io.projectreactor
+ reactor-test
+ test
+
+
+
+
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/test/kotlin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ -Xjsr305=strict
+
+
+ spring
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-allopen
+ ${kotlin.version}
+
+
+
+
+
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
diff --git a/Accountant/accountant-ports/accountant-wallet-proxy/src/main/kotlin/co/nilin/opex/port/accountant/wallet/config/WebClientConfig.kt b/Accountant/accountant-ports/accountant-wallet-proxy/src/main/kotlin/co/nilin/opex/port/accountant/wallet/config/WebClientConfig.kt
new file mode 100644
index 000000000..ce2bca7fd
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-wallet-proxy/src/main/kotlin/co/nilin/opex/port/accountant/wallet/config/WebClientConfig.kt
@@ -0,0 +1,38 @@
+package co.nilin.opex.port.accountant.wallet.config
+
+import co.nilin.opex.utility.log.interceptor.CustomLogger
+import org.springframework.cloud.client.ServiceInstance
+import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties
+import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer
+import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.http.client.reactive.ReactorClientHttpConnector
+import org.springframework.web.reactive.function.client.WebClient
+import reactor.netty.http.client.HttpClient
+
+@Configuration
+class WebClientConfig {
+
+ @Bean
+ fun webClient(loadBalancerFactory: ReactiveLoadBalancer.Factory): WebClient {
+ val logger = CustomLogger(HttpClient::class.java)
+ return WebClient.builder()
+ .clientConnector(
+ ReactorClientHttpConnector(
+ HttpClient
+ .create()
+ .doOnRequest { request, connection ->
+ connection.addHandlerFirst(logger)
+ }
+ )
+ )
+ .filter(
+ ReactorLoadBalancerExchangeFilterFunction(
+ loadBalancerFactory, LoadBalancerProperties(), emptyList()
+ )
+ )
+ .build()
+ }
+
+}
diff --git a/Accountant/accountant-ports/accountant-wallet-proxy/src/main/kotlin/co/nilin/opex/port/accountant/wallet/proxy/WalletProxyImpl.kt b/Accountant/accountant-ports/accountant-wallet-proxy/src/main/kotlin/co/nilin/opex/port/accountant/wallet/proxy/WalletProxyImpl.kt
new file mode 100644
index 000000000..480ee1f1d
--- /dev/null
+++ b/Accountant/accountant-ports/accountant-wallet-proxy/src/main/kotlin/co/nilin/opex/port/accountant/wallet/proxy/WalletProxyImpl.kt
@@ -0,0 +1,67 @@
+package co.nilin.opex.port.accountant.wallet.proxy
+
+import co.nilin.opex.accountant.core.spi.WalletProxy
+import kotlinx.coroutines.reactive.awaitFirst
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.core.ParameterizedTypeReference
+import org.springframework.stereotype.Component
+import org.springframework.web.reactive.function.client.WebClient
+import java.math.BigDecimal
+import java.net.URI
+import java.time.LocalDateTime
+
+inline fun typeRef(): ParameterizedTypeReference = object : ParameterizedTypeReference() {}
+data class TransferResult(
+ val date: Long,
+ val sourceBalanceBeforeAction: Amount,
+ val sourceBalanceAfterAction: Amount,
+ val amount: Amount
+)
+
+data class Amount(val currency: Currency, val amount: BigDecimal)
+data class Currency(val name: String, val symbol: String, val precision: Int)
+
+@Component
+class WalletProxyImpl(
+ @Value("\${app.wallet.url}") val walletBaseUrl: String, val webClient: WebClient
+) : WalletProxy {
+ override suspend fun transfer(
+ symbol: String,
+ senderWalletType: String,
+ senderUuid: String,
+ receiverWalletType: String,
+ receiverUuid: String,
+ amount: BigDecimal,
+ description: String?,
+ transferRef: String?
+ ) {
+ webClient.post()
+ .uri(URI.create("$walletBaseUrl/transfer/${amount}_${symbol}/from/${senderUuid}_${senderWalletType}/to/${receiverUuid}_${receiverWalletType}"))
+ .header("Content-Type", "application/json")
+ .retrieve()
+ .onStatus({ t -> t.isError }, { p ->
+ /*
+ p.bodyToMono(typeRef>()).map { t -> KycSejamException(p.statusCode().value().toString(), t.error?.errorCode.toString()
+ + "-" + t.error?.customMessage) }
+ */
+ throw RuntimeException()
+ })
+ .bodyToMono(typeRef())
+ .log()
+ .awaitFirst()
+
+ }
+
+ override suspend fun canFulfil(symbol: String, walletType: String, uuid: String, amount: BigDecimal): Boolean {
+ data class BooleanResponse(val result: Boolean)
+ return webClient.get()
+ .uri(URI.create("$walletBaseUrl/$uuid/wallet_type/${walletType}/can_withdraw/${amount}_${symbol}"))
+ .header("Content-Type", "application/json")
+ .retrieve()
+ .onStatus({ t -> t.isError }, { it.createException() })
+ .bodyToMono(typeRef())
+ .log()
+ .awaitFirst()
+ .result
+ }
+}
\ No newline at end of file
diff --git a/Accountant/pom.xml b/Accountant/pom.xml
new file mode 100644
index 000000000..436f574ac
--- /dev/null
+++ b/Accountant/pom.xml
@@ -0,0 +1,19 @@
+
+
+ 4.0.0
+ co.nilin.opex
+ accountant-root
+ 1.0-SNAPSHOT
+ accountant-root
+ pom
+ Accountant root of Opex
+
+ accountant-core
+ accountant-app
+ accountant-ports/accountant-persister-postgres
+ accountant-ports/accountant-wallet-proxy
+ accountant-ports/accountant-submitter-kafka
+ accountant-ports/accountant-eventlistener-kafka
+
+
diff --git a/Api/.gitignore b/Api/.gitignore
new file mode 100644
index 000000000..d6953c444
--- /dev/null
+++ b/Api/.gitignore
@@ -0,0 +1,3 @@
+*.iml
+.idea
+/.idea/
diff --git a/Api/.idea/.gitignore b/Api/.idea/.gitignore
new file mode 100644
index 000000000..73f69e095
--- /dev/null
+++ b/Api/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/Api/api-app/.gitignore b/Api/api-app/.gitignore
new file mode 100644
index 000000000..e7c460346
--- /dev/null
+++ b/Api/api-app/.gitignore
@@ -0,0 +1,80 @@
+# Created by .ignore support plugin (hsz.mobi)
+### Kotlin template
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+.idea/artifacts
+.idea/compiler.xml
+.idea/jarRepositories.xml
+.idea/modules.xml
+.idea/*.iml
+.idea/modules
+*.iml
+*.ipr
+.mvn/
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+target/
+
+.DS_Store
+
+
+
+
diff --git a/Api/api-app/Dockerfile b/Api/api-app/Dockerfile
new file mode 100644
index 000000000..f2cbd4c26
--- /dev/null
+++ b/Api/api-app/Dockerfile
@@ -0,0 +1,5 @@
+FROM openjdk:8-jdk-alpine
+VOLUME /tmp
+ARG JAR_FILE=target/*.jar
+COPY ${JAR_FILE} app.jar
+ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"]
\ No newline at end of file
diff --git a/Api/api-app/pom.xml b/Api/api-app/pom.xml
new file mode 100644
index 000000000..f430eb9c5
--- /dev/null
+++ b/Api/api-app/pom.xml
@@ -0,0 +1,213 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.4.4
+
+
+ co.nilin.opex
+ api-app
+ 1.0-SNAPSHOT
+ api-app
+ Api app Opex
+
+
+ 1.8
+ 1.4.31
+ ${version}
+ ${version}
+ ${version}
+ ${version}
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+
+
+ com.fasterxml.jackson.module
+ jackson-module-kotlin
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ co.nilin.opex
+ logging-handler
+ ${utility.version}
+
+
+ co.nilin.opex
+ error-handler
+ ${utility.version}
+
+
+ co.nilin.opex
+ interceptors
+ ${utility.version}
+ provided
+
+
+ co.nilin.opex
+ accountant-core
+ ${accountant.version}
+
+
+ co.nilin.opex
+ api-core
+ ${api.version}
+
+
+ co.nilin.opex
+ api-eventlistener-kafka
+ ${api.version}
+
+
+ co.nilin.opex
+ api-binance-rest
+ ${api.version}
+
+
+ co.nilin.opex
+ api-persister-postgres
+ ${api.version}
+
+
+ io.springfox
+ springfox-boot-starter
+ 3.0.0
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+
+
+
+
+
+
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/test/kotlin
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.18
+
+
+ ${skip.unit.tests}
+
+
+ **/*IntegrationTest.java
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ add-test-source
+ generate-test-sources
+
+ add-test-source
+
+
+
+ src/test/java
+
+
+
+
+ compile
+
+ add-source
+
+
+
+ src/main/java
+
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+
+
+ -Xjsr305=strict
+
+
+ spring
+
+ 1.8
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-allopen
+ ${kotlin.version}
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ testCompile
+ test-compile
+
+ testCompile
+
+
+
+
+
+ opex-accountant
+
+
diff --git a/Api/api-app/src/main/kotlin/co/nilin/opex/app/ApiApp.kt b/Api/api-app/src/main/kotlin/co/nilin/opex/app/ApiApp.kt
new file mode 100644
index 000000000..338e2bab1
--- /dev/null
+++ b/Api/api-app/src/main/kotlin/co/nilin/opex/app/ApiApp.kt
@@ -0,0 +1,29 @@
+package co.nilin.opex.app
+
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.runApplication
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.ComponentScan
+import org.springframework.security.core.annotation.AuthenticationPrincipal
+import springfox.documentation.builders.*
+import springfox.documentation.builders.PathSelectors.regex
+import springfox.documentation.service.*
+import springfox.documentation.spi.DocumentationType
+import springfox.documentation.spi.service.contexts.SecurityContext
+import springfox.documentation.spring.web.plugins.Docket
+import springfox.documentation.swagger.web.SecurityConfiguration
+import springfox.documentation.swagger.web.SecurityConfigurationBuilder
+import springfox.documentation.swagger2.annotations.EnableSwagger2
+import java.security.Principal
+import java.util.Collections.singletonList
+
+
+@SpringBootApplication
+@ComponentScan("co.nilin.opex")
+@EnableSwagger2
+class ApiApp
+
+fun main(args: Array) {
+ runApplication(*args)
+}
diff --git a/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/AppConfig.kt b/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/AppConfig.kt
new file mode 100644
index 000000000..d544cd605
--- /dev/null
+++ b/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/AppConfig.kt
@@ -0,0 +1,71 @@
+package co.nilin.opex.app.config
+
+import co.nilin.opex.accountant.core.inout.RichOrder
+import co.nilin.opex.accountant.core.inout.RichTrade
+import co.nilin.opex.api.core.spi.OrderPersister
+import co.nilin.opex.api.core.spi.TradePersister
+import co.nilin.opex.port.api.kafka.consumer.OrderKafkaListener
+import co.nilin.opex.port.api.kafka.spi.RichOrderListener
+import co.nilin.opex.port.api.kafka.consumer.TradeKafkaListener
+import co.nilin.opex.port.api.kafka.spi.RichTradeListener
+import kotlinx.coroutines.runBlocking
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+
+@Configuration
+class AppConfig {
+
+ @Bean
+ fun apiListener(
+ richOrderPersister: OrderPersister,
+ richTradePersister: TradePersister
+ ): ApiListenerImpl {
+ return ApiListenerImpl(richOrderPersister, richTradePersister)
+ }
+
+ @Autowired
+ fun configureListeners(
+ orderKafkaListener: OrderKafkaListener,
+ tradeKafkaListener: TradeKafkaListener,
+ appListener: ApiListenerImpl
+ ) {
+ orderKafkaListener.addOrderListener(appListener)
+ tradeKafkaListener.addTradeListener(appListener)
+ }
+
+ class ApiListenerImpl(
+ val richOrderPersister: OrderPersister,
+ val richTradePersister: TradePersister
+ ) : RichTradeListener, RichOrderListener {
+
+ override fun id(): String {
+ return "AppListener"
+ }
+
+ override fun onTrade(
+ trade: RichTrade,
+ partition: Int,
+ offset: Long,
+ timestamp: Long
+ ) {
+ println("RichTrade received")
+ runBlocking(AppDispatchers.kafkaExecutor) {
+ richTradePersister.save(trade)
+ }
+ }
+
+ override fun onOrder(
+ order: RichOrder,
+ partition: Int,
+ offset: Long,
+ timestamp: Long
+ ) {
+ runBlocking(AppDispatchers.kafkaExecutor) {
+ richOrderPersister.save(order)
+ }
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/AppDispatchers.kt b/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/AppDispatchers.kt
new file mode 100644
index 000000000..a63f2d6a6
--- /dev/null
+++ b/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/AppDispatchers.kt
@@ -0,0 +1,9 @@
+package co.nilin.opex.app.config
+
+import kotlinx.coroutines.asCoroutineDispatcher
+import java.util.concurrent.Executors
+
+object AppDispatchers {
+ val apiExecutor = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
+ val kafkaExecutor = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
+}
\ No newline at end of file
diff --git a/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/CorsConfig.kt b/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/CorsConfig.kt
new file mode 100644
index 000000000..e6e7a3480
--- /dev/null
+++ b/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/CorsConfig.kt
@@ -0,0 +1,21 @@
+package co.nilin.opex.app.config
+
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.context.annotation.Configuration
+import org.springframework.web.reactive.config.CorsRegistry
+import org.springframework.web.reactive.config.WebFluxConfigurer
+
+@Configuration
+class CorsConfig : WebFluxConfigurer {
+
+ @Value("\${app.cors.allowed-hosts}")
+ private lateinit var hosts: Array
+
+ override fun addCorsMappings(registry: CorsRegistry) {
+ registry.addMapping("/**")
+ .allowedOrigins(*hosts)
+ .allowedHeaders("*")
+ .allowedMethods("*")
+ }
+
+}
\ No newline at end of file
diff --git a/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/SwaggerConfig.kt b/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/SwaggerConfig.kt
new file mode 100644
index 000000000..c9fe3d51b
--- /dev/null
+++ b/Api/api-app/src/main/kotlin/co/nilin/opex/app/config/SwaggerConfig.kt
@@ -0,0 +1,97 @@
+package co.nilin.opex.app.config
+
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.core.annotation.AuthenticationPrincipal
+import springfox.documentation.builders.ApiInfoBuilder
+import springfox.documentation.builders.OAuthBuilder
+import springfox.documentation.builders.PathSelectors
+import springfox.documentation.builders.RequestParameterBuilder
+import springfox.documentation.service.*
+import springfox.documentation.spi.DocumentationType
+import springfox.documentation.spi.service.contexts.SecurityContext
+import springfox.documentation.spring.web.plugins.Docket
+import springfox.documentation.swagger.web.SecurityConfiguration
+import springfox.documentation.swagger.web.SecurityConfigurationBuilder
+import java.security.Principal
+import java.util.*
+
+@Configuration
+class SwaggerConfig {
+ @Value("\${swagger.authUrl}")
+ val authUrl: String = ""
+
+ @Bean
+ fun opexApi(): Docket {
+ return Docket(DocumentationType.SWAGGER_2)
+ .groupName("opex-api")
+ .apiInfo(apiInfo())
+ .select()
+ .paths(PathSelectors.regex("^/api/v3.*"))
+ .build()
+ .globalRequestParameters(
+ Collections.singletonList(
+ RequestParameterBuilder()
+ .name("content-type")
+ .description("content-type")
+ .`in`(ParameterType.HEADER)
+ .required(true)
+ .build()
+ )
+ )
+ .ignoredParameterTypes(AuthenticationPrincipal::class.java, Principal::class.java)
+ .useDefaultResponseMessages(false)
+ .securitySchemes(Collections.singletonList(oauth()))
+ .securityContexts(Collections.singletonList(securityContext()))
+ }
+
+ private fun apiInfo(): ApiInfo {
+ return ApiInfoBuilder()
+ .title("OPEX API")
+ .description("Backend for opex exchange.")
+ .license("MIT License")
+ .licenseUrl("https://github.com/opexdev/Back-end/blob/feature/1-MVP/LICENSE")
+ .version("0.1")
+ .build()
+ }
+
+ private fun oauth(): SecurityScheme {
+ return OAuthBuilder()
+ .name("opex")
+ .grantTypes(grantTypes())
+ .scopes(scopes())
+ .build()
+ }
+
+ private fun scopes(): List {
+ return listOf(AuthorizationScope("openid", "OpenId"))
+ }
+
+ private fun grantTypes(): List {
+ val tokenUrl = "$authUrl/auth/realms/opex/protocol/openid-connect/token"
+ val grantType = ResourceOwnerPasswordCredentialsGrant(tokenUrl)
+ return Collections.singletonList(grantType)
+ }
+
+ private fun securityContext(): SecurityContext {
+ val securityReference = SecurityReference.builder()
+ .reference("opex")
+ .scopes(emptyArray())
+ .build()
+ return SecurityContext.builder()
+ .securityReferences(Collections.singletonList(securityReference))
+ .operationSelector { true }
+ .build()
+ }
+
+ @Bean
+ fun securityInfo(): SecurityConfiguration {
+ return SecurityConfigurationBuilder.builder()
+ .clientId("admin-cli")
+ .realm("opex")
+ .appName("opex")
+ .scopeSeparator(",")
+ .build()
+ }
+}
\ No newline at end of file
diff --git a/Api/api-app/src/main/resources/application-docker.yml b/Api/api-app/src/main/resources/application-docker.yml
new file mode 100644
index 000000000..2c3e93788
--- /dev/null
+++ b/Api/api-app/src/main/resources/application-docker.yml
@@ -0,0 +1,25 @@
+spring:
+ kafka:
+ bootstrap-servers: ${KAFKA_IP_PORT}
+ redis:
+ host: ${REDIS_HOST}
+ r2dbc:
+ url: r2dbc:postgresql://${DB_IP_PORT}/opex_api
+ username: opex
+ password: hiopex
+ cloud:
+ consul:
+ host: ${CONSUL_HOST}
+ port: 8500
+
+app:
+ cors:
+ allowed-hosts: https://opex.dev, http://localhost:3000
+ accountant:
+ url: lb://opex-accountant
+ matching-gateway:
+ url: lb://opex-gateway
+ wallet:
+ url: lb://opex-wallet
+ auth:
+ cert-url: lb://opex-auth/auth/realms/opex/protocol/openid-connect/certs
diff --git a/Api/api-app/src/main/resources/application.yml b/Api/api-app/src/main/resources/application.yml
new file mode 100644
index 000000000..c1517ab0d
--- /dev/null
+++ b/Api/api-app/src/main/resources/application.yml
@@ -0,0 +1,42 @@
+server.port: 8094
+logging:
+ level:
+ co.nilin: DEBUG
+ reactor.netty.http.client: DEBUG
+spring:
+ application:
+ name: opex-api
+ main:
+ allow-bean-definition-overriding: false
+ kafka:
+ bootstrap-servers: 192.168.178.29:9092
+ consumer:
+ group-id: api
+ redis:
+ host: 127.0.0.1
+ port: 6379
+ r2dbc:
+ url: r2dbc:postgresql://localhost/opex_api
+ username: opex
+ password: hiopex
+ initialization-mode: always
+ cloud:
+ bootstrap:
+ enabled: true
+ consul:
+ port: 8500
+ discovery:
+ #healthCheckPath: ${management.context-path}/health
+ instance-id: ${spring.application.name}:${server.port}
+ healthCheckInterval: 20s
+ prefer-ip-address: true
+
+app:
+ accountant:
+ url: lb://opex-accountant
+ matching-gateway:
+ url: lb://opex-gateway
+ wallet:
+ url: lb://opex-wallet
+
+swagger.authUrl: https://api.opex.dev
\ No newline at end of file
diff --git a/Api/api-core/.gitignore b/Api/api-core/.gitignore
new file mode 100644
index 000000000..f3a8317d6
--- /dev/null
+++ b/Api/api-core/.gitignore
@@ -0,0 +1,4 @@
+*.iml
+target/
+.mvn/
+.idea/
\ No newline at end of file
diff --git a/Api/api-core/mvnw b/Api/api-core/mvnw
new file mode 100644
index 000000000..3c8a55373
--- /dev/null
+++ b/Api/api-core/mvnw
@@ -0,0 +1,322 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ]; then
+
+ if [ -f /etc/mavenrc ]; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ]; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false
+darwin=false
+mingw=false
+case "$(uname)" in
+CYGWIN*) cygwin=true ;;
+MINGW*) mingw=true ;;
+Darwin*)
+ darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="$(/usr/libexec/java_home)"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ]; then
+ if [ -r /etc/gentoo-release ]; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+fi
+
+if [ -z "$M2_HOME" ]; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ]; do
+ ls=$(ls -ld "$PRG")
+ link=$(expr "$ls" : '.*-> \(.*\)$')
+ if expr "$link" : '/.*' >/dev/null; then
+ PRG="$link"
+ else
+ PRG="$(dirname "$PRG")/$link"
+ fi
+ done
+
+ saveddir=$(pwd)
+
+ M2_HOME=$(dirname "$PRG")/..
+
+ # make it fully qualified
+ M2_HOME=$(cd "$M2_HOME" && pwd)
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=$(cygpath --unix "$M2_HOME")
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="$( (
+ cd "$M2_HOME"
+ pwd
+ ))"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="$( (
+ cd "$JAVA_HOME"
+ pwd
+ ))"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr \"$javaExecutable\" : '\([^ ]*\)')" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=$(which readlink)
+ if [ ! $(expr "$readLink" : '\([^ ]*\)') = "no" ]; then
+ if $darwin; then
+ javaHome="$(dirname \"$javaExecutable\")"
+ javaExecutable="$(cd \"$javaHome\" && pwd -P)/javac"
+ else
+ javaExecutable="$(readlink -f \"$javaExecutable\")"
+ fi
+ javaHome="$(dirname \"$javaExecutable\")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ]; then
+ if [ -n "$JAVA_HOME" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="$(which java)"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ]; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]; then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ]; do
+ if [ -d "$wdir"/.mvn ]; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=$(
+ cd "$wdir/.."
+ pwd
+ )
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' <"$1")"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(pwd)")
+if [ -z "$BASE_DIR" ]; then
+ exit 1
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in wrapperUrl)
+ jarUrl="$value"
+ break
+ ;;
+ esac
+ done <"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget >/dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl >/dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=$(cygpath --path --windows "$M2_HOME")
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/Api/api-core/mvnw.cmd b/Api/api-core/mvnw.cmd
new file mode 100644
index 000000000..c8d43372c
--- /dev/null
+++ b/Api/api-core/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/Api/api-core/pom.xml b/Api/api-core/pom.xml
new file mode 100644
index 000000000..b8ded6631
--- /dev/null
+++ b/Api/api-core/pom.xml
@@ -0,0 +1,104 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.4.4
+
+
+ co.nilin.opex
+ api-core
+ 1.0-SNAPSHOT
+ api-core
+ Api logic of Opex
+
+
+ 1.8
+ 1.4.31
+ ${version}
+ ${version}
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+
+
+ io.projectreactor.kotlin
+ reactor-kotlin-extensions
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-reactor
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-core
+
+
+ co.nilin.opex
+ matching-core
+ ${matching.version}
+
+
+ co.nilin.opex
+ accountant-core
+ ${accountant.version}
+ provided
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+
+
+
+
+ org.springframework
+ spring-tx
+ provided
+
+
+
+
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/test/kotlin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ -Xjsr305=strict
+
+
+ spring
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-allopen
+ ${kotlin.version}
+
+
+
+
+
+
+
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AggregatedOrderPriceModel.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AggregatedOrderPriceModel.kt
new file mode 100644
index 000000000..4537ff914
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AggregatedOrderPriceModel.kt
@@ -0,0 +1,6 @@
+package co.nilin.opex.api.core.inout
+
+data class AggregatedOrderPriceModel(
+ val price: Double?,
+ val quantity: Double?
+)
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AllOrderRequest.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AllOrderRequest.kt
new file mode 100644
index 000000000..6eb74b1f1
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AllOrderRequest.kt
@@ -0,0 +1,10 @@
+package co.nilin.opex.api.core.inout
+
+import java.util.*
+
+class AllOrderRequest(
+ val symbol: String?,
+ val startTime: Date?,
+ val endTime: Date?,
+ val limit: Int? = 500, //Default 500; max 1000.
+)
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CancelOrderRequest.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CancelOrderRequest.kt
new file mode 100644
index 000000000..874e5ff99
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CancelOrderRequest.kt
@@ -0,0 +1,3 @@
+package co.nilin.opex.api.core.inout
+
+class CancelOrderRequest(val ouid: String, val uuid: String, val orderId: Long, val symbol: String)
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CreateOrderRequest.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CreateOrderRequest.kt
new file mode 100644
index 000000000..7b51525c6
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CreateOrderRequest.kt
@@ -0,0 +1,19 @@
+package co.nilin.opex.api.core.inout
+
+import java.math.BigDecimal
+
+data class CreateOrderRequest(
+ val symbol: String,
+ val side: OrderSide,
+ val type: OrderType,
+ val timeInForce: TimeInForce?,
+ val quantity: BigDecimal?,
+ val quoteOrderQty: BigDecimal?,
+ val price: BigDecimal?,
+ val newClientOrderId: String?, /* A unique id among open orders. Automatically generated if not sent.
+ Orders with the same newClientOrderID can be accepted only when the previous one is filled, otherwise the order will be rejected.
+ */
+ val stopPrice: BigDecimal?, //Used with STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, and TAKE_PROFIT_LIMIT orders.
+ val icebergQty: BigDecimal?, //Used with LIMIT, STOP_LOSS_LIMIT, and TAKE_PROFIT_LIMIT to create an iceberg order.
+ val newOrderRespType: OrderResponseType?, //Set the response JSON. ACK, RESULT, or FULL; MARKET and LIMIT order types default to FULL, all other orders default to ACK.
+)
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CreateOrderResponse.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CreateOrderResponse.kt
new file mode 100644
index 000000000..9d2f64078
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CreateOrderResponse.kt
@@ -0,0 +1,21 @@
+package co.nilin.opex.api.core.inout
+
+import java.math.BigDecimal
+import java.util.*
+
+data class CreateOrderResponse(
+ val symbol: String,
+ val orderId: Long,
+ val orderListId: Long, //Unless OCO, value will be -1
+ val clientOrderId: String,
+ val transactTime: Date,
+ val price: BigDecimal?,
+ val origQty: BigDecimal?,
+ val executedQty: BigDecimal?,
+ val cummulativeQuoteQty: BigDecimal,
+ val status: OrderStatus?,
+ val timeInForce: TimeInForce?,
+ val type: OrderType?,
+ val side: OrderSide?,
+ val fills: List?
+)
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/MarketTradeResponse.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/MarketTradeResponse.kt
new file mode 100644
index 000000000..c8baafb1f
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/MarketTradeResponse.kt
@@ -0,0 +1,15 @@
+package co.nilin.opex.api.core.inout
+
+import java.math.BigDecimal
+import java.util.*
+
+data class MarketTradeResponse(
+ val symbol: String,
+ val id: Long,
+ val price: BigDecimal,
+ val qty: BigDecimal,
+ val quoteQty: BigDecimal,
+ val time: Date,
+ val isBestMatch: Boolean,
+ val isMakerBuyer: Boolean
+)
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderBookResponse.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderBookResponse.kt
new file mode 100644
index 000000000..53e8f0c01
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderBookResponse.kt
@@ -0,0 +1,8 @@
+package co.nilin.opex.api.core.inout
+
+import java.math.BigDecimal
+
+data class OrderBookResponse(
+ val price: BigDecimal?,
+ val quantity: BigDecimal?
+)
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderEnums.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderEnums.kt
new file mode 100644
index 000000000..70c43fec1
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderEnums.kt
@@ -0,0 +1,41 @@
+package co.nilin.opex.api.core.inout
+
+enum class TimeInForce {
+ GTC, //Good Til Canceled, An order will be on the book unless the order is canceled.
+ IOC, //Immediate Or Cancel, An order will try to fill the order as much as it can before the order expires.
+ FOK, //Fill or Kill, An order will expire if the full order cannot be filled upon execution.
+}
+
+enum class OrderStatus(val code: Int) {
+
+ NEW(1), //The order has been accepted by the engine.
+ PARTIALLY_FILLED(4), //A part of the order has been filled.
+ FILLED(5), //The order has been completed.
+ CANCELED(2), //The order has been canceled by the user.
+ PENDING_CANCEL(7), //Currently unused
+ REJECTED(3), //The order was not accepted by the engine and not processed.
+ EXPIRED(6) //The order was canceled according to the order type's rules (e.g. LIMIT FOK orders with no fill, LIMIT IOC or MARKET orders that partially fill) or by the exchange, (e.g. orders canceled during liquidation, orders canceled during maintenance)
+}
+
+enum class OrderType {
+ LIMIT, // timeInForce, quantity, price
+ MARKET, // quantity or quoteOrderQty
+ STOP_LOSS, // quantity, stopPrice
+ STOP_LOSS_LIMIT, // timeInForce, quantity, price, stopPrice
+ TAKE_PROFIT, // quantity, stopPrice
+ TAKE_PROFIT_LIMIT, // timeInForce, quantity, price, stopPrice
+ LIMIT_MAKER; // quantity, price
+
+ companion object {
+ fun activeTypes() = listOf(LIMIT, MARKET)
+ }
+}
+
+enum class OrderSide {
+ BUY,
+ SELL
+}
+
+enum class OrderResponseType {
+ ACK, RESULT, FULL
+}
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderSubmitResult.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderSubmitResult.kt
new file mode 100644
index 000000000..1e6d864c6
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderSubmitResult.kt
@@ -0,0 +1,3 @@
+package co.nilin.opex.api.core.inout
+
+data class OrderSubmitResult(val offset: Long?)
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderTradeData.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderTradeData.kt
new file mode 100644
index 000000000..86d45b08a
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderTradeData.kt
@@ -0,0 +1,10 @@
+package co.nilin.opex.api.core.inout
+
+import java.math.BigDecimal
+
+data class OrderTradeData(
+ val price: BigDecimal,
+ val qty: BigDecimal,
+ val commission: BigDecimal,
+ val commissionAsset: String
+)
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OwnerLimitsResponse.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OwnerLimitsResponse.kt
new file mode 100644
index 000000000..84267db98
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OwnerLimitsResponse.kt
@@ -0,0 +1,7 @@
+package co.nilin.opex.api.core.inout
+
+data class OwnerLimitsResponse(
+ val canTrade: Boolean,
+ val canWithdraw: Boolean,
+ val canDeposit: Boolean
+)
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PairInfoResponse.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PairInfoResponse.kt
new file mode 100644
index 000000000..8d4c8ab2c
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PairInfoResponse.kt
@@ -0,0 +1,9 @@
+package co.nilin.opex.api.core.inout
+
+data class PairInfoResponse(
+ val pair: String,
+ val leftSideWalletSymbol: String,
+ val rightSideWalletSymbol: String,
+ val leftSideFraction: Double,
+ val rightSideFraction: Double
+)
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceChangeResponse.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceChangeResponse.kt
new file mode 100644
index 000000000..666c38ded
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceChangeResponse.kt
@@ -0,0 +1,21 @@
+package co.nilin.opex.api.core.inout
+
+data class PriceChangeResponse(
+ val symbol: String,
+ val priceChange: Double = 0.0,
+ val priceChangePercent: Double = 0.0,
+ val weightedAvgPrice: Double = 0.0,
+ val lastPrice: Double = 0.0,
+ val lastQty: Double = 0.0,
+ val bidPrice: Double = 0.0,
+ val askPrice: Double = 0.0,
+ val openPrice: Double = 0.0,
+ val highPrice: Double = 0.0,
+ val lowPrice: Double = 0.0,
+ val volume: Double = 0.0,
+ val openTime: Long,
+ val closeTime: Long,
+ val firstId: Long = 0,
+ val lastId: Long = 0,
+ val count: Long = 0,
+)
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceTickerResponse.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceTickerResponse.kt
new file mode 100644
index 000000000..d370637ef
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceTickerResponse.kt
@@ -0,0 +1,6 @@
+package co.nilin.opex.api.core.inout
+
+data class PriceTickerResponse(
+ val symbol: String?,
+ val price: String?
+)
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/QueryOrderRequest.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/QueryOrderRequest.kt
new file mode 100644
index 000000000..5b0890e65
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/QueryOrderRequest.kt
@@ -0,0 +1,7 @@
+package co.nilin.opex.api.core.inout
+
+data class QueryOrderRequest(
+ val symbol: String,
+ val orderId: Long?,
+ val origClientOrderId: String?
+)
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/QueryOrderResponse.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/QueryOrderResponse.kt
new file mode 100644
index 000000000..72aa1a0d6
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/QueryOrderResponse.kt
@@ -0,0 +1,26 @@
+package co.nilin.opex.api.core.inout
+
+import java.math.BigDecimal
+import java.util.*
+
+data class QueryOrderResponse(
+ val symbol: String,
+ val ouid: String,
+ val orderId: Long,
+ val orderListId: Long, //Unless part of an OCO, the value will always be -1.
+ val clientOrderId: String,
+ val price: BigDecimal,
+ val origQty: BigDecimal,
+ val executedQty: BigDecimal,
+ val cummulativeQuoteQty: BigDecimal,
+ val status: OrderStatus,
+ val timeInForce: TimeInForce,
+ val type: OrderType,
+ val side: OrderSide,
+ val stopPrice: BigDecimal?,
+ val icebergQty: BigDecimal?,
+ val time: Date,
+ val updateTime: Date,
+ val isWorking: Boolean,
+ val origQuoteOrderQty: BigDecimal
+)
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/RateLimit.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/RateLimit.kt
new file mode 100644
index 000000000..7de6e91c3
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/RateLimit.kt
@@ -0,0 +1,10 @@
+package co.nilin.opex.api.core.inout
+
+enum class RateLimit(val rateLimitType: RateLimitType, val interval: String, val intervalNum: Int, val limit: Int) {
+
+ REQUEST_WEIGHT(RateLimitType.REQUEST_WEIGHT, "MINUTE", 1, 1200),
+ ORDERS_SECOND(RateLimitType.ORDERS, "SECOND", 10, 50),
+ ORDERS_DAY(RateLimitType.ORDERS, "DAY", 1, 16000),
+ RAW_REQUESTS(RateLimitType.RAW_REQUESTS, "MINUTE", 5, 6100)
+
+}
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/RateLimitType.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/RateLimitType.kt
new file mode 100644
index 000000000..63231003a
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/RateLimitType.kt
@@ -0,0 +1,9 @@
+package co.nilin.opex.api.core.inout
+
+enum class RateLimitType {
+
+ REQUEST_WEIGHT,
+ ORDERS,
+ RAW_REQUESTS
+
+}
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeRequest.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeRequest.kt
new file mode 100644
index 000000000..511949d47
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeRequest.kt
@@ -0,0 +1,11 @@
+package co.nilin.opex.api.core.inout
+
+import java.util.*
+
+class TradeRequest(
+ val symbol: String?,
+ val fromTrade: Long?,
+ val startTime: Date?,
+ val endTime: Date?,
+ val limit: Int? = 500 //Default 500; max 1000.
+)
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeResponse.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeResponse.kt
new file mode 100644
index 000000000..ffbb1d604
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeResponse.kt
@@ -0,0 +1,21 @@
+package co.nilin.opex.api.core.inout
+
+import java.math.BigDecimal
+import java.util.*
+
+data class TradeResponse(
+ val symbol: String,
+ val id: Long,
+ val orderId: Long,
+ val orderListId: Long = -1,
+ val price: BigDecimal,
+ val qty: BigDecimal,
+ val quoteQty: BigDecimal,
+ val commission: BigDecimal,
+ val commissionAsset: String,
+ val time: Date,
+ val isBuyer: Boolean,
+ val isMaker: Boolean,
+ val isBestMatch: Boolean,
+ val isMakerBuyer: Boolean
+)
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/Wallet.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/Wallet.kt
new file mode 100644
index 000000000..72e6d2f1b
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/Wallet.kt
@@ -0,0 +1,9 @@
+package co.nilin.opex.api.core.inout
+
+import java.math.BigDecimal
+
+data class Wallet(
+ val asset: String,
+ val balance: BigDecimal,
+ val type: String
+)
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/AccountantProxy.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/AccountantProxy.kt
new file mode 100644
index 000000000..16b831307
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/AccountantProxy.kt
@@ -0,0 +1,9 @@
+package co.nilin.opex.api.core.spi
+
+import co.nilin.opex.api.core.inout.PairInfoResponse
+
+interface AccountantProxy {
+
+ suspend fun getPairConfigs(): List
+
+}
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MEGatewayProxy.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MEGatewayProxy.kt
new file mode 100644
index 000000000..7a427891d
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MEGatewayProxy.kt
@@ -0,0 +1,25 @@
+package co.nilin.opex.api.core.spi
+
+import co.nilin.opex.api.core.inout.CancelOrderRequest
+import co.nilin.opex.api.core.inout.OrderSubmitResult
+import co.nilin.opex.matching.core.model.MatchConstraint
+import co.nilin.opex.matching.core.model.OrderDirection
+import co.nilin.opex.matching.core.model.OrderType
+import reactor.core.publisher.Mono
+import java.math.BigDecimal
+
+interface MEGatewayProxy {
+ data class CreateOrderRequest(
+ var uuid: String?,
+ val pair: String,
+ val price: BigDecimal,
+ val quantity: BigDecimal,
+ val direction: OrderDirection,
+ val matchConstraint: MatchConstraint?,
+ val orderType: OrderType
+ )
+
+ suspend fun createNewOrder(order: CreateOrderRequest, token: String?): OrderSubmitResult?
+
+ suspend fun cancelOrder(request: CancelOrderRequest, token: String?): OrderSubmitResult?
+}
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketQueryHandler.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketQueryHandler.kt
new file mode 100644
index 000000000..c14938dc4
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketQueryHandler.kt
@@ -0,0 +1,23 @@
+package co.nilin.opex.api.core.spi
+
+import co.nilin.opex.api.core.inout.*
+import kotlinx.coroutines.flow.Flow
+import java.time.LocalDateTime
+
+interface MarketQueryHandler {
+
+ suspend fun getTradeTickerData(startFrom: LocalDateTime): List
+
+ suspend fun getTradeTickerDataBySymbol(symbol: String, startFrom: LocalDateTime): PriceChangeResponse
+
+ suspend fun openBidOrders(symbol: String, limit: Int): List
+
+ suspend fun openAskOrders(symbol: String, limit: Int): List
+
+ suspend fun lastOrder(symbol: String): QueryOrderResponse?
+
+ suspend fun recentTrades(symbol: String, limit: Int): Flow
+
+ suspend fun lastPrice(symbol: String?): List
+
+}
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/OrderPersister.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/OrderPersister.kt
new file mode 100644
index 000000000..a8a128d04
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/OrderPersister.kt
@@ -0,0 +1,7 @@
+package co.nilin.opex.api.core.spi
+
+import co.nilin.opex.accountant.core.inout.RichOrder
+
+interface OrderPersister {
+ suspend fun save(order: RichOrder)
+}
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/SymbolMapper.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/SymbolMapper.kt
new file mode 100644
index 000000000..6e36e717c
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/SymbolMapper.kt
@@ -0,0 +1,10 @@
+package co.nilin.opex.api.core.spi
+
+interface SymbolMapper {
+
+ suspend fun map(symbol: String?): String?
+
+ suspend fun unmap(value: String?): String?
+
+ suspend fun getKeyValues(): Map
+}
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/TradePersister.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/TradePersister.kt
new file mode 100644
index 000000000..d07d91a1f
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/TradePersister.kt
@@ -0,0 +1,7 @@
+package co.nilin.opex.api.core.spi
+
+import co.nilin.opex.accountant.core.inout.RichTrade
+
+interface TradePersister {
+ suspend fun save(trade: RichTrade)
+}
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/UserQueryHandler.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/UserQueryHandler.kt
new file mode 100644
index 000000000..2c986aed3
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/UserQueryHandler.kt
@@ -0,0 +1,13 @@
+package co.nilin.opex.api.core.spi
+
+import co.nilin.opex.api.core.inout.*
+import co.nilin.opex.api.core.inout.*
+import kotlinx.coroutines.flow.Flow
+import java.security.Principal
+
+interface UserQueryHandler {
+ suspend fun queryOrder(principal: Principal, request: QueryOrderRequest): QueryOrderResponse?
+ suspend fun openOrders(principal: Principal, symbol: String?): Flow
+ suspend fun allOrders(principal: Principal, allOrderRequest: AllOrderRequest): Flow
+ suspend fun allTrades(principal: Principal, request: TradeRequest): Flow
+}
\ No newline at end of file
diff --git a/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/WalletProxy.kt b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/WalletProxy.kt
new file mode 100644
index 000000000..4ab3063b2
--- /dev/null
+++ b/Api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/WalletProxy.kt
@@ -0,0 +1,12 @@
+package co.nilin.opex.api.core.spi
+
+import co.nilin.opex.api.core.inout.OwnerLimitsResponse
+import co.nilin.opex.api.core.inout.Wallet
+
+interface WalletProxy {
+
+ suspend fun getWallets(uuid: String?, token: String?): List
+
+ suspend fun getOwnerLimits(uuid: String?, token: String?): OwnerLimitsResponse
+
+}
\ No newline at end of file
diff --git a/Api/api-ports/.gitignore b/Api/api-ports/.gitignore
new file mode 100644
index 000000000..f3a8317d6
--- /dev/null
+++ b/Api/api-ports/.gitignore
@@ -0,0 +1,4 @@
+*.iml
+target/
+.mvn/
+.idea/
\ No newline at end of file
diff --git a/Api/api-ports/api-binance-rest/.gitignore b/Api/api-ports/api-binance-rest/.gitignore
new file mode 100644
index 000000000..f3a8317d6
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/.gitignore
@@ -0,0 +1,4 @@
+*.iml
+target/
+.mvn/
+.idea/
\ No newline at end of file
diff --git a/Api/api-ports/api-binance-rest/mvnw b/Api/api-ports/api-binance-rest/mvnw
new file mode 100644
index 000000000..3c8a55373
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/mvnw
@@ -0,0 +1,322 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ]; then
+
+ if [ -f /etc/mavenrc ]; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ]; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false
+darwin=false
+mingw=false
+case "$(uname)" in
+CYGWIN*) cygwin=true ;;
+MINGW*) mingw=true ;;
+Darwin*)
+ darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="$(/usr/libexec/java_home)"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ]; then
+ if [ -r /etc/gentoo-release ]; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+fi
+
+if [ -z "$M2_HOME" ]; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ]; do
+ ls=$(ls -ld "$PRG")
+ link=$(expr "$ls" : '.*-> \(.*\)$')
+ if expr "$link" : '/.*' >/dev/null; then
+ PRG="$link"
+ else
+ PRG="$(dirname "$PRG")/$link"
+ fi
+ done
+
+ saveddir=$(pwd)
+
+ M2_HOME=$(dirname "$PRG")/..
+
+ # make it fully qualified
+ M2_HOME=$(cd "$M2_HOME" && pwd)
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=$(cygpath --unix "$M2_HOME")
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="$( (
+ cd "$M2_HOME"
+ pwd
+ ))"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="$( (
+ cd "$JAVA_HOME"
+ pwd
+ ))"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr \"$javaExecutable\" : '\([^ ]*\)')" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=$(which readlink)
+ if [ ! $(expr "$readLink" : '\([^ ]*\)') = "no" ]; then
+ if $darwin; then
+ javaHome="$(dirname \"$javaExecutable\")"
+ javaExecutable="$(cd \"$javaHome\" && pwd -P)/javac"
+ else
+ javaExecutable="$(readlink -f \"$javaExecutable\")"
+ fi
+ javaHome="$(dirname \"$javaExecutable\")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ]; then
+ if [ -n "$JAVA_HOME" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="$(which java)"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ]; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]; then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ]; do
+ if [ -d "$wdir"/.mvn ]; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=$(
+ cd "$wdir/.."
+ pwd
+ )
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' <"$1")"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(pwd)")
+if [ -z "$BASE_DIR" ]; then
+ exit 1
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in wrapperUrl)
+ jarUrl="$value"
+ break
+ ;;
+ esac
+ done <"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget >/dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl >/dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=$(cygpath --path --windows "$M2_HOME")
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/Api/api-ports/api-binance-rest/mvnw.cmd b/Api/api-ports/api-binance-rest/mvnw.cmd
new file mode 100644
index 000000000..c8d43372c
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/Api/api-ports/api-binance-rest/pom.xml b/Api/api-ports/api-binance-rest/pom.xml
new file mode 100644
index 000000000..1484b63cc
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/pom.xml
@@ -0,0 +1,176 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.4.4
+
+
+ co.nilin.opex
+ api-binance-rest
+ 1.0-SNAPSHOT
+ api-binance-rest
+ Api Binance Rest
+
+
+ 1.8
+ 1.4.31
+ ${version}
+ ${version}
+ 2020.0.2
+ ${version}
+
+
+
+
+ co.nilin.opex
+ matching-core
+ ${matching.version}
+ provided
+
+
+ co.nilin.opex
+ api-core
+ ${api.version}
+ provided
+
+
+ co.nilin.opex
+ logging-handler
+ ${utility.version}
+ provided
+
+
+ co.nilin.opex
+ error-handler
+ ${utility.version}
+ provided
+
+
+ co.nilin.opex
+ interceptors
+ ${utility.version}
+ provided
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.springframework.cloud
+ spring-cloud-starter-consul-all
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-starter-data-r2dbc
+
+
+ io.r2dbc
+ r2dbc-postgresql
+ runtime
+
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+ io.projectreactor.kotlin
+ reactor-kotlin-extensions
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-reactor
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+ 1.60
+
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-core
+
+
+ io.projectreactor
+ reactor-test
+ test
+
+
+ io.swagger
+ swagger-annotations
+ 1.5.20
+
+
+
+
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/test/kotlin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ -Xjsr305=strict
+
+
+ spring
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-allopen
+ ${kotlin.version}
+
+
+
+
+
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/ErrorHandlerConfig.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/ErrorHandlerConfig.kt
new file mode 100644
index 000000000..f500e712b
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/ErrorHandlerConfig.kt
@@ -0,0 +1,10 @@
+package co.nilin.opex.port.api.binance.config
+
+import co.nilin.opex.utility.error.EnableOpexErrorHandler
+import org.springframework.context.annotation.Configuration
+
+@Configuration
+@EnableOpexErrorHandler
+class ErrorHandlerConfig {
+
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/RestConfig.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/RestConfig.kt
new file mode 100644
index 000000000..4c6a23140
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/RestConfig.kt
@@ -0,0 +1,29 @@
+package co.nilin.opex.port.api.binance.config
+
+import co.nilin.opex.utility.interceptor.FormDataWorkaroundFilter
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.format.Formatter
+import java.util.*
+import org.springframework.web.server.WebFilter
+
+@Configuration
+class RestConfig {
+ @Bean
+ fun dateFormatter(): Formatter? {
+ return object : Formatter {
+ override fun print(date: Date, locale: Locale): String {
+ return date.time.toString()
+ }
+
+ override fun parse(date: String, locale: Locale): Date {
+ return Date(date.toLong())
+ }
+ }
+ }
+
+ @Bean
+ fun formDataWebFilter(): WebFilter {
+ return FormDataWorkaroundFilter()
+ }
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/SecurityConfig.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/SecurityConfig.kt
new file mode 100644
index 000000000..9d7bec883
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/SecurityConfig.kt
@@ -0,0 +1,49 @@
+package co.nilin.opex.port.api.binance.config
+
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.context.annotation.Bean
+import org.springframework.http.HttpMethod
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.web.server.ServerHttpSecurity
+import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder
+import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.web.reactive.function.client.WebClient
+
+@EnableWebFluxSecurity
+class SecurityConfig(private val webClient: WebClient) {
+
+ @Value("\${app.auth.cert-url}")
+ private lateinit var jwkUrl: String
+
+ @Bean
+ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? {
+ http.csrf().disable()
+ .cors().and()
+ .authorizeExchange()
+ .pathMatchers("/hello").permitAll()
+ .pathMatchers("/actuator/**").permitAll()
+ .pathMatchers("/swagger-ui/**").permitAll()
+ .pathMatchers("/swagger-resources/**").permitAll()
+ .pathMatchers("/v2/api-docs").permitAll()
+ .pathMatchers("/v3/depth").permitAll()
+ .pathMatchers("/v3/trades").permitAll()
+ .pathMatchers("/v3/ticker/**").permitAll()
+ .pathMatchers("/v3/exchangeInfo").permitAll()
+ .pathMatchers(HttpMethod.OPTIONS, "/**").permitAll()
+ .pathMatchers("/**").hasAuthority("SCOPE_trust")
+ .anyExchange().authenticated()
+ .and()
+ .oauth2ResourceServer()
+ .jwt()
+ return http.build()
+ }
+
+ @Bean
+ @Throws(Exception::class)
+ fun reactiveJwtDecoder(): ReactiveJwtDecoder? {
+ return NimbusReactiveJwtDecoder.withJwkSetUri(jwkUrl)
+ .webClient(webClient)
+ .build()
+ }
+}
diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/WebClientConfig.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/WebClientConfig.kt
new file mode 100644
index 000000000..84aa19305
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/config/WebClientConfig.kt
@@ -0,0 +1,38 @@
+package co.nilin.opex.port.api.binance.config
+
+import co.nilin.opex.utility.log.interceptor.CustomLogger
+import org.springframework.cloud.client.ServiceInstance
+import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties
+import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer
+import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.http.client.reactive.ReactorClientHttpConnector
+import org.springframework.web.reactive.function.client.WebClient
+import reactor.netty.http.client.HttpClient
+
+@Configuration
+class WebClientConfig {
+
+ @Bean
+ fun webClient(loadBalancerFactory: ReactiveLoadBalancer.Factory): WebClient {
+ val logger = CustomLogger(HttpClient::class.java)
+ return WebClient.builder()
+ .clientConnector(
+ ReactorClientHttpConnector(
+ HttpClient
+ .create()
+ .doOnRequest { request, connection ->
+ connection.addHandlerFirst(logger)
+ }
+ )
+ )
+ .filter(
+ ReactorLoadBalancerExchangeFilterFunction(
+ loadBalancerFactory, LoadBalancerProperties(), emptyList()
+ )
+ )
+ .build()
+ }
+
+}
diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/controller/AccountController.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/controller/AccountController.kt
new file mode 100644
index 000000000..080e82456
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/controller/AccountController.kt
@@ -0,0 +1,556 @@
+package co.nilin.opex.port.api.binance.controller
+
+import co.nilin.opex.api.core.inout.*
+import co.nilin.opex.api.core.spi.MEGatewayProxy
+import co.nilin.opex.api.core.spi.SymbolMapper
+import co.nilin.opex.api.core.spi.UserQueryHandler
+import co.nilin.opex.api.core.spi.WalletProxy
+import co.nilin.opex.port.api.binance.data.AccountInfoResponse
+import co.nilin.opex.port.api.binance.util.*
+import co.nilin.opex.utility.error.data.OpexError
+import co.nilin.opex.utility.error.data.OpexException
+import com.fasterxml.jackson.annotation.JsonInclude
+import io.swagger.annotations.*
+import java.math.BigDecimal
+import java.security.Principal
+import java.util.*
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import org.springframework.http.MediaType
+import org.springframework.security.core.annotation.CurrentSecurityContext
+import org.springframework.security.core.context.SecurityContext
+import org.springframework.web.bind.annotation.*
+
+@RestController
+class AccountController(
+ val queryHandler: UserQueryHandler,
+ val matchingGatewayProxy: MEGatewayProxy,
+ val walletProxy: WalletProxy,
+ val symbolMapper: SymbolMapper
+) {
+
+ data class FillsData(
+ val price: BigDecimal,
+ val qty: BigDecimal,
+ val commission: BigDecimal,
+ val commissionAsset: String
+ )
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ data class NewOrderResponse(
+ val symbol: String,
+ val orderId: Long,
+ val orderListId: Long, //Unless OCO, value will be -1
+ val clientOrderId: String?,
+ val transactTime: Date,
+ val price: BigDecimal?,
+ val origQty: BigDecimal?,
+ val executedQty: BigDecimal?,
+ val cummulativeQuoteQty: BigDecimal?,
+ val status: OrderStatus?,
+ val timeInForce: TimeInForce?,
+ val type: OrderType?,
+ val side: OrderSide?,
+ val fills: List?
+ )
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ data class CancelOrderResponse(
+ val symbol: String,
+ val origClientOrderId: String?,
+ val orderId: Long?,
+ val orderListId: Long, //Unless OCO, value will be -1
+ val clientOrderId: String?,
+ val price: BigDecimal?,
+ val origQty: BigDecimal?,
+ val executedQty: BigDecimal?,
+ val cummulativeQuoteQty: BigDecimal?,
+ val status: OrderStatus?,
+ val timeInForce: TimeInForce?,
+ val type: OrderType?,
+ val side: OrderSide?
+ )
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ data class QueryOrderResponse(
+ val symbol: String,
+ val orderId: Long,
+ val orderListId: Long, //Unless part of an OCO, the value will always be -1.
+ val clientOrderId: String,
+ val price: BigDecimal,
+ val origQty: BigDecimal,
+ val executedQty: BigDecimal,
+ val cummulativeQuoteQty: BigDecimal,
+ val status: OrderStatus,
+ val timeInForce: TimeInForce,
+ val type: OrderType,
+ val side: OrderSide,
+ val stopPrice: BigDecimal?,
+ val icebergQty: BigDecimal?,
+ val time: Date,
+ val updateTime: Date,
+ val isWorking: Boolean,
+ val origQuoteOrderQty: BigDecimal
+ )
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ data class TradeResponse(
+ val symbol: String,
+ val id: Long,
+ val orderId: Long,
+ val orderListId: Long = -1,
+ val price: BigDecimal,
+ val qty: BigDecimal,
+ val quoteQty: BigDecimal,
+ val commission: BigDecimal,
+ val commissionAsset: String,
+ val time: Date,
+ val isBuyer: Boolean,
+ val isMaker: Boolean,
+ val isBestMatch: Boolean
+ )
+
+ private val logger by LoggerDelegate()
+
+ /*
+ Send in a new order.
+ Weight: 1
+ Data Source: Matching Engine
+ */
+ @PostMapping(
+ "/v3/order",
+ consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE],
+ produces = [MediaType.APPLICATION_JSON_VALUE]
+ )
+ @ApiResponse(
+ message = "OK",
+ code = 200,
+ examples = Example(
+ ExampleProperty(
+ value = "{ \"symbol\": \"btc_usdt\", \"orderId\": -1, \"orderListId\": -1, \"transactTime\": \"2021-08-03T11:09:23.190+00:00\" }",
+ mediaType = "application/json"
+ )
+ )
+ )
+ suspend fun createNewOrder(
+ @RequestParam(name = "symbol")
+ symbol: String,
+ @RequestParam(name = "side")
+ side: OrderSide,
+ @RequestParam(name = "type")
+ type: OrderType,
+ @RequestParam(name = "timeInForce", required = false)
+ timeInForce: TimeInForce?,
+ @RequestParam(name = "quantity", required = false)
+ quantity: BigDecimal?,
+ @RequestParam(name = "quoteOrderQty", required = false)
+ quoteOrderQty: BigDecimal?,
+ @RequestParam(name = "price", required = false)
+ price: BigDecimal?,
+ @ApiParam(
+ value = "A unique id among open orders. Automatically generated if not sent.\n" +
+ "Orders with the same newClientOrderID can be accepted only when the previous one is filled, otherwise the order will be rejected."
+ )
+ @RequestParam(name = "newClientOrderId", required = false)
+ newClientOrderId: String?, /* A unique id among open orders. Automatically generated if not sent.
+ Orders with the same newClientOrderID can be accepted only when the previous one is filled, otherwise the order will be rejected.
+ */
+ @ApiParam(value = "Used with STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, and TAKE_PROFIT_LIMIT orders.")
+ @RequestParam(name = "stopPrice", required = false)
+ stopPrice: BigDecimal?, //Used with STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, and TAKE_PROFIT_LIMIT orders.
+ @RequestParam(name = "icebergQty", required = false)
+ @ApiParam(value = "Used with LIMIT, STOP_LOSS_LIMIT, and TAKE_PROFIT_LIMIT to create an iceberg order.")
+ icebergQty: BigDecimal?, //Used with LIMIT, STOP_LOSS_LIMIT, and TAKE_PROFIT_LIMIT to create an iceberg order.
+ @RequestParam(name = "newOrderRespType", required = false)
+ @ApiParam(value = "Set the response JSON. ACK, RESULT, or FULL; MARKET and LIMIT order types default to FULL, all other orders default to ACK.")
+ newOrderRespType: OrderResponseType?, //Set the response JSON. ACK, RESULT, or FULL; MARKET and LIMIT order types default to FULL, all other orders default to ACK.
+ @ApiParam(value = "The value cannot be greater than 60000")
+ @RequestParam(name = "recvWindow", required = false)
+ recvWindow: Long?, //The value cannot be greater than 60000
+ @RequestParam(name = "timestamp")
+ timestamp: Long,
+ @CurrentSecurityContext securityContext: SecurityContext
+ ): NewOrderResponse {
+ val internalSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound)
+
+ val request = MEGatewayProxy.CreateOrderRequest(
+ securityContext.jwtAuthentication().name,
+ internalSymbol,
+ price ?: BigDecimal.ZERO, // Maybe make this nullable as well?
+ quantity ?: BigDecimal.ZERO,
+ side.asOrderDirection(),
+ timeInForce?.asMatchConstraint(),
+ type.asMatchingOrderType()
+ )
+
+ matchingGatewayProxy.createNewOrder(request, securityContext.jwtAuthentication().tokenValue())
+ return NewOrderResponse(
+ symbol,
+ -1,
+ -1,
+ null,
+ Date(),
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ )
+ }
+
+ @DeleteMapping(
+ "/v3/order",
+ consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE],
+ produces = [MediaType.APPLICATION_JSON_VALUE]
+ )
+ suspend fun cancelOrder(
+ principal: Principal,
+ @RequestParam(name = "symbol")
+ symbol: String,
+ @RequestParam(name = "orderId", required = false)
+ orderId: Long?, //Either orderId or origClientOrderId must be sent.
+ @RequestParam(name = "origClientOrderId", required = false)
+ origClientOrderId: String?,
+ @RequestParam(name = "newClientOrderId", required = false)
+ newClientOrderId: String?,
+ @ApiParam(value = "The value cannot be greater than 60000")
+ @RequestParam(name = "recvWindow", required = false)
+ recvWindow: Long?, //The value cannot be greater than 60000
+ @RequestParam(name = "timestamp")
+ timestamp: Long,
+ @CurrentSecurityContext securityContext: SecurityContext
+ ): CancelOrderResponse {
+ val localSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound)
+ if (orderId == null && origClientOrderId == null)
+ throw OpexException(OpexError.BadRequest, message = "'orderId' or 'origClientOrderId' must be sent")
+
+ val order = queryHandler.queryOrder(principal, QueryOrderRequest(localSymbol, orderId, origClientOrderId))
+ ?: throw OpexException(OpexError.OrderNotFound)
+
+ val response = CancelOrderResponse(
+ symbol,
+ origClientOrderId,
+ orderId,
+ -1,
+ null,
+ order.price,
+ order.origQty,
+ order.executedQty,
+ order.cummulativeQuoteQty,
+ OrderStatus.CANCELED,
+ order.timeInForce,
+ order.type,
+ order.side
+ )
+
+ if (order.status == OrderStatus.CANCELED)
+ return response
+
+ if (order.status.equalsAny(OrderStatus.REJECTED, OrderStatus.EXPIRED, OrderStatus.FILLED))
+ throw OpexException(OpexError.CancelOrderNotAllowed)
+
+
+ val request = CancelOrderRequest(order.ouid, principal.name, order.orderId, localSymbol)
+ matchingGatewayProxy.cancelOrder(request, securityContext.jwtAuthentication().tokenValue())
+ return response
+ }
+
+ /*
+ Check an order's status.
+
+ Weight: 2
+ Data Source: Database
+ */
+ @GetMapping(
+ "/v3/order",
+ consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE],
+ produces = [MediaType.APPLICATION_JSON_VALUE]
+ )
+ @ApiResponse(
+ message = "OK",
+ code = 200,
+ examples = Example(
+ ExampleProperty(
+ value = "{ \"symbol\": \"btc_usdt\", \"orderId\": 12, \"orderListId\": -1, \"clientOrderId\": \"\", \"price\": 1, \"origQty\": 10, \"executedQty\": 0, \"cummulativeQuoteQty\": 0, \"status\": \"NEW\", \"timeInForce\": \"GTC\", \"type\": \"LIMIT\", \"side\": \"SELL\", \"time\": \"2021-08-04T12:10:13.488+00:00\", \"updateTime\": \"2021-08-04T12:10:13.488+00:00\", \"isWorking\": true, \"origQuoteOrderQty\": 10 }",
+ mediaType = "application/json"
+ )
+ )
+ )
+ suspend fun queryOrder(
+ principal: Principal,
+ @RequestParam(name = "symbol")
+ symbol: String,
+ @RequestParam(name = "orderId", required = false)
+ orderId: Long?,
+ @RequestParam(name = "origClientOrderId", required = false)
+ origClientOrderId: String?,
+ @ApiParam(value = "The value cannot be greater than 60000")
+ @RequestParam(name = "recvWindow", required = false)
+ recvWindow: Long?, //The value cannot be greater than 60000
+ @RequestParam(name = "timestamp")
+ timestamp: Long
+ ): QueryOrderResponse {
+ val internalSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound)
+
+ val response = queryHandler.queryOrder(principal, QueryOrderRequest(internalSymbol, orderId, origClientOrderId))
+ ?: throw OpexException(OpexError.OrderNotFound)
+
+ return QueryOrderResponse(
+ symbol,
+ response.orderId,
+ response.orderListId,
+ response.clientOrderId,
+ response.price,
+ response.origQty,
+ response.executedQty,
+ response.cummulativeQuoteQty,
+ response.status,
+ response.timeInForce,
+ response.type,
+ response.side,
+ response.stopPrice,
+ response.icebergQty,
+ response.time,
+ response.updateTime,
+ response.isWorking,
+ response.origQuoteOrderQty
+ )
+ }
+
+
+ /*
+ Get all open orders on a symbol. Careful when accessing this with no symbol.
+
+ Weight: 3 for a single symbol; 40 when the symbol parameter is omitted
+
+ Data Source: Database
+ */
+ @GetMapping(
+ "/v3/openOrders",
+ consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE],
+ produces = [MediaType.APPLICATION_JSON_VALUE]
+ )
+ @ApiResponse(
+ message = "OK",
+ code = 200,
+ examples = Example(
+ ExampleProperty(
+ value = "[ { \"symbol\": \"btc_usdt\", \"orderId\": 12, \"orderListId\": -1, \"clientOrderId\": \"\", \"price\": 1, \"origQty\": 10, \"executedQty\": 0, \"cummulativeQuoteQty\": 0, \"status\": \"NEW\", \"timeInForce\": \"GTC\", \"type\": \"LIMIT\", \"side\": \"SELL\", \"time\": \"2021-08-04T12:10:13.488+00:00\", \"updateTime\": \"2021-08-04T12:10:13.488+00:00\", \"isWorking\": true, \"origQuoteOrderQty\": 10 } ]",
+ mediaType = "application/json"
+ )
+ )
+ )
+ suspend fun fetchOpenOrders(
+ principal: Principal,
+ @RequestParam(name = "symbol", required = false)
+ symbol: String?,
+ @ApiParam(value = "The value cannot be greater than 60000")
+ @RequestParam(name = "recvWindow", required = false)
+ recvWindow: Long?, //The value cannot be greater than 60000
+ @RequestParam(name = "timestamp")
+ timestamp: Long
+ ): Flow {
+ val internalSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound)
+
+ return queryHandler.openOrders(principal, internalSymbol)
+ .map { response ->
+ QueryOrderResponse(
+ symbol ?: "",
+ response.orderId,
+ response.orderListId,
+ response.clientOrderId,
+ response.price,
+ response.origQty,
+ response.executedQty,
+ response.cummulativeQuoteQty,
+ response.status,
+ response.timeInForce,
+ response.type,
+ response.side,
+ response.stopPrice,
+ response.icebergQty,
+ response.time,
+ response.updateTime,
+ response.isWorking,
+ response.origQuoteOrderQty
+ )
+ }
+ }
+
+ /*
+ Get all account orders; active, canceled, or filled.
+ Weight: 10 with symbol
+ Data Source: Database
+ */
+ @GetMapping(
+ "/v3/allOrders",
+ consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE],
+ produces = [MediaType.APPLICATION_JSON_VALUE]
+ )
+ @ApiResponse(
+ message = "OK",
+ code = 200,
+ examples = Example(
+ ExampleProperty(
+ value = "{ }",
+ mediaType = "application/json"
+ )
+ )
+ )
+ suspend fun fetchAllOrders(
+ principal: Principal,
+ @RequestParam(name = "symbol", required = false)
+ symbol: String?,
+ @RequestParam(name = "startTime", required = false)
+ startTime: Date?,
+ @RequestParam(name = "endTime", required = false)
+ endTime: Date?,
+ @ApiParam(value = "Default 500; max 1000.")
+ @RequestParam(name = "limit", required = false)
+ limit: Int? = 500, //Default 500; max 1000.
+ @ApiParam(value = "The value cannot be greater than 60000")
+ @RequestParam(name = "recvWindow", required = false)
+ recvWindow: Long?, //The value cannot be greater than 60000
+ @RequestParam(name = "timestamp")
+ timestamp: Long
+ ): Flow {
+ val internalSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound)
+
+ return queryHandler.allOrders(principal, AllOrderRequest(internalSymbol, startTime, endTime, limit))
+ .map { response ->
+ QueryOrderResponse(
+ symbol ?: "",
+ response.orderId,
+ response.orderListId,
+ response.clientOrderId,
+ response.price,
+ response.origQty,
+ response.executedQty,
+ response.cummulativeQuoteQty,
+ response.status,
+ response.timeInForce,
+ response.type,
+ response.side,
+ response.stopPrice,
+ response.icebergQty,
+ response.time,
+ response.updateTime,
+ response.isWorking,
+ response.origQuoteOrderQty
+ )
+ }
+ }
+
+ /*
+ Get trades for a specific account and symbol.
+ If fromId is set, it will get trades >= that fromId. Otherwise most recent trades are returned.
+ Weight: 10 with symbol
+ Data Source: Database
+ */
+ @GetMapping(
+ "/v3/myTrades",
+ consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE],
+ produces = [MediaType.APPLICATION_JSON_VALUE]
+ )
+ @ApiResponse(
+ message = "OK",
+ code = 200,
+ examples = Example(
+ ExampleProperty(
+ value = "{ }",
+ mediaType = "application/json"
+ )
+ )
+ )
+ suspend fun fetchAllTrades(
+ principal: Principal,
+ @RequestParam(name = "symbol")
+ symbol: String?,
+ @RequestParam(name = "startTime", required = false)
+ startTime: Date?,
+ @RequestParam(name = "endTime", required = false)
+ endTime: Date?,
+ @ApiParam(value = "TradeId to fetch from. Default gets most recent trades.")
+ @RequestParam(name = "fromId", required = false)
+ fromId: Long?,//TradeId to fetch from. Default gets most recent trades.
+ @ApiParam(value = "Default 500; max 1000.")
+ @RequestParam(name = "limit", required = false)
+ limit: Int? = 500, //Default 500; max 1000.
+ @ApiParam(value = "The value cannot be greater than 60000")
+ @RequestParam(name = "recvWindow", required = false)
+ recvWindow: Long?, //The value cannot be greater than 60000
+ @RequestParam(name = "timestamp")
+ timestamp: Long
+ ): Flow {
+ val internalSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound)
+
+ return queryHandler.allTrades(principal, TradeRequest(internalSymbol, fromId, startTime, endTime, limit))
+ .map { response ->
+ TradeResponse(
+ symbol ?: "",
+ response.id,
+ response.orderId,
+ -1,
+ response.price,
+ response.qty,
+ response.quoteQty,
+ response.commission,
+ response.commissionAsset,
+ response.time,
+ response.isBuyer,
+ response.isMaker,
+ response.isBestMatch
+ )
+ }
+ }
+
+ @GetMapping(
+ "/v3/account",
+ consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE],
+ produces = [MediaType.APPLICATION_JSON_VALUE]
+ )
+ @ApiResponse(
+ message = "OK",
+ code = 200,
+ examples = Example(
+ ExampleProperty(
+ value = "{ \"makerCommission\": 0, \"takerCommission\": 0, \"buyerCommission\": 0, \"sellerCommission\": 0, \"canTrade\": true, \"canWithdraw\": true, \"canDeposit\": true, \"updateTime\": 1628420513843, \"accountType\": \"SPOT\", \"balances\": [ { \"asset\": \"usdt\", \"free\": 1000, \"locked\": 0 } ], \"permissions\": [ \"SPOT\" ] }",
+ mediaType = "application/json"
+ )
+ )
+ )
+ suspend fun accountInfo(
+ @CurrentSecurityContext securityContext: SecurityContext,
+ @ApiParam(value = "The value cannot be greater than 60000")
+ @RequestParam(name = "recvWindow", required = false)
+ recvWindow: Long?, //The value cannot be greater than 60000
+ @RequestParam(name = "timestamp")
+ timestamp: Long
+ ): AccountInfoResponse {
+ val auth = securityContext.jwtAuthentication()
+ val wallets = walletProxy.getWallets(auth.name, auth.tokenValue())
+ val limits = walletProxy.getOwnerLimits(auth.name, auth.tokenValue())
+ val parsedBalances = BalanceParser.parse(wallets)
+ val accountType = "SPOT"
+
+ //TODO replace commissions and accountType with actual data
+ return AccountInfoResponse(
+ 0,
+ 0,
+ 0,
+ 0,
+ limits.canTrade,
+ limits.canWithdraw,
+ limits.canDeposit,
+ Date().time,
+ accountType,
+ parsedBalances,
+ listOf(accountType)
+ )
+ }
+
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/controller/FiltersController.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/controller/FiltersController.kt
new file mode 100644
index 000000000..19ef78033
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/controller/FiltersController.kt
@@ -0,0 +1,7 @@
+package co.nilin.opex.port.api.binance.controller
+
+import org.springframework.web.bind.annotation.RestController
+
+@RestController
+class FiltersController {
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/controller/MarketController.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/controller/MarketController.kt
new file mode 100644
index 000000000..b0688a4ce
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/controller/MarketController.kt
@@ -0,0 +1,176 @@
+package co.nilin.opex.port.api.binance.controller
+
+import co.nilin.opex.api.core.spi.MarketQueryHandler
+import co.nilin.opex.api.core.spi.SymbolMapper
+import co.nilin.opex.port.api.binance.data.OrderBookResponse
+import co.nilin.opex.api.core.inout.PriceChangeResponse
+import co.nilin.opex.api.core.inout.PriceTickerResponse
+import co.nilin.opex.api.core.spi.AccountantProxy
+import co.nilin.opex.port.api.binance.data.ExchangeInfoResponse
+import co.nilin.opex.port.api.binance.data.ExchangeInfoSymbol
+import co.nilin.opex.port.api.binance.data.RecentTradeResponse
+import co.nilin.opex.utility.error.data.OpexError
+import co.nilin.opex.utility.error.data.OpexException
+import co.nilin.opex.utility.error.data.throwError
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.PathVariable
+import org.springframework.web.bind.annotation.RequestParam
+import org.springframework.web.bind.annotation.RestController
+import java.math.BigDecimal
+import java.security.Principal
+import java.time.Instant
+import java.time.LocalDateTime
+import java.time.ZoneId
+import java.util.*
+import java.util.concurrent.TimeUnit
+import kotlin.collections.ArrayList
+
+@RestController
+class MarketController(
+ private val accountantProxy: AccountantProxy,
+ private val marketQueryHandler: MarketQueryHandler,
+ private val symbolMapper: SymbolMapper,
+) {
+
+ private val orderBookValidLimits = arrayListOf(5, 10, 20, 50, 100, 500, 1000, 5000)
+ private val validDurations = arrayListOf("24h", "7d", "1m")
+
+ // Limit - Weight
+ // 5, 10, 20, 50, 100 - 1
+ // 500 - 5
+ // 1000 - 10
+ // 5000 - 50
+ @GetMapping("/v3/depth")
+ suspend fun orderBook(
+ @RequestParam("symbol")
+ symbol: String,
+ @RequestParam("limit", required = false)
+ limit: Int? // Default 100; max 5000. Valid limits:[5, 10, 20, 50, 100, 500, 1000, 5000]
+ ): OrderBookResponse {
+ val validLimit = limit ?: 100
+ val localSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound)
+ if (!orderBookValidLimits.contains(validLimit))
+ throwError(OpexError.InvalidLimitForOrderBook)
+
+ val mappedBidOrders = ArrayList>()
+ val mappedAskOrders = ArrayList>()
+
+ val bidOrders = marketQueryHandler.openBidOrders(localSymbol, validLimit)
+ val askOrders = marketQueryHandler.openAskOrders(localSymbol, validLimit)
+
+ bidOrders.forEach {
+ val mapped = arrayListOf().apply {
+ add(it.price ?: BigDecimal.ZERO)
+ add(it.quantity ?: BigDecimal.ZERO)
+ }
+ mappedBidOrders.add(mapped)
+ }
+
+ askOrders.forEach {
+ val mapped = arrayListOf().apply {
+ add(it.price ?: BigDecimal.ZERO)
+ add(it.quantity ?: BigDecimal.ZERO)
+ }
+ mappedAskOrders.add(mapped)
+ }
+
+ val lastOrder = marketQueryHandler.lastOrder(localSymbol)
+ return OrderBookResponse(lastOrder?.orderId ?: -1, mappedBidOrders, mappedAskOrders)
+ }
+
+ @GetMapping("/v3/trades")
+ suspend fun recentTrades(
+ principal: Principal,
+ @RequestParam("symbol")
+ symbol: String,
+ @RequestParam("limit", required = false)
+ limit: Int? // Default 500; max 1000.
+ ): Flow {
+ val validLimit = limit ?: 500
+ val localSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound)
+ if (validLimit !in 1..1000)
+ throwError(OpexError.InvalidLimitForRecentTrades)
+
+ return marketQueryHandler.recentTrades(localSymbol, validLimit)
+ .map {
+ RecentTradeResponse(
+ it.id,
+ it.price,
+ it.qty,
+ it.quoteQty,
+ it.time.time,
+ it.isMakerBuyer,
+ it.isBestMatch
+ )
+ }
+ }
+
+ @GetMapping("/v3/ticker/{duration:24h|7d|1m}")
+ suspend fun priceChange(
+ @PathVariable("duration")
+ duration: String,
+ @RequestParam("symbol", required = false)
+ symbol: String?,
+ ): List {
+ val localSymbol = if (symbol.isNullOrEmpty())
+ null
+ else
+ symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound)
+
+ if (!validDurations.contains(duration))
+ throwError(OpexError.InvalidPriceChangeDuration)
+
+ val now = Date().time
+ val before = when (duration) {
+ "24h" -> Date(now - TimeUnit.DAYS.toMillis(1))
+ "7d" -> Date(now - TimeUnit.DAYS.toMillis(7))
+ "1m" -> Date(now - TimeUnit.DAYS.toMillis(31))
+ else -> Date(now - TimeUnit.DAYS.toMillis(1))
+ }
+
+ val instant = Instant.ofEpochMilli(before.time)
+ val startDate = LocalDateTime.ofInstant(instant, ZoneId.systemDefault())
+
+ return if (symbol.isNullOrEmpty())
+ marketQueryHandler.getTradeTickerData(startDate)
+ else
+ listOf(marketQueryHandler.getTradeTickerDataBySymbol(localSymbol!!, startDate))
+ }
+
+ // Weight
+ // 1 for a single symbol
+ // 2 when the symbol parameter is omitted
+ @GetMapping("/v3/ticker/price")
+ suspend fun priceTicker(@RequestParam("symbol", required = false) symbol: String?): List {
+ val localSymbol = if (symbol == null)
+ null
+ else
+ symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound)
+ return marketQueryHandler.lastPrice(localSymbol)
+ }
+
+ @GetMapping("/v3/exchangeInfo")
+ suspend fun pairInfo(
+ @RequestParam("symbol", required = false)
+ symbol: String?,
+ @RequestParam("symbols", required = false)
+ symbols: String?
+ ): ExchangeInfoResponse {
+ val symbolsMap = symbolMapper.getKeyValues()
+ val pairConfigs = accountantProxy.getPairConfigs()
+ .map {
+ ExchangeInfoSymbol(
+ symbolsMap[it.pair] ?: it.pair,
+ "TRADING",
+ it.leftSideWalletSymbol.toUpperCase(),
+ BigDecimal.valueOf(it.leftSideFraction).scale(),
+ it.rightSideWalletSymbol.toUpperCase(),
+ BigDecimal.valueOf(it.rightSideFraction).scale()
+ )
+ }
+ return ExchangeInfoResponse(symbols = pairConfigs)
+ }
+
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/AccountInfoResponse.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/AccountInfoResponse.kt
new file mode 100644
index 000000000..0ace90f4c
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/AccountInfoResponse.kt
@@ -0,0 +1,18 @@
+package co.nilin.opex.port.api.binance.data
+
+import com.fasterxml.jackson.annotation.JsonInclude
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+data class AccountInfoResponse(
+ val makerCommission: Long,
+ val takerCommission: Long,
+ val buyerCommission: Long,
+ val sellerCommission: Long,
+ val canTrade: Boolean,
+ val canWithdraw: Boolean,
+ val canDeposit: Boolean,
+ val updateTime: Long,
+ val accountType: String, // Enum
+ val balances: List,
+ val permissions: List // Enum
+)
\ No newline at end of file
diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/BalanceResponse.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/BalanceResponse.kt
new file mode 100644
index 000000000..c81ef67b8
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/BalanceResponse.kt
@@ -0,0 +1,9 @@
+package co.nilin.opex.port.api.binance.data
+
+import java.math.BigDecimal
+
+data class BalanceResponse(
+ var asset: String,
+ var free: BigDecimal,
+ var locked: BigDecimal
+)
\ No newline at end of file
diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/ExchangeInfoResponse.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/ExchangeInfoResponse.kt
new file mode 100644
index 000000000..9a5cd229f
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/ExchangeInfoResponse.kt
@@ -0,0 +1,13 @@
+package co.nilin.opex.port.api.binance.data
+
+import co.nilin.opex.api.core.inout.RateLimit
+import java.util.*
+
+data class ExchangeInfoResponse(
+ val timezone: String = TimeZone.getDefault().id,
+ val serverTime: Long = Date().time,
+ val rateLimits: List = RateLimit.values()
+ .map { RateLimitResponse(it.rateLimitType, it.interval, it.intervalNum, it.limit) },
+ val exchangeFilters: List = emptyList(),
+ val symbols: List
+)
\ No newline at end of file
diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/ExchangeInfoSymbol.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/ExchangeInfoSymbol.kt
new file mode 100644
index 000000000..4679e05ea
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/ExchangeInfoSymbol.kt
@@ -0,0 +1,19 @@
+package co.nilin.opex.port.api.binance.data
+
+import co.nilin.opex.api.core.inout.OrderType
+
+data class ExchangeInfoSymbol(
+ val symbol: String,
+ val status: String,
+ val baseAsset: String,
+ val baseAssetPrecision: Int,
+ val quoteAsset: String,
+ val quoteAssetPrecision: Int,
+ val orderTypes: List = OrderType.activeTypes(),
+ val icebergAllowed: Boolean = false,
+ val ocoAllowed: Boolean = false,
+ val isSpotTradingAllowed: Boolean = false,
+ val isMarginTradingAllowed: Boolean = false,
+ val filters: List = emptyList(),
+ val permissions: List = listOf("SPOT")
+)
\ No newline at end of file
diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/OrderBookResponse.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/OrderBookResponse.kt
new file mode 100644
index 000000000..dbe8f96c5
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/OrderBookResponse.kt
@@ -0,0 +1,9 @@
+package co.nilin.opex.port.api.binance.data
+
+import java.math.BigDecimal
+
+data class OrderBookResponse(
+ val lastUpdateId: Long,
+ val bids: List>, // Inner list -> [0]: PRICE, [1]: QTY
+ val asks: List> // Inner list -> [0]: PRICE, [1]: QTY
+)
\ No newline at end of file
diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/RateLimitResponse.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/RateLimitResponse.kt
new file mode 100644
index 000000000..f102aeac6
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/RateLimitResponse.kt
@@ -0,0 +1,10 @@
+package co.nilin.opex.port.api.binance.data
+
+import co.nilin.opex.api.core.inout.RateLimitType
+
+data class RateLimitResponse(
+ val rateLimitType: RateLimitType,
+ val interval: String,
+ val intervalNum: Int,
+ val limit: Int
+)
\ No newline at end of file
diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/RecentTradeResponse.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/RecentTradeResponse.kt
new file mode 100644
index 000000000..2a6c76513
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/data/RecentTradeResponse.kt
@@ -0,0 +1,13 @@
+package co.nilin.opex.port.api.binance.data
+
+import java.math.BigDecimal
+
+data class RecentTradeResponse(
+ val id: Long,
+ val price: BigDecimal,
+ val qty: BigDecimal,
+ val quoteQty: BigDecimal,
+ val time: Long,
+ val isBuyerMaker: Boolean,
+ val isBestMatch: Boolean
+)
\ No newline at end of file
diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/proxy/AccountantProxyImpl.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/proxy/AccountantProxyImpl.kt
new file mode 100644
index 000000000..a457a1b75
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/proxy/AccountantProxyImpl.kt
@@ -0,0 +1,35 @@
+package co.nilin.opex.port.api.binance.proxy
+
+import co.nilin.opex.api.core.inout.PairInfoResponse
+import co.nilin.opex.api.core.spi.AccountantProxy
+import co.nilin.opex.port.api.binance.util.LoggerDelegate
+import kotlinx.coroutines.reactive.awaitSingle
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.core.ParameterizedTypeReference
+import org.springframework.http.MediaType
+import org.springframework.stereotype.Component
+import org.springframework.web.reactive.function.client.WebClient
+
+private inline fun typeRef(): ParameterizedTypeReference =
+ object : ParameterizedTypeReference() {}
+
+@Component
+class AccountantProxyImpl(private val webClient: WebClient) : AccountantProxy {
+
+ private val logger by LoggerDelegate()
+
+ @Value("\${app.accountant.url}")
+ private lateinit var baseUrl: String
+
+ override suspend fun getPairConfigs(): List {
+ logger.info("fetching pair configs")
+ return webClient.get()
+ .uri("$baseUrl/config/all")
+ .accept(MediaType.APPLICATION_JSON)
+ .retrieve()
+ .onStatus({ t -> t.isError }, { it.createException() })
+ .bodyToFlux(typeRef())
+ .collectList()
+ .awaitSingle()
+ }
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/proxy/MEGatewayProxyImpl.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/proxy/MEGatewayProxyImpl.kt
new file mode 100644
index 000000000..4b1433cd7
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/proxy/MEGatewayProxyImpl.kt
@@ -0,0 +1,55 @@
+package co.nilin.opex.port.api.binance.proxy
+
+import co.nilin.opex.api.core.inout.CancelOrderRequest
+import co.nilin.opex.api.core.inout.OrderSubmitResult
+import co.nilin.opex.api.core.spi.MEGatewayProxy
+import co.nilin.opex.port.api.binance.util.LoggerDelegate
+import kotlinx.coroutines.reactive.awaitSingleOrNull
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.core.ParameterizedTypeReference
+import org.springframework.http.MediaType
+import org.springframework.stereotype.Component
+import org.springframework.web.reactive.function.client.WebClient
+import org.springframework.web.reactive.function.client.body
+import reactor.core.publisher.Mono
+import java.net.URI
+
+private inline fun typeRef(): ParameterizedTypeReference =
+ object : ParameterizedTypeReference() {}
+
+@Component
+class MEGatewayProxyImpl(private val client: WebClient) : MEGatewayProxy {
+
+ private val logger by LoggerDelegate()
+
+ @Value("\${app.matching-gateway.url}")
+ private lateinit var baseUrl: String
+
+ override suspend fun createNewOrder(order: MEGatewayProxy.CreateOrderRequest, token: String?): OrderSubmitResult? {
+ logger.info("calling matching-gateway order create")
+ return client.post()
+ .uri(URI.create("$baseUrl/order"))
+ .accept(MediaType.APPLICATION_JSON)
+ .contentType(MediaType.APPLICATION_JSON)
+ .header("Authorization", "Bearer $token")
+ .body(Mono.just(order))
+ .retrieve()
+ .onStatus({ t -> t.isError }, { it.createException() })
+ .bodyToMono(typeRef())
+ .awaitSingleOrNull()
+ }
+
+ override suspend fun cancelOrder(request: CancelOrderRequest, token: String?): OrderSubmitResult? {
+ logger.info("calling matching-gateway order cancel")
+ return client.post()
+ .uri(URI.create("$baseUrl/order/cancel"))
+ .accept(MediaType.APPLICATION_JSON)
+ .contentType(MediaType.APPLICATION_JSON)
+ .header("Authorization", "Bearer $token")
+ .body(Mono.just(request))
+ .retrieve()
+ .onStatus({ t -> t.isError }, { it.createException() })
+ .bodyToMono(typeRef())
+ .awaitSingleOrNull()
+ }
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/proxy/WalletProxyImpl.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/proxy/WalletProxyImpl.kt
new file mode 100644
index 000000000..b5aedc3be
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/proxy/WalletProxyImpl.kt
@@ -0,0 +1,52 @@
+package co.nilin.opex.port.api.binance.proxy
+
+import co.nilin.opex.api.core.inout.OwnerLimitsResponse
+import co.nilin.opex.api.core.inout.Wallet
+import co.nilin.opex.api.core.spi.WalletProxy
+import co.nilin.opex.port.api.binance.util.LoggerDelegate
+import kotlinx.coroutines.reactive.awaitSingle
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.core.ParameterizedTypeReference
+import org.springframework.http.HttpHeaders
+import org.springframework.http.MediaType
+import org.springframework.stereotype.Component
+import org.springframework.web.reactive.function.client.WebClient
+import java.math.BigDecimal
+import java.time.LocalDateTime
+
+private inline fun typeRef(): ParameterizedTypeReference =
+ object : ParameterizedTypeReference() {}
+
+@Component
+class WalletProxyImpl(private val webClient: WebClient) : WalletProxy {
+
+ private val logger by LoggerDelegate()
+
+ @Value("\${app.wallet.url}")
+ private lateinit var baseUrl: String
+
+ override suspend fun getWallets(uuid: String?, token: String?): List {
+ logger.info("fetching wallets for $uuid")
+ return webClient.get()
+ .uri("$baseUrl/owner/wallet/all")
+ .accept(MediaType.APPLICATION_JSON)
+ .header(HttpHeaders.AUTHORIZATION, "Bearer $token")
+ .retrieve()
+ .onStatus({ t -> t.isError }, { it.createException() })
+ .bodyToFlux(typeRef())
+ .collectList()
+ .awaitSingle()
+ }
+
+ override suspend fun getOwnerLimits(uuid: String?, token: String?): OwnerLimitsResponse {
+ logger.info("fetching owner limits for $uuid")
+ return webClient.get()
+ .uri("$baseUrl/owner/limits")
+ .accept(MediaType.APPLICATION_JSON)
+ .header(HttpHeaders.AUTHORIZATION, "Bearer $token")
+ .retrieve()
+ .onStatus({ t -> t.isError }, { it.createException() })
+ .bodyToMono(typeRef())
+ .awaitSingle()
+ }
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/BalanceParser.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/BalanceParser.kt
new file mode 100644
index 000000000..8e91d8dc1
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/BalanceParser.kt
@@ -0,0 +1,32 @@
+package co.nilin.opex.port.api.binance.util
+
+import co.nilin.opex.api.core.inout.Wallet
+import co.nilin.opex.port.api.binance.data.BalanceResponse
+import java.math.BigDecimal
+
+object BalanceParser {
+
+ fun parse(list: List): List {
+ val result = arrayListOf()
+
+ for (w in list) {
+ result.addOrGet(w.asset).apply {
+ if (w.type == "exchange")
+ locked = w.balance
+ else
+ free = w.balance
+ }
+ }
+ return result
+ }
+
+ private fun ArrayList.addOrGet(symbol: String): BalanceResponse {
+ for (w in this)
+ if (w.asset == symbol)
+ return w
+
+ add(BalanceResponse(symbol, BigDecimal.ZERO, BigDecimal.ZERO))
+ return this.last()
+ }
+
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/EnumExtensions.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/EnumExtensions.kt
new file mode 100644
index 000000000..9f1c1974f
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/EnumExtensions.kt
@@ -0,0 +1,36 @@
+package co.nilin.opex.port.api.binance.util
+
+import co.nilin.opex.api.core.inout.OrderSide
+import co.nilin.opex.api.core.inout.TimeInForce
+import co.nilin.opex.matching.core.model.MatchConstraint
+import co.nilin.opex.matching.core.model.OrderType
+import co.nilin.opex.matching.core.model.OrderDirection
+
+fun OrderSide.asOrderDirection(): OrderDirection {
+ if (this == OrderSide.BUY)
+ return OrderDirection.BID
+ return OrderDirection.ASK
+}
+
+fun TimeInForce.asMatchConstraint(): MatchConstraint {
+ return when (this) {
+ TimeInForce.GTC -> MatchConstraint.GTC
+ TimeInForce.IOC -> MatchConstraint.IOC
+ TimeInForce.FOK -> MatchConstraint.FOK
+ }
+}
+
+fun co.nilin.opex.api.core.inout.OrderType.asMatchingOrderType(): OrderType {
+ return when (this) {
+ co.nilin.opex.api.core.inout.OrderType.LIMIT -> OrderType.LIMIT_ORDER
+ co.nilin.opex.api.core.inout.OrderType.MARKET -> OrderType.MARKET_ORDER
+ else -> OrderType.LIMIT_ORDER
+ }
+}
+
+fun > R.equalsAny(vararg equals: R): Boolean {
+ for (e in equals)
+ if (this == e)
+ return true
+ return false
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/LoggerDelegate.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/LoggerDelegate.kt
new file mode 100644
index 000000000..4f64aa4d2
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/LoggerDelegate.kt
@@ -0,0 +1,24 @@
+package co.nilin.opex.port.api.binance.util
+
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+class LoggerDelegate : ReadOnlyProperty {
+
+ companion object {
+ private fun createLogger(clazz: Class): Logger {
+ return LoggerFactory.getLogger(clazz)
+ }
+ }
+
+ private var logger: Logger? = null
+
+ override operator fun getValue(thisRef: Any?, property: KProperty<*>): Logger {
+ if (logger == null) {
+ logger = createLogger(thisRef!!.javaClass)
+ }
+ return logger!!
+ }
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/SecurityExtension.kt b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/SecurityExtension.kt
new file mode 100644
index 000000000..5b043512f
--- /dev/null
+++ b/Api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/port/api/binance/util/SecurityExtension.kt
@@ -0,0 +1,12 @@
+package co.nilin.opex.port.api.binance.util
+
+import org.springframework.security.core.context.SecurityContext
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken
+
+fun SecurityContext.jwtAuthentication(): JwtAuthenticationToken {
+ return authentication as JwtAuthenticationToken
+}
+
+fun JwtAuthenticationToken.tokenValue(): String {
+ return this.token.tokenValue
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-eventlistener-kafka/.gitignore b/Api/api-ports/api-eventlistener-kafka/.gitignore
new file mode 100644
index 000000000..bb9840a17
--- /dev/null
+++ b/Api/api-ports/api-eventlistener-kafka/.gitignore
@@ -0,0 +1,34 @@
+HELP.md
+target/
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+.mvn/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+
+
+### VS Code ###
+.vscode/
+
+.DS_Store
diff --git a/Api/api-ports/api-eventlistener-kafka/mvnw b/Api/api-ports/api-eventlistener-kafka/mvnw
new file mode 100644
index 000000000..3c8a55373
--- /dev/null
+++ b/Api/api-ports/api-eventlistener-kafka/mvnw
@@ -0,0 +1,322 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ]; then
+
+ if [ -f /etc/mavenrc ]; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ]; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false
+darwin=false
+mingw=false
+case "$(uname)" in
+CYGWIN*) cygwin=true ;;
+MINGW*) mingw=true ;;
+Darwin*)
+ darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="$(/usr/libexec/java_home)"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ]; then
+ if [ -r /etc/gentoo-release ]; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+fi
+
+if [ -z "$M2_HOME" ]; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ]; do
+ ls=$(ls -ld "$PRG")
+ link=$(expr "$ls" : '.*-> \(.*\)$')
+ if expr "$link" : '/.*' >/dev/null; then
+ PRG="$link"
+ else
+ PRG="$(dirname "$PRG")/$link"
+ fi
+ done
+
+ saveddir=$(pwd)
+
+ M2_HOME=$(dirname "$PRG")/..
+
+ # make it fully qualified
+ M2_HOME=$(cd "$M2_HOME" && pwd)
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=$(cygpath --unix "$M2_HOME")
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="$( (
+ cd "$M2_HOME"
+ pwd
+ ))"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="$( (
+ cd "$JAVA_HOME"
+ pwd
+ ))"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr \"$javaExecutable\" : '\([^ ]*\)')" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=$(which readlink)
+ if [ ! $(expr "$readLink" : '\([^ ]*\)') = "no" ]; then
+ if $darwin; then
+ javaHome="$(dirname \"$javaExecutable\")"
+ javaExecutable="$(cd \"$javaHome\" && pwd -P)/javac"
+ else
+ javaExecutable="$(readlink -f \"$javaExecutable\")"
+ fi
+ javaHome="$(dirname \"$javaExecutable\")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ]; then
+ if [ -n "$JAVA_HOME" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="$(which java)"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ]; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]; then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ]; do
+ if [ -d "$wdir"/.mvn ]; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=$(
+ cd "$wdir/.."
+ pwd
+ )
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' <"$1")"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(pwd)")
+if [ -z "$BASE_DIR" ]; then
+ exit 1
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in wrapperUrl)
+ jarUrl="$value"
+ break
+ ;;
+ esac
+ done <"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget >/dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl >/dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=$(cygpath --path --windows "$M2_HOME")
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/Api/api-ports/api-eventlistener-kafka/mvnw.cmd b/Api/api-ports/api-eventlistener-kafka/mvnw.cmd
new file mode 100644
index 000000000..c8d43372c
--- /dev/null
+++ b/Api/api-ports/api-eventlistener-kafka/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/Api/api-ports/api-eventlistener-kafka/pom.xml b/Api/api-ports/api-eventlistener-kafka/pom.xml
new file mode 100644
index 000000000..02f69f254
--- /dev/null
+++ b/Api/api-ports/api-eventlistener-kafka/pom.xml
@@ -0,0 +1,120 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.4.4
+
+
+ co.nilin.opex
+ api-eventlistener-kafka
+ 1.0-SNAPSHOT
+ api-eventlistener-kafka
+ Api kafka listener of Opex
+
+
+ 1.8
+ 1.4.31
+ ${version}
+ ${version}
+ ${version}
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ co.nilin.opex
+ matching-core
+ ${matching.version}
+ provided
+
+
+ co.nilin.opex
+ accountant-core
+ ${accountant.version}
+ provided
+
+
+ co.nilin.opex
+ api-core
+ ${api.version}
+ provided
+
+
+ org.springframework.kafka
+ spring-kafka
+
+
+ io.projectreactor.kotlin
+ reactor-kotlin-extensions
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-reactor
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-core
+
+
+ org.springframework.kafka
+ spring-kafka-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+
+
+
+
+
+
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/test/kotlin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ -Xjsr305=strict
+
+
+ spring
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-allopen
+ ${kotlin.version}
+
+
+
+
+
+
+
diff --git a/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/config/ApiKafkaConfig.kt b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/config/ApiKafkaConfig.kt
new file mode 100644
index 000000000..5269d6acf
--- /dev/null
+++ b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/config/ApiKafkaConfig.kt
@@ -0,0 +1,107 @@
+package co.nilin.opex.port.api.kafka.config
+
+import co.nilin.opex.matching.core.eventh.events.CoreEvent
+import co.nilin.opex.port.api.kafka.consumer.OrderKafkaListener
+import co.nilin.opex.port.api.kafka.consumer.EventKafkaListener
+import co.nilin.opex.port.api.kafka.consumer.TradeKafkaListener
+import org.apache.kafka.clients.admin.NewTopic
+import org.apache.kafka.clients.consumer.ConsumerConfig
+import org.apache.kafka.clients.producer.ProducerConfig
+import org.apache.kafka.common.serialization.StringDeserializer
+import org.apache.kafka.common.serialization.StringSerializer
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.beans.factory.annotation.Qualifier
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.context.support.GenericApplicationContext
+import org.springframework.kafka.core.*
+import org.springframework.kafka.listener.ConcurrentMessageListenerContainer
+import org.springframework.kafka.listener.ContainerProperties
+import org.springframework.kafka.support.serializer.JsonDeserializer
+import org.springframework.kafka.support.serializer.JsonSerializer
+import java.util.regex.Pattern
+
+@Configuration
+class ApiKafkaConfig {
+ @Value("\${spring.kafka.bootstrap-servers}")
+ private val bootstrapServers: String? = null
+
+ @Value("\${spring.kafka.consumer.group-id}")
+ private val groupId: String? = null
+
+ @Bean("apiConsumerConfig")
+ fun consumerConfigs(): Map? {
+ val props: MutableMap = HashMap()
+ props[ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers
+ props[ConsumerConfig.GROUP_ID_CONFIG] = groupId
+ props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java
+ props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = JsonDeserializer::class.java
+ props[JsonDeserializer.TRUSTED_PACKAGES] = "co.nilin.opex.*"
+ return props
+ }
+
+ @Bean("apiConsumerFactory")
+ fun consumerFactory(@Qualifier("apiConsumerConfig") consumerConfigs: Map): ConsumerFactory {
+ return DefaultKafkaConsumerFactory(consumerConfigs)
+ }
+
+ @Bean("apiProducerConfig")
+ fun producerConfigs(): Map {
+ val props: MutableMap = HashMap()
+ props[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootstrapServers
+ props[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java
+ props[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = JsonSerializer::class.java
+ return props
+ }
+
+ @Bean("apiProducerFactory")
+ fun producerFactory(@Qualifier("apiProducerConfig") producerConfigs: Map): ProducerFactory {
+ return DefaultKafkaProducerFactory(producerConfigs)
+ }
+
+ @Bean("apiKafkaTemplate")
+ fun kafkaTemplate(@Qualifier("apiProducerFactory") producerFactory: ProducerFactory): KafkaTemplate {
+ return KafkaTemplate(producerFactory)
+ }
+
+
+ @Autowired
+ @ConditionalOnBean(TradeKafkaListener::class)
+ fun configureTradeListener(tradeListener: TradeKafkaListener, @Qualifier("apiConsumerFactory") consumerFactory: ConsumerFactory) {
+ val containerProps = ContainerProperties(Pattern.compile("richTrade"))
+ containerProps.messageListener = tradeListener
+ val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps)
+ container.setBeanName("ApiTradeKafkaListenerContainer")
+ container.start()
+ }
+
+ @Autowired
+ @ConditionalOnBean(EventKafkaListener::class)
+ fun configureEventListener(eventListener: EventKafkaListener, @Qualifier("apiConsumerFactory") consumerFactory: ConsumerFactory) {
+ val containerProps = ContainerProperties(Pattern.compile("events_.*"))
+ containerProps.messageListener = eventListener
+ val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps)
+ container.setBeanName("ApiEventKafkaListenerContainer")
+ container.start()
+ }
+
+ @Autowired
+ @ConditionalOnBean(OrderKafkaListener::class)
+ fun configureOrderListener(orderListener: OrderKafkaListener, @Qualifier("apiConsumerFactory") consumerFactory: ConsumerFactory) {
+ val containerProps = ContainerProperties(Pattern.compile("richOrder"))
+ containerProps.messageListener = orderListener
+ val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps)
+ container.setBeanName("ApiOrderKafkaListenerContainer")
+ container.start()
+ }
+
+ @Autowired
+ fun createTopics(applicationContext: GenericApplicationContext) {
+ applicationContext.registerBean("topic_richOrder", NewTopic::class.java, "richOrder", 10, 1)
+ applicationContext.registerBean("topic_richTrade", NewTopic::class.java, "richTrade", 10, 1)
+ }
+
+
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/consumer/EventKafkaListener.kt b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/consumer/EventKafkaListener.kt
new file mode 100644
index 000000000..a5aafc0e0
--- /dev/null
+++ b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/consumer/EventKafkaListener.kt
@@ -0,0 +1,28 @@
+package co.nilin.opex.port.api.kafka.consumer
+
+
+import co.nilin.opex.matching.core.eventh.events.CoreEvent
+import co.nilin.opex.port.api.kafka.spi.EventListener
+import org.apache.kafka.clients.consumer.ConsumerRecord
+import org.springframework.kafka.listener.MessageListener
+import org.springframework.stereotype.Component
+
+@Component
+class EventKafkaListener : MessageListener {
+ val eventListeners = arrayListOf()
+ override fun onMessage(data: ConsumerRecord) {
+ eventListeners.forEach { tl ->
+ tl.onEvent(data.value(), data.partition(), data.offset(), data.timestamp())
+ }
+ }
+
+ fun addEventListener(tl: EventListener) {
+ eventListeners.add(tl)
+ }
+
+ fun removeEventListener(tl: EventListener) {
+ eventListeners.removeIf { item ->
+ item.id() == tl.id()
+ }
+ }
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/consumer/OrderKafkaListener.kt b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/consumer/OrderKafkaListener.kt
new file mode 100644
index 000000000..6fbe90511
--- /dev/null
+++ b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/consumer/OrderKafkaListener.kt
@@ -0,0 +1,28 @@
+package co.nilin.opex.port.api.kafka.consumer
+
+import co.nilin.opex.accountant.core.inout.RichOrder
+import co.nilin.opex.port.api.kafka.spi.RichOrderListener
+import org.apache.kafka.clients.consumer.ConsumerRecord
+import org.springframework.kafka.listener.MessageListener
+import org.springframework.stereotype.Component
+
+@Component
+class OrderKafkaListener : MessageListener {
+ val orderListeners = arrayListOf()
+ override fun onMessage(data: ConsumerRecord) {
+ orderListeners.forEach { tl ->
+ tl.onOrder(data.value(), data.partition(), data.offset(), data.timestamp())
+ }
+
+ }
+
+ fun addOrderListener(tl: RichOrderListener) {
+ orderListeners.add(tl)
+ }
+
+ fun removeOrderListener(tl: RichOrderListener) {
+ orderListeners.removeIf { item ->
+ item.id() == tl.id()
+ }
+ }
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/consumer/TradeKafkaListener.kt b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/consumer/TradeKafkaListener.kt
new file mode 100644
index 000000000..80a798b2d
--- /dev/null
+++ b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/consumer/TradeKafkaListener.kt
@@ -0,0 +1,27 @@
+package co.nilin.opex.port.api.kafka.consumer
+
+import co.nilin.opex.accountant.core.inout.RichTrade
+import co.nilin.opex.port.api.kafka.spi.RichTradeListener
+import org.apache.kafka.clients.consumer.ConsumerRecord
+import org.springframework.kafka.listener.MessageListener
+import org.springframework.stereotype.Component
+
+@Component
+class TradeKafkaListener : MessageListener {
+ val tradeListeners = arrayListOf()
+ override fun onMessage(data: ConsumerRecord) {
+ tradeListeners.forEach { tl ->
+ tl.onTrade(data.value(), data.partition(), data.offset(), data.timestamp())
+ }
+ }
+
+ fun addTradeListener(tl: RichTradeListener) {
+ tradeListeners.add(tl)
+ }
+
+ fun removeTradeListener(tl: RichTradeListener) {
+ tradeListeners.removeIf { item ->
+ item.id() == tl.id()
+ }
+ }
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/spi/EventListener.kt b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/spi/EventListener.kt
new file mode 100644
index 000000000..0d11ccf58
--- /dev/null
+++ b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/spi/EventListener.kt
@@ -0,0 +1,9 @@
+package co.nilin.opex.port.api.kafka.spi
+
+import co.nilin.opex.matching.core.eventh.events.CoreEvent
+
+
+interface EventListener {
+ fun id(): String
+ fun onEvent(coreEvent: CoreEvent, partition: Int, offset: Long, timestamp: Long)
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/spi/RichOrderListener.kt b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/spi/RichOrderListener.kt
new file mode 100644
index 000000000..75f759d06
--- /dev/null
+++ b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/spi/RichOrderListener.kt
@@ -0,0 +1,8 @@
+package co.nilin.opex.port.api.kafka.spi
+
+import co.nilin.opex.accountant.core.inout.RichOrder
+
+interface RichOrderListener {
+ fun id(): String
+ fun onOrder(order: RichOrder, partition: Int, offset: Long, timestamp: Long)
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/spi/RichTradeListener.kt b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/spi/RichTradeListener.kt
new file mode 100644
index 000000000..d81423890
--- /dev/null
+++ b/Api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/port/api/kafka/spi/RichTradeListener.kt
@@ -0,0 +1,8 @@
+package co.nilin.opex.port.api.kafka.spi
+
+import co.nilin.opex.accountant.core.inout.RichTrade
+
+interface RichTradeListener {
+ fun id(): String
+ fun onTrade(trade: RichTrade, partition: Int, offset: Long, timestamp: Long)
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-persister-postgres/.gitignore b/Api/api-ports/api-persister-postgres/.gitignore
new file mode 100644
index 000000000..de5a9214d
--- /dev/null
+++ b/Api/api-ports/api-persister-postgres/.gitignore
@@ -0,0 +1,34 @@
+HELP.md
+target/
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+.mvn/
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+.DS_Store
diff --git a/Api/api-ports/api-persister-postgres/mvnw b/Api/api-ports/api-persister-postgres/mvnw
new file mode 100644
index 000000000..3c8a55373
--- /dev/null
+++ b/Api/api-ports/api-persister-postgres/mvnw
@@ -0,0 +1,322 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ]; then
+
+ if [ -f /etc/mavenrc ]; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ]; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false
+darwin=false
+mingw=false
+case "$(uname)" in
+CYGWIN*) cygwin=true ;;
+MINGW*) mingw=true ;;
+Darwin*)
+ darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="$(/usr/libexec/java_home)"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ]; then
+ if [ -r /etc/gentoo-release ]; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+fi
+
+if [ -z "$M2_HOME" ]; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ]; do
+ ls=$(ls -ld "$PRG")
+ link=$(expr "$ls" : '.*-> \(.*\)$')
+ if expr "$link" : '/.*' >/dev/null; then
+ PRG="$link"
+ else
+ PRG="$(dirname "$PRG")/$link"
+ fi
+ done
+
+ saveddir=$(pwd)
+
+ M2_HOME=$(dirname "$PRG")/..
+
+ # make it fully qualified
+ M2_HOME=$(cd "$M2_HOME" && pwd)
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=$(cygpath --unix "$M2_HOME")
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="$( (
+ cd "$M2_HOME"
+ pwd
+ ))"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="$( (
+ cd "$JAVA_HOME"
+ pwd
+ ))"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr \"$javaExecutable\" : '\([^ ]*\)')" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=$(which readlink)
+ if [ ! $(expr "$readLink" : '\([^ ]*\)') = "no" ]; then
+ if $darwin; then
+ javaHome="$(dirname \"$javaExecutable\")"
+ javaExecutable="$(cd \"$javaHome\" && pwd -P)/javac"
+ else
+ javaExecutable="$(readlink -f \"$javaExecutable\")"
+ fi
+ javaHome="$(dirname \"$javaExecutable\")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ]; then
+ if [ -n "$JAVA_HOME" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="$(which java)"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ]; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]; then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ]; do
+ if [ -d "$wdir"/.mvn ]; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=$(
+ cd "$wdir/.."
+ pwd
+ )
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' <"$1")"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(pwd)")
+if [ -z "$BASE_DIR" ]; then
+ exit 1
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in wrapperUrl)
+ jarUrl="$value"
+ break
+ ;;
+ esac
+ done <"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget >/dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl >/dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=$(cygpath --path --windows "$M2_HOME")
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/Api/api-ports/api-persister-postgres/mvnw.cmd b/Api/api-ports/api-persister-postgres/mvnw.cmd
new file mode 100644
index 000000000..c8d43372c
--- /dev/null
+++ b/Api/api-ports/api-persister-postgres/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/Api/api-ports/api-persister-postgres/pom.xml b/Api/api-ports/api-persister-postgres/pom.xml
new file mode 100644
index 000000000..3beabdc05
--- /dev/null
+++ b/Api/api-ports/api-persister-postgres/pom.xml
@@ -0,0 +1,131 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.4.4
+
+
+ co.nilin.opex
+ api-persister-postgres
+ 1.0-SNAPSHOT
+ api-persister-postgres
+ Persist items of Opex api on Postgres
+
+
+ 1.8
+ 1.4.31
+ ${version}
+ ${version}
+ ${version}
+ ${version}
+
+
+
+
+ co.nilin.opex
+ matching-core
+ ${matching.version}
+ provided
+
+
+ co.nilin.opex
+ api-core
+ ${api.version}
+ provided
+
+
+ co.nilin.opex
+ accountant-core
+ ${accountant.version}
+ provided
+
+
+ co.nilin.opex
+ error-handler
+ ${utility.version}
+ provided
+
+
+ org.springframework.boot
+ spring-boot-starter-data-r2dbc
+
+
+ io.r2dbc
+ r2dbc-postgresql
+ runtime
+
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+
+ io.projectreactor.kotlin
+ reactor-kotlin-extensions
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-reactor
+
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-core
+
+
+ com.google.code.gson
+ gson
+
+
+ io.projectreactor
+ reactor-test
+ test
+
+
+
+
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/test/kotlin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ -Xjsr305=strict
+
+
+ spring
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-allopen
+ ${kotlin.version}
+
+
+
+
+
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+
+
diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/config/PostgresConfig.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/config/PostgresConfig.kt
new file mode 100644
index 000000000..c88b8f921
--- /dev/null
+++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/config/PostgresConfig.kt
@@ -0,0 +1,69 @@
+package co.nilin.opex.port.api.postgres.config
+
+import org.springframework.context.annotation.Configuration
+import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories
+import org.springframework.r2dbc.core.DatabaseClient
+
+@Configuration
+@EnableR2dbcRepositories(basePackages = ["co.nilin.opex"])
+class PostgresConfig(db: DatabaseClient) {
+
+ init {
+
+ val sql = """
+ CREATE TABLE IF NOT EXISTS orders (
+ id SERIAL PRIMARY KEY,
+ ouid VARCHAR(72) NOT NULL UNIQUE,
+ uuid VARCHAR(72) NOT NULL,
+ client_order_id VARCHAR(72),
+ symbol VARCHAR(20),
+ order_id numeric,
+ maker_fee decimal,
+ taker_fee decimal,
+ left_side_fraction decimal,
+ right_side_fraction decimal,
+ user_level VARCHAR(20),
+ side VARCHAR(20),
+ match_constraint VARCHAR(20),
+ order_type VARCHAR(20),
+ price decimal,
+ quantity decimal,
+ quote_quantity decimal,
+ executed_qty decimal,
+ accumulative_quote_qty decimal,
+ status integer,
+ create_date TIMESTAMP,
+ update_date TIMESTAMP
+ );
+ CREATE TABLE IF NOT EXISTS trades (
+ id SERIAL PRIMARY KEY,
+ trade_id numeric,
+ symbol VARCHAR(20),
+ matched_quantity decimal,
+ taker_price decimal,
+ maker_price decimal,
+ taker_commision decimal,
+ maker_commision decimal,
+ taker_commision_asset VARCHAR(20),
+ maker_commision_asset VARCHAR(20),
+ trade_date TIMESTAMP,
+ maker_ouid VARCHAR(72) NOT NULL,
+ taker_ouid VARCHAR(72) NOT NULL,
+ maker_uuid VARCHAR(72) NOT NULL,
+ taker_uuid VARCHAR(72) NOT NULL,
+ create_date TIMESTAMP
+ );
+ CREATE TABLE IF NOT EXISTS symbol_maps (
+ symbol VARCHAR(72) PRIMARY KEY,
+ value VARCHAR(72) UNIQUE NOT NULL
+ );
+ INSERT INTO symbol_maps(symbol, value) VALUES('btc_usdt', 'BTCUSDT') ON CONFLICT DO NOTHING;
+ INSERT INTO symbol_maps(symbol, value) VALUES('eth_usdt', 'ETHUSDT') ON CONFLICT DO NOTHING;
+ INSERT INTO symbol_maps(symbol, value) VALUES('eth_btc', 'ETHBTC') ON CONFLICT DO NOTHING;
+ """
+ val initDb = db.sql { sql }
+ initDb // initialize the database
+ .then()
+ .subscribe() // execute
+ }
+}
diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/dao/OrderRepository.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/dao/OrderRepository.kt
new file mode 100644
index 000000000..1c3824c93
--- /dev/null
+++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/dao/OrderRepository.kt
@@ -0,0 +1,103 @@
+package co.nilin.opex.port.api.postgres.dao
+
+import co.nilin.opex.matching.core.model.OrderDirection
+import co.nilin.opex.api.core.inout.AggregatedOrderPriceModel
+import co.nilin.opex.port.api.postgres.model.OrderModel
+import kotlinx.coroutines.flow.Flow
+import org.springframework.data.r2dbc.repository.Query
+import org.springframework.data.repository.query.Param
+import org.springframework.data.repository.reactive.ReactiveCrudRepository
+import org.springframework.stereotype.Repository
+import reactor.core.publisher.Flux
+import reactor.core.publisher.Mono
+import java.util.*
+
+@Repository
+interface OrderRepository : ReactiveCrudRepository {
+
+ @Query("select * from orders where ouid = :ouid")
+ fun findByOuid(@Param("ouid") ouid: String): Mono
+
+ @Query("select * from orders where symbol = :symbol and order_id = :orderId")
+ fun findBySymbolAndOrderId(
+ @Param("symbol")
+ symbol: String, @Param("orderId")
+ orderId: Long
+ ): Mono
+
+ @Query("select * from orders where symbol = :symbol and client_order_id = :origClientOrderId")
+ fun findBySymbolAndClientOrderId(
+ @Param("symbol")
+ symbol: String, @Param("origClientOrderId")
+ origClientOrderId: String
+ ): Mono
+
+ @Query("select * from orders where uuid = :uuid and (:symbol is null or symbol = :symbol) and status in (:statuses)")
+ fun findByUuidAndSymbolAndStatus(
+ @Param("uuid")
+ uuid: String,
+ @Param("symbol")
+ symbol: String?, @Param("statuses")
+ status: Collection
+ ): Flow
+
+ @Query(
+ "select * from orders where uuid = :uuid " +
+ "and (:symbol is null or symbol = :symbol) " +
+ "and (:startTime is null or update_date >= :startTime)" +
+ "and (:endTime is null or update_date < :endTime)"
+ )
+ fun findByUuidAndSymbolAndTimeBetween(
+ @Param("uuid")
+ uuid: String,
+ @Param("symbol")
+ symbol: String?,
+ @Param("startTime")
+ startTime: Date?,
+ @Param("endTime")
+ endTime: Date?
+ ): Flow
+
+ @Query(
+ """
+ select price, (sum(quantity) - sum(executed_qty)) as quantity from orders
+ where symbol = :symbol and side = :direction and status in (:statuses)
+ group by price
+ order by price asc
+ limit :limit
+ """
+ )
+ fun findBySymbolAndDirectionAndStatusSortAscendingByPrice(
+ @Param("symbol")
+ symbol: String,
+ @Param("direction")
+ direction: OrderDirection,
+ @Param("limit")
+ limit: Int,
+ @Param("statuses")
+ status: Collection
+ ): Flux
+
+ @Query(
+ """
+ select price, (sum(quantity) - sum(executed_qty)) as quantity from orders
+ where symbol = :symbol and side = :direction and status in (:statuses)
+ group by price
+ order by price desc
+ limit :limit
+ """
+ )
+ fun findBySymbolAndDirectionAndStatusSortDescendingByPrice(
+ @Param("symbol")
+ symbol: String,
+ @Param("direction")
+ direction: OrderDirection,
+ @Param("limit")
+ limit: Int,
+ @Param("statuses")
+ status: Collection
+ ): Flux
+
+ @Query("select * from orders where symbol = :symbol order by create_date desc limit 1")
+ fun findLastOrderBySymbol(@Param("symbol") symbol: String): Mono
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/dao/SymbolMapRepository.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/dao/SymbolMapRepository.kt
new file mode 100644
index 000000000..1a1d7dcf6
--- /dev/null
+++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/dao/SymbolMapRepository.kt
@@ -0,0 +1,18 @@
+package co.nilin.opex.port.api.postgres.dao
+
+import co.nilin.opex.port.api.postgres.model.SymbolMapModel
+import org.springframework.data.r2dbc.repository.Query
+import org.springframework.data.repository.query.Param
+import org.springframework.data.repository.reactive.ReactiveCrudRepository
+import org.springframework.stereotype.Repository
+import reactor.core.publisher.Mono
+
+@Repository
+interface SymbolMapRepository : ReactiveCrudRepository {
+
+ @Query("select * from symbol_maps where symbol = :symbol")
+ fun findBySymbol(@Param("symbol") symbol: String): Mono
+
+ @Query("select * from symbol_maps where value = :value")
+ fun findByValue(@Param("value") value: String): Mono
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/dao/TradeRepository.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/dao/TradeRepository.kt
new file mode 100644
index 000000000..414f18091
--- /dev/null
+++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/dao/TradeRepository.kt
@@ -0,0 +1,109 @@
+package co.nilin.opex.port.api.postgres.dao
+
+import co.nilin.opex.port.api.postgres.model.TradeModel
+import co.nilin.opex.port.api.postgres.model.TradeTickerData
+import kotlinx.coroutines.flow.Flow
+import org.springframework.data.r2dbc.repository.Query
+import org.springframework.data.repository.query.Param
+import org.springframework.data.repository.reactive.ReactiveCrudRepository
+import org.springframework.stereotype.Repository
+import reactor.core.publisher.Flux
+import reactor.core.publisher.Mono
+import java.time.LocalDateTime
+import java.util.*
+
+@Repository
+interface TradeRepository : ReactiveCrudRepository {
+
+ @Query("select * from trades where :ouid in (taker_ouid, maker_ouid) ")
+ fun findByOuid(@Param("ouid") ouid: String): Flow
+
+ @Query(
+ """
+ select * from trades where :uuid in (taker_uuid, maker_uuid)
+ and (:fromTrade is null or id > :fromTrade)
+ and (:symbol is null or symbol = :symbol)
+ and (:startTime is null or trade_date >= :startTime)
+ and (:endTime is null or trade_date < :endTime)
+ """
+ )
+ fun findByUuidAndSymbolAndTimeBetweenAndTradeIdGreaterThan(
+ @Param("uuid")
+ uuid: String,
+ @Param("symbol")
+ symbol: String?,
+ @Param("fromTrade")
+ fromTrade: Long?,
+ @Param("startTime")
+ startTime: Date?,
+ @Param("endTime")
+ endTime: Date?
+ ): Flow
+
+ @Query("select * from trades where symbol = :symbol order by create_date desc limit :limit")
+ fun findBySymbolSortDescendingByCreateDate(
+ @Param("symbol")
+ symbol: String,
+ @Param("limit")
+ limit: Int
+ ): Flow
+
+ @Query(
+ """
+ select symbol,
+ (select taker_price from trades where create_date > :date and symbol=t.symbol order by create_date desc limit 1) - (select taker_price from trades where create_date > :date and symbol=t.symbol order by create_date asc limit 1) as price_change,
+ ((((select taker_price from trades where create_date > :date and symbol=t.symbol order by create_date desc limit 1) - (select taker_price from trades where create_date > :date and symbol=t.symbol order by create_date asc limit 1))/(select taker_price from trades where create_date > :date and symbol=t.symbol order by create_date asc limit 1))*100) as price_change_percent,
+ (sum(matched_quantity)/sum(taker_price)) as weighted_avg_price,
+ (select taker_price from trades where create_date > :date and symbol=t.symbol order by create_date asc limit 1) as last_price,
+ (select matched_quantity from trades where create_date > :date and symbol=t.symbol order by create_date asc limit 1) as last_qty,
+ (select price from orders where create_date > :date and symbol=t.symbol and (status=1 or status=4) and side='BID' order by create_date desc limit 1) as bid_price,
+ (select price from orders where create_date > :date and symbol=t.symbol and (status=1 or status=4) and side='ASK' order by create_date asc limit 1) as ask_price,
+ (select price from orders where create_date > :date and symbol=t.symbol and (status=1 or status=4) order by create_date desc limit 1) as open_price,
+ max(taker_price) as high_price,
+ min(taker_price) as low_price,
+ sum(matched_quantity) as volume,
+ (select id from trades where create_date > :date and symbol=t.symbol order by create_date asc limit 1) as first_id,
+ (select id from trades where create_date > :date and symbol=t.symbol order by create_date desc limit 1) as last_id,
+ count(id) as count
+ from trades as t
+ where create_date > :date
+ group by symbol
+ """
+ )
+ fun tradeTicker(@Param("date") createDate: LocalDateTime): Flux
+
+ @Query(
+ """
+ select symbol,
+ (select taker_price from trades where create_date > :date and symbol=:symbol order by create_date desc limit 1) - (select taker_price from trades where create_date > :date and symbol=:symbol order by create_date asc limit 1) as price_change,
+ ((((select taker_price from trades where create_date > :date and symbol=:symbol order by create_date desc limit 1) - (select taker_price from trades where create_date > :date and symbol=:symbol order by create_date asc limit 1))/(select taker_price from trades where create_date > :date and symbol=:symbol order by create_date asc limit 1))*100) as price_change_percent,
+ (sum(matched_quantity)/sum(taker_price)) as weighted_avg_price,
+ (select taker_price from trades where create_date > :date and symbol=:symbol order by create_date asc limit 1) as last_price,
+ (select matched_quantity from trades where create_date > :date and symbol=:symbol order by create_date asc limit 1) as last_qty,
+ (select price from orders where create_date > :date and symbol=t.symbol and (status=1 or status=4) and side='BID' order by create_date desc limit 1) as bid_price,
+ (select price from orders where create_date > :date and symbol=t.symbol and (status=1 or status=4) and side='ASK' order by create_date asc limit 1) as ask_price,
+ (select price from orders where create_date > :date and symbol=t.symbol and (status=1 or status=4) order by create_date desc limit 1) as open_price,
+ max(taker_price) as high_price,
+ min(taker_price) as low_price,
+ sum(matched_quantity) as volume,
+ (select id from trades where create_date > :date and symbol=:symbol order by create_date asc limit 1) as first_id,
+ (select id from trades where create_date > :date and symbol=:symbol order by create_date desc limit 1) as last_id,
+ count(id) as count
+ from trades as t
+ where create_date > :date and symbol = :symbol
+ group by symbol
+ """
+ )
+ fun tradeTickerBySymbol(
+ @Param("symbol")
+ symbol: String,
+ @Param("date")
+ createDate: LocalDateTime,
+ ): Mono
+
+ @Query("select * from trades where create_date in (select max(create_date) from trades group by symbol) and symbol = :symbol")
+ fun findBySymbolGroupBySymbol(@Param("symbol") symbol: String): Flux
+
+ @Query("select * from trades where create_date in (select max(create_date) from trades group by symbol)")
+ fun findAllGroupBySymbol(): Flux
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/MarketQueryHandlerImpl.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/MarketQueryHandlerImpl.kt
new file mode 100644
index 000000000..6d9f508b2
--- /dev/null
+++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/MarketQueryHandlerImpl.kt
@@ -0,0 +1,169 @@
+package co.nilin.opex.port.api.postgres.impl
+
+import co.nilin.opex.api.core.inout.*
+import co.nilin.opex.api.core.spi.MarketQueryHandler
+import co.nilin.opex.api.core.spi.SymbolMapper
+import co.nilin.opex.matching.core.model.OrderDirection
+import co.nilin.opex.port.api.postgres.dao.OrderRepository
+import co.nilin.opex.port.api.postgres.dao.TradeRepository
+import co.nilin.opex.port.api.postgres.model.OrderModel
+import co.nilin.opex.port.api.postgres.model.TradeTickerData
+import co.nilin.opex.port.api.postgres.util.*
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.reactive.awaitFirst
+import kotlinx.coroutines.reactive.awaitFirstOrElse
+import kotlinx.coroutines.reactive.awaitFirstOrNull
+import org.springframework.stereotype.Component
+import java.lang.Exception
+import java.time.LocalDateTime
+import java.time.ZoneId
+import java.time.ZoneOffset
+import java.util.*
+import kotlin.math.max
+import kotlin.math.min
+
+@Component
+class MarketQueryHandlerImpl(
+ private val orderRepository: OrderRepository,
+ private val tradeRepository: TradeRepository,
+ private val symbolMapper: SymbolMapper,
+) : MarketQueryHandler {
+
+ override suspend fun getTradeTickerData(startFrom: LocalDateTime): List {
+ return tradeRepository.tradeTicker(startFrom)
+ .collectList()
+ .awaitFirstOrElse { emptyList() }
+ .map { it.asPriceChangeResponse(Date().time, startFrom.toInstant(ZoneOffset.UTC).toEpochMilli()) }
+
+ }
+
+ override suspend fun getTradeTickerDataBySymbol(symbol: String, startFrom: LocalDateTime): PriceChangeResponse {
+ return tradeRepository.tradeTickerBySymbol(symbol, startFrom)
+ .awaitFirstOrNull()
+ ?.asPriceChangeResponse(Date().time, startFrom.toInstant(ZoneOffset.UTC).toEpochMilli())
+ ?: PriceChangeResponse(
+ symbol = symbol,
+ openTime = Date().time,
+ closeTime = startFrom.toInstant(ZoneOffset.UTC).toEpochMilli()
+ )
+ }
+
+ override suspend fun openBidOrders(symbol: String, limit: Int): List {
+ return orderRepository.findBySymbolAndDirectionAndStatusSortDescendingByPrice(
+ symbol,
+ OrderDirection.BID,
+ limit,
+ listOf(OrderStatus.NEW.code, OrderStatus.PARTIALLY_FILLED.code)
+ ).collectList()
+ .awaitFirstOrElse { emptyList() }
+ .map { OrderBookResponse(it.price?.toBigDecimal(), it.quantity?.toBigDecimal()) }
+ }
+
+ override suspend fun openAskOrders(symbol: String, limit: Int): List {
+ return orderRepository.findBySymbolAndDirectionAndStatusSortAscendingByPrice(
+ symbol,
+ OrderDirection.ASK,
+ limit,
+ listOf(OrderStatus.NEW.code, OrderStatus.PARTIALLY_FILLED.code)
+ ).collectList()
+ .awaitFirstOrElse { emptyList() }
+ .map { OrderBookResponse(it.price?.toBigDecimal(), it.quantity?.toBigDecimal()) }
+ }
+
+ override suspend fun lastOrder(symbol: String): QueryOrderResponse? {
+ return orderRepository.findLastOrderBySymbol(symbol)
+ .awaitFirstOrNull()
+ ?.asQueryOrderResponse()
+ }
+
+ override suspend fun recentTrades(symbol: String, limit: Int): Flow {
+ return tradeRepository.findBySymbolSortDescendingByCreateDate(symbol, limit)
+ .map {
+ val takerOrder = orderRepository.findByOuid(it.takerOuid).awaitFirst()
+ val makerOrder = orderRepository.findByOuid(it.makerOuid).awaitFirst()
+ val isMakerBuyer = makerOrder.direction == OrderDirection.BID
+ MarketTradeResponse(
+ it.symbol,
+ it.tradeId,
+ if (isMakerBuyer) it.makerPrice.toBigDecimal() else it.takerPrice.toBigDecimal(),
+ it.matchedQuantity.toBigDecimal(),
+ if (isMakerBuyer)
+ makerOrder.quoteQuantity!!.toBigDecimal()
+ else
+ takerOrder.quoteQuantity!!.toBigDecimal(),
+ Date.from(it.createDate.atZone(ZoneId.systemDefault()).toInstant()),
+ true,
+ isMakerBuyer
+ )
+ }
+ }
+
+ override suspend fun lastPrice(symbol: String?): List {
+ val list = if (symbol.isNullOrEmpty())
+ tradeRepository.findAllGroupBySymbol()
+ else
+ tradeRepository.findBySymbolGroupBySymbol(symbol)
+ return list.collectList()
+ .awaitFirstOrElse { emptyList() }
+ .map {
+ val makerOrder = orderRepository.findByOuid(it.makerOuid).awaitFirst()
+ val apiSymbol = try {
+ symbolMapper.map(it.symbol)
+ } catch (e: Exception) {
+ it.symbol
+ }
+ val isMakerBuyer = makerOrder.direction == OrderDirection.BID
+ PriceTickerResponse(
+ apiSymbol,
+ if (isMakerBuyer)
+ min(it.takerPrice, it.makerPrice).toString()
+ else
+ max(it.takerPrice, it.makerPrice).toString()
+ )
+ }
+
+ }
+
+ private fun OrderModel.asQueryOrderResponse() = QueryOrderResponse(
+ symbol,
+ ouid,
+ orderId ?: -1,
+ -1,
+ clientOrderId ?: "",
+ price!!.toBigDecimal(),
+ quantity!!.toBigDecimal(),
+ executedQuantity!!.toBigDecimal(),
+ (accumulativeQuoteQty ?: 0.0).toBigDecimal(),
+ status!!.toOrderStatus(),
+ constraint!!.toTimeInForce(),
+ type!!.toApiOrderType(),
+ direction!!.toOrderSide(),
+ null,
+ null,
+ Date.from(createDate!!.atZone(ZoneId.systemDefault()).toInstant()),
+ Date.from(updateDate.atZone(ZoneId.systemDefault()).toInstant()),
+ status.toOrderStatus().isWorking(),
+ quoteQuantity!!.toBigDecimal()
+ )
+
+ private fun TradeTickerData.asPriceChangeResponse(openTime: Long, closeTime: Long) = PriceChangeResponse(
+ symbol,
+ priceChange ?: 0.0,
+ priceChangePercent ?: 0.0,
+ weightedAvgPrice ?: 0.0,
+ lastPrice ?: 0.0,
+ lastQty ?: 0.0,
+ bidPrice ?: 0.0,
+ askPrice ?: 0.0,
+ openPrice ?: 0.0,
+ highPrice ?: 0.0,
+ lowPrice ?: 0.0,
+ volume ?: 0.0,
+ openTime,
+ closeTime,
+ firstId ?: -1,
+ lastId ?: -1,
+ count ?: 0
+ )
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/OrderPersisterImpl.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/OrderPersisterImpl.kt
new file mode 100644
index 000000000..04d2d5241
--- /dev/null
+++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/OrderPersisterImpl.kt
@@ -0,0 +1,87 @@
+package co.nilin.opex.port.api.postgres.impl
+
+import co.nilin.opex.accountant.core.inout.RichOrder
+import co.nilin.opex.accountant.core.inout.comesAfter
+import co.nilin.opex.api.core.spi.OrderPersister
+import co.nilin.opex.port.api.postgres.dao.OrderRepository
+import co.nilin.opex.port.api.postgres.model.OrderModel
+import kotlinx.coroutines.reactive.awaitFirstOrNull
+import org.springframework.stereotype.Component
+import java.time.LocalDateTime
+
+@Component
+class OrderPersisterImpl(val orderRepository: OrderRepository) : OrderPersister {
+ override suspend fun save(order: RichOrder) {
+ var alreadySaved = false
+ val existingOrder = orderRepository
+ .findByOuid(order.ouid)
+ .awaitFirstOrNull()
+ if (existingOrder == null
+ || existingOrder.executedQuantity?.compareTo(order.executedQuantity.toDouble()) == -1
+ || order.status.comesAfter(existingOrder.status)
+ ) {
+ alreadySaved = true
+ orderRepository.save(
+ OrderModel(
+ existingOrder?.id,
+ order.ouid,
+ order.uuid,
+ null,
+ order.pair,
+ order.orderId,
+ order.makerFee.toDouble(),
+ order.takerFee.toDouble(),
+ order.leftSideFraction.toDouble(),
+ order.rightSideFraction.toDouble(),
+ order.userLevel,
+ order.direction,
+ order.constraint,
+ order.type,
+ existingOrder?.price ?: order.price.toDouble(),
+ existingOrder?.quantity ?: order.quantity.toDouble(),
+ existingOrder?.quoteQuantity ?: order.quoteQuantity.toDouble(),
+ existingOrder?.executedQuantity ?: order.executedQuantity.toDouble(),
+ existingOrder?.accumulativeQuoteQty ?: order.accumulativeQuoteQty.toDouble(),
+ order.status,
+ existingOrder?.createDate ?: LocalDateTime.now(),
+ LocalDateTime.now()
+ )
+ ).awaitFirstOrNull()
+ }
+
+ existingOrder?.apply {
+ if (
+ !alreadySaved &&
+ (makerFee == null || takerFee == null || leftSideFraction == null
+ || rightSideFraction == null || constraint == null || type == null)
+ ) {
+ orderRepository.save(
+ OrderModel(
+ existingOrder.id,
+ existingOrder.ouid,
+ existingOrder.uuid,
+ null,
+ existingOrder.symbol,
+ existingOrder.orderId,
+ order.makerFee.toDouble(),
+ order.takerFee.toDouble(),
+ order.leftSideFraction.toDouble(),
+ order.rightSideFraction.toDouble(),
+ order.userLevel,
+ order.direction,
+ order.constraint,
+ order.type,
+ existingOrder.price,
+ existingOrder.quantity,
+ existingOrder.quoteQuantity,
+ existingOrder.executedQuantity,
+ existingOrder.accumulativeQuoteQty,
+ existingOrder.status,
+ existingOrder.createDate ?: LocalDateTime.now(),
+ existingOrder.updateDate
+ )
+ ).awaitFirstOrNull()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/SymbolMapperImpl.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/SymbolMapperImpl.kt
new file mode 100644
index 000000000..5f6795eb4
--- /dev/null
+++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/SymbolMapperImpl.kt
@@ -0,0 +1,30 @@
+package co.nilin.opex.port.api.postgres.impl
+
+import co.nilin.opex.api.core.spi.SymbolMapper
+import co.nilin.opex.port.api.postgres.dao.SymbolMapRepository
+import kotlinx.coroutines.reactive.awaitFirstOrElse
+import kotlinx.coroutines.reactive.awaitFirstOrNull
+import org.springframework.stereotype.Component
+
+@Component
+class SymbolMapperImpl(val symbolMapRepository: SymbolMapRepository) : SymbolMapper {
+
+ override suspend fun map(symbol: String?): String? {
+ if (symbol == null) return null
+ return symbolMapRepository.findBySymbol(symbol).awaitFirstOrNull()?.value
+ }
+
+ override suspend fun unmap(value: String?): String? {
+ if (value == null) return null
+ return symbolMapRepository.findByValue(value).awaitFirstOrNull()?.symbol
+ }
+
+ override suspend fun getKeyValues(): Map {
+ val map = HashMap()
+ symbolMapRepository.findAll()
+ .collectList()
+ .awaitFirstOrElse { emptyList() }
+ .forEach { map[it.symbol] = it.value }
+ return map
+ }
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/TradePersisterImpl.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/TradePersisterImpl.kt
new file mode 100644
index 000000000..1e0ce1a41
--- /dev/null
+++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/TradePersisterImpl.kt
@@ -0,0 +1,147 @@
+package co.nilin.opex.port.api.postgres.impl
+
+import co.nilin.opex.accountant.core.inout.OrderStatus
+import co.nilin.opex.accountant.core.inout.RichTrade
+import co.nilin.opex.accountant.core.inout.comesBefore
+import co.nilin.opex.api.core.spi.TradePersister
+import co.nilin.opex.port.api.postgres.dao.OrderRepository
+import co.nilin.opex.port.api.postgres.dao.TradeRepository
+import co.nilin.opex.port.api.postgres.model.OrderModel
+import co.nilin.opex.port.api.postgres.model.TradeModel
+import kotlinx.coroutines.reactive.awaitFirstOrNull
+import org.springframework.stereotype.Component
+import org.springframework.transaction.annotation.Transactional
+import java.math.BigDecimal
+import java.time.LocalDateTime
+
+@Component
+class TradePersisterImpl(val tradeRepository: TradeRepository, val orderRepository: OrderRepository) : TradePersister {
+
+ @Transactional
+ override suspend fun save(trade: RichTrade) {
+ println("RichTrade save")
+ tradeRepository.save(
+ TradeModel(
+ null,
+ trade.id,
+ trade.pair,
+ trade.matchedQuantity.toDouble(),
+ trade.takerPrice.toDouble(),
+ trade.makerPrice.toDouble(),
+ trade.takerCommision.toDouble(),
+ trade.makerCommision.toDouble(),
+ trade.takerCommisionAsset,
+ trade.makerCommisionAsset,
+ trade.tradeDateTime,
+ trade.makerOuid,
+ trade.takerOuid,
+ trade.makerUuid,
+ trade.takerUuid,
+ LocalDateTime.now()
+ )
+ ).awaitFirstOrNull()
+ println("RichTrade save/update maker order")
+ saveMakerOrder(trade)
+ println("RichTrade save/update taker order")
+ saveTakerOrder(trade)
+
+ }
+
+ private suspend fun saveTakerOrder(trade: RichTrade) {
+ val existingOrder = orderRepository
+ .findByOuid(trade.takerOuid)
+ .awaitFirstOrNull()
+
+ val executedQuantity = (trade.takerQuantity.minus(trade.takerRemainedQuantity)).toDouble()
+ val status = if (trade.takerRemainedQuantity.compareTo(BigDecimal.ZERO) == 0) {
+ OrderStatus.FILLED.code
+ } else {
+ OrderStatus.PARTIALLY_FILLED.code
+ }
+
+ if (existingOrder == null || existingOrder.status.comesBefore(status) || (existingOrder.executedQuantity
+ ?: 0.0) < executedQuantity
+ )
+ orderRepository.save(
+ OrderModel(
+ existingOrder?.id,
+ trade.takerOuid,
+ trade.takerUuid,
+ null,
+ trade.pair,
+ trade.takerOrderId,
+ existingOrder?.makerFee,
+ existingOrder?.takerFee,
+ existingOrder?.leftSideFraction,
+ existingOrder?.rightSideFraction,
+ existingOrder?.userLevel,
+ trade.takerDirection,
+ existingOrder?.constraint,
+ existingOrder?.type,
+ trade.takerPrice.toDouble(),
+ trade.takerQuantity.toDouble(),
+ trade.takerQuoteQuantity.toDouble(),
+ (trade.takerQuantity.minus(trade.takerRemainedQuantity)).toDouble(),
+ trade.takerPrice.multiply(
+ (trade.takerQuantity.minus(trade.takerRemainedQuantity))
+ ).toDouble(),
+ if (trade.takerRemainedQuantity.compareTo(BigDecimal.ZERO) == 0) {
+ OrderStatus.FILLED.code
+ } else {
+ OrderStatus.PARTIALLY_FILLED.code
+ },
+ existingOrder?.createDate,
+ LocalDateTime.now()
+ )
+ ).awaitFirstOrNull()
+ }
+
+ private suspend fun saveMakerOrder(trade: RichTrade) {
+ val existingOrder = orderRepository
+ .findByOuid(trade.makerOuid)
+ .awaitFirstOrNull()
+
+ val executedQuantity = (trade.makerQuantity.minus(trade.makerRemainedQuantity)).toDouble()
+ val status = if (trade.makerRemainedQuantity.compareTo(BigDecimal.ZERO) == 0) {
+ OrderStatus.FILLED.code
+ } else {
+ OrderStatus.PARTIALLY_FILLED.code
+ }
+
+ if (existingOrder == null || existingOrder.status.comesBefore(status) || (existingOrder.executedQuantity
+ ?: 0.0) < executedQuantity
+ )
+ orderRepository.save(
+ OrderModel(
+ existingOrder?.id,
+ trade.makerOuid,
+ trade.makerUuid,
+ null,
+ trade.pair,
+ trade.makerOrderId,
+ existingOrder?.makerFee,
+ existingOrder?.takerFee,
+ existingOrder?.leftSideFraction,
+ existingOrder?.rightSideFraction,
+ existingOrder?.userLevel,
+ trade.makerDirection,
+ existingOrder?.constraint,
+ existingOrder?.type,
+ trade.makerPrice.toDouble(),
+ trade.makerQuantity.toDouble(),
+ trade.makerQuoteQuantity.toDouble(),
+ (trade.makerQuantity.minus(trade.makerRemainedQuantity)).toDouble(),
+ trade.makerPrice.multiply(
+ (trade.makerQuantity.minus(trade.makerRemainedQuantity))
+ ).toDouble(),
+ if (trade.makerRemainedQuantity.compareTo(BigDecimal.ZERO) == 0) {
+ OrderStatus.FILLED.code
+ } else {
+ OrderStatus.PARTIALLY_FILLED.code
+ },
+ existingOrder?.createDate ?: LocalDateTime.now(),
+ LocalDateTime.now()
+ )
+ ).awaitFirstOrNull()
+ }
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/UserQueryHandlerImpl.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/UserQueryHandlerImpl.kt
new file mode 100644
index 000000000..1b967dded
--- /dev/null
+++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/impl/UserQueryHandlerImpl.kt
@@ -0,0 +1,137 @@
+package co.nilin.opex.port.api.postgres.impl
+
+import co.nilin.opex.api.core.inout.*
+import co.nilin.opex.api.core.spi.UserQueryHandler
+import co.nilin.opex.matching.core.model.OrderDirection
+import co.nilin.opex.port.api.postgres.dao.OrderRepository
+import co.nilin.opex.port.api.postgres.dao.TradeRepository
+import co.nilin.opex.port.api.postgres.model.OrderModel
+import co.nilin.opex.port.api.postgres.util.*
+import co.nilin.opex.api.core.inout.*
+import co.nilin.opex.utility.error.data.OpexError
+import co.nilin.opex.utility.error.data.OpexException
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.reactive.awaitFirst
+import kotlinx.coroutines.reactive.awaitFirstOrNull
+import org.springframework.stereotype.Component
+import java.math.BigDecimal
+import java.security.Principal
+import java.time.ZoneId
+import java.util.*
+
+@Component
+class UserQueryHandlerImpl(
+ val orderRepository: OrderRepository,
+ val tradeRepository: TradeRepository
+) : UserQueryHandler {
+
+ override suspend fun queryOrder(principal: Principal, request: QueryOrderRequest): QueryOrderResponse? {
+ val order = (if (request.origClientOrderId != null) {
+ orderRepository.findBySymbolAndClientOrderId(request.symbol, request.origClientOrderId!!)
+ } else {
+ orderRepository.findBySymbolAndOrderId(request.symbol, request.orderId!!)
+
+ }).awaitFirstOrNull()
+ if (order?.constraint != null) {
+ if (order.uuid != principal.name)
+ throw OpexException(OpexError.Forbidden)
+ return orderToQueryResponse(order)
+ }
+ return null
+ }
+
+ override suspend fun openOrders(principal: Principal, symbol: String?): Flow {
+ return orderRepository.findByUuidAndSymbolAndStatus(
+ principal.name,
+ symbol,
+ listOf(OrderStatus.NEW.code, OrderStatus.PARTIALLY_FILLED.code)
+ ).filter { orderModel -> orderModel.constraint != null }
+ .map { order -> orderToQueryResponse(order) }
+ }
+
+ override suspend fun allOrders(principal: Principal, allOrderRequest: AllOrderRequest): Flow {
+ return orderRepository.findByUuidAndSymbolAndTimeBetween(
+ principal.name,
+ allOrderRequest.symbol,
+ allOrderRequest.startTime,
+ allOrderRequest.endTime
+ ).filter { orderModel -> orderModel.constraint != null }
+ .map { order -> orderToQueryResponse(order) }
+ }
+
+ override suspend fun allTrades(principal: Principal, request: TradeRequest): Flow {
+ return tradeRepository.findByUuidAndSymbolAndTimeBetweenAndTradeIdGreaterThan(
+ principal.name, request.symbol, request.fromTrade, request.startTime, request.endTime
+ ).map { trade ->
+ val takerOrder = orderRepository.findByOuid(trade.takerOuid).awaitFirst()
+ val makerOrder = orderRepository.findByOuid(trade.makerOuid).awaitFirst()
+ val isMakerBuyer = makerOrder.direction == OrderDirection.BID
+ TradeResponse(
+ trade.symbol,
+ trade.tradeId,
+ if (trade.takerUuid == principal.name) {
+ takerOrder.orderId!!
+ } else {
+ makerOrder.orderId!!
+ },
+ -1,
+ if (trade.takerUuid == principal.name) {
+ trade.takerPrice.toBigDecimal()
+ } else {
+ trade.makerPrice.toBigDecimal()
+ },
+ trade.matchedQuantity.toBigDecimal(),
+ if (isMakerBuyer) {
+ makerOrder.quoteQuantity!!.toBigDecimal()
+ } else {
+ takerOrder.quoteQuantity!!.toBigDecimal()
+ },
+ if (trade.takerUuid == principal.name) {
+ trade.takerCommision!!.toBigDecimal()
+ } else {
+ trade.makerCommision!!.toBigDecimal()
+ },
+ if (trade.takerUuid == principal.name) {
+ trade.takerCommisionAsset!!
+ } else {
+ trade.makerCommisionAsset!!
+ },
+ Date.from(
+ trade.createDate.atZone(ZoneId.systemDefault()).toInstant()
+ ),
+ if (trade.takerUuid == principal.name) {
+ OrderDirection.ASK == takerOrder.direction
+ } else {
+ OrderDirection.ASK == makerOrder.direction
+ },
+ trade.makerUuid == principal.name,
+ true,
+ isMakerBuyer
+ )
+ }
+ }
+
+
+ private fun orderToQueryResponse(order: OrderModel) = QueryOrderResponse(
+ order.symbol,
+ order.ouid,
+ order.orderId ?: -1,
+ -1,
+ order.clientOrderId ?: "",
+ order.price!!.toBigDecimal(),
+ order.quantity!!.toBigDecimal(),
+ order.executedQuantity!!.toBigDecimal(),
+ (order.accumulativeQuoteQty ?: 0.0).toBigDecimal(),
+ order.status!!.toOrderStatus(),
+ order.constraint!!.toTimeInForce(),
+ order.type!!.toApiOrderType(),
+ order.direction!!.toOrderSide(),
+ null,
+ null,
+ Date.from(order.createDate!!.atZone(ZoneId.systemDefault()).toInstant()),
+ Date.from(order.updateDate.atZone(ZoneId.systemDefault()).toInstant()),
+ order.status.toOrderStatus().isWorking(), order.quoteQuantity!!.toBigDecimal()
+ )
+}
\ No newline at end of file
diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/OrderModel.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/OrderModel.kt
new file mode 100644
index 000000000..291851ae8
--- /dev/null
+++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/OrderModel.kt
@@ -0,0 +1,38 @@
+package co.nilin.opex.port.api.postgres.model
+
+
+import co.nilin.opex.matching.core.model.MatchConstraint
+import co.nilin.opex.matching.core.model.OrderDirection
+import co.nilin.opex.matching.core.model.OrderType
+import org.springframework.data.annotation.Id
+import org.springframework.data.relational.core.mapping.Column
+import org.springframework.data.relational.core.mapping.Table
+import java.time.LocalDateTime
+
+@Table("orders")
+class OrderModel(
+ @Id var id: Long?,
+ @Column(value = "ouid")
+ val ouid: String,
+ val uuid: String,
+ @Column(value = "client_order_id")
+ val clientOrderId: String?,
+ val symbol: String,
+ @Column(value = "order_id") val orderId: Long?,
+ @Column("maker_fee") val makerFee: Double?,
+ @Column("taker_fee") val takerFee: Double?,
+ @Column("left_side_fraction") val leftSideFraction: Double?,
+ @Column("right_side_fraction") val rightSideFraction: Double?,
+ @Column("user_level") val userLevel: String?,
+ @Column("side") val direction: OrderDirection?,
+ @Column("match_constraint") val constraint: MatchConstraint?,
+ @Column("order_type") val type: OrderType?,
+ @Column("price") val price: Double?,
+ @Column("quantity") val quantity: Double?,
+ @Column("quote_quantity") val quoteQuantity: Double?,
+ @Column("executed_qty") val executedQuantity: Double?,
+ @Column("accumulative_quote_qty") val accumulativeQuoteQty: Double?,
+ @Column("status") val status: Int?,
+ @Column("create_date") val createDate: LocalDateTime?,
+ @Column("update_date") val updateDate: LocalDateTime
+)
\ No newline at end of file
diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/SymbolMapModel.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/SymbolMapModel.kt
new file mode 100644
index 000000000..d43ee4e63
--- /dev/null
+++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/SymbolMapModel.kt
@@ -0,0 +1,12 @@
+package co.nilin.opex.port.api.postgres.model
+
+
+import org.springframework.data.annotation.Id
+import org.springframework.data.relational.core.mapping.Column
+import org.springframework.data.relational.core.mapping.Table
+
+@Table("symbol_maps")
+class SymbolMapModel(
+ @Id val symbol: String,
+ @Column("value") val value: String,
+)
\ No newline at end of file
diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/TradeModel.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/TradeModel.kt
new file mode 100644
index 000000000..1fc1206cd
--- /dev/null
+++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/TradeModel.kt
@@ -0,0 +1,27 @@
+package co.nilin.opex.port.api.postgres.model
+
+
+import org.springframework.data.annotation.Id
+import org.springframework.data.relational.core.mapping.Column
+import org.springframework.data.relational.core.mapping.Table
+import java.time.LocalDateTime
+
+@Table("trades")
+class TradeModel(
+ @Id var id: Long?,
+ @Column("trade_id") val tradeId: Long,
+ val symbol: String,
+ @Column("matched_quantity") val matchedQuantity: Double,
+ @Column("taker_price") val takerPrice: Double,
+ @Column("maker_price") val makerPrice: Double,
+ @Column("taker_commision") val takerCommision: Double?,
+ @Column("maker_commision") val makerCommision: Double?,
+ @Column("taker_commision_asset") val takerCommisionAsset: String?,
+ @Column("maker_commision_asset") val makerCommisionAsset: String?,
+ @Column("trade_date") val tradeDate: LocalDateTime,
+ @Column("maker_ouid") val makerOuid: String,
+ @Column("taker_ouid") val takerOuid: String,
+ @Column("maker_uuid") val makerUuid: String,
+ @Column("taker_uuid") val takerUuid: String,
+ @Column("create_date") val createDate: LocalDateTime
+)
\ No newline at end of file
diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/TradeTickerData.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/TradeTickerData.kt
new file mode 100644
index 000000000..c0e7bdd79
--- /dev/null
+++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/model/TradeTickerData.kt
@@ -0,0 +1,33 @@
+package co.nilin.opex.port.api.postgres.model
+
+import org.springframework.data.relational.core.mapping.Column
+
+data class TradeTickerData(
+ val symbol: String,
+ @Column("price_change")
+ val priceChange: Double?,
+ @Column("price_change_percent")
+ val priceChangePercent: Double?,
+ @Column("weighted_avg_price")
+ val weightedAvgPrice: Double?,
+ @Column("last_price")
+ val lastPrice: Double?,
+ @Column("last_qty")
+ val lastQty: Double?,
+ @Column("bid_price")
+ val bidPrice: Double?,
+ @Column("ask_price")
+ val askPrice: Double?,
+ @Column("open_price")
+ val openPrice: Double?,
+ @Column("high_price")
+ val highPrice: Double?,
+ @Column("low_price")
+ val lowPrice: Double?,
+ val volume: Double?,
+ @Column("first_id")
+ val firstId: Long?,
+ @Column("last_id")
+ val lastId: Long?,
+ val count: Long?,
+)
diff --git a/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/util/EnumExtensions.kt b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/util/EnumExtensions.kt
new file mode 100644
index 000000000..8c993b8db
--- /dev/null
+++ b/Api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/port/api/postgres/util/EnumExtensions.kt
@@ -0,0 +1,45 @@
+package co.nilin.opex.port.api.postgres.util
+
+import co.nilin.opex.api.core.inout.OrderSide
+import co.nilin.opex.api.core.inout.OrderStatus
+import co.nilin.opex.api.core.inout.TimeInForce
+import co.nilin.opex.matching.core.model.MatchConstraint
+import co.nilin.opex.matching.core.model.OrderDirection
+import co.nilin.opex.matching.core.model.OrderType
+
+fun MatchConstraint.toTimeInForce(): TimeInForce {
+ if (this == MatchConstraint.FOK_BUDGET)
+ return TimeInForce.FOK
+ if (this == MatchConstraint.IOC_BUDGET)
+ return TimeInForce.IOC
+ return TimeInForce.valueOf(this.name)
+}
+
+
+fun TimeInForce.toMatchConstraint(): MatchConstraint {
+ return MatchConstraint.valueOf(this.name)
+}
+
+fun OrderType.toApiOrderType(): co.nilin.opex.api.core.inout.OrderType {
+ if (this == OrderType.LIMIT_ORDER)
+ return co.nilin.opex.api.core.inout.OrderType.LIMIT
+ if (this == OrderType.MARKET_ORDER)
+ return co.nilin.opex.api.core.inout.OrderType.MARKET
+ throw IllegalArgumentException("OrderType $this is not supported!")
+}
+
+fun OrderDirection.toOrderSide(): OrderSide {
+ if (this == OrderDirection.BID)
+ return OrderSide.BUY
+ return OrderSide.SELL
+}
+
+fun OrderStatus.isWorking(): Boolean {
+ return listOf(OrderStatus.NEW, OrderStatus.PARTIALLY_FILLED).contains(this)
+}
+
+fun Int.toOrderStatus(): OrderStatus {
+ val status = co.nilin.opex.accountant.core.inout.OrderStatus.values()
+ .find { s -> s.code == this }
+ return OrderStatus.valueOf(status!!.name)
+}
\ No newline at end of file
diff --git a/Api/pom.xml b/Api/pom.xml
new file mode 100644
index 000000000..8e670c159
--- /dev/null
+++ b/Api/pom.xml
@@ -0,0 +1,18 @@
+
+
+ 4.0.0
+ co.nilin.opex
+ api-root
+ 1.0-SNAPSHOT
+ api-root
+ pom
+ Api root of Opex
+
+ api-core
+ api-app
+ api-ports/api-persister-postgres
+ api-ports/api-binance-rest
+ api-ports/api-eventlistener-kafka
+
+
diff --git a/BlockchainGateway/.gitignore b/BlockchainGateway/.gitignore
new file mode 100644
index 000000000..785786ed7
--- /dev/null
+++ b/BlockchainGateway/.gitignore
@@ -0,0 +1,47 @@
+# Created by .ignore support plugin (hsz.mobi)
+### Kotlin template
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+### JetBrains template
+.idea/**
+.idea
+.idea/
+*.iml
+*.ipr
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+target/
+
+
+
+
+!/.idea/
+
+.DS_Store
diff --git a/BlockchainGateway/bc-gateway-app/.gitignore b/BlockchainGateway/bc-gateway-app/.gitignore
new file mode 100644
index 000000000..0d6c2228e
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-app/.gitignore
@@ -0,0 +1,35 @@
+HELP.md
+target/
+.mvn/
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+.DS_Store
diff --git a/BlockchainGateway/bc-gateway-app/Dockerfile b/BlockchainGateway/bc-gateway-app/Dockerfile
new file mode 100644
index 000000000..f2cbd4c26
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-app/Dockerfile
@@ -0,0 +1,5 @@
+FROM openjdk:8-jdk-alpine
+VOLUME /tmp
+ARG JAR_FILE=target/*.jar
+COPY ${JAR_FILE} app.jar
+ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"]
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-app/mvnw b/BlockchainGateway/bc-gateway-app/mvnw
new file mode 100644
index 000000000..a16b5431b
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-app/mvnw
@@ -0,0 +1,310 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/BlockchainGateway/bc-gateway-app/mvnw.cmd b/BlockchainGateway/bc-gateway-app/mvnw.cmd
new file mode 100644
index 000000000..c8d43372c
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-app/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/BlockchainGateway/bc-gateway-app/pom.xml b/BlockchainGateway/bc-gateway-app/pom.xml
new file mode 100644
index 000000000..71355c3df
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-app/pom.xml
@@ -0,0 +1,232 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.4.4
+
+
+ co.nilin.opex.external
+ bc-gateway-app
+ 1.0-SNAPSHOT
+ bc-gateway-app
+ Blockchain gateway app of Opex
+
+
+ 1.8
+ 1.4.31
+ ${version}
+ ${version}
+ 2020.0.2
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ com.fasterxml.jackson.module
+ jackson-module-kotlin
+
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+
+
+ io.projectreactor.kotlin
+ reactor-kotlin-extensions
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-reactor
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-core
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+
+
+
+
+ io.projectreactor
+ reactor-test
+ test
+
+
+ org.springframework.cloud
+ spring-cloud-starter-consul-all
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+ 1.60
+
+
+ co.nilin.opex.external
+ bc-gateway-core
+ ${bc-gateway.version}
+
+
+ co.nilin.opex.external
+ bc-gateway-persister-postgres
+ ${bc-gateway.version}
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/test/kotlin
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.18
+
+
+ ${skip.unit.tests}
+
+
+ **/*IntegrationTest.java
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ add-test-source
+ generate-test-sources
+
+ add-test-source
+
+
+
+ src/test/java
+
+
+
+
+ compile
+
+ add-source
+
+
+
+ src/main/java
+
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+
+
+ -Xjsr305=strict
+
+
+ spring
+
+ 1.8
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-allopen
+ ${kotlin.version}
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ testCompile
+ test-compile
+
+ testCompile
+
+
+
+
+
+ opex-bc-gateway
+
+
+
diff --git a/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/BCGatewayApp.kt b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/BCGatewayApp.kt
new file mode 100644
index 000000000..3762d89a6
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/BCGatewayApp.kt
@@ -0,0 +1,13 @@
+package co.nilin.opex.bcgateway.app
+
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.runApplication
+import org.springframework.context.annotation.ComponentScan
+
+@SpringBootApplication
+@ComponentScan("co.nilin.opex")
+class BCGatewayApp
+
+fun main(args: Array) {
+ runApplication(*args)
+}
diff --git a/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/AppConfig.kt b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/AppConfig.kt
new file mode 100644
index 000000000..831ed2b07
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/AppConfig.kt
@@ -0,0 +1,64 @@
+package co.nilin.opex.bcgateway.app.config
+
+
+import co.nilin.opex.bcgateway.core.api.AssignAddressService
+import co.nilin.opex.bcgateway.core.api.ChainSyncService
+import co.nilin.opex.bcgateway.core.api.InfoService
+import co.nilin.opex.bcgateway.core.api.WalletSyncService
+import co.nilin.opex.bcgateway.core.service.AssignAddressServiceImpl
+import co.nilin.opex.bcgateway.core.service.ChainSyncServiceImpl
+import co.nilin.opex.bcgateway.core.service.InfoServiceImpl
+import co.nilin.opex.bcgateway.core.service.WalletSyncServiceImpl
+import co.nilin.opex.bcgateway.core.spi.*
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.transaction.reactive.TransactionalOperator
+
+@Configuration
+class AppConfig {
+
+ @Bean
+ fun assignAddressService(
+ currencyLoader: CurrencyLoader,
+ assignedAddressHandler: AssignedAddressHandler,
+ reservedAddressHandler: ReservedAddressHandler
+ ): AssignAddressService {
+ return AssignAddressServiceImpl(currencyLoader, assignedAddressHandler, reservedAddressHandler)
+ }
+
+ @Bean
+ fun chainSyncService(
+ chainSyncSchedulerHandler: ChainSyncSchedulerHandler,
+ chainEndpointProxyFinder: ChainEndpointProxyFinder,
+ chainSyncRecordHandler: ChainSyncRecordHandler,
+ walletSyncRecordHandler: WalletSyncRecordHandler,
+ currencyLoader: CurrencyLoader,
+ operator: TransactionalOperator
+ ): ChainSyncService {
+ return ChainSyncServiceImpl(
+ chainSyncSchedulerHandler,
+ chainEndpointProxyFinder,
+ chainSyncRecordHandler,
+ walletSyncRecordHandler,
+ currencyLoader,
+ operator,
+ AppDispatchers.chainSyncExecutor
+ )
+ }
+
+ @Bean
+ fun walletSyncService(
+ syncSchedulerHandler: WalletSyncSchedulerHandler,
+ walletProxy: WalletProxy,
+ walletSyncRecordHandler: WalletSyncRecordHandler,
+ assignedAddressHandler: AssignedAddressHandler,
+ currencyLoader: CurrencyLoader
+ ): WalletSyncService {
+ return WalletSyncServiceImpl(syncSchedulerHandler, walletProxy, walletSyncRecordHandler, assignedAddressHandler, currencyLoader, AppDispatchers.walletSyncExecutor)
+ }
+
+ @Bean
+ fun infoService(): InfoService {
+ return InfoServiceImpl()
+ }
+}
diff --git a/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/AppDispatchers.kt b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/AppDispatchers.kt
new file mode 100644
index 000000000..f03b24dc8
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/AppDispatchers.kt
@@ -0,0 +1,9 @@
+package co.nilin.opex.bcgateway.app.config
+
+import kotlinx.coroutines.asCoroutineDispatcher
+import java.util.concurrent.Executors
+
+object AppDispatchers {
+ val chainSyncExecutor = Executors.newFixedThreadPool(5).asCoroutineDispatcher()
+ val walletSyncExecutor = Executors.newFixedThreadPool(2).asCoroutineDispatcher()
+}
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/SecurityConfig.kt b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/SecurityConfig.kt
new file mode 100644
index 000000000..dfd54722a
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/SecurityConfig.kt
@@ -0,0 +1,45 @@
+package co.nilin.opex.bcgateway.app.config
+
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.context.annotation.Bean
+import org.springframework.core.io.ClassPathResource
+import org.springframework.core.io.Resource
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.web.server.ServerHttpSecurity
+import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder
+import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.util.Base64Utils
+import org.springframework.util.FileCopyUtils
+import org.springframework.web.reactive.function.client.WebClient
+import java.security.KeyFactory
+import java.security.interfaces.RSAPublicKey
+import java.security.spec.X509EncodedKeySpec
+
+@EnableWebFluxSecurity
+class SecurityConfig(private val webClient: WebClient) {
+
+ @Value("\${app.auth.cert-url}")
+ private lateinit var jwkUrl: String
+
+ @Bean
+ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? {
+ http.csrf().disable()
+ .authorizeExchange()
+ .pathMatchers("/filter/**").hasAuthority("SCOPE_trust")
+ .pathMatchers("/**").permitAll()
+ .anyExchange().authenticated()
+ .and()
+ .oauth2ResourceServer()
+ .jwt()
+ return http.build()
+ }
+
+ @Bean
+ @Throws(Exception::class)
+ fun reactiveJwtDecoder(): ReactiveJwtDecoder? {
+ return NimbusReactiveJwtDecoder.withJwkSetUri(jwkUrl)
+ .webClient(webClient)
+ .build()
+ }
+}
diff --git a/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/WebClientConfig.kt b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/WebClientConfig.kt
new file mode 100644
index 000000000..934bbd706
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/config/WebClientConfig.kt
@@ -0,0 +1,25 @@
+package co.nilin.opex.bcgateway.app.config
+
+import org.springframework.cloud.client.ServiceInstance
+import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties
+import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer
+import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.web.reactive.function.client.WebClient
+
+@Configuration
+class WebClientConfig {
+
+ @Bean
+ fun webClient(loadBalancerFactory: ReactiveLoadBalancer.Factory): WebClient {
+ return WebClient.builder()
+ .filter(
+ ReactorLoadBalancerExchangeFilterFunction(
+ loadBalancerFactory, LoadBalancerProperties(), emptyList()
+ )
+ )
+ .build()
+ }
+
+}
diff --git a/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/controller/DepositController.kt b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/controller/DepositController.kt
new file mode 100644
index 000000000..e67bef938
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/controller/DepositController.kt
@@ -0,0 +1,22 @@
+package co.nilin.opex.bcgateway.app.controller
+
+import co.nilin.opex.bcgateway.core.api.AssignAddressService
+import co.nilin.opex.bcgateway.core.model.AssignedAddress
+import co.nilin.opex.bcgateway.core.model.Currency
+import org.springframework.web.bind.annotation.PostMapping
+import org.springframework.web.bind.annotation.RequestBody
+import org.springframework.web.bind.annotation.RestController
+
+@RestController
+class DepositController(val assignAddressService: AssignAddressService) {
+ data class AssignAddressRequest(val uuid: String, val currency: String)
+ data class AssignAddressResponse(val addresses: List)
+
+ @PostMapping("deposits/assign")
+ suspend fun assignAddress(@RequestBody assignAddressRequest: AssignAddressRequest): AssignAddressResponse {
+ val assignedAddress = assignAddressService
+ .assignAddress(assignAddressRequest.uuid,
+ Currency(assignAddressRequest.currency, assignAddressRequest.currency))
+ return AssignAddressResponse(assignedAddress)
+ }
+}
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-app/src/main/resources/application-docker.yml b/BlockchainGateway/bc-gateway-app/src/main/resources/application-docker.yml
new file mode 100644
index 000000000..ffbf14176
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-app/src/main/resources/application-docker.yml
@@ -0,0 +1,22 @@
+server.port: 8095
+spring:
+ application:
+ name: opex-bc-gateway
+ kafka:
+ bootstrap-servers: ${KAFKA_IP_PORT}
+ redis:
+ host: ${REDIS_HOST}
+ port: 6379
+ r2dbc:
+ url: r2dbc:postgresql://${DB_IP_PORT}/opex_bc_gateway
+ username: opex
+ password: hiopex
+ initialization-mode: always
+ cloud:
+ consul:
+ host: ${CONSUL_HOST}
+ port: 8500
+
+app:
+ auth:
+ cert-url: lb://opex-auth/auth/realms/opex/protocol/openid-connect/certs
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-app/src/main/resources/application.yml b/BlockchainGateway/bc-gateway-app/src/main/resources/application.yml
new file mode 100644
index 000000000..c21510415
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-app/src/main/resources/application.yml
@@ -0,0 +1,31 @@
+server.port: 8095
+spring:
+ application:
+ name: opex-bc-gateway
+ main:
+ allow-bean-definition-overriding: false
+ kafka:
+ bootstrap-servers: localhost:2181
+ consumer:
+ group-id: opex-bc-gateway
+ redis:
+ host: 127.0.0.1
+ port: 6379
+ r2dbc:
+ url: r2dbc:postgresql://localhost/opex_bc_gateway
+ username: opex
+ password: hiopex
+ initialization-mode: always
+ cloud:
+ bootstrap:
+ enabled: true
+ consul:
+ port: 8500
+ discovery:
+ #healthCheckPath: ${management.context-path}/health
+ instance-id: ${spring.application.name}:${server.port}
+ healthCheckInterval: 20s
+ prefer-ip-address: true
+logging:
+ level:
+ org.apache.kafka: DEBUG
diff --git a/BlockchainGateway/bc-gateway-core/.gitignore b/BlockchainGateway/bc-gateway-core/.gitignore
new file mode 100644
index 000000000..8e22e6c21
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/.gitignore
@@ -0,0 +1,35 @@
+HELP.md
+target/
+.mvn
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+.DS_Store
diff --git a/BlockchainGateway/bc-gateway-core/mvnw b/BlockchainGateway/bc-gateway-core/mvnw
new file mode 100644
index 000000000..a16b5431b
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/mvnw
@@ -0,0 +1,310 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/BlockchainGateway/bc-gateway-core/mvnw.cmd b/BlockchainGateway/bc-gateway-core/mvnw.cmd
new file mode 100644
index 000000000..c8d43372c
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/BlockchainGateway/bc-gateway-core/pom.xml b/BlockchainGateway/bc-gateway-core/pom.xml
new file mode 100644
index 000000000..bf2a407dd
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/pom.xml
@@ -0,0 +1,97 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.4.4
+
+
+ co.nilin.opex.external
+ bc-gateway-core
+ 1.0-SNAPSHOT
+ bc-gateway-core
+ Blockchain gateway core of Opex
+
+
+ 1.8
+ 1.4.31
+ 3.2.0
+
+
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-reactor
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-core
+
+
+ io.projectreactor.kotlin
+ reactor-kotlin-extensions
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+
+
+ org.springframework
+ spring-tx
+ provided
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+
+
+
+
+ org.mockito.kotlin
+ mockito-kotlin
+ ${mockito-kotlin.version}
+
+
+
+
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/test/kotlin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ -Xjsr305=strict
+
+
+ spring
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-allopen
+ ${kotlin.version}
+
+
+
+
+
+
+
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/AssignAddressService.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/AssignAddressService.kt
new file mode 100644
index 000000000..275c8ec48
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/AssignAddressService.kt
@@ -0,0 +1,8 @@
+package co.nilin.opex.bcgateway.core.api
+
+import co.nilin.opex.bcgateway.core.model.AssignedAddress
+import co.nilin.opex.bcgateway.core.model.Currency
+
+interface AssignAddressService {
+ suspend fun assignAddress(user: String, currency: Currency): List
+}
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/ChainSyncService.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/ChainSyncService.kt
new file mode 100644
index 000000000..ec62c097f
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/ChainSyncService.kt
@@ -0,0 +1,5 @@
+package co.nilin.opex.bcgateway.core.api
+
+interface ChainSyncService {
+ suspend fun startSyncWithChain()
+}
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/InfoService.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/InfoService.kt
new file mode 100644
index 000000000..838e2a7d8
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/InfoService.kt
@@ -0,0 +1,8 @@
+package co.nilin.opex.bcgateway.core.api
+
+import co.nilin.opex.bcgateway.core.model.CurrencyInfo
+
+interface InfoService {
+ suspend fun countReservedAddresses(): Long
+ suspend fun getCurrencyInfo(): CurrencyInfo
+}
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/WalletSyncService.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/WalletSyncService.kt
new file mode 100644
index 000000000..df81a410d
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/api/WalletSyncService.kt
@@ -0,0 +1,5 @@
+package co.nilin.opex.bcgateway.core.api
+
+interface WalletSyncService {
+ suspend fun startSyncWithWallet()
+}
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Address.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Address.kt
new file mode 100644
index 000000000..ff7a125fe
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Address.kt
@@ -0,0 +1,27 @@
+package co.nilin.opex.bcgateway.core.model
+
+data class AddressType(val id: Long, val type: String, val addressRegex: String, val memoRegex: String)
+data class ReservedAddress(val address: String, val memo: String?, val type: AddressType)
+data class AssignedAddress(val uuid: String, val address: String, val memo: String?, val type: AddressType, val chains: MutableList ){
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as AssignedAddress
+
+ if (uuid != other.uuid) return false
+ if (address != other.address) return false
+ if (memo != other.memo) return false
+ if (type != other.type) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = uuid.hashCode()
+ result = 31 * result + address.hashCode()
+ result = 31 * result + (memo?.hashCode() ?: 0)
+ result = 31 * result + type.hashCode()
+ return result
+ }
+}
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Chain.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Chain.kt
new file mode 100644
index 000000000..d88430445
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Chain.kt
@@ -0,0 +1,16 @@
+package co.nilin.opex.bcgateway.core.model
+
+import java.time.LocalDateTime
+
+data class Endpoint(val url: String)
+data class Chain(val name: String, val addressTypes: List, val endpoints: List)
+data class ChainSyncSchedule(val chainName: String, val retryTime: LocalDateTime, val delay: Long)
+data class ChainSyncRecord(
+ val chainName: String,
+ val time: LocalDateTime,
+ val endpoint: Endpoint,
+ val latestBlock: Long?,
+ val success: Boolean,
+ val error: String?,
+ val records: List
+)
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Currency.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Currency.kt
new file mode 100644
index 000000000..73a9c0db5
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Currency.kt
@@ -0,0 +1,17 @@
+package co.nilin.opex.bcgateway.core.model
+
+import java.math.BigDecimal
+
+data class Currency(val symbol: String, val name: String)
+data class CurrencyImplementation(
+ val currency: Currency,
+ val chain: Chain,
+ val token: Boolean,
+ val tokenAddress: String?,
+ val tokenName: String?,
+ val withdrawEnabled: Boolean,
+ val withdrawFee: BigDecimal,
+ val withdrawMin: BigDecimal
+)
+
+data class CurrencyInfo(val currency: Currency, val implementations: List)
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Deposit.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Deposit.kt
new file mode 100644
index 000000000..d355d6df3
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/Deposit.kt
@@ -0,0 +1,13 @@
+package co.nilin.opex.bcgateway.core.model
+
+import java.math.BigDecimal
+
+data class Deposit(
+ val id: Long?,
+ val depositor: String,
+ val depositorMemo: String?,
+ val amount: BigDecimal,
+ val chain: String,
+ val token: Boolean,
+ val tokenAddress: String?
+)
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/WalletSync.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/WalletSync.kt
new file mode 100644
index 000000000..439e5e053
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/model/WalletSync.kt
@@ -0,0 +1,8 @@
+package co.nilin.opex.bcgateway.core.model
+
+import java.time.LocalDateTime
+
+data class WalletSyncSchedule(val retryTime: LocalDateTime, val delay: Long, val batchSize: Long?)
+data class WalletSyncRecord(
+ val time: LocalDateTime, val success: Boolean, val error: String?, val deposit: Deposit
+)
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/AssignAddressServiceImpl.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/AssignAddressServiceImpl.kt
new file mode 100644
index 000000000..ef4cb3a45
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/AssignAddressServiceImpl.kt
@@ -0,0 +1,66 @@
+package co.nilin.opex.bcgateway.core.service
+
+import co.nilin.opex.bcgateway.core.api.AssignAddressService
+import co.nilin.opex.bcgateway.core.model.AddressType
+import co.nilin.opex.bcgateway.core.model.AssignedAddress
+import co.nilin.opex.bcgateway.core.model.Chain
+import co.nilin.opex.bcgateway.core.model.Currency
+import co.nilin.opex.bcgateway.core.spi.AssignedAddressHandler
+import co.nilin.opex.bcgateway.core.spi.ReservedAddressHandler
+import co.nilin.opex.bcgateway.core.spi.CurrencyLoader
+import java.lang.RuntimeException
+
+class AssignAddressServiceImpl(
+ val currencyLoader: CurrencyLoader,
+ val assignedAddressHandler: AssignedAddressHandler,
+ val reservedAddressHandler: ReservedAddressHandler
+) : AssignAddressService {
+
+ override suspend fun assignAddress(user: String, currency: Currency): List {
+ val currencyInfo = currencyLoader.fetchCurrencyInfo(currency.symbol)
+ val chains = currencyInfo.implementations
+ .map { imp -> imp.chain }
+ val addressTypes = chains
+ .flatMap { chain -> chain.addressTypes }
+ .distinct()
+ val chainAddressTypeMap = HashMap>()
+ chains.forEach { chain ->
+ chain.addressTypes.forEach { addressType ->
+ chainAddressTypeMap.putIfAbsent(addressType, mutableListOf())
+ chainAddressTypeMap.get(addressType)!!.add(chain)
+ }
+ }
+ val userAssignedAddresses = (assignedAddressHandler.fetchAssignedAddresses(user, addressTypes)).toMutableList()
+ val result = mutableSetOf()
+ addressTypes.forEach { addressType ->
+ val assigned = userAssignedAddresses.firstOrNull { assignAddress -> assignAddress.type.equals(addressType) }
+ if (assigned != null) {
+ chainAddressTypeMap.get(addressType)?.forEach { chain ->
+ if (!assigned.chains.contains(chain)) {
+ assigned.chains.add(chain)
+ }
+ }
+ result.add(assigned)
+ } else {
+ val reservedAddress = reservedAddressHandler.peekReservedAddress(addressType)
+ if (reservedAddress != null) {
+ val newAssigned = AssignedAddress(
+ user,
+ reservedAddress.address,
+ reservedAddress.memo,
+ addressType,
+ chainAddressTypeMap.get(addressType)!!
+ )
+ reservedAddressHandler.remove(reservedAddress)
+ result.add(newAssigned)
+ } else
+ throw RuntimeException("No reserved address available for $addressType")
+
+ }
+ }
+ result.forEach { address ->
+ assignedAddressHandler.persist(address)
+ }
+ return result.toMutableList()
+ }
+}
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/ChainSyncServiceImpl.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/ChainSyncServiceImpl.kt
new file mode 100644
index 000000000..f8f8f4144
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/ChainSyncServiceImpl.kt
@@ -0,0 +1,54 @@
+package co.nilin.opex.bcgateway.core.service
+
+import co.nilin.opex.bcgateway.core.api.ChainSyncService
+import co.nilin.opex.bcgateway.core.spi.*
+import kotlinx.coroutines.*
+import org.springframework.transaction.reactive.TransactionalOperator
+import org.springframework.transaction.reactive.executeAndAwait
+import java.time.LocalDateTime
+import java.time.temporal.ChronoUnit
+import kotlin.coroutines.coroutineContext
+
+open class ChainSyncServiceImpl(
+ private val chainSyncSchedulerHandler: ChainSyncSchedulerHandler,
+ private val chainEndpointProxyFinder: ChainEndpointProxyFinder,
+ private val chainSyncRecordHandler: ChainSyncRecordHandler,
+ private val walletSyncRecordHandler: WalletSyncRecordHandler,
+ private val currencyLoader: CurrencyLoader,
+ private val operator: TransactionalOperator,
+ private val dispatcher: ExecutorCoroutineDispatcher
+) : ChainSyncService {
+
+ override suspend fun startSyncWithChain() {
+ withContext(coroutineContext) {
+ val schedules = chainSyncSchedulerHandler.fetchActiveSchedules(currentTime())
+ schedules.map { syncSchedule ->
+ async(dispatcher) {
+ val syncHandler = chainEndpointProxyFinder.findChainEndpointProxy(syncSchedule.chainName)
+ val lastSync = chainSyncRecordHandler.loadLastSuccessRecord(syncSchedule.chainName)
+ val tokens = currencyLoader.findImplementationsWithTokenOnChain(syncSchedule.chainName)
+ .map { impl -> impl.tokenAddress!! }
+ .toList()
+ val syncResult =
+ syncHandler.syncTransfers(
+ ChainEndpointProxy.DepositFilter(
+ lastSync?.latestBlock, null, tokens
+ )
+ )
+ operator.executeAndAwait {
+ walletSyncRecordHandler.saveReadyToSyncTransfers(syncResult.chainName, syncResult.records)
+ chainSyncRecordHandler.saveSyncRecord(syncResult)
+ if (syncResult.success) {
+ chainSyncSchedulerHandler.prepareScheduleForNextTry(
+ syncSchedule,
+ currentTime().plus(syncSchedule.delay, ChronoUnit.SECONDS)
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+
+ protected open fun currentTime() = LocalDateTime.now()
+}
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/InfoServiceImpl.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/InfoServiceImpl.kt
new file mode 100644
index 000000000..e600f2369
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/InfoServiceImpl.kt
@@ -0,0 +1,14 @@
+package co.nilin.opex.bcgateway.core.service
+
+import co.nilin.opex.bcgateway.core.api.InfoService
+import co.nilin.opex.bcgateway.core.model.CurrencyInfo
+
+class InfoServiceImpl: InfoService {
+ override suspend fun countReservedAddresses(): Long {
+ TODO()
+ }
+
+ override suspend fun getCurrencyInfo(): CurrencyInfo {
+ TODO()
+ }
+}
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/WalletSyncServiceImpl.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/WalletSyncServiceImpl.kt
new file mode 100644
index 000000000..977202000
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/WalletSyncServiceImpl.kt
@@ -0,0 +1,51 @@
+package co.nilin.opex.bcgateway.core.service
+
+import co.nilin.opex.bcgateway.core.api.WalletSyncService
+import co.nilin.opex.bcgateway.core.model.WalletSyncRecord
+import co.nilin.opex.bcgateway.core.spi.*
+import kotlinx.coroutines.ExecutorCoroutineDispatcher
+import kotlinx.coroutines.async
+import kotlinx.coroutines.withContext
+import java.time.LocalDateTime
+import java.time.temporal.ChronoUnit
+import kotlin.coroutines.coroutineContext
+
+class WalletSyncServiceImpl(
+ private val syncSchedulerHandler: WalletSyncSchedulerHandler,
+ private val walletProxy: WalletProxy,
+ private val walletSyncRecordHandler: WalletSyncRecordHandler,
+ private val assignedAddressHandler: AssignedAddressHandler,
+ private val currencyLoader: CurrencyLoader,
+ private val dispatcher: ExecutorCoroutineDispatcher
+) : WalletSyncService {
+
+ override suspend fun startSyncWithWallet() {
+ withContext(coroutineContext) {
+ val schedule = syncSchedulerHandler.fetchActiveSchedule(LocalDateTime.now())
+ if (schedule != null) {
+ val deposits = walletSyncRecordHandler.findReadyToSyncTransfers(schedule.batchSize)
+ deposits.map { deposit ->
+ async(dispatcher) {
+ val uuid = assignedAddressHandler.findUuid(deposit.depositor, deposit.depositorMemo)
+ if (uuid != null) {
+ val symbol = currencyLoader.findSymbol(deposit.chain, deposit.tokenAddress)
+ if (symbol != null) walletProxy.transfer(uuid, symbol, deposit.amount)
+ walletSyncRecordHandler.saveWalletSyncRecord(
+ WalletSyncRecord(
+ LocalDateTime.now(),
+ true,
+ null,
+ deposit
+ )
+ )
+ }
+ }
+ }
+ syncSchedulerHandler.prepareScheduleForNextTry(
+ schedule, LocalDateTime.now()
+ .plus(schedule.delay, ChronoUnit.SECONDS)
+ )
+ }
+ }
+ }
+}
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/AssignedAddressHandler.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/AssignedAddressHandler.kt
new file mode 100644
index 000000000..1ac2cc2f8
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/AssignedAddressHandler.kt
@@ -0,0 +1,10 @@
+package co.nilin.opex.bcgateway.core.spi
+
+import co.nilin.opex.bcgateway.core.model.AddressType
+import co.nilin.opex.bcgateway.core.model.AssignedAddress
+
+interface AssignedAddressHandler {
+ suspend fun fetchAssignedAddresses(user: String, addressTypes: List): List
+ suspend fun persist(assignedAddress: AssignedAddress)
+ suspend fun findUuid(address: String, memo: String?): String?
+}
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainEndpointProxy.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainEndpointProxy.kt
new file mode 100644
index 000000000..b73d94eec
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainEndpointProxy.kt
@@ -0,0 +1,8 @@
+package co.nilin.opex.bcgateway.core.spi
+
+import co.nilin.opex.bcgateway.core.model.ChainSyncRecord
+
+interface ChainEndpointProxy {
+ data class DepositFilter(val startBlock: Long?, val endBlock: Long?, val tokenAddresses: List?)
+ suspend fun syncTransfers(filter: DepositFilter): ChainSyncRecord
+}
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainEndpointProxyFinder.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainEndpointProxyFinder.kt
new file mode 100644
index 000000000..7db1ba870
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainEndpointProxyFinder.kt
@@ -0,0 +1,5 @@
+package co.nilin.opex.bcgateway.core.spi
+
+interface ChainEndpointProxyFinder {
+ suspend fun findChainEndpointProxy(chainName: String):ChainEndpointProxy
+}
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainLoader.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainLoader.kt
new file mode 100644
index 000000000..6834b58fa
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainLoader.kt
@@ -0,0 +1,8 @@
+package co.nilin.opex.bcgateway.core.spi
+
+import co.nilin.opex.bcgateway.core.model.Chain
+import co.nilin.opex.bcgateway.core.model.CurrencyInfo
+
+interface ChainLoader {
+ suspend fun fetchChainInfo(chain: String): Chain
+}
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainSyncRecordHandler.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainSyncRecordHandler.kt
new file mode 100644
index 000000000..e8cc7106a
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainSyncRecordHandler.kt
@@ -0,0 +1,8 @@
+package co.nilin.opex.bcgateway.core.spi
+
+import co.nilin.opex.bcgateway.core.model.ChainSyncRecord
+
+interface ChainSyncRecordHandler {
+ suspend fun loadLastSuccessRecord(chainName: String): ChainSyncRecord?
+ suspend fun saveSyncRecord(syncRecord: ChainSyncRecord)
+}
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainSyncSchedulerHandler.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainSyncSchedulerHandler.kt
new file mode 100644
index 000000000..a04e498af
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ChainSyncSchedulerHandler.kt
@@ -0,0 +1,9 @@
+package co.nilin.opex.bcgateway.core.spi
+
+import co.nilin.opex.bcgateway.core.model.ChainSyncSchedule
+import java.time.LocalDateTime
+
+interface ChainSyncSchedulerHandler {
+ suspend fun fetchActiveSchedules(time: LocalDateTime): List
+ suspend fun prepareScheduleForNextTry(syncSchedule: ChainSyncSchedule, time: LocalDateTime)
+}
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/CurrencyLoader.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/CurrencyLoader.kt
new file mode 100644
index 000000000..32aa395f1
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/CurrencyLoader.kt
@@ -0,0 +1,10 @@
+package co.nilin.opex.bcgateway.core.spi
+
+import co.nilin.opex.bcgateway.core.model.CurrencyImplementation
+import co.nilin.opex.bcgateway.core.model.CurrencyInfo
+
+interface CurrencyLoader {
+ suspend fun fetchCurrencyInfo(symbol: String): CurrencyInfo
+ suspend fun findSymbol(chain: String, address: String?): String?
+ suspend fun findImplementationsWithTokenOnChain(chain: String): List
+}
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ReservedAddressHandler.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ReservedAddressHandler.kt
new file mode 100644
index 000000000..67a3c837b
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/ReservedAddressHandler.kt
@@ -0,0 +1,9 @@
+package co.nilin.opex.bcgateway.core.spi
+
+import co.nilin.opex.bcgateway.core.model.AddressType
+import co.nilin.opex.bcgateway.core.model.ReservedAddress
+
+interface ReservedAddressHandler {
+ suspend fun peekReservedAddress(addressType: AddressType): ReservedAddress?
+ suspend fun remove(reservedAddress: ReservedAddress)
+}
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/WalletProxy.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/WalletProxy.kt
new file mode 100644
index 000000000..0437da445
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/WalletProxy.kt
@@ -0,0 +1,7 @@
+package co.nilin.opex.bcgateway.core.spi
+
+import java.math.BigDecimal
+
+interface WalletProxy {
+ fun transfer(uuid: String, symbol: String, amount: BigDecimal)
+}
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/WalletSyncRecordHandler.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/WalletSyncRecordHandler.kt
new file mode 100644
index 000000000..90979ab28
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/WalletSyncRecordHandler.kt
@@ -0,0 +1,10 @@
+package co.nilin.opex.bcgateway.core.spi
+
+import co.nilin.opex.bcgateway.core.model.Deposit
+import co.nilin.opex.bcgateway.core.model.WalletSyncRecord
+
+interface WalletSyncRecordHandler {
+ suspend fun saveReadyToSyncTransfers(chainName: String, deposits: List)
+ suspend fun saveWalletSyncRecord(syncRecord: WalletSyncRecord)
+ suspend fun findReadyToSyncTransfers(count: Long?): List
+}
diff --git a/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/WalletSyncSchedulerHandler.kt b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/WalletSyncSchedulerHandler.kt
new file mode 100644
index 000000000..637b430fe
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/spi/WalletSyncSchedulerHandler.kt
@@ -0,0 +1,9 @@
+package co.nilin.opex.bcgateway.core.spi
+
+import co.nilin.opex.bcgateway.core.model.WalletSyncSchedule
+import java.time.LocalDateTime
+
+interface WalletSyncSchedulerHandler {
+ suspend fun fetchActiveSchedule(time: LocalDateTime): WalletSyncSchedule?
+ suspend fun prepareScheduleForNextTry(syncSchedule: WalletSyncSchedule, time: LocalDateTime)
+}
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-core/src/main/resources/application.properties b/BlockchainGateway/bc-gateway-core/src/main/resources/application.properties
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/main/resources/application.properties
@@ -0,0 +1 @@
+
diff --git a/BlockchainGateway/bc-gateway-core/src/test/kotlin/co/nilin/opex/bcgateway/core/service/AssignAddressServiceImplUnitTest.kt b/BlockchainGateway/bc-gateway-core/src/test/kotlin/co/nilin/opex/bcgateway/core/service/AssignAddressServiceImplUnitTest.kt
new file mode 100644
index 000000000..be773908d
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/test/kotlin/co/nilin/opex/bcgateway/core/service/AssignAddressServiceImplUnitTest.kt
@@ -0,0 +1,162 @@
+package co.nilin.opex.bcgateway.core.service
+
+import co.nilin.opex.bcgateway.core.model.AddressType
+import co.nilin.opex.bcgateway.core.model.AssignedAddress
+import co.nilin.opex.bcgateway.core.model.ReservedAddress
+import co.nilin.opex.bcgateway.core.model.Chain
+import co.nilin.opex.bcgateway.core.model.Currency
+import co.nilin.opex.bcgateway.core.model.CurrencyImplementation
+import co.nilin.opex.bcgateway.core.model.CurrencyInfo
+import co.nilin.opex.bcgateway.core.spi.AssignedAddressHandler
+import co.nilin.opex.bcgateway.core.spi.ReservedAddressHandler
+import co.nilin.opex.bcgateway.core.spi.CurrencyLoader
+import java.lang.RuntimeException
+import java.math.BigDecimal
+import java.util.UUID
+import kotlinx.coroutines.runBlocking
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+class AssignAddressServiceImplUnitTest {
+ @Mock
+ lateinit var currencyLoader: CurrencyLoader
+
+ @Mock
+ lateinit var assignedAddressHandler: AssignedAddressHandler
+
+ @Mock
+ lateinit var reservedAddressHandler: ReservedAddressHandler
+
+ val assignAddressServiceImpl: AssignAddressServiceImpl
+
+ val currency = Currency("ETH", "Ethereum")
+ val ethAddressType = AddressType(1, "ETH", "+*", ".*")
+ val ethMemoAddressType = AddressType(2, "ETH", "+*", "+*")
+ val ethChain = Chain("ETH_MAINNET", arrayListOf(ethAddressType), emptyList())
+ val bscChain = Chain("BSC_MAINNET", arrayListOf(ethAddressType, ethMemoAddressType), emptyList())
+
+
+ init {
+ MockitoAnnotations.openMocks(this)
+ assignAddressServiceImpl = AssignAddressServiceImpl(
+ currencyLoader, assignedAddressHandler, reservedAddressHandler
+ )
+ runBlocking {
+ val eth =
+ CurrencyImplementation(currency, ethChain, false, null, null, true, BigDecimal.ONE, BigDecimal.TEN)
+ val wrappedEth = CurrencyImplementation(
+ currency,
+ bscChain,
+ false,
+ "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
+ "WETH",
+ true,
+ BigDecimal.ONE,
+ BigDecimal.ONE
+ )
+
+ Mockito.`when`(currencyLoader.fetchCurrencyInfo(currency.symbol))
+ .thenReturn(CurrencyInfo(currency, listOf(eth, wrappedEth)))
+ }
+
+
+ }
+
+ @Test
+ fun givenReservedAddressAndUserWithNoAssignedAddress_whenAssignAddress_thenReservedAddressAssigned() {
+ runBlocking {
+ val user = UUID.randomUUID().toString()
+ Mockito.`when`(assignedAddressHandler.fetchAssignedAddresses(user, listOf(ethAddressType, ethMemoAddressType))).thenReturn(
+ emptyList()
+ )
+ Mockito.`when`(reservedAddressHandler.peekReservedAddress(ethAddressType)).thenReturn(
+ ReservedAddress("0x1", null, ethAddressType)
+ )
+ Mockito.`when`(reservedAddressHandler.peekReservedAddress(ethMemoAddressType)).thenReturn(
+ ReservedAddress("0x2", "Memo", ethMemoAddressType)
+ )
+ val assignedAddress = assignAddressServiceImpl.assignAddress(user, currency)
+ Assertions.assertEquals(
+ listOf(
+ AssignedAddress(
+ user,
+ "0x1",
+ null,
+ ethAddressType,
+ mutableListOf(ethChain, bscChain)
+ ),
+ AssignedAddress(
+ user,
+ "0x2",
+ "Memo",
+ ethMemoAddressType,
+ mutableListOf(bscChain)
+ )
+ ), assignedAddress
+ )
+ }
+ }
+
+ @Test
+ fun givenNoReservedAddressAndUserWithNoAssignedAddress_whenAssignAddress_thenExcpetion() {
+ runBlocking {
+ val user = UUID.randomUUID().toString()
+ Mockito.`when`(assignedAddressHandler.fetchAssignedAddresses(user, listOf(ethAddressType, ethMemoAddressType))).thenReturn(
+ emptyList()
+ )
+ Mockito.`when`(reservedAddressHandler.peekReservedAddress(ethAddressType)).thenReturn(null)
+
+ Assertions.assertThrows(RuntimeException::class.java) {
+ runBlocking {
+ assignAddressServiceImpl.assignAddress(user, currency)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun givenReservedAddressAndUserOneAssignedAddress_whenAssignAddress_thenReservedAddressAssigned() {
+ runBlocking {
+ val user = UUID.randomUUID().toString()
+ Mockito.`when`(assignedAddressHandler.fetchAssignedAddresses(user, listOf(ethAddressType, ethMemoAddressType))).thenReturn(
+ mutableListOf(
+ AssignedAddress( user,
+ "0x1",
+ null,
+ ethAddressType,
+ mutableListOf(ethChain)
+ )
+ )
+ )
+ Mockito.`when`(reservedAddressHandler.peekReservedAddress(ethAddressType)).thenReturn(
+ ReservedAddress("0x1", null, ethAddressType)
+ )
+ Mockito.`when`(reservedAddressHandler.peekReservedAddress(ethMemoAddressType)).thenReturn(
+ ReservedAddress("0x2", "Memo", ethMemoAddressType)
+ )
+ val assignedAddress = assignAddressServiceImpl.assignAddress(user, currency)
+ Assertions.assertEquals(
+ listOf(
+ AssignedAddress(
+ user,
+ "0x1",
+ null,
+ ethAddressType,
+ mutableListOf(ethChain, bscChain)
+ ),
+ AssignedAddress(
+ user,
+ "0x2",
+ "Memo",
+ ethMemoAddressType,
+ mutableListOf(bscChain)
+ )
+ ), assignedAddress
+ )
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-core/src/test/kotlin/co/nilin/opex/bcgateway/core/service/ChainSyncServiceImplTest.kt b/BlockchainGateway/bc-gateway-core/src/test/kotlin/co/nilin/opex/bcgateway/core/service/ChainSyncServiceImplTest.kt
new file mode 100644
index 000000000..f9997357e
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/test/kotlin/co/nilin/opex/bcgateway/core/service/ChainSyncServiceImplTest.kt
@@ -0,0 +1,141 @@
+package co.nilin.opex.bcgateway.core.service
+
+import co.nilin.opex.bcgateway.core.model.ChainSyncRecord
+import co.nilin.opex.bcgateway.core.model.ChainSyncSchedule
+import co.nilin.opex.bcgateway.core.model.Endpoint
+import co.nilin.opex.bcgateway.core.spi.ChainEndpointProxy
+import co.nilin.opex.bcgateway.core.spi.ChainEndpointProxyFinder
+import co.nilin.opex.bcgateway.core.spi.CurrencyLoader
+import co.nilin.opex.bcgateway.core.spi.ChainSyncRecordHandler
+import co.nilin.opex.bcgateway.core.spi.ChainSyncSchedulerHandler
+import co.nilin.opex.bcgateway.core.spi.WalletSyncRecordHandler
+import co.nilin.opex.bcgateway.test.OPERATOR
+import java.time.LocalDateTime
+import java.time.temporal.ChronoUnit
+import java.util.concurrent.Executors
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.runBlocking
+import org.junit.jupiter.api.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
+
+internal class ChainSyncServiceImplTest {
+
+ val ethChain = "ETH_MAINNET"
+ val bscChain = "BSC_MAINNET"
+ val time = LocalDateTime.now()
+ val syncService: ChainSyncServiceImpl
+
+ @Mock
+ lateinit var chainSyncSchedulerHandler: ChainSyncSchedulerHandler
+
+ @Mock
+ lateinit var chainEndpointProxyFinder: ChainEndpointProxyFinder
+
+ @Mock
+ lateinit var chainSyncRecordHandler: ChainSyncRecordHandler
+
+ @Mock
+ lateinit var walletSyncRecordHandler: WalletSyncRecordHandler
+
+ @Mock
+ lateinit var currencyLoader: CurrencyLoader
+
+ val endpointProxy: ChainEndpointProxy = mock()
+
+ init {
+ MockitoAnnotations.openMocks(this)
+ runBlocking {
+ Mockito.`when`(chainEndpointProxyFinder.findChainEndpointProxy(ethChain))
+ .thenReturn(endpointProxy)
+ Mockito.`when`(currencyLoader.findImplementationsWithTokenOnChain(ethChain)).thenReturn(emptyList())
+ }
+
+ syncService = object : ChainSyncServiceImpl(
+ chainSyncSchedulerHandler,
+ chainEndpointProxyFinder,
+ chainSyncRecordHandler,
+ walletSyncRecordHandler,
+ currencyLoader,
+ OPERATOR,
+ Executors.newFixedThreadPool(2).asCoroutineDispatcher()
+ ) {
+ override fun currentTime() = time
+ }
+ }
+
+ @Test
+ fun givenNoActiveSchedules_whenStartSync_thenNoOp() {
+ runBlocking {
+ //given
+ Mockito.`when`(chainSyncSchedulerHandler.fetchActiveSchedules(any())).thenReturn(emptyList())
+
+ //when
+ syncService.startSyncWithChain()
+
+ //then
+ verifyZeroInteractions(
+ chainEndpointProxyFinder,
+ chainSyncRecordHandler,
+ walletSyncRecordHandler,
+ currencyLoader
+ )
+ }
+ }
+
+ @Test
+ fun givenAnActiveScheduleAndChainEndpointWorking_whenStartSync_thenSyncedSuccessfully() {
+ runBlocking {
+ //given
+ val delay = 100L
+ val syncSchedule = ChainSyncSchedule(ethChain, time, delay)
+ Mockito.`when`(chainSyncSchedulerHandler.fetchActiveSchedules(any()))
+ .thenReturn(listOf(syncSchedule))
+ Mockito.`when`(endpointProxy.syncTransfers(any())).thenReturn(
+ ChainSyncRecord(
+ ethChain, LocalDateTime.now(), Endpoint(""), 100, true, null, emptyList()
+ )
+ )
+
+ //when
+ syncService.startSyncWithChain()
+
+ //then
+ verify(chainSyncRecordHandler).saveSyncRecord(any())
+ verify(walletSyncRecordHandler).saveReadyToSyncTransfers(any(), any())
+ verify(chainSyncSchedulerHandler).prepareScheduleForNextTry(syncSchedule, time.plus(delay, ChronoUnit.SECONDS))
+ }
+ }
+
+ @Test
+ fun givenAnActiveScheduleAndChainEndpointFailed_whenStartSync_thenSyncedFailed() {
+ runBlocking {
+ //given
+ val delay = 100L
+ val syncSchedule = ChainSyncSchedule(ethChain, time, delay)
+ Mockito.`when`(chainSyncSchedulerHandler.fetchActiveSchedules(any()))
+ .thenReturn(listOf(syncSchedule))
+ Mockito.`when`(endpointProxy.syncTransfers(any())).thenReturn(
+ ChainSyncRecord(
+ ethChain, LocalDateTime.now(), Endpoint(""), 100, false, "error", emptyList()
+ )
+ )
+
+ //when
+ syncService.startSyncWithChain()
+
+ //then
+ verify(chainSyncRecordHandler).saveSyncRecord(any())
+ verify(walletSyncRecordHandler).saveReadyToSyncTransfers(any(), any())
+ verify(chainSyncSchedulerHandler, times(0)).prepareScheduleForNextTry(any(), any())
+ }
+ }
+
+
+}
diff --git a/BlockchainGateway/bc-gateway-core/src/test/kotlin/co/nilin/opex/bcgateway/test/MockTxExtension.kt b/BlockchainGateway/bc-gateway-core/src/test/kotlin/co/nilin/opex/bcgateway/test/MockTxExtension.kt
new file mode 100644
index 000000000..0a67d8d55
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-core/src/test/kotlin/co/nilin/opex/bcgateway/test/MockTxExtension.kt
@@ -0,0 +1,27 @@
+package co.nilin.opex.bcgateway.test
+
+import org.springframework.transaction.ReactiveTransaction
+import org.springframework.transaction.reactive.TransactionCallback
+import org.springframework.transaction.reactive.TransactionalOperator
+import reactor.core.publisher.Flux
+import reactor.core.publisher.Mono
+
+
+val OPERATOR = object : TransactionalOperator {
+ override fun transactional(mono: Mono): Mono {
+ return Mono.empty()
+ }
+
+ override fun execute(action: TransactionCallback): Flux {
+ return Flux.from(action.doInTransaction(object : ReactiveTransaction {
+ override fun isNewTransaction() = true
+
+ override fun setRollbackOnly() {
+ }
+
+ override fun isRollbackOnly() = false
+
+ override fun isCompleted() = true
+ }))
+ }
+}
diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/.gitignore b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/.gitignore
new file mode 100644
index 000000000..f4e066ca5
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/.gitignore
@@ -0,0 +1,36 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+!/.mvn/
+
+.DS_Store
diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/mvnw b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/mvnw
new file mode 100644
index 000000000..a16b5431b
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/mvnw
@@ -0,0 +1,310 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/mvnw.cmd b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/mvnw.cmd
new file mode 100644
index 000000000..c8d43372c
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/pom.xml b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/pom.xml
new file mode 100644
index 000000000..321effaa2
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/pom.xml
@@ -0,0 +1,105 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.4.4
+
+
+ co.nilin.opex.external
+ bc-gateway-persister-postgres
+ 1.0-SNAPSHOT
+ bc-gateway-persister-postgres
+ Persist items of Opex blockchain gateway on Postgres
+
+
+ 1.8
+ 1.4.31
+ ${version}
+ ${version}
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-r2dbc
+
+
+ io.r2dbc
+ r2dbc-postgresql
+ runtime
+
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+
+ io.projectreactor.kotlin
+ reactor-kotlin-extensions
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-reactor
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-core
+
+
+ co.nilin.opex.external
+ bc-gateway-core
+ ${bc-gateway.version}
+
+
+ io.projectreactor
+ reactor-test
+ test
+
+
+
+
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/test/kotlin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ -Xjsr305=strict
+
+
+ spring
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-allopen
+ ${kotlin.version}
+
+
+
+
+
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+
+
diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/config/PostgresConfig.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/config/PostgresConfig.kt
new file mode 100644
index 000000000..df4ca430b
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/config/PostgresConfig.kt
@@ -0,0 +1,110 @@
+package co.nilin.opex.port.bcgateway.postgres.config
+
+import org.springframework.context.annotation.Configuration
+import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories
+import org.springframework.r2dbc.core.DatabaseClient
+
+@Configuration
+@EnableR2dbcRepositories(basePackages = ["co.nilin.opex"])
+class PostgresConfig(db: DatabaseClient) {
+
+ init {
+ val initDb = db.sql {
+ """
+ CREATE TABLE IF NOT EXISTS address_types (
+ id SERIAL PRIMARY KEY,
+ address_type VARCHAR(72) NOT NULL,
+ address_regex VARCHAR(72) NOT NULL,
+ memo_regex VARCHAR(72) NOT NULL
+ );
+ CREATE TABLE IF NOT EXISTS assigned_addresses (
+ id SERIAL PRIMARY KEY,
+ uuid VARCHAR(72) NOT NULL,
+ address VARCHAR(72) NOT NULL,
+ memo VARCHAR(72),
+ addr_type_id INTEGER NOT NULL,
+ UNIQUE (address, memo)
+ );
+ CREATE TABLE IF NOT EXISTS assigned_address_chains (
+ id SERIAL PRIMARY KEY,
+ assigned_address_id INTEGER NOT NULL,
+ chain VARCHAR(72) NOT NULL
+ );
+ CREATE TABLE IF NOT EXISTS reserved_addresses (
+ id SERIAL PRIMARY KEY,
+ address VARCHAR(72) NOT NULL,
+ memo VARCHAR(72),
+ address_type VARCHAR(72) NOT NULL,
+ UNIQUE (address, memo)
+ );
+ CREATE TABLE IF NOT EXISTS chains (
+ name VARCHAR(72) PRIMARY KEY
+ );
+ CREATE TABLE IF NOT EXISTS chain_address_types (
+ id SERIAL PRIMARY KEY,
+ chain_name VARCHAR(72) NOT NULL REFERENCES chains (name),
+ addr_type_id INTEGER NOT NULL REFERENCES address_types (id)
+ );
+ CREATE TABLE IF NOT EXISTS chain_endpoints (
+ id SERIAL PRIMARY KEY,
+ chain_name VARCHAR(72) NOT NULL,
+ endpoint_url VARCHAR(72) NOT NULL,
+ endpoint_user VARCHAR(72),
+ endpoint_password VARCHAR(72)
+ );
+ CREATE TABLE IF NOT EXISTS chain_sync_schedules (
+ chain VARCHAR(72) PRIMARY KEY,
+ retry_time TIMESTAMP NOT NULL,
+ delay INTEGER NOT NULL
+ );
+ CREATE TABLE IF NOT EXISTS chain_sync_records (
+ chain VARCHAR(72) PRIMARY KEY,
+ time TIMESTAMP NOT NULL,
+ endpoint_url VARCHAR(72) NOT NULL,
+ latest_block INTEGER,
+ success BOOLEAN NOT NULL,
+ error VARCHAR(100)
+ );
+ CREATE TABLE IF NOT EXISTS wallet_sync_schedules (
+ id INTEGER PRIMARY KEY DEFAULT(1) CHECK(id = 1),
+ retry_time TIMESTAMP NOT NULL,
+ delay INTEGER NOT NULL,
+ batch_size INTEGER
+ );
+ CREATE TABLE IF NOT EXISTS wallet_sync_records (
+ id SERIAL PRIMARY KEY,
+ time TIMESTAMP NOT NULL,
+ success BOOLEAN NOT NULL,
+ error VARCHAR(100)
+ );
+ CREATE TABLE IF NOT EXISTS deposits (
+ id SERIAL PRIMARY KEY,
+ wallet_sync_record INTEGER NOT NULL,
+ chain VARCHAR(72) NOT NULL,
+ token BOOLEAN NOT NULL,
+ token_address VARCHAR(72),
+ amount NUMERIC NOT NULL,
+ depositor VARCHAR(72) NOT NULL,
+ depositor_memo VARCHAR(72)
+ );
+ CREATE TABLE IF NOT EXISTS currency (
+ symbol VARCHAR(72) PRIMARY KEY,
+ name VARCHAR(72) NOT NULL
+ );
+ CREATE TABLE IF NOT EXISTS currency_implementations (
+ symbol VARCHAR(72) PRIMARY KEY,
+ chain VARCHAR(72) NOT NULL,
+ token BOOLEAN NOT NULL,
+ token_address VARCHAR(72),
+ token_name VARCHAR(72),
+ withdraw_enabled BOOLEAN NOT NULL,
+ withdraw_fee NUMERIC NOT NULL,
+ withdraw_min NUMERIC NOT NULL
+ );
+ """
+ }
+ initDb // initialize the database
+ .then()
+ .subscribe() // execute
+ }
+}
diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/AddressTypeRepository.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/AddressTypeRepository.kt
new file mode 100644
index 000000000..657a2caf4
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/AddressTypeRepository.kt
@@ -0,0 +1,8 @@
+package co.nilin.opex.port.bcgateway.postgres.dao
+
+import co.nilin.opex.port.bcgateway.postgres.model.AddressTypeModel
+import org.springframework.data.repository.reactive.ReactiveCrudRepository
+import org.springframework.stereotype.Repository
+
+@Repository
+interface AddressTypeRepository : ReactiveCrudRepository
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/AssignedAddressChainRepository.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/AssignedAddressChainRepository.kt
new file mode 100644
index 000000000..47b7d7524
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/AssignedAddressChainRepository.kt
@@ -0,0 +1,15 @@
+package co.nilin.opex.port.bcgateway.postgres.dao
+
+import co.nilin.opex.port.bcgateway.postgres.model.AssignedAddressChainModel
+import co.nilin.opex.port.bcgateway.postgres.model.AssignedAddressModel
+import kotlinx.coroutines.flow.Flow
+import org.springframework.data.r2dbc.repository.Query
+import org.springframework.data.repository.query.Param
+import org.springframework.data.repository.reactive.ReactiveCrudRepository
+import org.springframework.stereotype.Repository
+
+@Repository
+interface AssignedAddressChainRepository : ReactiveCrudRepository {
+ @Query("select * from assigned_address_chains where assigned_address_id = :assignedAddress")
+ fun findByAssignedAddress(@Param("assignedAddress") type: Long): Flow
+}
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/AssignedAddressRepository.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/AssignedAddressRepository.kt
new file mode 100644
index 000000000..e6e7e147c
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/AssignedAddressRepository.kt
@@ -0,0 +1,22 @@
+package co.nilin.opex.port.bcgateway.postgres.dao
+
+import co.nilin.opex.port.bcgateway.postgres.model.AssignedAddressModel
+import kotlinx.coroutines.flow.Flow
+import org.springframework.data.r2dbc.repository.Query
+import org.springframework.data.repository.query.Param
+import org.springframework.data.repository.reactive.ReactiveCrudRepository
+import org.springframework.stereotype.Repository
+import reactor.core.publisher.Mono
+
+@Repository
+interface AssignedAddressRepository : ReactiveCrudRepository {
+ @Query("select * from assigned_addresses where uuid = :uuid and addr_type_id in (:addressTypes)")
+ fun findByUuidAndAddressType(
+ @Param("uuid") uuid: String, @Param("addressTypes") types: List
+ ): Flow
+
+ @Query("select * from assigned_addresses where address = :address and (:memo is null or memo = :memo)")
+ fun findByAddressAndMemo(
+ @Param("address") address: String, @Param("memo") memo: String?
+ ): Mono
+}
\ No newline at end of file
diff --git a/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/ChainAddressTypeRepository.kt b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/ChainAddressTypeRepository.kt
new file mode 100644
index 000000000..2248a9474
--- /dev/null
+++ b/BlockchainGateway/bc-gateway-ports/bc-persister-postgres/src/main/kotlin/co/nilin/opex/port/bcgateway/postgres/dao/ChainAddressTypeRepository.kt
@@ -0,0 +1,8 @@
+package co.nilin.opex.port.bcgateway.postgres.dao
+
+import co.nilin.opex.port.bcgateway.postgres.model.ChainAddressTypeModel
+import org.springframework.data.repository.reactive.ReactiveCrudRepository
+import org.springframework.stereotype.Repository
+
+@Repository
+interface ChainAddressTypeRepository : ReactiveCrudRepository