1use std::time::Duration;
4
5use async_trait::async_trait;
6use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STANDARD};
7use serde::{Deserialize, Serialize};
8
9use super::{RpcSubmitConfig, RpcSubmitTransport, SubmitTransportError};
10
11#[derive(Debug, Clone)]
13pub struct JsonRpcTransport {
14 client: reqwest::Client,
16 rpc_url: String,
18}
19
20impl JsonRpcTransport {
21 pub fn new(rpc_url: impl Into<String>) -> Result<Self, SubmitTransportError> {
27 let client = reqwest::Client::builder()
28 .timeout(Duration::from_secs(10))
29 .build()
30 .map_err(|error| SubmitTransportError::Config {
31 message: error.to_string(),
32 })?;
33 Ok(Self {
34 client,
35 rpc_url: rpc_url.into(),
36 })
37 }
38}
39
40#[derive(Debug, Deserialize)]
42struct JsonRpcResponse {
43 result: Option<String>,
45 error: Option<JsonRpcError>,
47}
48
49#[derive(Debug, Deserialize)]
51struct JsonRpcError {
52 code: i64,
54 message: String,
56}
57
58#[async_trait]
59impl RpcSubmitTransport for JsonRpcTransport {
60 async fn submit_rpc(
61 &self,
62 tx_bytes: &[u8],
63 config: &RpcSubmitConfig,
64 ) -> Result<String, SubmitTransportError> {
65 #[derive(Debug, Serialize)]
66 struct RpcConfig<'config> {
67 encoding: &'config str,
69 #[serde(rename = "skipPreflight")]
71 skip_preflight: bool,
72 #[serde(
74 rename = "preflightCommitment",
75 skip_serializing_if = "Option::is_none"
76 )]
77 preflight_commitment: Option<&'config str>,
78 }
79
80 let encoded_tx = BASE64_STANDARD.encode(tx_bytes);
81 let payload = serde_json::json!({
82 "jsonrpc": "2.0",
83 "id": 1,
84 "method": "sendTransaction",
85 "params": [
86 encoded_tx,
87 RpcConfig {
88 encoding: "base64",
89 skip_preflight: config.skip_preflight,
90 preflight_commitment: config.preflight_commitment.as_deref(),
91 }
92 ]
93 });
94
95 let response = self
96 .client
97 .post(&self.rpc_url)
98 .json(&payload)
99 .send()
100 .await
101 .map_err(|error| SubmitTransportError::Failure {
102 message: error.to_string(),
103 })?;
104
105 let response =
106 response
107 .error_for_status()
108 .map_err(|error| SubmitTransportError::Failure {
109 message: error.to_string(),
110 })?;
111
112 let parsed: JsonRpcResponse =
113 response
114 .json()
115 .await
116 .map_err(|error| SubmitTransportError::Failure {
117 message: error.to_string(),
118 })?;
119
120 if let Some(signature) = parsed.result {
121 return Ok(signature);
122 }
123 if let Some(error) = parsed.error {
124 return Err(SubmitTransportError::Failure {
125 message: format!("rpc error {}: {}", error.code, error.message),
126 });
127 }
128
129 Err(SubmitTransportError::Failure {
130 message: "rpc returned neither result nor error".to_owned(),
131 })
132 }
133}