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    };
104    let era = match lifetime {
105      Some(0) => Era::immortal(),
106      lifetime => {
107        let current = self
108          .get_block_header(None)
109          .await?
110          .ok_or_else(|| Error::RpcClient("Failed to get current block".into()))?;
111        let number = current.number;
112        // Need to use the current block hash.
113        addititional.current_hash = current.hash();
114        Era::mortal(number, lifetime)
115      }
116    };
117
118    Ok((addititional, era))
119  }
120
121  #[cfg(feature = "serde")]
122  async fn request<'a, R>(&self, method: &'a str, params: ArrayParams) -> Result<R>
123  where
124    R: DeserializeOwned,
125  {
126    self.rpc.request(method, params).await
127  }
128
129  #[cfg(feature = "serde")]
130  async fn batch_request<'a, R>(
131    &self,
132    batch: BatchRequestBuilder<'a>,
133  ) -> Result<BatchResponse<'a, R>>
134  where
135    R: DeserializeOwned + Default + Clone + alloc::fmt::Debug + 'a,
136  {
137    self.rpc.batch_request(batch).await
138  }
139
140  #[cfg(feature = "serde")]
141  async fn subscribe<'a, Notif>(
142    &self,
143    subscribe_method: &'a str,
144    params: ArrayParams,
145    unsubscribe_method: &'a str,
146  ) -> Result<Subscription<Notif>>
147  where
148    Notif: DeserializeOwned,
149  {
150    self
151      .rpc
152      .subscribe(subscribe_method, params, unsubscribe_method)
153      .await
154  }
155
156  async fn rpc_get_block_hash(rpc: &RpcClient, block_number: u32) -> Result<Option<BlockHash>> {
157    let params = rpc_params!(block_number);
158    Ok(rpc.request("chain_getBlockHash", params).await?)
159  }
160
161  /// Get the header of a block.
162  async fn get_block_header(&self, block: Option<BlockHash>) -> Result<Option<Header>> {
163    Ok(self.request("chain_getHeader", rpc_params!(block)).await?)
164  }
165
166  /// Get the block hash for a `block_number`.
167  async fn get_block_hash(&self, block_number: u32) -> Result<Option<BlockHash>> {
168    Self::rpc_get_block_hash(&self.rpc, block_number).await
169  }
170
171  async fn rpc_get_runtime_version(
172    rpc: &RpcClient,
173    block: Option<BlockHash>,
174  ) -> Result<Option<RuntimeVersion>> {
175    let params = rpc_params!(block);
176    rpc.request("state_getRuntimeVersion", params).await
177  }
178
179  /// Get the RuntimeVersion of a block.
180  async fn get_block_runtime_version(
181    &self,
182    block: Option<BlockHash>,
183  ) -> Result<Option<RuntimeVersion>> {
184    Self::rpc_get_runtime_version(&self.rpc, block).await
185  }
186
187  #[cfg(feature = "type_info")]
188  async fn rpc_get_metadata(
189    rpc: &RpcClient,
190    block: Option<BlockHash>,
191  ) -> Result<Option<RuntimeMetadataPrefixed>> {
192    use hex::FromHex;
193    let params = rpc_params!(block);
194    let hex: String = rpc.request("state_getMetadata", params).await?;
195
196    let bytes = Vec::from_hex(&hex[2..])?;
197    Ok(Some(RuntimeMetadataPrefixed::decode(
198      &mut bytes.as_slice(),
199    )?))
200  }
201
202  /// Get the RuntimeMetadata of a block.
203  #[cfg(feature = "type_info")]
204  async fn get_block_metadata(
205    &self,
206    block: Option<BlockHash>,
207  ) -> Result<Option<RuntimeMetadataPrefixed>> {
208    Self::rpc_get_metadata(&self.rpc, block).await
209  }
210}
211
212#[derive(Clone)]
213pub struct Client {
214  inner: Arc<InnerClient>,
215}
216
217impl Client {
218  pub async fn new(url: &str) -> Result<Self> {
219    Ok(Self {
220      inner: Arc::new(InnerClient::new(url).await?),
221    })
222  }
223
224  pub fn get_transaction_version(&self) -> i64 {
225    self.inner.get_transaction_version()
226  }
227
228  #[cfg(feature = "type_info")]
229  pub fn get_metadata(&self) -> &RuntimeMetadataPrefixed {
230    self.inner.get_metadata()
231  }
232
233  pub fn get_genesis_hash(&self) -> BlockHash {
234    self.inner.get_genesis_hash()
235  }
236
237  pub async fn get_additional_signed(
238    &self,
239    lifetime: Option<u64>,
240  ) -> Result<(AdditionalSigned, Era)> {
241    self.inner.get_additional_signed(lifetime).await
242  }
243
244  /// Get the `SystemProperties` of the chain.
245  pub async fn get_system_properties(&self) -> Result<SystemProperties> {
246    self.request("system_properties", rpc_params!()).await
247  }
248
249  pub async fn get_storage_keys_paged(
250    &self,
251    prefix: &StorageKey,
252    count: usize,
253    start_key: Option<&StorageKey>,
254    at: Option<BlockHash>,
255  ) -> Result<Vec<StorageKey>> {
256    let params = rpc_params!(prefix, count, start_key.unwrap_or(prefix), at);
257    self
258      .request::<Vec<StorageKey>>("state_getKeysPaged", params)
259      .await
260  }
261
262  pub async fn get_storage_by_key<T: Decode>(
263    &self,
264    key: StorageKey,
265    at: Option<BlockHash>,
266  ) -> Result<Option<T>> {
267    let value = self
268      .get_storage_data_by_key(key, at)
269      .await?
270      .map(|data| T::decode(&mut data.0.as_slice()))
271      .transpose()?;
272    Ok(value)
273  }
274
275  pub async fn get_storage_data_by_key(
276    &self,
277    key: StorageKey,
278    at: Option<BlockHash>,
279  ) -> Result<Option<StorageData>> {
280    Ok(
281      self
282        .request("state_getStorage", rpc_params!(key, at))
283        .await?,
284    )
285  }
286
287  /// Subscribe to new blocks.
288  pub async fn subscribe_blocks(&self) -> Result<Subscription<Header>> {
289    Ok(
290      self
291        .subscribe(
292          "chain_subscribeNewHeads",
293          rpc_params!(),
294          "chain_unsubscribeNewHeads",
295        )
296        .await?,
297    )
298  }
299
300  /// Subscribe to new finalized blocks.
301  pub async fn subscribe_finalized_blocks(&self) -> Result<Subscription<Header>> {
302    Ok(
303      self
304        .subscribe(
305          "chain_subscribeFinalizedHeads",
306          rpc_params!(),
307          "chain_unsubscribeFinalizedHeads",
308        )
309        .await?,
310    )
311  }
312
313  /// Submit and watch a transaction.
314  pub async fn submit_and_watch(&self, tx_hex: String) -> Result<Subscription<TransactionStatus>> {
315    Ok(
316      self
317        .subscribe(
318          "author_submitAndWatchExtrinsic",
319          rpc_params!(tx_hex),
320          "author_unwatchExtrinsic",
321        )
322        .await?,
323    )
324  }
325
326  /// Make a RPC request to the node.
327  #[cfg(feature = "serde")]
328  pub async fn request<'a, R>(&self, method: &'a str, params: ArrayParams) -> Result<R>
329  where
330    R: DeserializeOwned,
331  {
332    self.inner.request(method, params).await
333  }
334
335  /// Make a batch of RPC requests to the node.
336  #[cfg(feature = "serde")]
337  pub async fn batch_request<'a, R>(
338    &self,
339    batch: BatchRequestBuilder<'a>,
340  ) -> Result<BatchResponse<'a, R>>
341  where
342    R: DeserializeOwned + Default + Clone + alloc::fmt::Debug + 'a,
343  {
344    self.inner.batch_request(batch).await
345  }
346
347  /// Subscribe to RPC updates.
348  #[cfg(feature = "serde")]
349  pub async fn subscribe<'a, Notif>(
350    &self,
351    subscribe_method: &'a str,
352    params: ArrayParams,
353    unsubscribe_method: &'a str,
354  ) -> Result<Subscription<Notif>>
355  where
356    Notif: DeserializeOwned,
357  {
358    self
359      .inner
360      .subscribe(subscribe_method, params, unsubscribe_method)
361      .await
362  }
363
364  /// Get the current finalized block hash.
365  pub async fn get_finalized_block(&self) -> Result<BlockHash> {
366    Ok(
367      self
368        .request("chain_getFinalizedHead", rpc_params!())
369        .await?,
370    )
371  }
372
373  /// Get a block.
374  pub async fn get_signed_block(&self, block: Option<BlockHash>) -> Result<Option<SignedBlock>> {
375    Ok(self.request("chain_getBlock", rpc_params!(block)).await?)
376  }
377
378  /// Get a block.
379  pub async fn get_block(&self, block: Option<BlockHash>) -> Result<Option<Block>> {
380    let block = self.get_signed_block(block).await?;
381    Ok(block.map(|b| b.block))
382  }
383
384  /// Get find extrinsic index in block.
385  /// The extrinsic index is used to filter the block events for that extrinsic.
386  pub async fn find_extrinsic_block_index(
387    &self,
388    block: BlockHash,
389    tx_hash: TxHash,
390  ) -> Result<Option<usize>> {
391    let block = self.get_block(Some(block)).await?;
392    Ok(block.and_then(|b| b.find_extrinsic(tx_hash)))
393  }
394
395  /// Get the header of a block.
396  pub async fn get_block_header(&self, block: Option<BlockHash>) -> Result<Option<Header>> {
397    self.inner.get_block_header(block).await
398  }
399
400  /// Get the block hash for a `block_number`.
401  pub async fn get_block_hash(&self, block_number: u32) -> Result<Option<BlockHash>> {
402    self.inner.get_block_hash(block_number).await
403  }
404
405  /// Subscribe to RuntimeVersion updates.
406  pub async fn subscribe_runtime_version(&self) -> Result<Subscription<RuntimeVersion>> {
407    Ok(
408      self
409        .subscribe(
410          "chain_subscribeRuntimeVersion",
411          rpc_params!(),
412          "chain_unsubscribeRuntimeVersion",
413        )
414        .await?,
415    )
416  }
417
418  /// Get the RuntimeVersion of a block.
419  pub async fn get_block_runtime_version(
420    &self,
421    block: Option<BlockHash>,
422  ) -> Result<Option<RuntimeVersion>> {
423    self.inner.get_block_runtime_version(block).await
424  }
425
426  /// Get the RuntimeMetadata of a block.
427  #[cfg(feature = "type_info")]
428  pub async fn get_block_metadata(
429    &self,
430    block: Option<BlockHash>,
431  ) -> Result<Option<RuntimeMetadataPrefixed>> {
432    self.inner.get_block_metadata(block).await
433  }
434}