From e424ef9c8a78b2514075c3e874ad16d6def1da21 Mon Sep 17 00:00:00 2001 From: chyngyz Date: Thu, 15 Jul 2021 14:31:18 +0600 Subject: [PATCH] Add 1inch transaction decorations --- .../ethereumkit/sample/MainViewModel.kt | 8 +- .../contracts/ContractMethodHelper.kt | 30 +++- .../ethereumkit/contracts/SolidityTypes.kt | 5 + .../decorations/ContractCallDecorator.kt | 4 +- oneinchkit/build.gradle | 10 +- .../OneInchInternalTransactionSyncer.kt | 60 ++++++++ .../oneinchkit/OneInchKit.kt | 13 +- .../OneInchContractMethodFactories.kt | 12 ++ .../oneinchkit/contracts/SwapMethod.kt | 32 +++++ .../oneinchkit/contracts/SwapMethodFactory.kt | 40 ++++++ .../oneinchkit/contracts/UnoswapMethod.kt | 23 +++ .../contracts/UnoswapMethodFactory.kt | 27 ++++ .../decorations/OneInchMethodDecoration.kt | 13 ++ .../OneInchSwapMethodDecoration.kt | 45 ++++++ .../OneInchTransactionDecorator.kt | 135 ++++++++++++++++++ .../OneInchUnoswapMethodDecoration.kt | 43 ++++++ 16 files changed, 483 insertions(+), 17 deletions(-) create mode 100644 ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/contracts/SolidityTypes.kt create mode 100644 oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/OneInchInternalTransactionSyncer.kt create mode 100644 oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/contracts/OneInchContractMethodFactories.kt create mode 100644 oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/contracts/SwapMethod.kt create mode 100644 oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/contracts/SwapMethodFactory.kt create mode 100644 oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/contracts/UnoswapMethod.kt create mode 100644 oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/contracts/UnoswapMethodFactory.kt create mode 100644 oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/decorations/OneInchMethodDecoration.kt create mode 100644 oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/decorations/OneInchSwapMethodDecoration.kt create mode 100644 oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/decorations/OneInchTransactionDecorator.kt create mode 100644 oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/decorations/OneInchUnoswapMethodDecoration.kt diff --git a/app/src/main/java/io/horizontalsystems/ethereumkit/sample/MainViewModel.kt b/app/src/main/java/io/horizontalsystems/ethereumkit/sample/MainViewModel.kt index 3128daee..2941d021 100644 --- a/app/src/main/java/io/horizontalsystems/ethereumkit/sample/MainViewModel.kt +++ b/app/src/main/java/io/horizontalsystems/ethereumkit/sample/MainViewModel.kt @@ -12,6 +12,7 @@ import io.horizontalsystems.ethereumkit.sample.core.Erc20Adapter import io.horizontalsystems.ethereumkit.sample.core.EthereumAdapter import io.horizontalsystems.ethereumkit.sample.core.TransactionRecord import io.horizontalsystems.hdwalletkit.Mnemonic +import io.horizontalsystems.oneinchkit.OneInchKit import io.horizontalsystems.uniswapkit.UniswapKit import io.horizontalsystems.uniswapkit.models.SwapData import io.horizontalsystems.uniswapkit.models.Token @@ -75,6 +76,7 @@ class MainViewModel : ViewModel() { ethereumKit.addDecorator(Erc20Kit.decorator(ethereumKit)) ethereumKit.addDecorator(UniswapKit.decorator(ethereumKit)) + ethereumKit.addDecorator(OneInchKit.decorator(ethereumKit)) updateBalance() updateErc20Balance() @@ -236,7 +238,7 @@ class MainViewModel : ViewModel() { } private fun updateTransactionList() { - val list = when(showTxType){ + val list = when (showTxType) { ShowTxType.Eth -> ethTxs ShowTxType.Erc20 -> erc20Txs } @@ -331,7 +333,7 @@ class MainViewModel : ViewModel() { } fun filterTransactions(ethTx: Boolean) { - if (ethTx){ + if (ethTx) { updateEthTransactions() showTxType = ShowTxType.Eth } else { @@ -454,7 +456,7 @@ class MainViewModel : ViewModel() { } -enum class ShowTxType{ +enum class ShowTxType { Eth, Erc20 } diff --git a/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/contracts/ContractMethodHelper.kt b/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/contracts/ContractMethodHelper.kt index cb63a196..4143ee10 100644 --- a/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/contracts/ContractMethodHelper.kt +++ b/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/contracts/ContractMethodHelper.kt @@ -6,7 +6,6 @@ import io.horizontalsystems.ethereumkit.spv.core.toBigInteger import io.horizontalsystems.ethereumkit.spv.core.toInt import java.math.BigInteger import kotlin.math.max -import kotlin.reflect.KClass object ContractMethodHelper { fun getMethodId(methodSignature: String): ByteArray { @@ -39,7 +38,9 @@ object ContractMethodHelper { return data + arraysData } - fun decodeABI(inputArguments: ByteArray, argumentTypes: List>): List { + data class StructParameter(val argumentTypes: List) + + fun decodeABI(inputArguments: ByteArray, argumentTypes: List): List { var position = 0 val parsedArguments = mutableListOf() argumentTypes.forEach { type -> @@ -60,6 +61,17 @@ object ContractMethodHelper { val byteArray: ByteArray = parseByteArray(arrayPosition, inputArguments) parsedArguments.add(byteArray) } + Bytes32Array::class -> { + val arrayPosition = inputArguments.copyOfRange(position, position + 32).toInt() + val bytes32Array = parseBytes32Array(arrayPosition, inputArguments) + parsedArguments.add(bytes32Array) + } + is StructParameter -> { + val argumentsPosition = inputArguments.copyOfRange(position, position + 32).toInt() + val structParameterData = inputArguments.copyOfRange(argumentsPosition, inputArguments.size) + val structParameter = decodeABI(structParameterData, type.argumentTypes) + parsedArguments.add(structParameter) + } } position += 32 } @@ -67,11 +79,23 @@ object ContractMethodHelper { return parsedArguments } + private fun parseBytes32Array(startPosition: Int, inputArguments: ByteArray): Bytes32Array { + val dataStartPosition = startPosition + 32 + val size = inputArguments.copyOfRange(startPosition, dataStartPosition).toInt() + val array: Array = Array(size) { byteArrayOf() } + + for (i in 0 until size) { + array[i] = inputArguments.copyOfRange(dataStartPosition + 32 * i, dataStartPosition + 32 * (i + 1)) + } + + return Bytes32Array(array) + } + private fun parseAddress(address: ByteArray): Address { return Address(address.copyOfRange(address.size - 20, address.size)) } - private fun parseByteArray(startPosition: Int, inputArguments: ByteArray) : ByteArray { + private fun parseByteArray(startPosition: Int, inputArguments: ByteArray): ByteArray { val dataStartPosition = startPosition + 32 val size = inputArguments.copyOfRange(startPosition, dataStartPosition).toInt() return inputArguments.copyOfRange(dataStartPosition, dataStartPosition + size) diff --git a/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/contracts/SolidityTypes.kt b/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/contracts/SolidityTypes.kt new file mode 100644 index 00000000..f2716644 --- /dev/null +++ b/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/contracts/SolidityTypes.kt @@ -0,0 +1,5 @@ +package io.horizontalsystems.ethereumkit.contracts + + +// Class for solidity type bytes32[] +class Bytes32Array(val array: Array) diff --git a/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/decorations/ContractCallDecorator.kt b/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/decorations/ContractCallDecorator.kt index 1c6b4d03..dc9a3826 100644 --- a/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/decorations/ContractCallDecorator.kt +++ b/ethereumkit/src/main/java/io/horizontalsystems/ethereumkit/decorations/ContractCallDecorator.kt @@ -14,8 +14,8 @@ class ContractCallDecorator(val address: Address) : IDecorator { private var methods = mutableMapOf() init { - addMethod("deposit", "deposit(uint256)", listOf(BigInteger::class)) - addMethod("tradeWithHintAndFee", "tradeWithHintAndFee(address,uint256,address,address,uint256,uint256,address,uint256,bytes)", + addMethod("Deposit", "deposit(uint256)", listOf(BigInteger::class)) + addMethod("TradeWithHintAndFee", "tradeWithHintAndFee(address,uint256,address,address,uint256,uint256,address,uint256,bytes)", listOf(Address::class, BigInteger::class, Address::class, Address::class, BigInteger::class, BigInteger::class, Address::class, BigInteger::class, ByteArray::class)) } diff --git a/oneinchkit/build.gradle b/oneinchkit/build.gradle index 7a9d33d7..98fcc37b 100644 --- a/oneinchkit/build.gradle +++ b/oneinchkit/build.gradle @@ -26,12 +26,7 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation 'androidx.core:core-ktx:1.3.2' - implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'com.google.android.material:material:1.3.0' - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1") implementation 'io.reactivex.rxjava2:rxjava:2.2.19' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' @@ -43,5 +38,6 @@ dependencies { implementation 'com.squareup.retrofit2:converter-scalars:2.9.0' implementation 'com.google.code.gson:gson:2.8.6' - api project(':ethereumkit') + implementation project(':ethereumkit') + implementation project(':erc20kit') } diff --git a/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/OneInchInternalTransactionSyncer.kt b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/OneInchInternalTransactionSyncer.kt new file mode 100644 index 00000000..bf183f5c --- /dev/null +++ b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/OneInchInternalTransactionSyncer.kt @@ -0,0 +1,60 @@ +package io.horizontalsystems.oneinchkit + +import io.horizontalsystems.ethereumkit.core.EthereumKit +import io.horizontalsystems.ethereumkit.core.toHexString +import io.horizontalsystems.ethereumkit.models.FullTransaction +import io.horizontalsystems.oneinchkit.decorations.OneInchMethodDecoration +import io.horizontalsystems.oneinchkit.decorations.OneInchSwapMethodDecoration +import io.reactivex.disposables.CompositeDisposable +import kotlinx.coroutines.* +import kotlin.coroutines.CoroutineContext + +class OneInchInternalTransactionSyncer( + private val evmKit: EthereumKit +) : CoroutineScope { + + private val disposables = CompositeDisposable() + private var syncingTransactions = mutableMapOf() + private val maxRetryCount = 3 + private val delayTime = 10 * 1000L // seconds + + private val job = Job() + + init { + evmKit.allTransactionsFlowable + .subscribe { transactions -> + launch { + transactions.forEach { fullTransaction -> + handle(fullTransaction) + } + } + } + .let { + disposables.add(it) + } + } + + override val coroutineContext: CoroutineContext = job + Dispatchers.IO + + private suspend fun handle(fullTransaction: FullTransaction) { + if (fullTransaction.internalTransactions.isEmpty()) { + + val decoration = fullTransaction.mainDecoration + if (decoration is OneInchSwapMethodDecoration + && decoration.toToken is OneInchMethodDecoration.Token.EvmCoin + && fullTransaction.transaction.from == evmKit.receiveAddress + && decoration.recipient != evmKit.receiveAddress) { + + val transaction = fullTransaction.transaction + val count = syncingTransactions[transaction.hash.toHexString()] ?: 0 + + if (count < maxRetryCount) { + delay(delayTime) + syncingTransactions[transaction.hash.toHexString()] = count + 1 + evmKit.syncInternalTransactions(transaction) + } + } + } + } + +} diff --git a/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/OneInchKit.kt b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/OneInchKit.kt index 3593d185..e9e69b79 100644 --- a/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/OneInchKit.kt +++ b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/OneInchKit.kt @@ -3,15 +3,19 @@ package io.horizontalsystems.oneinchkit import com.google.gson.annotations.SerializedName import io.horizontalsystems.ethereumkit.core.EthereumKit import io.horizontalsystems.ethereumkit.core.EthereumKit.NetworkType +import io.horizontalsystems.ethereumkit.core.IDecorator import io.horizontalsystems.ethereumkit.core.toHexString import io.horizontalsystems.ethereumkit.models.Address +import io.horizontalsystems.oneinchkit.contracts.OneInchContractMethodFactories +import io.horizontalsystems.oneinchkit.decorations.OneInchTransactionDecorator import io.reactivex.Single import java.math.BigInteger import java.util.* class OneInchKit( private val evmKit: EthereumKit, - private val service: OneInchService + private val service: OneInchService, + private val internalTransactionSyncer: OneInchInternalTransactionSyncer ) { val smartContractAddress: Address = when (evmKit.networkType) { @@ -62,7 +66,12 @@ class OneInchKit( fun getInstance(evmKit: EthereumKit): OneInchKit { val service = OneInchService(evmKit.networkType) - return OneInchKit(evmKit, service) + val internalTransactionSyncer = OneInchInternalTransactionSyncer(evmKit) + return OneInchKit(evmKit, service, internalTransactionSyncer) + } + + fun decorator(evmKit: EthereumKit): IDecorator { + return OneInchTransactionDecorator(evmKit.receiveAddress, OneInchContractMethodFactories) } } diff --git a/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/contracts/OneInchContractMethodFactories.kt b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/contracts/OneInchContractMethodFactories.kt new file mode 100644 index 00000000..1e4eb769 --- /dev/null +++ b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/contracts/OneInchContractMethodFactories.kt @@ -0,0 +1,12 @@ +package io.horizontalsystems.oneinchkit.contracts + +import io.horizontalsystems.ethereumkit.contracts.ContractMethodFactories + +object OneInchContractMethodFactories : ContractMethodFactories() { + + init { + val swapContractMethodFactories = listOf(UnoswapMethodFactory()) + registerMethodFactories(swapContractMethodFactories) + } + +} diff --git a/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/contracts/SwapMethod.kt b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/contracts/SwapMethod.kt new file mode 100644 index 00000000..4768899d --- /dev/null +++ b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/contracts/SwapMethod.kt @@ -0,0 +1,32 @@ +package io.horizontalsystems.oneinchkit.contracts + +import io.horizontalsystems.ethereumkit.contracts.ContractMethod +import io.horizontalsystems.ethereumkit.models.Address +import java.math.BigInteger + +class SwapMethod( + val caller: Address, + val swapDescription: SwapDescription, + val data: ByteArray +) : ContractMethod() { + + override val methodSignature = Companion.methodSignature + + override fun getArguments() = listOf(caller, swapDescription, data) + + data class SwapDescription( + val srcToken: Address, + val dstToken: Address, + val srcReceiver: Address, + val dstReceiver: Address, + val amount: BigInteger, + val minReturnAmount: BigInteger, + val flags: BigInteger, + val permit: ByteArray + ) + + companion object { + val methodSignature = "swap(address,(address,address,address,address,uint256,uint256,uint256,bytes),bytes)" + } + +} diff --git a/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/contracts/SwapMethodFactory.kt b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/contracts/SwapMethodFactory.kt new file mode 100644 index 00000000..816ba7c9 --- /dev/null +++ b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/contracts/SwapMethodFactory.kt @@ -0,0 +1,40 @@ +package io.horizontalsystems.oneinchkit.contracts + +import io.horizontalsystems.ethereumkit.contracts.ContractMethod +import io.horizontalsystems.ethereumkit.contracts.ContractMethodFactory +import io.horizontalsystems.ethereumkit.contracts.ContractMethodHelper +import io.horizontalsystems.ethereumkit.models.Address +import java.math.BigInteger + +class SwapMethodFactory : ContractMethodFactory { + + override val methodId = ContractMethodHelper.getMethodId(SwapMethod.methodSignature) + + override fun createMethod(inputArguments: ByteArray): ContractMethod { + val argumentTypes = listOf( + Address::class, + ContractMethodHelper.StructParameter(listOf(Address::class, Address::class, Address::class, Address::class, BigInteger::class, BigInteger::class, BigInteger::class, ByteArray::class)), + ByteArray::class + ) + val parsedArguments = ContractMethodHelper.decodeABI(inputArguments, argumentTypes) + + val caller = parsedArguments[0] as Address + val swapDescriptionArguments = parsedArguments[1] as List<*> + + val srcToken = swapDescriptionArguments[0] as Address + val dstToken = swapDescriptionArguments[1] as Address + val srcReceiver = swapDescriptionArguments[2] as Address + val dstReceiver = swapDescriptionArguments[3] as Address + val amount = swapDescriptionArguments[4] as BigInteger + val minReturnAmount = swapDescriptionArguments[5] as BigInteger + val flags = swapDescriptionArguments[6] as BigInteger + val permit = swapDescriptionArguments[7] as ByteArray + + val swapDescription = SwapMethod.SwapDescription(srcToken, dstToken, srcReceiver, dstReceiver, amount, minReturnAmount, flags, permit) + + val data = parsedArguments[2] as ByteArray + + return SwapMethod(caller, swapDescription, data) + } + +} diff --git a/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/contracts/UnoswapMethod.kt b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/contracts/UnoswapMethod.kt new file mode 100644 index 00000000..f387736a --- /dev/null +++ b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/contracts/UnoswapMethod.kt @@ -0,0 +1,23 @@ +package io.horizontalsystems.oneinchkit.contracts + +import io.horizontalsystems.ethereumkit.contracts.Bytes32Array +import io.horizontalsystems.ethereumkit.contracts.ContractMethod +import io.horizontalsystems.ethereumkit.models.Address +import java.math.BigInteger + +class UnoswapMethod( + val srcToken: Address, + val amount: BigInteger, + val minReturn: BigInteger, + val params: Bytes32Array +) : ContractMethod() { + + override val methodSignature = Companion.methodSignature + + override fun getArguments() = listOf(srcToken, amount, minReturn, params) + + companion object { + val methodSignature = "unoswap(address,uint256,uint256,bytes32[])" + } + +} diff --git a/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/contracts/UnoswapMethodFactory.kt b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/contracts/UnoswapMethodFactory.kt new file mode 100644 index 00000000..69be10b7 --- /dev/null +++ b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/contracts/UnoswapMethodFactory.kt @@ -0,0 +1,27 @@ +package io.horizontalsystems.oneinchkit.contracts + +import io.horizontalsystems.ethereumkit.contracts.Bytes32Array +import io.horizontalsystems.ethereumkit.contracts.ContractMethod +import io.horizontalsystems.ethereumkit.contracts.ContractMethodFactory +import io.horizontalsystems.ethereumkit.contracts.ContractMethodHelper +import io.horizontalsystems.ethereumkit.models.Address +import java.math.BigInteger + +class UnoswapMethodFactory : ContractMethodFactory { + + override val methodId = ContractMethodHelper.getMethodId(UnoswapMethod.methodSignature) + + override fun createMethod(inputArguments: ByteArray): ContractMethod { + val argumentTypes = listOf(Address::class, BigInteger::class, BigInteger::class, Bytes32Array::class) + + val parsedArguments = ContractMethodHelper.decodeABI(inputArguments, argumentTypes) + + val srcToken = parsedArguments[0] as Address + val amount = parsedArguments[1] as BigInteger + val minReturn = parsedArguments[2] as BigInteger + val params = parsedArguments[3] as Bytes32Array + + return UnoswapMethod(srcToken, amount, minReturn, params) + } + +} diff --git a/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/decorations/OneInchMethodDecoration.kt b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/decorations/OneInchMethodDecoration.kt new file mode 100644 index 00000000..da726672 --- /dev/null +++ b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/decorations/OneInchMethodDecoration.kt @@ -0,0 +1,13 @@ +package io.horizontalsystems.oneinchkit.decorations + +import io.horizontalsystems.ethereumkit.decorations.ContractMethodDecoration +import io.horizontalsystems.ethereumkit.models.Address + +abstract class OneInchMethodDecoration: ContractMethodDecoration() { + + sealed class Token { + object EvmCoin : Token() + class Eip20(val address: Address) : Token() + } + +} diff --git a/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/decorations/OneInchSwapMethodDecoration.kt b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/decorations/OneInchSwapMethodDecoration.kt new file mode 100644 index 00000000..f7634ca1 --- /dev/null +++ b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/decorations/OneInchSwapMethodDecoration.kt @@ -0,0 +1,45 @@ +package io.horizontalsystems.oneinchkit.decorations + +import io.horizontalsystems.ethereumkit.models.Address +import java.math.BigInteger + +class OneInchSwapMethodDecoration( + val fromToken: Token, + val toToken: Token, + val fromAmount: BigInteger, + val toAmount: BigInteger, + val flags: BigInteger, + val permit: ByteArray, + val data: ByteArray, + val recipient: Address +) : OneInchMethodDecoration() { + + override fun tags(fromAddress: Address, toAddress: Address, userAddress: Address): List { + val tags = mutableListOf(toAddress.hex, "swap") + + when (fromToken) { + Token.EvmCoin -> { + tags.addAll(listOf("ETH_outgoing", "ETH", "outgoing")) + } + is Token.Eip20 -> { + val addressHex = fromToken.address.hex + tags.addAll(listOf("${addressHex}_outgoing", addressHex, "outgoing")) + } + } + + if (recipient == userAddress) { + when (toToken) { + Token.EvmCoin -> { + tags.addAll(listOf("ETH_incoming", "ETH", "incoming")) + } + is Token.Eip20 -> { + val addressHex = toToken.address.hex + tags.addAll(listOf("${addressHex}_incoming", addressHex, "incoming")) + } + } + } + + return tags + } + +} diff --git a/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/decorations/OneInchTransactionDecorator.kt b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/decorations/OneInchTransactionDecorator.kt new file mode 100644 index 00000000..2af75598 --- /dev/null +++ b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/decorations/OneInchTransactionDecorator.kt @@ -0,0 +1,135 @@ +package io.horizontalsystems.oneinchkit.decorations + +import io.horizontalsystems.erc20kit.core.getErc20Event +import io.horizontalsystems.erc20kit.decorations.TransferEventDecoration +import io.horizontalsystems.ethereumkit.core.IDecorator +import io.horizontalsystems.ethereumkit.decorations.ContractEventDecoration +import io.horizontalsystems.ethereumkit.decorations.ContractMethodDecoration +import io.horizontalsystems.ethereumkit.models.* +import io.horizontalsystems.oneinchkit.contracts.OneInchContractMethodFactories +import io.horizontalsystems.oneinchkit.contracts.SwapMethod +import io.horizontalsystems.oneinchkit.contracts.UnoswapMethod +import java.math.BigInteger + +class OneInchTransactionDecorator( + private val address: Address, + private val contractMethodFactories: OneInchContractMethodFactories +) : IDecorator { + + private val evmTokenAddresses = mutableListOf("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "0x0000000000000000000000000000000000000000") + + override fun decorate(logs: List): List { + return listOf() + } + + override fun decorate(transactionData: TransactionData, fullTransaction: FullTransaction?): ContractMethodDecoration? { + val contractMethod = contractMethodFactories.createMethodFromInput(transactionData.input) ?: return null + + if (fullTransaction != null && fullTransaction.transaction.from != address) { + // We only parse transactions created by the user (owner of this wallet). + // If a swap was initiated by someone else and "recipient" is set to user's it should be shown as just an incoming transaction + return null + } + + return when (contractMethod) { + is UnoswapMethod -> { + var toToken: OneInchMethodDecoration.Token? = null + var toAmount: BigInteger? = null + fullTransaction?.let { + totalETHIncoming(address, fullTransaction.internalTransactions)?.let { amount -> + toAmount = amount + toToken = OneInchMethodDecoration.Token.EvmCoin + } + } + + val logs = fullTransaction?.receiptWithLogs?.logs + if (toToken == null && logs != null) { + val incomingEip20Log = logs.firstOrNull { log -> + (log.getErc20Event() as? TransferEventDecoration)?.to == address + } + + (incomingEip20Log?.getErc20Event() as? TransferEventDecoration)?.let { transferEventDecoration -> + totalTokenIncoming(address, transferEventDecoration.contractAddress, logs)?.let { amount -> + toAmount = amount + toToken = OneInchMethodDecoration.Token.Eip20(transferEventDecoration.contractAddress) + } + } + } + + OneInchUnoswapMethodDecoration( + fromToken = addressToToken(contractMethod.srcToken), + toToken = toToken, + fromAmount = contractMethod.amount, + toAmount = toAmount ?: contractMethod.minReturn, + params = contractMethod.params + ) + } + is SwapMethod -> { + var toAmount: BigInteger? = null + val swapDescription = contractMethod.swapDescription + + fullTransaction?.let { + totalETHIncoming(swapDescription.dstReceiver, fullTransaction.internalTransactions)?.let { amount -> + toAmount = amount + } + } + + if (toAmount == null) { + fullTransaction?.receiptWithLogs?.logs?.let { logs -> + totalTokenIncoming(swapDescription.dstReceiver, swapDescription.dstToken, logs)?.let { amount -> + toAmount = amount + } + } + } + OneInchSwapMethodDecoration( + fromToken = addressToToken(swapDescription.srcToken), + toToken = addressToToken(swapDescription.dstToken), + fromAmount = swapDescription.amount, + toAmount = toAmount ?: swapDescription.minReturnAmount, + flags = swapDescription.flags, + permit = swapDescription.permit, + data = contractMethod.data, + recipient = swapDescription.dstReceiver + ) + } + else -> null + } + + } + + private fun totalTokenIncoming(userAddress: Address, tokenAddress: Address, logs: List): BigInteger? { + var amountOut = BigInteger.ZERO + + for (log in logs) { + if (log.address == tokenAddress) { + (log.getErc20Event() as? TransferEventDecoration)?.let { transferEventDecoration -> + if (transferEventDecoration.to == userAddress) { + amountOut += transferEventDecoration.value + } + } + } + } + + return if (amountOut > BigInteger.ZERO) amountOut else null + } + + private fun totalETHIncoming(userAddress: Address, transactions: List): BigInteger? { + var amountOut = BigInteger.ZERO + + for (transaction in transactions) { + if (transaction.to == userAddress) { + amountOut += transaction.value + } + } + + return if (amountOut > BigInteger.ZERO) amountOut else null + } + + private fun addressToToken(address: Address): OneInchMethodDecoration.Token { + return if (evmTokenAddresses.contains(address.eip55)) + OneInchMethodDecoration.Token.EvmCoin + else + OneInchMethodDecoration.Token.Eip20(address) + } + +} diff --git a/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/decorations/OneInchUnoswapMethodDecoration.kt b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/decorations/OneInchUnoswapMethodDecoration.kt new file mode 100644 index 00000000..776c4278 --- /dev/null +++ b/oneinchkit/src/main/java/io/horizontalsystems/oneinchkit/decorations/OneInchUnoswapMethodDecoration.kt @@ -0,0 +1,43 @@ +package io.horizontalsystems.oneinchkit.decorations + +import io.horizontalsystems.ethereumkit.contracts.Bytes32Array +import io.horizontalsystems.ethereumkit.models.Address +import java.math.BigInteger + +class OneInchUnoswapMethodDecoration( + val fromToken: Token, + val toToken: Token?, + val fromAmount: BigInteger, + val toAmount: BigInteger, + val params: Bytes32Array +) : OneInchMethodDecoration() { + + override fun tags(fromAddress: Address, toAddress: Address, userAddress: Address): List { + val tags = mutableListOf(toAddress.hex, "swap") + + when (fromToken) { + Token.EvmCoin -> { + tags.addAll(listOf("ETH_outgoing", "ETH", "outgoing")) + } + is Token.Eip20 -> { + val addressHex = fromToken.address.hex + tags.addAll(listOf("${addressHex}_outgoing", addressHex, "outgoing")) + } + } + + toToken?.let { + when (toToken) { + Token.EvmCoin -> { + tags.addAll(listOf("ETH_incoming", "ETH", "incoming")) + } + is Token.Eip20 -> { + val addressHex = toToken.address.hex + tags.addAll(listOf("${addressHex}_incoming", addressHex, "incoming")) + } + } + } + + return tags + } + +}