All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.dev.icerock.moko.web3.Web3.kt Maven / Gradle / Ivy

There is a newer version: 0.18.4-ktor2_ionspinbignum
Show newest version
/*
 * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
 */

package dev.icerock.moko.web3

import dev.icerock.moko.web3.annotation.DelicateWeb3Api
import dev.icerock.moko.web3.entity.RpcRequest
import dev.icerock.moko.web3.entity.RpcResponse
import io.ktor.client.HttpClient
import io.ktor.client.call.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.http.*
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.json.*

/**
 * Default http implementation for web3 requests.
 * @delicate Don't use the default constructor since it is for test purposes only
 */
class Web3 @DelicateWeb3Api constructor(
    private val httpClient: HttpClient,
    private val json: Json,
    private val endpointUrl: String
) : Web3Executor {

    @OptIn(DelicateWeb3Api::class)
    constructor(endpointUrl: String) : this(
        httpClient = HttpClient {
            install(DefaultRequest) {
                // some networks require the content type to be set
                contentType(ContentType.Application.Json)
            }
        },
        json = Json {
            // some networks return an additional info in models that may not be documented
            ignoreUnknownKeys = true
        },
        endpointUrl = endpointUrl
    )

    override suspend fun  executeBatch(requests: List>): List {
        // Used later for logging if exception
        val rawRequests = requests
            .mapIndexed { index, web3Request ->
                RpcRequest(
                    method = web3Request.method,
                    id = index,
                    params = web3Request.params
                )
            }

        val encodedToStringBody = rawRequests
            .mapIndexed { index, request ->
                val encodedParams = request.params.map { param ->
                    val (value, serializer) = unsafeCast(param, requests[index].paramsSerializer)
                    json.encodeToJsonElement(
                        serializer = serializer,
                        value = value
                    )
                }
                json.encodeToJsonElement(
                    serializer = RpcRequest.serializer(JsonElement.serializer()),
                    // cannot use copy since generics mismatch
                    value = RpcRequest(
                        method = request.method,
                        id = request.id,
                        jsonrpc = request.jsonrpc,
                        params = encodedParams
                    )
                )
            }.let { list -> json.encodeToString(list) }

        val responses = httpClient
            .post {
                url(endpointUrl)
                setBody(encodedToStringBody)
            }.let { raw ->
                json.decodeFromString>(raw.body())
            }

        // Here we are restoring the order
        return requests.mapIndexed { index, request ->
            val response = responses.first { response ->
                val id = response.getValue(key = "id").jsonPrimitive.int
                return@first id == index
            }

            return@mapIndexed processResponse(
                request = rawRequests[index],
                deserializer = request.resultSerializer,
                content = response.toString()
            )
        }
    }

    private fun  RpcResponse.typed(
        deserializer: DeserializationStrategy
    ) = RpcResponse(
        jsonrpc = jsonrpc,
        id = id,
        result = json.decodeFromJsonElement(deserializer, element = result ?: JsonNull),
        error = error
    )

    private fun  processResponse(
        request: RpcRequest<*>,
        deserializer: DeserializationStrategy,
        content: String
    ): T {
        val response = json.decodeFromString(RpcResponse.serializer(JsonElement.serializer()), content)

        @Suppress("UNCHECKED_CAST")
        when {
            response.error != null -> throw Web3RpcException(
                code = response.error.code,
                message = response.error.message,
                request = request
            )
            else -> return response.typed(deserializer).result as T
        }
    }

    @Suppress("UNCHECKED_CAST")
    private inline fun  unsafeCast(value: T, serializer: SerializationStrategy<*>): Pair> =
        value to (serializer as SerializationStrategy)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy