Skip to content

Instantly share code, notes, and snippets.

@Legion1900
Last active October 13, 2020 09:35
Show Gist options
  • Save Legion1900/58c432faf798ee1e8a96e1e9101569bd to your computer and use it in GitHub Desktop.
Save Legion1900/58c432faf798ee1e8a96e1e9101569bd to your computer and use it in GitHub Desktop.
/*
* Code of this class belongs to https://github.com/horizontalsystems/ethereum-kit-android library;
* Modified by Sapien Wallet team on 10.12.2020
* */
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import io.horizontalsystems.ethereumkit.api.models.ApiError
import io.horizontalsystems.ethereumkit.core.EthereumKit
import io.horizontalsystems.ethereumkit.core.removeLeadingZeros
import io.horizontalsystems.ethereumkit.core.stripHexPrefix
import io.horizontalsystems.ethereumkit.core.toHexString
import io.horizontalsystems.ethereumkit.models.Address
import io.horizontalsystems.ethereumkit.models.Block
import io.horizontalsystems.ethereumkit.models.EthereumLog
import io.horizontalsystems.ethereumkit.models.TransactionStatus
import io.reactivex.Single
import retrofit2.http.Body
import retrofit2.http.POST
import retrofit2.http.Path
import java.math.BigInteger
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class InfuraService @Inject constructor(
private val service: InfuraServiceAPI,
private val infuraCredentials: EthereumKit.InfuraCredentials
) {
fun getLastBlockHeight(): Single<Long> {
val request = Request("eth_blockNumber")
return service.makeRequestForBigInteger(infuraCredentials.projectId, request).flatMap {
returnResultOrError(it).map { blockNumber ->
blockNumber.toLong()
}
}
}
fun getTransactionCount(address: Address): Single<Long> {
val request = Request("eth_getTransactionCount", listOf(address.hex, "pending"))
return service.makeRequestForBigInteger(infuraCredentials.projectId, request).flatMap {
returnResultOrError(it).map { txCount ->
txCount.toLong()
}
}
}
fun getBalance(address: Address): Single<BigInteger> {
val request = Request("eth_getBalance", listOf(address.hex, "latest"))
return service.makeRequestForBigInteger(infuraCredentials.projectId, request).flatMap {
returnResultOrError(it)
}
}
fun send(signedTransaction: ByteArray): Single<Unit> {
val request = Request("eth_sendRawTransaction", listOf(signedTransaction.toHexString()))
return service.makeRequestForString(infuraCredentials.projectId, request).flatMap {
returnResultOrError(it)
}.map { Unit }
}
fun getLogs(
address: Address?, fromBlock: Long?, toBlock: Long?,
topics: List<ByteArray?>
): Single<List<EthereumLog>> {
val fromBlockStr = fromBlock?.toBigInteger()?.toString(16)?.let { "0x$it" } ?: "earliest"
val toBlockStr = toBlock?.toBigInteger()?.toString(16)?.let { "0x$it" } ?: "latest"
val params: MutableMap<String, Any> = mutableMapOf(
"fromBlock" to fromBlockStr,
"toBlock" to toBlockStr,
"topics" to topics.map { it?.toHexString() })
address?.let {
params["address"] = address.hex
}
val request = Request("eth_getLogs", listOf(params))
return service.makeRequestForLogs(infuraCredentials.projectId, request).flatMap {
returnResultOrError(it)
}
}
fun getStorageAt(contractAddress: Address, position: String, blockNumber: Long?): Single<String> {
val request = Request("eth_getStorageAt", listOf(contractAddress.hex, position, "latest"))
return service.makeRequestForString(infuraCredentials.projectId, request).flatMap {
returnResultOrError(it)
}
}
fun transactionReceiptStatus(transactionHash: ByteArray): Single<TransactionStatus> {
val request = Request("eth_getTransactionReceipt", listOf(transactionHash.toHexString()))
return service.makeRequestForString(infuraCredentials.projectId, request)
.flatMap {
if (it.error != null)
Single.just(TransactionStatus.FAILED)
else {
it.result?.let { result ->
val txStatusMap: Map<String, String> =
Gson().fromJson(result, object : TypeToken<Map<String, Any>>() {}.type)
txStatusMap["status"]?.let { statusStr ->
val success = Integer.parseInt(statusStr.stripHexPrefix(), 16)
if (success == 0)
Single.just(TransactionStatus.SUCCESS)
else
Single.just(TransactionStatus.FAILED)
}
} ?: Single.just(TransactionStatus.NOTFOUND)
}
}
}
fun transactionExist(transactionHash: ByteArray): Single<Boolean> {
val request = Request("eth_getTransactionByHash", listOf(transactionHash.toHexString()))
return service.makeRequestForString(infuraCredentials.projectId, request).flatMap {
if (it.error != null || it.result == null)
Single.just(false)
else
Single.just(true)
}
}
fun estimateGas(
from: Address?,
to: Address,
value: BigInteger?,
gasLimit: Long?,
gasPrice: Long?,
data: String?
): Single<String> {
val params: MutableMap<String, String> = mutableMapOf("to" to to.hex)
from?.let { params.put("from", from.hex) }
gasLimit?.let { params.put("gas", "0x${gasLimit.toString(16).removeLeadingZeros()}") }
gasPrice?.let { params.put("gasPrice", "0x${gasPrice.toString(16).removeLeadingZeros()}") }
value?.let { params.put("value", "0x${value.toString(16).removeLeadingZeros()}") }
data?.let { params.put("data", data) }
val request = Request("eth_estimateGas", listOf(params))
return service.makeRequestForString(infuraCredentials.projectId, request).flatMap {
returnResultOrParsedError(it)
}
}
fun getBlockByNumber(blockNumber: Long): Single<Block> {
val request = Request("eth_getBlockByNumber", listOf("0x${blockNumber.toString(16)}", false))
return service.makeRequestForBlock(infuraCredentials.projectId, request).flatMap {
returnResultOrError(it)
}
}
fun call(contractAddress: Address, data: ByteArray, blockNumber: Long?): Single<String> {
val request = Request(
"eth_call",
listOf(
mapOf("to" to contractAddress.hex, "data" to data.toHexString()),
"latest"
)
)
return service.makeRequestForString(infuraCredentials.projectId, request).flatMap {
returnResultOrError(it)
}
}
private fun <T> parseInfuraError(errorResponse: Response<T>): Exception {
if (errorResponse.error != null) {
return ApiError.InfuraError(errorResponse.error.code, errorResponse.error.message)
}
return ApiError.InvalidData
}
private fun <T> returnResultOrParsedError(response: Response<T>): Single<T> {
return if (response.error != null) {
Single.error(parseInfuraError(response))
} else {
Single.just(response.result)
}
}
private fun <T> returnResultOrError(response: Response<T>): Single<T> {
return if (response.error != null) {
Single.error(Exception(response.error.message))
} else {
Single.just(response.result)
}
}
class Request(val method: String, val params: List<Any> = listOf(), val id: Long = 1, val jsonrpc: String = "2.0")
class Error(val code: Int, val message: String)
class Response<T>(val result: T?, val error: Error?, val id: Long, val jsonrpc: String)
interface InfuraServiceAPI {
@POST("{projectId}")
fun makeRequestForBigInteger(
@Path("projectId") apiKey: String,
@Body request: Request
): Single<Response<BigInteger>>
@POST("{projectId}")
fun makeRequestForString(
@Path("projectId") apiKey: String,
@Body request: Request
): Single<Response<String>>
@POST("{projectId}")
fun makeRequestForLogs(
@Path("projectId") apiKey: String,
@Body request: Request
): Single<Response<List<EthereumLog>>>
@POST("{projectId}")
fun makeRequestForBlock(
@Path("projectId") apiKey: String,
@Body request: Request
): Single<Response<Block>>
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment