polymesh_api_client/
client.rs

1#[cfg(not(feature = "std"))]
2use alloc::{collections::btree_map::BTreeMap, sync::Arc};
3#[cfg(feature = "std")]
4use std::{collections::BTreeMap, sync::Arc};
5
6use jsonrpsee::core::client::BatchResponse;
7pub use jsonrpsee::core::client::Subscription;
8use jsonrpsee::core::params::{ArrayParams, BatchRequestBuilder};
9use jsonrpsee::rpc_params;
10
11use codec::Decode;
12
13#[cfg(feature = "serde")]
14use serde::{de::DeserializeOwned, Deserialize, Serialize};
15#[cfg(feature = "serde")]
16use serde_json::Value;
17
18#[cfg(not(feature = "std"))]
19use alloc::{format, string::String};
20use sp_std::prelude::*;
21
22#[cfg(feature = "type_info")]
23use frame_metadata::RuntimeMetadataPrefixed;
24
25use crate::rpc::*;
26use crate::*;
27
28#[derive(Clone, Debug, Default)]
29#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
30#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
31pub struct RuntimeVersion {
32  pub spec_name: String,
33  pub impl_name: String,
34  pub authoring_version: u32,
35  pub spec_version: u32,
36  pub impl_version: u32,
37  #[cfg_attr(feature = "serde", serde(default))]
38  pub transaction_version: u32,
39
40  #[cfg_attr(feature = "serde", serde(flatten))]
41  #[cfg(feature = "serde")]
42  pub extra: BTreeMap<String, Value>,
43}
44
45#[derive(Clone, Debug)]
46#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
47#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
48pub struct SystemProperties {
49  pub ss58_format: u16,
50  pub token_decimals: u32,
51  pub token_symbol: String,
52}
53
54struct InnerClient {
55  rpc: RpcClient,
56  runtime_version: RuntimeVersion,
57  #[cfg(feature = "type_info")]
58  metadata: RuntimeMetadataPrefixed,
59  genesis_hash: BlockHash,
60}
61
62impl InnerClient {
63  async fn new(url: &str) -> Result<Self> {
64    let rpc = RpcClient::new(url).await?;
65    let runtime_version = Self::rpc_get_runtime_version(&rpc, None)
66      .await?
67      .ok_or_else(|| Error::RpcClient(format!("Failed to get RuntimeVersion")))?;
68    #[cfg(feature = "type_info")]
69    let metadata = Self::rpc_get_metadata(&rpc, None)
70      .await?
71      .ok_or_else(|| Error::RpcClient(format!("Failed to get chain metadata")))?;
72    let genesis_hash = Self::rpc_get_block_hash(&rpc, 0)
73      .await?
74      .ok_or_else(|| Error::RpcClient(format!("Failed to get chain Genesis hash")))?;
75    Ok(Self {
76      rpc,
77      runtime_version,
78      #[cfg(feature = "type_info")]
79      metadata,
80      genesis_hash,
81    })
82  }
83
84  fn get_transaction_version(&self) -> i64 {
85    self.runtime_version.transaction_version as i64
86  }
87
88  #[cfg(feature = "type_info")]
89  fn get_metadata(&self) -> &RuntimeMetadataPrefixed {
90    &self.metadata
91  }
92
93  fn get_genesis_hash(&self) -> BlockHash {
94    self.genesis_hash
95  }
96
97  async fn get_additional_signed(&self, lifetime: Option<u64>) -> Result<(AdditionalSigned, Era)> {
98    let mut addititional = AdditionalSigned {
99      spec_version: self.runtime_version.spec_version,
100      tx_version: self.runtime_version.transaction_version,
101      genesis_hash: self.genesis_hash,
102      current_hash: self.genesis_hash,
103      metadata_hash: None,
104    };
105    let era = match lifetime {
106      Some(0) => Era::immortal(),
107      lifetime => {
108        let current = self
109          .get_block_header(None)
110          .await?
111          .ok_or_else(|| Error::RpcClient("Failed to get current block".into()))?;
112        let number = current.number;
113        // Need to use the current block hash.
114        addititional.current_hash = current.hash();
115        Era::mortal(number, lifetime)
116      }
117    };
118
119    Ok((addititional, era))
120  }
121
122  #[cfg(feature = "serde")]
123  async fn request<'a, R>(&self, method: &'a str, params: ArrayParams) -> Result<R>
124  where
125    R: DeserializeOwned,
126  {
127    self.rpc.request(method, params).await
128  }
129
130  #[cfg(feature = "serde")]
131  async fn batch_request<'a, R>(
132    &self,
133    batch: BatchRequestBuilder<'a>,
134  ) -> Result<BatchResponse<'a, R>>
135  where
136    R: DeserializeOwned + Default + Clone + alloc::fmt::Debug + 'a,
137  {
138    self.rpc.batch_request(batch).await
139  }
140
141  #[cfg(feature = "serde")]
142  async fn subscribe<'a, Notif>(
143    &self,
144    subscribe_method: &'a str,
145    params: ArrayParams,
146    unsubscribe_method: &'a str,
147  ) -> Result<Subscription<Notif>>
148  where
149    Notif: DeserializeOwned,
150  {
151    self
152      .rpc
153      .subscribe(subscribe_method, params, unsubscribe_method)
154      .await
155  }
156
157  async fn rpc_get_block_hash(rpc: &RpcClient, block_number: u32) -> Result<Option<BlockHash>> {
158    let params = rpc_params!(block_number);
159    Ok(rpc.request("chain_getBlockHash", params).await?)
160  }
161
162  /// Get the header of a block.
163  async fn get_block_header(&self, block: Option<BlockHash>) -> Result<Option<Header>> {
164    Ok(self.request("chain_getHeader", rpc_params!(block)).await?)
165  }
166
167  /// Get the block hash for a `block_number`.
168  async fn get_block_hash(&self, block_number: u32) -> Result<Option<BlockHash>> {
169    Self::rpc_get_block_hash(&self.rpc, block_number).await
170  }
171
172  async fn rpc_get_runtime_version(
173    rpc: &RpcClient,
174    block: Option<BlockHash>,
175  ) -> Result<Option<RuntimeVersion>> {
176    let params = rpc_params!(block);
177    rpc.request("state_getRuntimeVersion", params).await
178  }
179
180  /// Get the RuntimeVersion of a block.
181  async fn get_block_runtime_version(
182    &self,
183    block: Option<BlockHash>,
184  ) -> Result<Option<RuntimeVersion>> {
185    Self::rpc_get_runtime_version(&self.rpc, block).await
186  }
187
188  #[cfg(feature = "type_info")]
189  async fn rpc_get_metadata(
190    rpc: &RpcClient,
191    block: Option<BlockHash>,
192  ) -> Result<Option<RuntimeMetadataPrefixed>> {
193    use hex::FromHex;
194    let params = rpc_params!(block);
195    let hex: String = rpc.request("state_getMetadata", params).await?;
196
197    let bytes = Vec::from_hex(&hex[2..])?;
198    Ok(Some(RuntimeMetadataPrefixed::decode(
199      &mut bytes.as_slice(),
200    )?))
201  }
202
203  /// Get the RuntimeMetadata of a block.
204  #[cfg(feature = "type_info")]
205  async fn get_block_metadata(
206    &self,
207    block: Option<BlockHash>,
208  ) -> Result<Option<RuntimeMetadataPrefixed>> {
209    Self::rpc_get_metadata(&self.rpc, block).await
210  }
211}
212
213#[derive(Clone)]
214pub struct Client {
215  inner: Arc<InnerClient>,
216}
217
218impl Client {
219  pub async fn new(url: &str) -> Result<Self> {
220    Ok(Self {
221      inner: Arc::new(InnerClient::new(url).await?),
222    })
223  }
224
225  pub fn get_transaction_version(&self) -> i64 {
226    self.inner.get_transaction_version()
227  }
228
229  #[cfg(feature = "type_info")]
230  pub fn get_metadata(&self) -> &RuntimeMetadataPrefixed {
231    self.inner.get_metadata()
232  }
233
234  pub fn get_genesis_hash(&self) -> BlockHash {
235    self.inner.get_genesis_hash()
236  }
237
238  pub async fn get_additional_signed(
239    &self,
240    lifetime: Option<u64>,
241  ) -> Result<(AdditionalSigned, Era)> {
242    self.inner.get_additional_signed(lifetime).await
243  }
244
245  /// Get the `SystemProperties` of the chain.
246  pub async fn get_system_properties(&self) -> Result<SystemProperties> {
247    self.request("system_properties", rpc_params!()).await
248  }
249
250  pub async fn get_storage_keys_paged(
251    &self,
252    prefix: &StorageKey,
253    count: usize,
254    start_key: Option<&StorageKey>,
255    at: Option<BlockHash>,
256  ) -> Result<Vec<StorageKey>> {
257    let params = rpc_params!(prefix, count, start_key.unwrap_or(prefix), at);
258    self
259      .request::<Vec<StorageKey>>("state_getKeysPaged", params)
260      .await
261  }
262
263  pub async fn get_storage_by_key<T: Decode>(
264    &self,
265    key: StorageKey,
266    at: Option<BlockHash>,
267  ) -> Result<Option<T>> {
268    let value = self
269      .get_storage_data_by_key(key, at)
270      .await?
271      .map(|data| T::decode(&mut data.0.as_slice()))
272      .transpose()?;
273    Ok(value)
274  }
275
276  pub async fn get_storage_data_by_key(
277    &self,
278    key: StorageKey,
279    at: Option<BlockHash>,
280  ) -> Result<Option<StorageData>> {
281    Ok(
282      self
283        .request("state_getStorage", rpc_params!(key, at))
284        .await?,
285    )
286  }
287
288  /// Subscribe to new blocks.
289  pub async fn subscribe_blocks(&self) -> Result<Subscription<Header>> {
290    Ok(
291      self
292        .subscribe(
293          "chain_subscribeNewHeads",
294          rpc_params!(),
295          "chain_unsubscribeNewHeads",
296        )
297        .await?,
298    )
299  }
300
301  /// Subscribe to new finalized blocks.
302  pub async fn subscribe_finalized_blocks(&self) -> Result<Subscription<Header>> {
303    Ok(
304      self
305        .subscribe(
306          "chain_subscribeFinalizedHeads",
307          rpc_params!(),
308          "chain_unsubscribeFinalizedHeads",
309        )
310        .await?,
311    )
312  }
313
314  /// Submit and watch a transaction.
315  pub async fn submit_and_watch(&self, tx_hex: String) -> Result<Subscription<TransactionStatus>> {
316    Ok(
317      self
318        .subscribe(
319          "author_submitAndWatchExtrinsic",
320          rpc_params!(tx_hex),
321          "author_unwatchExtrinsic",
322        )
323        .await?,
324    )
325  }
326
327  /// Make a RPC request to the node.
328  #[cfg(feature = "serde")]
329  pub async fn request<'a, R>(&self, method: &'a str, params: ArrayParams) -> Result<R>
330  where
331    R: DeserializeOwned,
332  {
333    self.inner.request(method, params).await
334  }
335
336  /// Make a batch of RPC requests to the node.
337  #[cfg(feature = "serde")]
338  pub async fn batch_request<'a, R>(
339    &self,
340    batch: BatchRequestBuilder<'a>,
341  ) -> Result<BatchResponse<'a, R>>
342  where
343    R: DeserializeOwned + Default + Clone + alloc::fmt::Debug + 'a,
344  {
345    self.inner.batch_request(batch).await
346  }
347
348  /// Subscribe to RPC updates.
349  #[cfg(feature = "serde")]
350  pub async fn subscribe<'a, Notif>(
351    &self,
352    subscribe_method: &'a str,
353    params: ArrayParams,
354    unsubscribe_method: &'a str,
355  ) -> Result<Subscription<Notif>>
356  where
357    Notif: DeserializeOwned,
358  {
359    self
360      .inner
361      .subscribe(subscribe_method, params, unsubscribe_method)
362      .await
363  }
364
365  /// Get the current finalized block hash.
366  pub async fn get_finalized_block(&self) -> Result<BlockHash> {
367    Ok(
368      self
369        .request("chain_getFinalizedHead", rpc_params!())
370        .await?,
371    )
372  }
373
374  /// Get a block.
375  pub async fn get_signed_block(&self, block: Option<BlockHash>) -> Result<Option<SignedBlock>> {
376    Ok(self.request("chain_getBlock", rpc_params!(block)).await?)
377  }
378
379  /// Get a block.
380  pub async fn get_block(&self, block: Option<BlockHash>) -> Result<Option<Block>> {
381    let block = self.get_signed_block(block).await?;
382    Ok(block.map(|b| b.block))
383  }
384
385  /// Get find extrinsic index in block.
386  /// The extrinsic index is used to filter the block events for that extrinsic.
387  pub async fn find_extrinsic_block_index(
388    &self,
389    block: BlockHash,
390    tx_hash: TxHash,
391  ) -> Result<Option<usize>> {
392    let block = self.get_block(Some(block)).await?;
393    Ok(block.and_then(|b| b.find_extrinsic(tx_hash)))
394  }
395
396  /// Get the header of a block.
397  pub async fn get_block_header(&self, block: Option<BlockHash>) -> Result<Option<Header>> {
398    self.inner.get_block_header(block).await
399  }
400
401  /// Get the block hash for a `block_number`.
402  pub async fn get_block_hash(&self, block_number: u32) -> Result<Option<BlockHash>> {
403    self.inner.get_block_hash(block_number).await
404  }
405
406  /// Subscribe to RuntimeVersion updates.
407  pub async fn subscribe_runtime_version(&self) -> Result<Subscription<RuntimeVersion>> {
408    Ok(
409      self
410        .subscribe(
411          "chain_subscribeRuntimeVersion",
412          rpc_params!(),
413          "chain_unsubscribeRuntimeVersion",
414        )
415        .await?,
416    )
417  }
418
419  /// Get the RuntimeVersion of a block.
420  pub async fn get_block_runtime_version(
421    &self,
422    block: Option<BlockHash>,
423  ) -> Result<Option<RuntimeVersion>> {
424    self.inner.get_block_runtime_version(block).await
425  }
426
427  /// Get the RuntimeMetadata of a block.
428  #[cfg(feature = "type_info")]
429  pub async fn get_block_metadata(
430    &self,
431    block: Option<BlockHash>,
432  ) -> Result<Option<RuntimeMetadataPrefixed>> {
433    self.inner.get_block_metadata(block).await
434  }
435}