1use crate::base58::Pubkey;
26use crate::versioned_tx::Blockhash;
27use base64::prelude::{BASE64_STANDARD, Engine as _};
28use serde_json::{json, Value};
29use std::cell::RefCell;
30use std::collections::VecDeque;
31use std::str::FromStr;
32use std::sync::atomic::{AtomicU64, Ordering};
33
34#[derive(Clone, PartialEq, Eq, Debug)]
36pub struct BlockhashInfo {
37 pub blockhash: Blockhash,
38 pub last_valid_block_height: u64,
39}
40
41#[derive(Clone, PartialEq, Eq, Debug)]
44pub struct AccountInfo {
45 pub data: Vec<u8>,
46 pub owner: Pubkey,
47 pub lamports: u64,
48 pub executable: bool,
49}
50
51#[derive(Clone, PartialEq, Eq, Debug)]
53pub struct TxSummary {
54 pub signature: String,
55 pub err: Option<String>,
57 pub slot: u64,
58 pub block_time: Option<i64>,
59}
60
61#[derive(Clone, PartialEq, Eq, Debug)]
62pub enum RpcError {
63 Transport(String),
65 Json(String),
67 Rpc { code: i64, message: String },
69 Unexpected(String),
71 Base58(String),
73 Base64(String),
75}
76
77pub trait Rpc {
79 fn get_latest_blockhash(&self) -> Result<BlockhashInfo, RpcError>;
80 fn get_account_info(&self, pubkey: &Pubkey) -> Result<Option<AccountInfo>, RpcError>;
81 fn get_signatures_for_address(&self, pubkey: &Pubkey, limit: usize) -> Result<Vec<TxSummary>, RpcError>;
82 fn send_transaction(&self, tx: &[u8]) -> Result<String, RpcError>;
85}
86
87pub fn rpc_request(id: u64, method: &str, params: Value) -> Value {
91 json!({ "jsonrpc": "2.0", "id": id, "method": method, "params": params })
92}
93
94pub fn rpc_result(v: &Value) -> Result<&Value, RpcError> {
96 if let Some(err) = v.get("error") {
97 return Err(RpcError::Rpc {
98 code: err.get("code").and_then(|c| c.as_i64()).unwrap_or(0),
99 message: err.get("message").and_then(|m| m.as_str()).unwrap_or("").to_string(),
100 });
101 }
102 v.get("result")
103 .ok_or_else(|| RpcError::Unexpected("missing `result` or `error` in JSON-RPC response".to_string()))
104}
105
106pub fn parse_blockhash(v: &Value) -> Result<BlockhashInfo, RpcError> {
108 let value = v
109 .get("value")
110 .ok_or_else(|| RpcError::Unexpected("missing result.value".to_string()))?;
111 let bh_str = value
112 .get("blockhash")
113 .and_then(|b| b.as_str())
114 .ok_or_else(|| RpcError::Unexpected("missing blockhash".to_string()))?;
115 let blockhash = Pubkey::from_str(bh_str)
116 .map_err(|e| RpcError::Base58(e.to_string()))?
117 .to_bytes();
118 let last_valid_block_height = value
119 .get("lastValidBlockHeight")
120 .and_then(|h| h.as_u64())
121 .ok_or_else(|| RpcError::Unexpected("missing lastValidBlockHeight".to_string()))?;
122 Ok(BlockhashInfo { blockhash, last_valid_block_height })
123}
124
125pub fn parse_account_info(v: &Value) -> Result<Option<AccountInfo>, RpcError> {
127 let value = match v.get("value") {
128 None => return Err(RpcError::Unexpected("missing result.value".to_string())),
129 Some(Value::Null) => return Ok(None),
130 Some(v) => v,
131 };
132 let data_arr = value
133 .get("data")
134 .ok_or_else(|| RpcError::Unexpected("missing data".to_string()))?;
135 let b64 = data_arr
136 .get(0)
137 .and_then(|d| d.as_str())
138 .ok_or_else(|| RpcError::Unexpected("data[0] not a string".to_string()))?;
139 let data = BASE64_STANDARD
140 .decode(b64)
141 .map_err(|e| RpcError::Base64(e.to_string()))?;
142 let owner_str = value
143 .get("owner")
144 .and_then(|o| o.as_str())
145 .ok_or_else(|| RpcError::Unexpected("missing owner".to_string()))?;
146 let owner = Pubkey::from_str(owner_str).map_err(|e| RpcError::Base58(e.to_string()))?;
147 let lamports = value
148 .get("lamports")
149 .and_then(|l| l.as_u64())
150 .ok_or_else(|| RpcError::Unexpected("missing lamports".to_string()))?;
151 let executable = value.get("executable").and_then(|e| e.as_bool()).unwrap_or(false);
152 Ok(Some(AccountInfo { data, owner, lamports, executable }))
153}
154
155pub fn parse_signatures(v: &Value) -> Result<Vec<TxSummary>, RpcError> {
157 let arr = v
158 .as_array()
159 .ok_or_else(|| RpcError::Unexpected("signatures result not an array".to_string()))?;
160 let mut out = Vec::with_capacity(arr.len());
161 for item in arr {
162 let signature = item
163 .get("signature")
164 .and_then(|s| s.as_str())
165 .ok_or_else(|| RpcError::Unexpected("missing signature".to_string()))?
166 .to_string();
167 let err = match item.get("err") {
168 None | Some(Value::Null) => None,
169 Some(e) => Some(if let Some(s) = e.as_str() { s.to_string() } else { e.to_string() }),
170 };
171 let slot = item
172 .get("slot")
173 .and_then(|s| s.as_u64())
174 .ok_or_else(|| RpcError::Unexpected("missing slot".to_string()))?;
175 let block_time = item.get("blockTime").and_then(|b| b.as_i64());
176 out.push(TxSummary { signature, err, slot, block_time });
177 }
178 Ok(out)
179}
180
181pub fn parse_send_tx(v: &Value) -> Result<String, RpcError> {
183 v.as_str()
184 .map(|s| s.to_string())
185 .ok_or_else(|| RpcError::Unexpected("sendTransaction result not a string".to_string()))
186}
187
188pub struct WakiRpc {
192 endpoint: String,
193 api_key: Option<String>,
194 id: AtomicU64,
195}
196
197impl WakiRpc {
198 pub fn new(endpoint: String, api_key: Option<String>) -> Self {
202 Self { endpoint, api_key, id: AtomicU64::new(0) }
203 }
204 pub fn endpoint(&self) -> &str { &self.endpoint }
205 pub fn api_key(&self) -> Option<&str> { self.api_key.as_deref() }
206
207 fn next_id(&self) -> u64 { self.id.fetch_add(1, Ordering::Relaxed) + 1 }
208
209 fn post_json(&self, req: &Value) -> Result<Value, RpcError> {
210 let mut builder = waki::Client::new().post(&self.endpoint);
212 if let Some(key) = &self.api_key {
213 let auth = format!("Bearer {key}");
214 builder = builder.header("Authorization", auth.as_str());
215 }
216 let resp = builder
217 .json(req)
218 .send()
219 .map_err(|e| RpcError::Transport(e.to_string()))?;
220 resp.json::<Value>().map_err(|e| RpcError::Json(e.to_string()))
221 }
222}
223
224impl Rpc for WakiRpc {
225 fn get_latest_blockhash(&self) -> Result<BlockhashInfo, RpcError> {
226 let req = rpc_request(self.next_id(), "getLatestBlockhash", json!([]));
227 let resp = self.post_json(&req)?;
228 parse_blockhash(rpc_result(&resp)?)
229 }
230 fn get_account_info(&self, pubkey: &Pubkey) -> Result<Option<AccountInfo>, RpcError> {
231 let req = rpc_request(
232 self.next_id(),
233 "getAccountInfo",
234 json!([pubkey.to_string(), { "encoding": "base64" }]),
235 );
236 let resp = self.post_json(&req)?;
237 parse_account_info(rpc_result(&resp)?)
238 }
239 fn get_signatures_for_address(&self, pubkey: &Pubkey, limit: usize) -> Result<Vec<TxSummary>, RpcError> {
240 let req = rpc_request(
241 self.next_id(),
242 "getSignaturesForAddress",
243 json!([pubkey.to_string(), { "limit": limit }]),
244 );
245 let resp = self.post_json(&req)?;
246 parse_signatures(rpc_result(&resp)?)
247 }
248 fn send_transaction(&self, tx: &[u8]) -> Result<String, RpcError> {
249 let b64 = BASE64_STANDARD.encode(tx);
250 let req = rpc_request(self.next_id(), "sendTransaction", json!([b64, { "encoding": "base64" }]));
251 let resp = self.post_json(&req)?;
252 parse_send_tx(rpc_result(&resp)?)
253 }
254}
255
256pub struct MockRpc {
263 responses: RefCell<VecDeque<Value>>,
264}
265
266impl MockRpc {
267 pub fn new(responses: Vec<Value>) -> Self {
268 Self { responses: RefCell::new(responses.into()) }
269 }
270 fn next_response(&self) -> Result<Value, RpcError> {
271 self.responses
272 .borrow_mut()
273 .pop_front()
274 .ok_or_else(|| RpcError::Unexpected("MockRpc: no more scripted responses".to_string()))
275 }
276}
277
278impl Rpc for MockRpc {
279 fn get_latest_blockhash(&self) -> Result<BlockhashInfo, RpcError> {
280 let resp = self.next_response()?;
281 parse_blockhash(rpc_result(&resp)?)
282 }
283 fn get_account_info(&self, _pubkey: &Pubkey) -> Result<Option<AccountInfo>, RpcError> {
284 let resp = self.next_response()?;
285 parse_account_info(rpc_result(&resp)?)
286 }
287 fn get_signatures_for_address(&self, _pubkey: &Pubkey, _limit: usize) -> Result<Vec<TxSummary>, RpcError> {
288 let resp = self.next_response()?;
289 parse_signatures(rpc_result(&resp)?)
290 }
291 fn send_transaction(&self, _tx: &[u8]) -> Result<String, RpcError> {
292 let resp = self.next_response()?;
293 parse_send_tx(rpc_result(&resp)?)
294 }
295}