From bb687bba1cbb95d98e99642d6fa4defa43d9cdf8 Mon Sep 17 00:00:00 2001 From: chyngyz Date: Fri, 12 Jan 2024 15:32:03 +0600 Subject: [PATCH 1/3] Refactor UniswapKit to make usable without EthereumKit --- .../sample/modules/main/MainViewModel.kt | 52 +++++++------- .../modules/uniswapV3/UniswapV3Fragment.kt | 8 ++- .../modules/uniswapV3/UniswapV3ViewModel.kt | 24 ++++--- .../ethereumkit/core/EthereumKit.kt | 62 +++++++++++++++-- .../uniswapkit/PairSelector.kt | 17 ++--- .../uniswapkit/TokenFactory.kt | 9 ++- .../uniswapkit/TradeManager.kt | 30 ++++----- .../uniswapkit/UniswapKit.kt | 26 +++---- .../uniswapkit/UniswapV3Kit.kt | 47 ++++++++----- .../uniswapkit/v3/PriceImpactManager.kt | 8 ++- .../uniswapkit/v3/pool/PoolManager.kt | 28 ++++---- .../uniswapkit/v3/quoter/QuoterV2.kt | 67 ++++++++++++++----- .../uniswapkit/v3/router/SwapRouter.kt | 34 ++++++---- 13 files changed, 269 insertions(+), 143 deletions(-) diff --git a/app/src/main/java/io/horizontalsystems/ethereumkit/sample/modules/main/MainViewModel.kt b/app/src/main/java/io/horizontalsystems/ethereumkit/sample/modules/main/MainViewModel.kt index 16d54e22..cc8771ea 100644 --- a/app/src/main/java/io/horizontalsystems/ethereumkit/sample/modules/main/MainViewModel.kt +++ b/app/src/main/java/io/horizontalsystems/ethereumkit/sample/modules/main/MainViewModel.kt @@ -9,7 +9,11 @@ import io.horizontalsystems.ethereumkit.core.EthereumKit.SyncState import io.horizontalsystems.ethereumkit.core.eip1559.Eip1559GasPriceProvider import io.horizontalsystems.ethereumkit.core.signer.Signer import io.horizontalsystems.ethereumkit.core.toHexString -import io.horizontalsystems.ethereumkit.models.* +import io.horizontalsystems.ethereumkit.models.Address +import io.horizontalsystems.ethereumkit.models.Chain +import io.horizontalsystems.ethereumkit.models.GasPrice +import io.horizontalsystems.ethereumkit.models.RpcSource +import io.horizontalsystems.ethereumkit.models.TransactionSource import io.horizontalsystems.ethereumkit.sample.App import io.horizontalsystems.ethereumkit.sample.Configuration import io.horizontalsystems.ethereumkit.sample.SingleLiveEvent @@ -38,6 +42,8 @@ class MainViewModel : ViewModel() { lateinit var ethereumKit: EthereumKit lateinit var ethereumAdapter: EthereumAdapter lateinit var signer: Signer + lateinit var rpcSource: RpcSource + private lateinit var transactionSource: TransactionSource lateinit var erc20Adapter: Erc20Adapter @@ -72,15 +78,20 @@ class MainViewModel : ViewModel() { val toToken: Erc20Token = Configuration.erc20Tokens[1] lateinit var gasPriceHelper: GasPriceHelper + private val chain: Chain + get() = ethereumKit.chain + fun init() { val words = Configuration.defaultsWords.split(" ") val seed = Mnemonic().toSeed(words) signer = Signer.getInstance(seed, Configuration.chain) ethereumKit = createKit() ethereumAdapter = EthereumAdapter(ethereumKit, signer) - erc20Adapter = Erc20Adapter(App.instance, fromToken ?: toToken - ?: Configuration.erc20Tokens.first(), ethereumKit, signer) - uniswapKit = UniswapKit.getInstance(ethereumKit) + erc20Adapter = Erc20Adapter( + App.instance, fromToken ?: toToken + ?: Configuration.erc20Tokens.first(), ethereumKit, signer + ) + uniswapKit = UniswapKit.getInstance() Erc20Kit.addTransactionSyncer(ethereumKit) Erc20Kit.addDecorators(ethereumKit) @@ -179,14 +190,12 @@ class MainViewModel : ViewModel() { } private fun createKit(): EthereumKit { - val rpcSource: RpcSource? - val transactionSource: TransactionSource? - when (Configuration.chain) { Chain.BinanceSmartChain -> { transactionSource = TransactionSource.bscscan(Configuration.bscScanKey) rpcSource = RpcSource.binanceSmartChainHttp() } + Chain.Ethereum -> { transactionSource = TransactionSource.ethereumEtherscan(Configuration.etherscanKey) rpcSource = if (Configuration.webSocket) @@ -194,10 +203,12 @@ class MainViewModel : ViewModel() { else RpcSource.ethereumInfuraHttp(Configuration.infuraProjectId, Configuration.infuraSecret) } + Chain.ArbitrumOne -> { transactionSource = TransactionSource.arbiscan(Configuration.arbiscanApiKey) rpcSource = RpcSource.arbitrumOneRpcHttp() } + Chain.EthereumGoerli -> { transactionSource = TransactionSource.goerliEtherscan(Configuration.etherscanKey) rpcSource = if (Configuration.webSocket) @@ -205,20 +216,12 @@ class MainViewModel : ViewModel() { else RpcSource.goerliInfuraHttp(Configuration.infuraProjectId, Configuration.infuraSecret) } + else -> { - rpcSource = null - transactionSource = null + throw Exception("Could not get rpcSource & transactionSource!") } } - checkNotNull(rpcSource) { - throw Exception("Could not get rpcSource!") - } - - checkNotNull(transactionSource) { - throw Exception("Could not get transactionSource!") - } - return if (Configuration.watchAddress != null) { EthereumKit.getInstance( App.instance, Address(Configuration.watchAddress), @@ -402,7 +405,7 @@ class MainViewModel : ViewModel() { val tokenIn = uniswapToken(fromToken) val tokenOut = uniswapToken(toToken) - uniswapKit.swapData(tokenIn, tokenOut) + uniswapKit.swapData(rpcSource, chain, tokenIn, tokenOut) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ @@ -415,7 +418,7 @@ class MainViewModel : ViewModel() { } fun syncAllowance() { - erc20Adapter.allowance(uniswapKit.routerAddress) + erc20Adapter.allowance(uniswapKit.routerAddress(chain)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ @@ -428,7 +431,7 @@ class MainViewModel : ViewModel() { } fun approve(decimalAmount: BigDecimal) { - val spenderAddress = uniswapKit.routerAddress + val spenderAddress = uniswapKit.routerAddress(chain) val token = fromToken ?: return val amount = decimalAmount.movePointRight(token.decimals).toBigInteger() @@ -457,7 +460,7 @@ class MainViewModel : ViewModel() { private fun uniswapToken(token: Erc20Token?): Token { if (token == null) - return uniswapKit.etherToken() + return uniswapKit.etherToken(chain) return uniswapKit.token(token.contractAddress, token.decimals) } @@ -489,12 +492,12 @@ class MainViewModel : ViewModel() { fun swap() { tradeData.value?.let { tradeData -> - val transactionData = uniswapKit.transactionData(tradeData) + val transactionData = uniswapKit.transactionData(ethereumKit.receiveAddress, chain, tradeData) ethereumKit.estimateGas(transactionData, gasPrice) .flatMap { gasLimit -> logger.info("gas limit: $gasLimit") - val transactionData = uniswapKit.transactionData(tradeData) + val transactionData = uniswapKit.transactionData(ethereumKit.receiveAddress, chain, tradeData) ethereumKit.rawTransaction(transactionData, gasPrice, gasLimit) } .flatMap { rawTransaction -> @@ -524,4 +527,5 @@ data class Erc20Token( val name: String, val code: String, val contractAddress: Address, - val decimals: Int) + val decimals: Int +) diff --git a/app/src/main/java/io/horizontalsystems/ethereumkit/sample/modules/uniswapV3/UniswapV3Fragment.kt b/app/src/main/java/io/horizontalsystems/ethereumkit/sample/modules/uniswapV3/UniswapV3Fragment.kt index 8854f237..882acbb3 100644 --- a/app/src/main/java/io/horizontalsystems/ethereumkit/sample/modules/uniswapV3/UniswapV3Fragment.kt +++ b/app/src/main/java/io/horizontalsystems/ethereumkit/sample/modules/uniswapV3/UniswapV3Fragment.kt @@ -23,6 +23,7 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewmodel.compose.viewModel import io.horizontalsystems.ethereumkit.core.EthereumKit import io.horizontalsystems.ethereumkit.core.signer.Signer +import io.horizontalsystems.ethereumkit.models.RpcSource import io.horizontalsystems.ethereumkit.sample.core.Erc20Adapter import io.horizontalsystems.ethereumkit.sample.core.EthereumAdapter import io.horizontalsystems.ethereumkit.sample.modules.main.GasPriceHelper @@ -49,6 +50,7 @@ class UniswapV3Fragment : Fragment() { mainViewModel.ethereumAdapter, mainViewModel.gasPriceHelper, mainViewModel.signer, + mainViewModel.rpcSource ) } } @@ -61,14 +63,16 @@ fun UniswapV3Screen( erc20Adapter: Erc20Adapter, ethereumAdapter: EthereumAdapter, gasPriceHelper: GasPriceHelper, - signer: Signer + signer: Signer, + rpcSource: RpcSource ) { val factory = UniswapV3ViewModel.Factory( ethereumKit, erc20Adapter, ethereumAdapter, gasPriceHelper, - signer + signer, + rpcSource ) val viewModel = viewModel(factory = factory) diff --git a/app/src/main/java/io/horizontalsystems/ethereumkit/sample/modules/uniswapV3/UniswapV3ViewModel.kt b/app/src/main/java/io/horizontalsystems/ethereumkit/sample/modules/uniswapV3/UniswapV3ViewModel.kt index 022d5cac..9c43fdef 100644 --- a/app/src/main/java/io/horizontalsystems/ethereumkit/sample/modules/uniswapV3/UniswapV3ViewModel.kt +++ b/app/src/main/java/io/horizontalsystems/ethereumkit/sample/modules/uniswapV3/UniswapV3ViewModel.kt @@ -10,6 +10,7 @@ import androidx.lifecycle.viewModelScope import io.horizontalsystems.ethereumkit.core.EthereumKit import io.horizontalsystems.ethereumkit.core.signer.Signer import io.horizontalsystems.ethereumkit.models.GasPrice +import io.horizontalsystems.ethereumkit.models.RpcSource import io.horizontalsystems.ethereumkit.sample.Configuration import io.horizontalsystems.ethereumkit.sample.core.Erc20Adapter import io.horizontalsystems.ethereumkit.sample.core.EthereumAdapter @@ -31,10 +32,12 @@ class UniswapV3ViewModel( private val erc20Adapter: Erc20Adapter, private val ethereumAdapter: EthereumAdapter, private val gasPriceHelper: GasPriceHelper, - private val signer: Signer + private val signer: Signer, + private val rpcSource: RpcSource ) : ViewModel() { - private var uniswapV3Kit = UniswapV3Kit.getInstance(ethereumKit, DexType.PancakeSwap) + private val chain = ethereumKit.chain + private var uniswapV3Kit = UniswapV3Kit.getInstance(DexType.PancakeSwap) private var gasPrice: GasPrice = GasPrice.Legacy(20_000_000_000) val fromToken: Erc20Token? = Configuration.erc20Tokens[5] @@ -83,7 +86,7 @@ class UniswapV3ViewModel( allowance = BigDecimal(Int.MAX_VALUE) } else { allowance = try { - erc20Adapter.allowance(uniswapV3Kit.routerAddress).await().stripTrailingZeros() + erc20Adapter.allowance(uniswapV3Kit.routerAddress(chain)).await().stripTrailingZeros() } catch (it: Throwable) { Log.e("AAA", "allowance error", it) BigDecimal.ZERO @@ -93,7 +96,7 @@ class UniswapV3ViewModel( fun approve() { val tmpAmountIn = amountIn ?: return - val spenderAddress = uniswapV3Kit.routerAddress + val spenderAddress = uniswapV3Kit.routerAddress(chain) val token = fromUniswapToken val amount = tmpAmountIn.movePointRight(token.decimals).toBigInteger() @@ -138,6 +141,8 @@ class UniswapV3ViewModel( job = viewModelScope.launch(Dispatchers.IO) { try { val bestTradeExactIn = uniswapV3Kit.bestTradeExactIn( + rpcSource = rpcSource, + chain = chain, tokenIn = fromUniswapToken, tokenOut = toUniswapToken, amountIn = amountIn, @@ -156,7 +161,7 @@ class UniswapV3ViewModel( } private fun uniswapToken(token: Erc20Token?) = when (token) { - null -> uniswapV3Kit.etherToken() + null -> uniswapV3Kit.etherToken(chain) else -> uniswapV3Kit.token(token.contractAddress, token.decimals) } @@ -177,6 +182,8 @@ class UniswapV3ViewModel( job = viewModelScope.launch(Dispatchers.IO) { try { val bestTradeExactOut = uniswapV3Kit.bestTradeExactOut( + rpcSource = rpcSource, + chain = chain, tokenIn = fromUniswapToken, tokenOut = toUniswapToken, amountOut = amountOut, @@ -222,7 +229,7 @@ class UniswapV3ViewModel( val tradeData = tradeData ?: return viewModelScope.launch(Dispatchers.IO) { - val transactionData = uniswapV3Kit.transactionData(tradeData) + val transactionData = uniswapV3Kit.transactionData(ethereumKit.receiveAddress, chain, tradeData) loading = true emitState() @@ -249,11 +256,12 @@ class UniswapV3ViewModel( private val erc20Adapter: Erc20Adapter, private val ethereumAdapter: EthereumAdapter, private val gasPriceHelper: GasPriceHelper, - private val signer: Signer + private val signer: Signer, + private val rpcSource: RpcSource ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { - return UniswapV3ViewModel(ethereumKit, erc20Adapter, ethereumAdapter, gasPriceHelper, signer) as T + return UniswapV3ViewModel(ethereumKit, erc20Adapter, ethereumAdapter, gasPriceHelper, signer, rpcSource) as T } } } diff --git a/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/core/EthereumKit.kt b/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/core/EthereumKit.kt index c9f5b7e3..3cb8da7a 100644 --- a/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/core/EthereumKit.kt +++ b/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/core/EthereumKit.kt @@ -4,7 +4,13 @@ import android.app.Application import android.content.Context import com.google.gson.GsonBuilder import com.google.gson.reflect.TypeToken -import io.horizontalsystems.ethereumkit.api.core.* +import io.horizontalsystems.ethereumkit.api.core.ApiRpcSyncer +import io.horizontalsystems.ethereumkit.api.core.IRpcApiProvider +import io.horizontalsystems.ethereumkit.api.core.IRpcSyncer +import io.horizontalsystems.ethereumkit.api.core.NodeApiProvider +import io.horizontalsystems.ethereumkit.api.core.NodeWebSocket +import io.horizontalsystems.ethereumkit.api.core.RpcBlockchain +import io.horizontalsystems.ethereumkit.api.core.WebSocketRpcSyncer import io.horizontalsystems.ethereumkit.api.jsonrpc.JsonRpc import io.horizontalsystems.ethereumkit.api.jsonrpc.models.RpcBlock import io.horizontalsystems.ethereumkit.api.jsonrpc.models.RpcTransaction @@ -21,8 +27,26 @@ import io.horizontalsystems.ethereumkit.crypto.InternalBouncyCastleProvider import io.horizontalsystems.ethereumkit.decorations.DecorationManager import io.horizontalsystems.ethereumkit.decorations.EthereumDecorator import io.horizontalsystems.ethereumkit.decorations.TransactionDecoration -import io.horizontalsystems.ethereumkit.models.* -import io.horizontalsystems.ethereumkit.network.* +import io.horizontalsystems.ethereumkit.models.Address +import io.horizontalsystems.ethereumkit.models.Chain +import io.horizontalsystems.ethereumkit.models.DefaultBlockParameter +import io.horizontalsystems.ethereumkit.models.FullTransaction +import io.horizontalsystems.ethereumkit.models.GasPrice +import io.horizontalsystems.ethereumkit.models.RawTransaction +import io.horizontalsystems.ethereumkit.models.RpcSource +import io.horizontalsystems.ethereumkit.models.Signature +import io.horizontalsystems.ethereumkit.models.TransactionData +import io.horizontalsystems.ethereumkit.models.TransactionLog +import io.horizontalsystems.ethereumkit.models.TransactionSource +import io.horizontalsystems.ethereumkit.network.AddressTypeAdapter +import io.horizontalsystems.ethereumkit.network.BigIntegerTypeAdapter +import io.horizontalsystems.ethereumkit.network.ByteArrayTypeAdapter +import io.horizontalsystems.ethereumkit.network.ConnectionManager +import io.horizontalsystems.ethereumkit.network.DefaultBlockParameterTypeAdapter +import io.horizontalsystems.ethereumkit.network.EtherscanService +import io.horizontalsystems.ethereumkit.network.IntTypeAdapter +import io.horizontalsystems.ethereumkit.network.LongTypeAdapter +import io.horizontalsystems.ethereumkit.network.OptionalTypeAdapter import io.horizontalsystems.ethereumkit.transactionsyncers.EthereumTransactionSyncer import io.horizontalsystems.ethereumkit.transactionsyncers.InternalTransactionSyncer import io.horizontalsystems.ethereumkit.transactionsyncers.TransactionSyncManager @@ -36,7 +60,8 @@ import io.reactivex.subjects.PublishSubject import org.bouncycastle.jce.provider.BouncyCastleProvider import java.math.BigInteger import java.security.Security -import java.util.* +import java.util.Objects +import java.util.Optional import java.util.logging.Logger class EthereumKit( @@ -132,6 +157,7 @@ class EthereumKit( fun getNonce(defaultBlockParameter: DefaultBlockParameter): Single { return blockchain.getNonce(defaultBlockParameter) } + fun getFullTransactionsFlowable(tags: List>): Flowable> { return transactionManager.getFullTransactionsFlowable(tags) } @@ -340,11 +366,34 @@ class EthereumKit( .registerTypeAdapter(ByteArray::class.java, ByteArrayTypeAdapter()) .registerTypeAdapter(Address::class.java, AddressTypeAdapter()) .registerTypeHierarchyAdapter(DefaultBlockParameter::class.java, DefaultBlockParameterTypeAdapter()) - .registerTypeAdapter(object : TypeToken>() {}.type, OptionalTypeAdapter(RpcTransaction::class.java)) - .registerTypeAdapter(object : TypeToken>() {}.type, OptionalTypeAdapter(RpcTransactionReceipt::class.java)) + .registerTypeAdapter( + object : TypeToken>() {}.type, + OptionalTypeAdapter(RpcTransaction::class.java) + ) + .registerTypeAdapter( + object : TypeToken>() {}.type, + OptionalTypeAdapter(RpcTransactionReceipt::class.java) + ) .registerTypeAdapter(object : TypeToken>() {}.type, OptionalTypeAdapter(RpcBlock::class.java)) .create() + fun call( + rpcSource: RpcSource, + contractAddress: Address, + data: ByteArray, + defaultBlockParameter: DefaultBlockParameter = DefaultBlockParameter.Latest + ): Single { + val rpcApiProvider: IRpcApiProvider = when (rpcSource) { + is RpcSource.Http -> { + NodeApiProvider(rpcSource.uris, gson, rpcSource.auth) + } + + is RpcSource.WebSocket -> throw IllegalStateException("Websocket not supported") + } + val rpc = RpcBlockchain.callRpc(contractAddress, data, defaultBlockParameter) + return rpcApiProvider.single(rpc) + } + fun init() { Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME) Security.addProvider(InternalBouncyCastleProvider.getInstance()) @@ -385,6 +434,7 @@ class EthereumKit( webSocketRpcSyncer } + is RpcSource.Http -> { val apiProvider = NodeApiProvider(rpcSource.uris, gson, rpcSource.auth) ApiRpcSyncer(apiProvider, connectionManager, chain.syncInterval) diff --git a/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/PairSelector.kt b/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/PairSelector.kt index a4646f34..34daf740 100644 --- a/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/PairSelector.kt +++ b/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/PairSelector.kt @@ -1,16 +1,17 @@ package io.horizontalsystems.uniswapkit +import io.horizontalsystems.ethereumkit.models.Chain import io.horizontalsystems.uniswapkit.models.Token class PairSelector( - private val tokenFactory: TokenFactory + private val tokenFactory: TokenFactory ) { - fun tokenPairs(tokenA: Token, tokenB: Token): List> = - if (tokenA.isEther || tokenB.isEther) { - listOf(Pair(tokenA, tokenB)) - } else { - val etherToken = tokenFactory.etherToken() + fun tokenPairs(chain: Chain, tokenA: Token, tokenB: Token): List> = + if (tokenA.isEther || tokenB.isEther) { + listOf(Pair(tokenA, tokenB)) + } else { + val etherToken = tokenFactory.etherToken(chain) - listOf(Pair(tokenA, tokenB), Pair(tokenA, etherToken), Pair(tokenB, etherToken)) - } + listOf(Pair(tokenA, tokenB), Pair(tokenA, etherToken), Pair(tokenB, etherToken)) + } } diff --git a/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/TokenFactory.kt b/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/TokenFactory.kt index 6cf6d616..f0858bf5 100644 --- a/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/TokenFactory.kt +++ b/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/TokenFactory.kt @@ -4,15 +4,14 @@ import io.horizontalsystems.ethereumkit.models.Address import io.horizontalsystems.ethereumkit.models.Chain import io.horizontalsystems.uniswapkit.models.Token -class TokenFactory(chain: Chain) { - val wethAddress = getWethAddress(chain) +class TokenFactory { sealed class UnsupportedChainError : Throwable() { object NoWethAddress : UnsupportedChainError() } - fun etherToken(): Token { - return Token.Ether(wethAddress) + fun etherToken(chain: Chain): Token { + return Token.Ether(getWethAddress(chain)) } fun token(contractAddress: Address, decimals: Int): Token { @@ -20,7 +19,7 @@ class TokenFactory(chain: Chain) { } companion object { - private fun getWethAddress(chain: Chain): Address { + fun getWethAddress(chain: Chain): Address { val wethAddressHex = when (chain) { Chain.Ethereum -> "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" Chain.Optimism -> "0x4200000000000000000000000000000000000006" diff --git a/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/TradeManager.kt b/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/TradeManager.kt index 693962ec..dbd183a9 100644 --- a/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/TradeManager.kt +++ b/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/TradeManager.kt @@ -5,6 +5,7 @@ import io.horizontalsystems.ethereumkit.core.EthereumKit import io.horizontalsystems.ethereumkit.core.toHexString import io.horizontalsystems.ethereumkit.models.Address import io.horizontalsystems.ethereumkit.models.Chain +import io.horizontalsystems.ethereumkit.models.RpcSource import io.horizontalsystems.ethereumkit.models.TransactionData import io.horizontalsystems.uniswapkit.contract.* import io.horizontalsystems.uniswapkit.models.* @@ -15,31 +16,25 @@ import java.math.BigInteger import java.util.* import java.util.logging.Logger -class TradeManager( - private val evmKit: EthereumKit -) { - private val address: Address = evmKit.receiveAddress +class TradeManager { private val logger = Logger.getLogger(this.javaClass.simpleName) - val routerAddress: Address = getRouterAddress(evmKit.chain) - val factoryAddressString: String = getFactoryAddressString(evmKit.chain) - val initCodeHashString: String = getInitCodeHashString(evmKit.chain) - sealed class UnsupportedChainError : Throwable() { object NoRouterAddress : UnsupportedChainError() object NoFactoryAddress : UnsupportedChainError() object NoInitCodeHash : UnsupportedChainError() } - fun pair(tokenA: Token, tokenB: Token): Single { - + fun pair(rpcSource: RpcSource, chain: Chain, tokenA: Token, tokenB: Token): Single { val (token0, token1) = if (tokenA.sortsBefore(tokenB)) Pair(tokenA, tokenB) else Pair(tokenB, tokenA) + val factoryAddressString = getFactoryAddressString(chain) + val initCodeHashString = getInitCodeHashString(chain) val pairAddress = Pair.address(token0, token1, factoryAddressString, initCodeHashString) logger.info("pairAddress: ${pairAddress.hex}") - return evmKit.call(pairAddress, GetReservesMethod().encodedABI()) + return EthereumKit.call(rpcSource, pairAddress, GetReservesMethod().encodedABI()) .map { data -> logger.info("getReserves data: ${data.toHexString()}") @@ -60,22 +55,25 @@ class TradeManager( } } - fun transactionData(tradeData: TradeData): TransactionData { - return buildSwapData(tradeData).let { + fun transactionData(receiveAddress: Address, chain: Chain, tradeData: TradeData): TransactionData { + val routerAddress = getRouterAddress(chain) + + return buildSwapData(receiveAddress, tradeData).let { + TransactionData(routerAddress, it.amount, it.input) } } private class SwapData(val amount: BigInteger, val input: ByteArray) - private fun buildSwapData(tradeData: TradeData): SwapData { + private fun buildSwapData(receiveAddress: Address, tradeData: TradeData): SwapData { val trade = tradeData.trade val tokenIn = trade.tokenAmountIn.token val tokenOut = trade.tokenAmountOut.token val path = trade.route.path.map { it.address } - val to = tradeData.options.recipient ?: address + val to = tradeData.options.recipient ?: receiveAddress val deadline = (Date().time / 1000 + tradeData.options.ttl).toBigInteger() val method = when (trade.type) { @@ -121,7 +119,7 @@ class TradeManager( companion object { - private fun getRouterAddress(chain: Chain) = + fun getRouterAddress(chain: Chain) = when (chain) { Chain.Ethereum, Chain.EthereumGoerli -> Address("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D") Chain.BinanceSmartChain -> Address("0x10ED43C718714eb63d5aA57B78B54704E256024E") diff --git a/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/UniswapKit.kt b/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/UniswapKit.kt index 2856580c..85008c8f 100644 --- a/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/UniswapKit.kt +++ b/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/UniswapKit.kt @@ -2,6 +2,8 @@ package io.horizontalsystems.uniswapkit import io.horizontalsystems.ethereumkit.core.EthereumKit import io.horizontalsystems.ethereumkit.models.Address +import io.horizontalsystems.ethereumkit.models.Chain +import io.horizontalsystems.ethereumkit.models.RpcSource import io.horizontalsystems.ethereumkit.models.TransactionData import io.horizontalsystems.uniswapkit.contract.SwapContractMethodFactories import io.horizontalsystems.uniswapkit.models.* @@ -16,21 +18,21 @@ class UniswapKit( ) { private val logger = Logger.getLogger(this.javaClass.simpleName) - val routerAddress: Address - get() = tradeManager.routerAddress + fun routerAddress(chain: Chain): Address + = TradeManager.getRouterAddress(chain) - fun etherToken(): Token { - return tokenFactory.etherToken() + fun etherToken(chain: Chain): Token { + return tokenFactory.etherToken(chain) } fun token(contractAddress: Address, decimals: Int): Token { return tokenFactory.token(contractAddress, decimals) } - fun swapData(tokenIn: Token, tokenOut: Token): Single { - val tokenPairs = pairSelector.tokenPairs(tokenIn, tokenOut) + fun swapData(rpcSource: RpcSource, chain: Chain, tokenIn: Token, tokenOut: Token): Single { + val tokenPairs = pairSelector.tokenPairs(chain, tokenIn, tokenOut) val singles = tokenPairs.map { (tokenA, tokenB) -> - tradeManager.pair(tokenA, tokenB) + tradeManager.pair(rpcSource, chain, tokenA, tokenB) } return Single.zip(singles) { array -> @@ -77,14 +79,14 @@ class UniswapKit( return TradeData(trade, options) } - fun transactionData(tradeData: TradeData): TransactionData { - return tradeManager.transactionData(tradeData) + fun transactionData(receiveAddress: Address, chain: Chain, tradeData: TradeData): TransactionData { + return tradeManager.transactionData(receiveAddress, chain, tradeData) } companion object { - fun getInstance(ethereumKit: EthereumKit): UniswapKit { - val tradeManager = TradeManager(ethereumKit) - val tokenFactory = TokenFactory(ethereumKit.chain) + fun getInstance(): UniswapKit { + val tradeManager = TradeManager() + val tokenFactory = TokenFactory() val pairSelector = PairSelector(tokenFactory) return UniswapKit(tradeManager, pairSelector, tokenFactory) diff --git a/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/UniswapV3Kit.kt b/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/UniswapV3Kit.kt index 0d2173d9..08fc8425 100644 --- a/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/UniswapV3Kit.kt +++ b/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/UniswapV3Kit.kt @@ -2,6 +2,8 @@ package io.horizontalsystems.uniswapkit import io.horizontalsystems.ethereumkit.core.EthereumKit import io.horizontalsystems.ethereumkit.models.Address +import io.horizontalsystems.ethereumkit.models.Chain +import io.horizontalsystems.ethereumkit.models.RpcSource import io.horizontalsystems.uniswapkit.models.DexType import io.horizontalsystems.uniswapkit.models.Token import io.horizontalsystems.uniswapkit.models.TradeOptions @@ -22,10 +24,10 @@ class UniswapV3Kit( private val tokenFactory: TokenFactory, private val priceImpactManager: PriceImpactManager ) { - val routerAddress get() = swapRouter.swapRouterAddress + fun routerAddress(chain: Chain): Address = swapRouter.swapRouterAddress(chain) - fun etherToken(): Token { - return tokenFactory.etherToken() + fun etherToken(chain: Chain): Token { + return tokenFactory.etherToken(chain) } fun token(contractAddress: Address, decimals: Int): Token { @@ -33,11 +35,15 @@ class UniswapV3Kit( } suspend fun bestTradeExactIn( + rpcSource: RpcSource, + chain: Chain, tokenIn: Token, tokenOut: Token, amountIn: BigDecimal, tradeOptions: TradeOptions ) = bestTradeExactIn( + rpcSource, + chain, tokenIn, tokenOut, amountIn.movePointRight(tokenIn.decimals).toBigInteger(), @@ -45,25 +51,31 @@ class UniswapV3Kit( ) suspend fun bestTradeExactIn( + rpcSource: RpcSource, + chain: Chain, tokenIn: Token, tokenOut: Token, amountIn: BigInteger, tradeOptions: TradeOptions ): TradeDataV3 { - val bestTrade = quoter.bestTradeExactIn(tokenIn, tokenOut, amountIn) + val bestTrade = quoter.bestTradeExactIn(rpcSource, chain, tokenIn, tokenOut, amountIn) return TradeDataV3( bestTrade, tradeOptions, - priceImpactManager.getPriceImpact(bestTrade) + priceImpactManager.getPriceImpact(rpcSource, chain, bestTrade) ) } suspend fun bestTradeExactOut( + rpcSource: RpcSource, + chain: Chain, tokenIn: Token, tokenOut: Token, amountOut: BigDecimal, tradeOptions: TradeOptions ) = bestTradeExactOut( + rpcSource, + chain, tokenIn, tokenOut, amountOut.movePointRight(tokenOut.decimals).toBigInteger(), @@ -71,36 +83,41 @@ class UniswapV3Kit( ) suspend fun bestTradeExactOut( + rpcSource: RpcSource, + chain: Chain, tokenIn: Token, tokenOut: Token, amountOut: BigInteger, tradeOptions: TradeOptions ): TradeDataV3 { - val bestTrade = quoter.bestTradeExactOut(tokenIn, tokenOut, amountOut) + val bestTrade = quoter.bestTradeExactOut(rpcSource, chain, tokenIn, tokenOut, amountOut) return TradeDataV3( bestTrade, tradeOptions, - priceImpactManager.getPriceImpact(bestTrade) + priceImpactManager.getPriceImpact(rpcSource, chain, bestTrade) ) } - fun transactionData(tradeData: TradeDataV3) = swapRouter.transactionData(tradeData) + fun transactionData( + receiveAddress: Address, + chain: Chain, + tradeData: TradeDataV3 + ) = swapRouter.transactionData(receiveAddress, chain, tradeData) companion object { - fun getInstance(ethereumKit: EthereumKit, dexType: DexType): UniswapV3Kit { - val tokenFactory = TokenFactory(ethereumKit.chain) - val quoter = QuoterV2(ethereumKit, tokenFactory.etherToken(), dexType) - val swapRouter = SwapRouter(ethereumKit, dexType) - val poolManager = PoolManager(ethereumKit, dexType) + fun getInstance(dexType: DexType): UniswapV3Kit { + val tokenFactory = TokenFactory() + val quoter = QuoterV2(tokenFactory, dexType) + val swapRouter = SwapRouter(dexType) + val poolManager = PoolManager(dexType) val priceImpactManager = PriceImpactManager(poolManager) return UniswapV3Kit(quoter, swapRouter, tokenFactory, priceImpactManager) } fun addDecorators(ethereumKit: EthereumKit) { - val tokenFactory = TokenFactory(ethereumKit.chain) ethereumKit.addMethodDecorator(UniswapV3MethodDecorator(UniswapV3ContractMethodFactories)) - ethereumKit.addTransactionDecorator(UniswapV3TransactionDecorator(tokenFactory.wethAddress)) + ethereumKit.addTransactionDecorator(UniswapV3TransactionDecorator(TokenFactory.getWethAddress(ethereumKit.chain))) } } diff --git a/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/v3/PriceImpactManager.kt b/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/v3/PriceImpactManager.kt index b7c20dba..d306d797 100644 --- a/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/v3/PriceImpactManager.kt +++ b/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/v3/PriceImpactManager.kt @@ -1,5 +1,7 @@ package io.horizontalsystems.uniswapkit.v3 +import io.horizontalsystems.ethereumkit.models.Chain +import io.horizontalsystems.ethereumkit.models.RpcSource import io.horizontalsystems.uniswapkit.models.Fraction import io.horizontalsystems.uniswapkit.models.TradeType import io.horizontalsystems.uniswapkit.v3.pool.PoolManager @@ -8,15 +10,15 @@ import java.math.BigDecimal class PriceImpactManager(private val poolManager: PoolManager) { - suspend fun getPriceImpact(bestTrade: BestTrade): BigDecimal? { + suspend fun getPriceImpact(rpcSource: RpcSource, chain: Chain, bestTrade: BestTrade): BigDecimal? { val tradePrice = Fraction(bestTrade.amountIn, bestTrade.amountOut) val poolPrices = when (bestTrade.tradeType) { TradeType.ExactIn -> bestTrade.swapPath.items.map { - poolManager.getPoolPrice(it.token2, it.token1, it.fee) + poolManager.getPoolPrice(rpcSource, chain, it.token2, it.token1, it.fee) } TradeType.ExactOut -> bestTrade.swapPath.items.map { - poolManager.getPoolPrice(it.token1, it.token2, it.fee) + poolManager.getPoolPrice(rpcSource, chain, it.token1, it.token2, it.fee) } } diff --git a/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/v3/pool/PoolManager.kt b/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/v3/pool/PoolManager.kt index 7274f809..4852a2b1 100644 --- a/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/v3/pool/PoolManager.kt +++ b/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/v3/pool/PoolManager.kt @@ -3,6 +3,7 @@ package io.horizontalsystems.uniswapkit.v3.pool import io.horizontalsystems.ethereumkit.core.EthereumKit import io.horizontalsystems.ethereumkit.models.Address import io.horizontalsystems.ethereumkit.models.Chain +import io.horizontalsystems.ethereumkit.models.RpcSource import io.horizontalsystems.ethereumkit.spv.core.toBigInteger import io.horizontalsystems.uniswapkit.models.DexType import io.horizontalsystems.uniswapkit.models.Fraction @@ -11,12 +12,11 @@ import kotlinx.coroutines.rx2.await import java.math.BigInteger class PoolManager( - private val ethereumKit: EthereumKit, - dexType: DexType + private val dexType: DexType ) { - private val factoryAddress = when (dexType) { - DexType.Uniswap -> getUniswapFactoryAddress(ethereumKit.chain) - DexType.PancakeSwap -> getPancakeSwapFactoryAddress(ethereumKit.chain) + private fun factoryAddress(chain: Chain) = when (dexType) { + DexType.Uniswap -> getUniswapFactoryAddress(chain) + DexType.PancakeSwap -> getPancakeSwapFactoryAddress(chain) } private fun getUniswapFactoryAddress(chain: Chain)= when (chain) { @@ -26,19 +26,19 @@ class PoolManager( Chain.ArbitrumOne, Chain.EthereumGoerli -> "0x1F98431c8aD98523631AE4a59f267346ea31F984" Chain.BinanceSmartChain -> "0xdB1d10011AD0Ff90774D0C6Bb92e5C5c8b4461F7" - else -> throw IllegalStateException("Not supported Uniswap chain ${ethereumKit.chain}") + else -> throw IllegalStateException("Not supported Uniswap chain ${chain}") } private fun getPancakeSwapFactoryAddress(chain: Chain)= when (chain) { Chain.BinanceSmartChain, Chain.Ethereum -> "0x0BFbCF9fa4f9C56B0F40a671Ad40E0805A091865" - else -> throw IllegalStateException("Not supported PancakeSwap chain ${ethereumKit.chain}") + else -> throw IllegalStateException("Not supported PancakeSwap chain ${chain}") } // get price of tokenA in tokenB - suspend fun getPoolPrice(tokenA: Address, tokenB: Address, fee: FeeAmount): Fraction { - val poolAddress = getPoolAddress(tokenA, tokenB, fee) - val callResponse = ethCall(poolAddress, Slot0Method().encodedABI()) + suspend fun getPoolPrice(rpcSource: RpcSource, chain: Chain, tokenA: Address, tokenB: Address, fee: FeeAmount): Fraction { + val poolAddress = getPoolAddress(rpcSource, chain, tokenA, tokenB, fee) + val callResponse = ethCall(rpcSource, poolAddress, Slot0Method().encodedABI()) val sqrtPriceX96 = callResponse.sliceArray(IntRange(0, 31)).toBigInteger() val price = Fraction(sqrtPriceX96.pow(2), BigInteger.valueOf(2).pow(192)) @@ -48,13 +48,13 @@ class PoolManager( } } - private suspend fun getPoolAddress(tokenA: Address, tokenB: Address, fee: FeeAmount): Address { - val callResponse = ethCall(Address(factoryAddress), GetPoolMethod(tokenA, tokenB, fee.value).encodedABI()) + private suspend fun getPoolAddress(rpcSource: RpcSource, chain: Chain, tokenA: Address, tokenB: Address, fee: FeeAmount): Address { + val callResponse = ethCall(rpcSource, Address(factoryAddress(chain)), GetPoolMethod(tokenA, tokenB, fee.value).encodedABI()) return Address(callResponse.sliceArray(IntRange(0, 31))) } - private suspend fun ethCall(contractAddress: Address, data: ByteArray): ByteArray { - return ethereumKit.call(contractAddress, data).await() + private suspend fun ethCall(rpcSource: RpcSource, contractAddress: Address, data: ByteArray): ByteArray { + return EthereumKit.call(rpcSource, contractAddress, data).await() } } diff --git a/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/v3/quoter/QuoterV2.kt b/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/v3/quoter/QuoterV2.kt index 9fe20f93..5520e1cf 100644 --- a/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/v3/quoter/QuoterV2.kt +++ b/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/v3/quoter/QuoterV2.kt @@ -3,7 +3,9 @@ package io.horizontalsystems.uniswapkit.v3.quoter import io.horizontalsystems.ethereumkit.core.EthereumKit import io.horizontalsystems.ethereumkit.models.Address import io.horizontalsystems.ethereumkit.models.Chain +import io.horizontalsystems.ethereumkit.models.RpcSource import io.horizontalsystems.ethereumkit.spv.core.toBigInteger +import io.horizontalsystems.uniswapkit.TokenFactory import io.horizontalsystems.uniswapkit.TradeError import io.horizontalsystems.uniswapkit.models.DexType import io.horizontalsystems.uniswapkit.models.Token @@ -17,16 +19,15 @@ import java.math.BigInteger import kotlin.coroutines.coroutineContext class QuoterV2( - private val ethereumKit: EthereumKit, - private val weth: Token, - dexType: DexType + private val tokenFactory: TokenFactory, + private val dexType: DexType ) { private val feeAmounts = FeeAmount.sorted(dexType) - private val quoterAddress = when (dexType) { - DexType.Uniswap -> getUniswapQuoterAddress(ethereumKit.chain) - DexType.PancakeSwap -> getPancakeSwapQuoterAddress(ethereumKit.chain) + private fun quoterAddress(chain: Chain) = when (dexType) { + DexType.Uniswap -> getUniswapQuoterAddress(chain) + DexType.PancakeSwap -> getPancakeSwapQuoterAddress(chain) } private fun getUniswapQuoterAddress(chain: Chain) = when (chain) { @@ -35,31 +36,37 @@ class QuoterV2( Chain.Optimism, Chain.ArbitrumOne, Chain.EthereumGoerli -> "0x61fFE014bA17989E743c5F6cB21bF9697530B21e" + Chain.BinanceSmartChain -> "0x78D78E420Da98ad378D7799bE8f4AF69033EB077" - else -> throw IllegalStateException("Not supported Uniswap chain ${ethereumKit.chain}") + else -> throw IllegalStateException("Not supported Uniswap chain $chain") } private fun getPancakeSwapQuoterAddress(chain: Chain) = when (chain) { Chain.BinanceSmartChain, Chain.Ethereum -> "0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997" - else -> throw IllegalStateException("Not supported PancakeSwap chain ${ethereumKit.chain}") + + else -> throw IllegalStateException("Not supported PancakeSwap chain $chain") } suspend fun bestTradeExactIn( + rpcSource: RpcSource, + chain: Chain, tokenIn: Token, tokenOut: Token, amountIn: BigInteger ): BestTrade { - quoteExactInputSingle(tokenIn, tokenOut, amountIn)?.let { + quoteExactInputSingle(rpcSource, chain, tokenIn, tokenOut, amountIn)?.let { return it } - quoteExactInputMultihop(tokenIn, tokenOut, amountIn)?.let { + quoteExactInputMultihop(rpcSource, chain, tokenIn, tokenOut, amountIn)?.let { return it } throw TradeError.TradeNotFound() } private suspend fun quoteExactInputSingle( + rpcSource: RpcSource, + chain: Chain, tokenIn: Token, tokenOut: Token, amountIn: BigInteger @@ -70,7 +77,8 @@ class QuoterV2( coroutineContext.ensureActive() try { val callResponse = ethCall( - contractAddress = Address(quoterAddress), + rpcSource = rpcSource, + chain = chain, data = QuoteExactInputSingleMethod( tokenIn = tokenIn.address, tokenOut = tokenOut.address, @@ -98,18 +106,25 @@ class QuoterV2( } private suspend fun quoteExactInputMultihop( + rpcSource: RpcSource, + chain: Chain, tokenIn: Token, tokenOut: Token, amountIn: BigInteger ): BestTrade? { + val weth = tokenFactory.etherToken(chain) val swapInToWeth = quoteExactInputSingle( + rpcSource = rpcSource, + chain = chain, tokenIn = tokenIn, tokenOut = weth, amountIn = amountIn ) ?: return null val swapWethToOut = quoteExactInputSingle( + rpcSource = rpcSource, + chain = chain, tokenIn = weth, tokenOut = tokenOut, amountIn = swapInToWeth.amountOut @@ -120,7 +135,8 @@ class QuoterV2( coroutineContext.ensureActive() return try { val callResponse = ethCall( - contractAddress = Address(quoterAddress), + rpcSource = rpcSource, + chain = chain, data = QuoteExactInputMethod( path = path, amountIn = amountIn, @@ -142,20 +158,24 @@ class QuoterV2( } suspend fun bestTradeExactOut( + rpcSource: RpcSource, + chain: Chain, tokenIn: Token, tokenOut: Token, amountOut: BigInteger ): BestTrade { - quoteExactOutputSingle(tokenIn, tokenOut, amountOut)?.let { + quoteExactOutputSingle(rpcSource, chain, tokenIn, tokenOut, amountOut)?.let { return it } - quoteExactOutputMultihop(tokenIn, tokenOut, amountOut)?.let { + quoteExactOutputMultihop(rpcSource, chain, tokenIn, tokenOut, amountOut)?.let { return it } throw TradeError.TradeNotFound() } private suspend fun quoteExactOutputSingle( + rpcSource: RpcSource, + chain: Chain, tokenIn: Token, tokenOut: Token, amountOut: BigInteger @@ -166,7 +186,8 @@ class QuoterV2( coroutineContext.ensureActive() try { val callResponse = ethCall( - contractAddress = Address(quoterAddress), + rpcSource = rpcSource, + chain = chain, data = QuoteExactOutputSingleMethod( tokenIn = tokenIn.address, tokenOut = tokenOut.address, @@ -194,17 +215,25 @@ class QuoterV2( } private suspend fun quoteExactOutputMultihop( + rpcSource: RpcSource, + chain: Chain, tokenIn: Token, tokenOut: Token, amountOut: BigInteger ): BestTrade? { + val weth = tokenFactory.etherToken(chain) + val swapWethToOut = quoteExactOutputSingle( + rpcSource = rpcSource, + chain = chain, tokenIn = weth, tokenOut = tokenOut, amountOut = amountOut ) ?: return null val swapInToWeth = quoteExactOutputSingle( + rpcSource = rpcSource, + chain = chain, tokenIn = tokenIn, tokenOut = weth, amountOut = swapWethToOut.amountIn @@ -215,7 +244,8 @@ class QuoterV2( coroutineContext.ensureActive() return try { val callResponse = ethCall( - contractAddress = Address(quoterAddress), + rpcSource = rpcSource, + chain = chain, data = QuoteExactOutputMethod( path = path, amountOut = amountOut, @@ -236,7 +266,8 @@ class QuoterV2( } } - private suspend fun ethCall(contractAddress: Address, data: ByteArray): ByteArray { - return ethereumKit.call(contractAddress, data).await() + private suspend fun ethCall(rpcSource: RpcSource, chain: Chain, data: ByteArray): ByteArray { + val quoterAddress = Address(quoterAddress(chain)) + return EthereumKit.call(rpcSource, quoterAddress, data).await() } } diff --git a/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/v3/router/SwapRouter.kt b/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/v3/router/SwapRouter.kt index cb094195..385cecd7 100644 --- a/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/v3/router/SwapRouter.kt +++ b/uniswapkit/src/main/java/io/horizontalsystems/uniswapkit/v3/router/SwapRouter.kt @@ -1,6 +1,5 @@ package io.horizontalsystems.uniswapkit.v3.router -import io.horizontalsystems.ethereumkit.core.EthereumKit import io.horizontalsystems.ethereumkit.models.Address import io.horizontalsystems.ethereumkit.models.Chain import io.horizontalsystems.ethereumkit.models.TransactionData @@ -9,30 +8,37 @@ import io.horizontalsystems.uniswapkit.models.TradeType import io.horizontalsystems.uniswapkit.v3.TradeDataV3 import java.math.BigInteger -class SwapRouter(private val ethereumKit: EthereumKit, dexType: DexType) { - val swapRouterAddress = when (dexType) { - DexType.Uniswap -> getUniswapRouterAddress(ethereumKit.chain) - DexType.PancakeSwap -> getPancakeSwapRouterAddress(ethereumKit.chain) +class SwapRouter(private val dexType: DexType) { + + fun swapRouterAddress(chain: Chain): Address = when (dexType) { + DexType.Uniswap -> getUniswapRouterAddress(chain) + DexType.PancakeSwap -> getPancakeSwapRouterAddress(chain) } - private fun getUniswapRouterAddress(chain: Chain)= when (chain) { + private fun getUniswapRouterAddress(chain: Chain) = when (chain) { Chain.Ethereum, Chain.Polygon, Chain.Optimism, Chain.ArbitrumOne, Chain.EthereumGoerli -> Address("0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45") + Chain.BinanceSmartChain -> Address("0xB971eF87ede563556b2ED4b1C0b0019111Dd85d2") - else -> throw IllegalStateException("Not supported Uniswap chain ${ethereumKit.chain}") + else -> throw IllegalStateException("Not supported Uniswap chain ${chain}") } - private fun getPancakeSwapRouterAddress(chain: Chain)= when (chain) { + private fun getPancakeSwapRouterAddress(chain: Chain) = when (chain) { Chain.BinanceSmartChain, Chain.Ethereum -> Address("0x13f4EA83D0bd40E75C8222255bc855a974568Dd4") - else -> throw IllegalStateException("Not supported PancakeSwap chain ${ethereumKit.chain}") + + else -> throw IllegalStateException("Not supported PancakeSwap chain ${chain}") } - fun transactionData(tradeData: TradeDataV3): TransactionData { - val recipient = tradeData.options.recipient ?: ethereumKit.receiveAddress + fun transactionData( + receiveAddress: Address, + chain: Chain, + tradeData: TradeDataV3 + ): TransactionData { + val recipient = tradeData.options.recipient ?: receiveAddress val swapRecipient = when { tradeData.tokenOut.isEther -> Address("0x0000000000000000000000000000000000000002") @@ -53,6 +59,7 @@ class SwapRouter(private val ethereumKit: EthereumKit, dexType: DexType) { tradeData.tokenIn.isEther && tradeData.tradeType == TradeType.ExactOut -> { add(RefundETHMethod()) } + tradeData.tokenOut.isEther -> { add(UnwrapWETH9Method(tradeData.amountOutMinimum, recipient)) } @@ -62,7 +69,7 @@ class SwapRouter(private val ethereumKit: EthereumKit, dexType: DexType) { val method = methods.singleOrNull() ?: MulticallMethod(methods) return TransactionData( - to = swapRouterAddress, + to = swapRouterAddress(chain), value = ethValue, input = method.encodedABI() ) @@ -84,6 +91,7 @@ class SwapRouter(private val ethereumKit: EthereumKit, dexType: DexType) { sqrtPriceLimitX96 = BigInteger.ZERO ) } + TradeType.ExactOut -> { ExactOutputSingleMethod( tokenIn = tradeData.tokenIn.address, @@ -96,6 +104,7 @@ class SwapRouter(private val ethereumKit: EthereumKit, dexType: DexType) { ) } } + else -> when (tradeData.tradeType) { TradeType.ExactIn -> { ExactInputMethod( @@ -105,6 +114,7 @@ class SwapRouter(private val ethereumKit: EthereumKit, dexType: DexType) { amountOutMinimum = tradeData.amountOutMinimum, ) } + TradeType.ExactOut -> { ExactOutputMethod( path = tradeData.swapPath.abiEncodePacked(), From 7cb8c652d2b3edcc0689cd972679d209a8535501 Mon Sep 17 00:00:00 2001 From: chyngyz Date: Sat, 13 Jan 2024 20:08:41 +0600 Subject: [PATCH 2/3] Refactor OneInchKit to make usable without EthereumKit --- .../oneinchkit/OneInchKit.kt | 42 ++++++++++--------- .../oneinchkit/OneInchService.kt | 33 +++++++++------ 2 files changed, 44 insertions(+), 31 deletions(-) diff --git a/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/OneInchKit.kt b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/OneInchKit.kt index a2cdd384..0f43ad48 100644 --- a/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/OneInchKit.kt +++ b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/OneInchKit.kt @@ -13,26 +13,14 @@ import java.math.BigInteger import java.util.* class OneInchKit( - private val evmKit: EthereumKit, private val service: OneInchService ) { - val routerAddress: Address = when (evmKit.chain) { - Chain.Ethereum, - Chain.BinanceSmartChain, - Chain.Polygon, - Chain.Optimism, - Chain.ArbitrumOne, - Chain.Gnosis, - Chain.Fantom, - Chain.Avalanche -> Address("0x1111111254eeb25477b68fb85ed929f73a960582") - else -> throw IllegalArgumentException("Invalid Chain: ${evmKit.chain.id}") - } - - fun getApproveCallDataAsync(tokenAddress: Address, amount: BigInteger) = - service.getApproveCallDataAsync(tokenAddress, amount) + fun getApproveCallDataAsync(chain: Chain, tokenAddress: Address, amount: BigInteger) = + service.getApproveCallDataAsync(chain, tokenAddress, amount) fun getQuoteAsync( + chain: Chain, fromToken: Address, toToken: Address, amount: BigInteger, @@ -44,6 +32,7 @@ class OneInchKit( mainRouteParts: Int? = null, parts: Int? = null ) = service.getQuoteAsync( + chain, fromToken, toToken, amount, @@ -57,6 +46,8 @@ class OneInchKit( ) fun getSwapAsync( + chain: Chain, + receiveAddress: Address, fromToken: Address, toToken: Address, amount: BigInteger, @@ -72,10 +63,11 @@ class OneInchKit( parts: Int? = null, mainRouteParts: Int? = null ) = service.getSwapAsync( + chain, fromToken, toToken, amount, - evmKit.receiveAddress, + receiveAddress, slippagePercentage, protocols, recipient, @@ -90,15 +82,27 @@ class OneInchKit( ) companion object { - fun getInstance(evmKit: EthereumKit, apiKey: String): OneInchKit { - val service = OneInchService(evmKit.chain, apiKey) - return OneInchKit(evmKit, service) + fun getInstance(apiKey: String): OneInchKit { + return OneInchKit(OneInchService(apiKey)) } fun addDecorators(evmKit: EthereumKit) { evmKit.addMethodDecorator(OneInchMethodDecorator(OneInchContractMethodFactories)) evmKit.addTransactionDecorator(OneInchTransactionDecorator(evmKit.receiveAddress)) } + + fun routerAddress(chain: Chain): Address = when (chain) { + Chain.Ethereum, + Chain.BinanceSmartChain, + Chain.Polygon, + Chain.Optimism, + Chain.ArbitrumOne, + Chain.Gnosis, + Chain.Fantom, + Chain.Avalanche -> Address("0x1111111254eeb25477b68fb85ed929f73a960582") + + else -> throw IllegalArgumentException("Invalid Chain: ${chain.id}") + } } } diff --git a/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/OneInchService.kt b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/OneInchService.kt index 5520cde6..1d87f643 100644 --- a/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/OneInchService.kt +++ b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/OneInchService.kt @@ -13,16 +13,16 @@ import retrofit2.Retrofit import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.converter.gson.GsonConverterFactory import retrofit2.http.GET +import retrofit2.http.Path import retrofit2.http.Query import java.math.BigInteger import java.util.logging.Logger class OneInchService( - chain: Chain, apiKey: String ) { private val logger = Logger.getLogger("OneInchService") - private val url = "https://api.1inch.dev/swap/v5.2/${chain.id}/" + private val url = "https://api.1inch.dev/swap/v5.2/" private val service: OneInchServiceApi init { @@ -60,10 +60,11 @@ class OneInchService( service = retrofit.create(OneInchServiceApi::class.java) } - fun getApproveCallDataAsync(tokenAddress: Address, amount: BigInteger) = - service.getApproveCallData(tokenAddress.hex, amount) + fun getApproveCallDataAsync(chain: Chain, tokenAddress: Address, amount: BigInteger) = + service.getApproveCallData(tokenAddress = tokenAddress.hex, amount = amount, chainId = chain.id) fun getQuoteAsync( + chain: Chain, fromToken: Address, toToken: Address, amount: BigInteger, @@ -86,7 +87,8 @@ class OneInchService( connectorTokens = connectorTokens?.joinToString(","), gasLimit = gasLimit, parts = parts, - mainRouteParts = mainRouteParts + mainRouteParts = mainRouteParts, + chainId = chain.id ) } else { service.getQuote( @@ -99,11 +101,13 @@ class OneInchService( connectorTokens = connectorTokens?.joinToString(","), gasLimit = gasLimit, parts = parts, - mainRouteParts = mainRouteParts + mainRouteParts = mainRouteParts, + chainId = chain.id ) } fun getSwapAsync( + chain: Chain, fromTokenAddress: Address, toTokenAddress: Address, amount: BigInteger, @@ -136,7 +140,8 @@ class OneInchService( allowPartialFill = allowPartialFill, gasLimit = gasLimit, parts = parts, - mainRouteParts = mainRouteParts + mainRouteParts = mainRouteParts, + chainId = chain.id ) } else { service.getSwap( @@ -154,19 +159,21 @@ class OneInchService( allowPartialFill = allowPartialFill, gasLimit = gasLimit, parts = parts, - mainRouteParts = mainRouteParts + mainRouteParts = mainRouteParts, + chainId = chain.id ) } private interface OneInchServiceApi { - @GET("approve/calldata") + @GET("{chain_id}/approve/calldata") fun getApproveCallData( @Query("tokenAddress") tokenAddress: String, @Query("amount") amount: BigInteger? = null, - @Query("infinity") infinity: Boolean? = null + @Query("infinity") infinity: Boolean? = null, + @Path("chain_id") chainId: Int ): Single - @GET("quote") + @GET("{chain_id}/quote") fun getQuote( @Query("src") fromTokenAddress: String, @Query("dst") toTokenAddress: String, @@ -183,9 +190,10 @@ class OneInchService( @Query("includeTokensInfo") includeTokensInfo: Boolean = true, @Query("includeProtocols") includeProtocols: Boolean = true, @Query("includeGas") includeGas: Boolean = true, + @Path("chain_id") chainId: Int ): Single - @GET("swap") + @GET("{chain_id}/swap") fun getSwap( @Query("src") fromTokenAddress: String, @Query("dst") toTokenAddress: String, @@ -208,6 +216,7 @@ class OneInchService( @Query("includeTokensInfo") includeTokensInfo: Boolean = true, @Query("includeProtocols") includeProtocols: Boolean = true, @Query("includeGas") includeGas: Boolean = true, + @Path("chain_id") chainId: Int ): Single } From f187f69e659a55ff274bb1f6a70e26a1fdfc21d0 Mon Sep 17 00:00:00 2001 From: chyngyz Date: Sat, 13 Jan 2024 20:46:55 +0600 Subject: [PATCH 3/3] Add static estimateGas methods --- .../ethereumkit/api/core/RpcBlockchain.kt | 146 +++++++++++++----- .../ethereumkit/core/EthereumKit.kt | 28 +++- 2 files changed, 131 insertions(+), 43 deletions(-) diff --git a/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/api/core/RpcBlockchain.kt b/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/api/core/RpcBlockchain.kt index 38a04dec..bfed2009 100644 --- a/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/api/core/RpcBlockchain.kt +++ b/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/api/core/RpcBlockchain.kt @@ -1,23 +1,46 @@ package io.horizontalsystems.ethereumkit.api.core -import io.horizontalsystems.ethereumkit.api.jsonrpc.* +import io.horizontalsystems.ethereumkit.api.jsonrpc.BlockNumberJsonRpc +import io.horizontalsystems.ethereumkit.api.jsonrpc.CallJsonRpc +import io.horizontalsystems.ethereumkit.api.jsonrpc.DataJsonRpc +import io.horizontalsystems.ethereumkit.api.jsonrpc.EstimateGasJsonRpc +import io.horizontalsystems.ethereumkit.api.jsonrpc.GetBalanceJsonRpc +import io.horizontalsystems.ethereumkit.api.jsonrpc.GetBlockByNumberJsonRpc +import io.horizontalsystems.ethereumkit.api.jsonrpc.GetLogsJsonRpc +import io.horizontalsystems.ethereumkit.api.jsonrpc.GetStorageAtJsonRpc +import io.horizontalsystems.ethereumkit.api.jsonrpc.GetTransactionByHashJsonRpc +import io.horizontalsystems.ethereumkit.api.jsonrpc.GetTransactionCountJsonRpc +import io.horizontalsystems.ethereumkit.api.jsonrpc.GetTransactionReceiptJsonRpc +import io.horizontalsystems.ethereumkit.api.jsonrpc.JsonRpc +import io.horizontalsystems.ethereumkit.api.jsonrpc.SendRawTransactionJsonRpc import io.horizontalsystems.ethereumkit.api.jsonrpc.models.RpcBlock import io.horizontalsystems.ethereumkit.api.jsonrpc.models.RpcTransaction import io.horizontalsystems.ethereumkit.api.jsonrpc.models.RpcTransactionReceipt import io.horizontalsystems.ethereumkit.api.models.AccountState -import io.horizontalsystems.ethereumkit.core.* +import io.horizontalsystems.ethereumkit.core.EthereumKit import io.horizontalsystems.ethereumkit.core.EthereumKit.SyncState -import io.horizontalsystems.ethereumkit.models.* +import io.horizontalsystems.ethereumkit.core.IApiStorage +import io.horizontalsystems.ethereumkit.core.IBlockchain +import io.horizontalsystems.ethereumkit.core.IBlockchainListener +import io.horizontalsystems.ethereumkit.core.TransactionBuilder +import io.horizontalsystems.ethereumkit.models.Address +import io.horizontalsystems.ethereumkit.models.DefaultBlockParameter +import io.horizontalsystems.ethereumkit.models.GasPrice +import io.horizontalsystems.ethereumkit.models.RawTransaction +import io.horizontalsystems.ethereumkit.models.RpcSource +import io.horizontalsystems.ethereumkit.models.Signature +import io.horizontalsystems.ethereumkit.models.Transaction +import io.horizontalsystems.ethereumkit.models.TransactionLog import io.reactivex.Single import io.reactivex.disposables.CompositeDisposable import io.reactivex.schedulers.Schedulers import java.math.BigInteger class RpcBlockchain( - private val address: Address, - private val storage: IApiStorage, - private val syncer: IRpcSyncer, - private val transactionBuilder: TransactionBuilder + private val address: Address, + private val storage: IApiStorage, + private val syncer: IRpcSyncer, + private val transactionBuilder: TransactionBuilder ) : IBlockchain, IRpcSyncerListener { private val disposables = CompositeDisposable() @@ -34,32 +57,32 @@ class RpcBlockchain( private fun syncLastBlockHeight() { syncer.single(BlockNumberJsonRpc()) - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.io()) - .subscribe({ lastBlockNumber -> - onUpdateLastBlockHeight(lastBlockNumber) - }, { - syncState = SyncState.NotSynced(it) - }).let { - disposables.add(it) - } + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .subscribe({ lastBlockNumber -> + onUpdateLastBlockHeight(lastBlockNumber) + }, { + syncState = SyncState.NotSynced(it) + }).let { + disposables.add(it) + } } override fun syncAccountState() { Single.zip( - syncer.single(GetBalanceJsonRpc(address, DefaultBlockParameter.Latest)), - syncer.single(GetTransactionCountJsonRpc(address, DefaultBlockParameter.Latest)) + syncer.single(GetBalanceJsonRpc(address, DefaultBlockParameter.Latest)), + syncer.single(GetTransactionCountJsonRpc(address, DefaultBlockParameter.Latest)) ) { t1, t2 -> Pair(t1, t2) } - .subscribeOn(Schedulers.io()) - .subscribe({ (balance, nonce) -> - onUpdateAccountState(AccountState(balance, nonce)) - syncState = SyncState.Synced() - }, { - it?.printStackTrace() - syncState = SyncState.NotSynced(it) - }).let { - disposables.add(it) - } + .subscribeOn(Schedulers.io()) + .subscribe({ (balance, nonce) -> + onUpdateAccountState(AccountState(balance, nonce)) + syncState = SyncState.Synced() + }, { + it?.printStackTrace() + syncState = SyncState.NotSynced(it) + }).let { + disposables.add(it) + } } @@ -92,10 +115,12 @@ class RpcBlockchain( when (syncer.state) { SyncerState.Preparing -> { } + SyncerState.Ready -> { syncAccountState() syncLastBlockHeight() } + is SyncerState.NotReady -> { syncer.start() } @@ -111,7 +136,7 @@ class RpcBlockchain( val encoded = transactionBuilder.encode(rawTransaction, signature) return syncer.single(SendRawTransactionJsonRpc(encoded)) - .map { transaction } + .map { transaction } } override fun getNonce(defaultBlockParameter: DefaultBlockParameter): Single { @@ -134,15 +159,28 @@ class RpcBlockchain( return syncer.single(GetBlockByNumberJsonRpc(blockNumber)) } - override fun getLogs(address: Address?, topics: List, fromBlock: Long, toBlock: Long, pullTimestamps: Boolean): Single> { - return syncer.single(GetLogsJsonRpc(address, DefaultBlockParameter.BlockNumber(fromBlock), DefaultBlockParameter.BlockNumber(toBlock), topics)) - .flatMap { logs -> - if (pullTimestamps) { - pullTransactionTimestamps(logs) - } else { - Single.just(logs) - } + override fun getLogs( + address: Address?, + topics: List, + fromBlock: Long, + toBlock: Long, + pullTimestamps: Boolean + ): Single> { + return syncer.single( + GetLogsJsonRpc( + address, + DefaultBlockParameter.BlockNumber(fromBlock), + DefaultBlockParameter.BlockNumber(toBlock), + topics + ) + ) + .flatMap { logs -> + if (pullTimestamps) { + pullTransactionTimestamps(logs) + } else { + Single.just(logs) } + } } private fun pullTransactionTimestamps(logs: List): Single> { @@ -150,7 +188,7 @@ class RpcBlockchain( for (log in logs) { val logs: MutableList = logsByBlockNumber[log.blockNumber] - ?: mutableListOf() + ?: mutableListOf() logs.add(log) logsByBlockNumber[log.blockNumber] = logs } @@ -200,11 +238,13 @@ class RpcBlockchain( SyncerState.Preparing -> { syncState = SyncState.Syncing() } + SyncerState.Ready -> { syncState = SyncState.Syncing() syncAccountState() syncLastBlockHeight() } + is SyncerState.NotReady -> { syncState = SyncState.NotSynced(state.error) disposables.clear() @@ -215,10 +255,12 @@ class RpcBlockchain( //endregion companion object { - fun instance(address: Address, - storage: IApiStorage, - syncer: IRpcSyncer, - transactionBuilder: TransactionBuilder): RpcBlockchain { + fun instance( + address: Address, + storage: IApiStorage, + syncer: IRpcSyncer, + transactionBuilder: TransactionBuilder + ): RpcBlockchain { val rpcBlockchain = RpcBlockchain(address, storage, syncer, transactionBuilder) syncer.listener = rpcBlockchain @@ -228,5 +270,25 @@ class RpcBlockchain( fun callRpc(contractAddress: Address, data: ByteArray, defaultBlockParameter: DefaultBlockParameter): DataJsonRpc = CallJsonRpc(contractAddress, data, defaultBlockParameter) + + fun estimateGas( + rpcSource: RpcSource, + from: Address, + to: Address?, + amount: BigInteger?, + gasLimit: Long?, + gasPrice: GasPrice, + data: ByteArray? + ): Single { + val rpcApiProvider: IRpcApiProvider = when (rpcSource) { + is RpcSource.Http -> { + NodeApiProvider(rpcSource.uris, EthereumKit.gson, rpcSource.auth) + } + + is RpcSource.WebSocket -> throw IllegalStateException("Websocket not supported") + } + + return rpcApiProvider.single(EstimateGasJsonRpc(from, to, amount, gasLimit, gasPrice, data)) + } } } diff --git a/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/core/EthereumKit.kt b/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/core/EthereumKit.kt index 3cb8da7a..0f6cc72a 100644 --- a/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/core/EthereumKit.kt +++ b/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/core/EthereumKit.kt @@ -252,7 +252,11 @@ class EthereumKit( return blockchain.getStorageAt(contractAddress, position, defaultBlockParameter) } - fun call(contractAddress: Address, data: ByteArray, defaultBlockParameter: DefaultBlockParameter = DefaultBlockParameter.Latest): Single { + fun call( + contractAddress: Address, + data: ByteArray, + defaultBlockParameter: DefaultBlockParameter = DefaultBlockParameter.Latest + ): Single { return blockchain.call(contractAddress, data, defaultBlockParameter) } @@ -394,6 +398,28 @@ class EthereumKit( return rpcApiProvider.single(rpc) } + fun estimateGas( + rpcSource: RpcSource, + chain: Chain, + from: Address, + to: Address?, + value: BigInteger?, + gasPrice: GasPrice, + data: ByteArray? + ): Single { + return RpcBlockchain.estimateGas(rpcSource, from, to, value, chain.gasLimit, gasPrice, data) + } + + fun estimateGas( + rpcSource: RpcSource, + chain: Chain, + from: Address, + transactionData: TransactionData, + gasPrice: GasPrice + ): Single { + return estimateGas(rpcSource, chain, from, transactionData.to, transactionData.value, gasPrice, transactionData.input) + } + fun init() { Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME) Security.addProvider(InternalBouncyCastleProvider.getInstance())