Skip to content
This repository has been archived by the owner on Jan 29, 2019. It is now read-only.

Commit

Permalink
#185 refactor Etherdelta connector
Browse files Browse the repository at this point in the history
add OrderBook entity
  • Loading branch information
Valentin Stavetski committed Apr 28, 2018
1 parent 8344285 commit 9700c0a
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 193 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package fund.cyber.markets.common.model

data class OrderBook(
val asks: MutableList<Order>,
val bids: MutableList<Order>,
val timestamp: Long
)

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package fund.cyber.markets.connector.configuration

import fund.cyber.markets.common.EXCHANGES
import fund.cyber.markets.common.EXCHANGES_DEFAULT
import fund.cyber.markets.common.PARITY_URL
import fund.cyber.markets.common.PARITY_URL_DEFAULT
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.web3j.protocol.Web3j
import org.web3j.protocol.http.HttpService
import org.web3j.utils.Async

const val WEB3J_POLLING_INTERVAL = 5 * 1000L

@Configuration
class ConnectorConfiguration(
@Value("\${$EXCHANGES:$EXCHANGES_DEFAULT}")
private val exchangesProperty: String,

@Value("\${$PARITY_URL:$PARITY_URL_DEFAULT}")
val parityUrl: String
) {
val exchanges: Set<String> = exchangesProperty.split(",").map { it.trim().toUpperCase() }.toSet()

@Bean
fun web3j(): Web3j {
return Web3j.build(HttpService(parityUrl), WEB3J_POLLING_INTERVAL, Async.defaultExecutorService())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package fund.cyber.markets.connector.etherdelta

import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import org.apache.commons.io.IOUtils
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.support.GenericApplicationContext
import org.springframework.stereotype.Component
import org.web3j.protocol.Web3j
import org.web3j.tx.Contract
import org.web3j.tx.ReadonlyTransactionManager
import java.math.BigInteger
import java.net.URL
import java.nio.charset.Charset
import java.util.regex.Pattern

private const val ETHERDELTA_CONFIG_URL = "https://raw.githubusercontent.com/etherdelta/etherdelta.github.io/master/config/main.json"
private const val PARITY_TOKEN_REGISTRY_CONTRACT_ADDRESS = "0x5F0281910Af44bFb5fC7e86A404d0304B0e042F1"
private const val PARITY_TOKEN_REGISTRY_EMPTY_ADDRESS = "0x0000000000000000000000000000000000000000"

@Component
class EtherdeltaTokenResolver {
private val log = LoggerFactory.getLogger(EtherdeltaTokenResolver::class.java)!!

private lateinit var exchangeTokensPairs: MutableMap<String, EtherdeltaToken>

@Autowired
private lateinit var web3j: Web3j

@Autowired
private lateinit var resourceLoader: GenericApplicationContext

/**
* Get ERC20 token definitions from different sources
*/
fun updateTokensPairs() {
log.info("Updating Etherdelta tokens pairs")

val parityTokenRegistryTokens = getParityTokenRegistryTokens()
val etherdeltaConfigTokens = getEtherdeltaConfigTokens()
val myEtherWalletTokens = getMyEtherWalletTokens()
exchangeTokensPairs = parityTokenRegistryTokens
exchangeTokensPairs.putAll(etherdeltaConfigTokens)
exchangeTokensPairs.putAll(myEtherWalletTokens)

log.info("Etherdelta tokens pairs updated. Count: ${exchangeTokensPairs.size}")
}

/**
* Get ERC20 token definitions from etherdelta configuration JSON file.
* @return a map of ERC20 token address and token definition.
*/
private fun getEtherdeltaConfigTokens(): MutableMap<String, EtherdeltaToken> {
val mapper = ObjectMapper()
val tokens = mutableMapOf<String, EtherdeltaToken>()
val base = BigInteger.TEN

try {
val configTree = mapper.readTree(URL(ETHERDELTA_CONFIG_URL))
configTree.get("tokens").asIterable().forEach { tokenNode ->

if (tokenNode.get("decimals").asInt() != 0) {
val tokenContractAddress = tokenNode.get("addr").asText()
val tokenSymbol = tokenNode.get("name").asText()
val tokenDecimal = tokenNode.get("decimals").asInt()
val tokenBase = base.pow(tokenDecimal)

tokens[tokenContractAddress] = EtherdeltaToken(tokenSymbol, tokenBase, tokenDecimal)
}
}
} catch (e: Exception) {
log.warn("Cant get tokens definitions from Etherdelta config")
}

return tokens
}

/**
* Get ERC20 token definitions from MyEtherWallet token list.
* @return a map of ERC20 token address and token definition.
*/
private fun getMyEtherWalletTokens(): MutableMap<String, EtherdeltaToken> {
val mapper = ObjectMapper()
val tokens = mutableMapOf<String, EtherdeltaToken>()
val base = BigInteger.TEN

try {
val tokenDefinitionInputStream = resourceLoader.getResource("classpath:tokens-eth.json").inputStream
val tokenDefinitionString = IOUtils.toString(tokenDefinitionInputStream, Charset.forName("UTF-8"))

val tokenListTree = mapper.readValue<Iterable<JsonNode>>(tokenDefinitionString)
tokenListTree.asIterable().forEach { tokenNode ->

if (tokenNode.get("decimals").asInt() != 0) {
val tokenContractAddress = tokenNode.get("address").asText()
val tokenSymbol = tokenNode.get("symbol").asText()
val tokenDecimal = tokenNode.get("decimals").asInt()
val tokenBase = base.pow(tokenDecimal)

tokens[tokenContractAddress] = EtherdeltaToken(tokenSymbol, tokenBase, tokenDecimal)
}
}
} catch (e: Exception) {
log.warn("Cant get tokens definitions from MyEtherWallet config")
}

return tokens
}

/**
* Get ERC20 token definitions from parity token registry smart contract.
* @return a map of ERC20 token address and token definition.
*/
private fun getParityTokenRegistryTokens(): MutableMap<String, EtherdeltaToken> {
val tokens = mutableMapOf<String, EtherdeltaToken>()

try {
val transactionManager = ReadonlyTransactionManager(web3j, PARITY_TOKEN_REGISTRY_CONTRACT_ADDRESS)
val parityTokenRegistryContract = ParityTokenRegistryContract.load(PARITY_TOKEN_REGISTRY_CONTRACT_ADDRESS,
web3j, transactionManager, Contract.GAS_PRICE, Contract.GAS_LIMIT)

val tokensCount = parityTokenRegistryContract.tokenCount().send().toLong()
for (index in 0 until tokensCount) {
val token = parityTokenRegistryContract.token(BigInteger.valueOf(index)).send()

if (token.value1 == PARITY_TOKEN_REGISTRY_EMPTY_ADDRESS || !validBase(token.value3.toString())) {
continue
}

val tokenContractAddress = token.value1
val tokenSymbol = token.value2
val tokenBase = token.value3
val tokenDecimals = tokenBase.toString().length - 1

tokens[tokenContractAddress] = EtherdeltaToken(tokenSymbol, tokenBase, tokenDecimals)
}
} catch (e: Exception) {
log.warn("Cant get parity token registry tokens")
}

return tokens
}

/**
* Get ERC20 token definitions from its own smart contract.
* @return a map of ERC20 token address and token definition.
*/
private fun getTokenDefinitionByAddress(address: String): EtherdeltaToken? {
var tokenDefinition: EtherdeltaToken? = null

try {
val transactionManager = ReadonlyTransactionManager(web3j, address)
val erc20Contract = Erc20Contract.load(address, web3j, transactionManager, Contract.GAS_PRICE, Contract.GAS_LIMIT)

val tokenSymbol = erc20Contract.symbol().send().trim()
val tokenDecimals = erc20Contract.decimals().send().intValueExact()
val tokenBase = BigInteger.TEN.pow(tokenDecimals)

tokenDefinition = EtherdeltaToken(tokenSymbol, tokenBase, tokenDecimals)

log.info("Resolve new token: symbol=$tokenSymbol, decimals=$tokenDecimals")
exchangeTokensPairs[address] = tokenDefinition
} catch (e: Exception) {
log.info("Cant get token definition from address: $address")
}

return tokenDefinition
}

fun resolveToken(address: String?): EtherdeltaToken? {
val tokenDefiniton = exchangeTokensPairs[address]

if (tokenDefiniton == null && address != null) {
getTokenDefinitionByAddress(address)
}

return tokenDefiniton
}

/**
* Check that @param tokenBase is valid power of 10.
* We need check this because parity token registry contains not valid data.
* @return boolean result
*/
private fun validBase(tokenBase: String): Boolean {
val pattern = Pattern.compile("10*")
val matcher = pattern.matcher(tokenBase)

val result = (matcher.regionEnd() - matcher.regionStart()) == tokenBase.length || tokenBase == "0"

return result
}

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package fund.cyber.markets.connector.orderbook

import fund.cyber.markets.common.model.TokensPair
import fund.cyber.markets.connector.Connector
import org.knowm.xchange.currency.CurrencyPair
import org.knowm.xchange.dto.marketdata.OrderBook

interface OrderbookConnector : Connector {
var orderbooks: MutableMap<CurrencyPair, OrderBook>
fun getOrderBookSnapshot(pair: TokensPair): fund.cyber.markets.common.model.OrderBook
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package fund.cyber.markets.connector.orderbook

import fund.cyber.markets.common.model.TokensPair
import fund.cyber.markets.connector.AbstarctXchangeConnector
import info.bitrich.xchangestream.core.ProductSubscription
import info.bitrich.xchangestream.core.StreamingExchangeFactory
Expand Down Expand Up @@ -45,4 +46,8 @@ class XchangeOrderbookConnector : AbstarctXchangeConnector, OrderbookConnector {
}
}

override fun getOrderBookSnapshot(pair: TokensPair): fund.cyber.markets.common.model.OrderBook {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}

}
Loading

0 comments on commit 9700c0a

Please sign in to comment.