なつねこメモ

主にプログラミング関連のメモ帳 ♪(✿╹ヮ╹)ノ 書いてあるコードは自己責任でご自由にどうぞ。記事本文の無断転載は禁止です。

Rust から Ethereum の JSON RPC を呼び出したい

なんやかんやで仕事で Ethereum に関わっているんですが、こいつの JSON RPC を呼び出したいときがあるので、その方法のメモ。
なぜわざわざ Rust 経由でやっているのかは数日後~数週間後にわかります。きっと。
ちなみに 1 度だけこれを言ったらミュートにしてきた VRChatter いるのは知っています。職業差別かな。

ということで、やり方。
JSON-RPC をさすがに生でやりとりするのはつらいので、それっぽいクライアントライブラリを入れます。

[dependencies]
jsonrpc-core = "18"
jsonrpc-core-client = { version = "18", features = ["http", "tls"]}
jsonrpc-derive = "18"
serde = { version = "1", features = "derive" }
serde_json = "1"
tokio = { version = "1.16.1", features = ["full"] }

このとき、 jsonrpc-core-clientfeatures を書かないと、一切通信できないので気をつけましょう。
どうやらデフォルトではすべてのトランスポート層の実装を外している様子。

そして JSON RPC 呼び出し部分。

use jsonrpc_core_client::futures::Future;
use jsonrpc_core_client::{RpcChannel, RpcResult, TypedClient};

#[derive(Clone)]
pub struct EthereumClient(TypedClient);

impl From<RpcChannel> for EthereumClient {
    fn from(channel: RpcChannel) -> Self {
        EthereumClient(channel.into())
    }
}

impl EthereumClient {
    pub fn get_accounts(&self) -> impl Future<Output = RpcResult<Vec<String>>> {
        self.0.call_method("eth_accounts", "Vec<String>", ())
    }
}

pub async fn get_client(endpoint: Option<String>) -> Result<EthereumClient> {
    let endpoint = endpoint.unwrap_or_else(|| "http://localhost:8545".to_owned());
    let client = jsonrpc_core_client::transports::http::connect::<EthereumClient>(endpoint.as_str()).await;
    let client = match client {
        Ok(client) => client,
        Err(err) => return Err(err),
    };

    return Ok(client);
}

このクライアントライブラリの優秀なところは、パラメータのシリアライズ、デシリアライズは serde::Serializeserde::Deserialize を実装さえしていれば、勝手にやってくれます。
なので、こんな感じに何も書かなくても良い感じに変換した結果を送信してくれたり、返してくれます。

ちなみにパラメーターは、 self.0.call_method("eth_accounts", "Vec<String>", ()) の第 3 引数に Tuple の形で投げつければ OK。
eth_feeHistory とかだとこんな感じにすれば良い (戻り値は適当に)。

pub fn get_fee_history_by_str<'a>(
    &self,
    block_count: u16,
    newest_block: String,
    reward_percentiles: Vec<f32>,
) -> impl Future<Output = RpcResult<entity::FeeHistory>> + 'a {
    self.0.call_method(
        "eth_feeHistory",
        "FeeHistory",
        (block_count, newest_block, reward_percentiles),
    )
}

ということで、今日のメモでした!