polymesh_api_client_extras/
user.rs

1use core::ops::{Deref, DerefMut};
2
3use polymesh_api::{
4  client::{dev, AccountId, DefaultSigner, IdentityId, Result, Signer},
5  polymesh::types::{
6    frame_system::AccountInfo,
7    polymesh_primitives::secondary_key::KeyRecord,
8  },
9  Api,
10};
11#[cfg(not(feature = "polymesh_v8"))]
12use polymesh_api::polymesh::types::pallet_balances::AccountData;
13#[cfg(feature = "polymesh_v8")]
14use polymesh_api::polymesh::types::pallet_balances::types::AccountData;
15
16use crate::*;
17
18#[derive(Clone)]
19pub struct PolymeshHelper {
20  api: Api,
21  pub init_polyx: u128,
22  pub cdd: DefaultSigner,
23}
24
25impl PolymeshHelper {
26  pub async fn new(url: &str) -> Result<Self> {
27    let api = Api::new(url).await?;
28    Ok(Self {
29      api,
30      init_polyx: 100_000 * ONE_POLYX,
31      cdd: dev::alice(),
32    })
33  }
34
35  async fn batch_load_did_balance(&self, users: &mut [PolymeshUser]) -> Result<Vec<u128>> {
36    let mut balances = Vec::with_capacity(users.len());
37    for users in users.chunks_mut(200) {
38      let mut tasks = Vec::with_capacity(200);
39      for user in users.iter() {
40        let account = user.account();
41        let need_did = user.did.is_none();
42        let helper = self.clone();
43        let task = tokio::spawn(async move { helper.get_did_and_balance(account, need_did).await });
44        tasks.push(task);
45      }
46      for (idx, task) in tasks.into_iter().enumerate() {
47        let (did, balance) = task.await.unwrap()?;
48        if did.is_some() {
49          users[idx].did = did;
50        }
51        balances.push(balance);
52      }
53    }
54    Ok(balances)
55  }
56
57  async fn batch_load_dids(&self, users: &mut [PolymeshUser]) -> Result<()> {
58    for users in users.chunks_mut(200) {
59      let mut tasks = Vec::with_capacity(200);
60      for (idx, user) in users.iter().enumerate() {
61        let account = user.account();
62        let helper = self.clone();
63        if user.did.is_none() {
64          let task = tokio::spawn(async move { helper.get_did(account).await });
65          tasks.push((idx, task));
66        }
67      }
68      for (idx, task) in tasks {
69        let did = task.await.unwrap()?;
70        if did.is_some() {
71          users[idx].did = did;
72        }
73      }
74    }
75    Ok(())
76  }
77
78  /// Generate new users from names.
79  ///
80  /// The users will be onboarded with the CDD provider, if they don't have an identity.
81  /// Also they will be funded with `init_polyx` POLYX.
82  pub async fn generate_named_users(&mut self, names: &[&str]) -> Result<Vec<PolymeshUser>> {
83    let mut users = Vec::with_capacity(names.len());
84    for name in names {
85      // Get or create user.
86      users.push(PolymeshUser::new(name)?);
87    }
88    self.onboard_users(&mut users).await?;
89    Ok(users)
90  }
91
92  /// Generate new users from prefix.
93  ///
94  /// The users will be onboarded with the CDD provider, if they don't have an identity.
95  /// Also they will be funded with `init_polyx` POLYX.
96  pub async fn generate_prefix_users(
97    &mut self,
98    prefix: &str,
99    count: usize,
100  ) -> Result<Vec<PolymeshUser>> {
101    let mut users = Vec::with_capacity(count);
102    for idx in 0..count {
103      // Get or create user.
104      users.push(PolymeshUser::new(&format!("{prefix}_{idx}"))?);
105    }
106    self.onboard_users(&mut users).await?;
107    Ok(users)
108  }
109
110  async fn onboard_users(&mut self, users: &mut [PolymeshUser]) -> Result<()> {
111    let mut batches = Vec::new();
112    for users in users.chunks_mut(200) {
113      let balances = self.batch_load_did_balance(users).await?;
114      // Calls for registering users and funding them.
115      let mut did_calls = Vec::new();
116      let mut fund_calls = Vec::new();
117      for (idx, user) in users.iter_mut().enumerate() {
118        let account = user.account();
119        // If the user doesn't have an identity, then register them.
120        if user.did.is_none() {
121          // User needs an identity.
122          did_calls.push(
123            self
124              .api
125              .call()
126              .identity()
127              .cdd_register_did_with_cdd(account, vec![], None)?
128              .into(),
129          );
130        }
131        // Add calls to fund the users.
132        let balance = balances[idx];
133        if balance < self.init_polyx {
134          // Transfer some funds to the user.
135          #[cfg(not(feature = "polymesh_v8"))]
136          fund_calls.push(
137            self
138              .api
139              .call()
140              .balances()
141              .set_balance(account.into(), self.init_polyx, 0)?
142              .into(),
143          );
144          #[cfg(feature = "polymesh_v8")]
145          fund_calls.push(
146            self
147              .api
148              .call()
149              .balances()
150              .force_set_balance(account.into(), self.init_polyx)?
151              .into(),
152          );
153        }
154      }
155      if did_calls.len() > 0 {
156        let res = self
157          .api
158          .call()
159          .utility()
160          .batch(did_calls)?
161          .submit_and_watch(&mut self.cdd)
162          .await?;
163        batches.push(res);
164      }
165      if fund_calls.len() > 0 {
166        let res = self
167          .api
168          .call()
169          .sudo()
170          .sudo(self.api.call().utility().batch(fund_calls)?.into())?
171          .submit_and_watch(&mut self.cdd)
172          .await?;
173        batches.push(res);
174      }
175    }
176    // Check if we have any batches to process.
177    if batches.len() == 0 {
178      return Ok(());
179    }
180    // Wait for all batches to be finalized.
181    for mut res in batches {
182      res.wait_finalized().await?;
183    }
184    // Get new identities.
185    self.batch_load_dids(users).await?;
186    Ok(())
187  }
188
189  pub async fn key_records(&self, account: AccountId) -> Result<Option<KeyRecord<AccountId>>> {
190    Ok(self.api.query().identity().key_records(account).await?)
191  }
192
193  pub async fn get_did(&self, account: AccountId) -> Result<Option<IdentityId>> {
194    let did = match self.key_records(account).await? {
195      Some(KeyRecord::PrimaryKey(did)) => Some(did),
196      Some(KeyRecord::SecondaryKey(did)) => Some(did),
197      _ => None,
198    };
199    Ok(did)
200  }
201
202  #[cfg(not(feature = "polymesh_v8"))]
203  pub async fn get_account_info(
204    &self,
205    account: AccountId,
206  ) -> Result<AccountInfo<u32, AccountData>> {
207    Ok(self.api.query().system().account(account).await?)
208  }
209
210  #[cfg(feature = "polymesh_v8")]
211  pub async fn get_account_info(
212    &self,
213    account: AccountId,
214  ) -> Result<AccountInfo<u32, AccountData<u128>>> {
215    Ok(self.api.query().system().account(account).await?)
216  }
217
218  pub async fn get_account_balance(&self, account: AccountId) -> Result<u128> {
219    self
220      .get_account_info(account)
221      .await
222      .map(|info| info.data.free)
223  }
224
225  async fn get_did_and_balance(
226    &self,
227    account: AccountId,
228    need_did: bool,
229  ) -> Result<(Option<IdentityId>, u128)> {
230    let did: Option<IdentityId> = if need_did {
231      self.get_did(account).await?
232    } else {
233      None
234    };
235    let balance = self.get_account_balance(account).await?;
236    Ok((did, balance))
237  }
238
239  pub async fn register_and_fund(&mut self, account: AccountId) -> Result<IdentityId> {
240    let did = match self.get_did(account).await? {
241      Some(did) => did,
242      None => {
243        // `account` is not linked to an identity.
244        // Create a new identity with `account` as the primary key.
245        let mut res = self
246          .api
247          .call()
248          .utility()
249          .batch(vec![
250            self
251              .api
252              .call()
253              .identity()
254              .cdd_register_did_with_cdd(account, vec![], None)?
255              .into(),
256            self
257              .api
258              .call()
259              .balances()
260              .transfer_with_memo(account.into(), self.init_polyx, None)?
261              .into(),
262          ])?
263          .execute(&mut self.cdd)
264          .await?;
265        get_identity_id(&mut res).await?.unwrap()
266      }
267    };
268    Ok(did)
269  }
270}
271
272#[derive(Clone)]
273pub struct PolymeshUser {
274  pub name: String,
275  signer: DefaultSigner,
276  pub did: Option<IdentityId>,
277}
278
279impl Deref for PolymeshUser {
280  type Target = DefaultSigner;
281
282  fn deref(&self) -> &Self::Target {
283    &self.signer
284  }
285}
286
287impl DerefMut for PolymeshUser {
288  fn deref_mut(&mut self) -> &mut Self::Target {
289    &mut self.signer
290  }
291}
292
293impl PolymeshUser {
294  pub fn new(name: &str) -> Result<Self> {
295    Ok(Self {
296      name: name.to_string(),
297      signer: DefaultSigner::from_string(&format!("//{name}"), None)?,
298      did: None,
299    })
300  }
301
302  pub fn from_signer(name: &str, signer: DefaultSigner) -> Self {
303    Self {
304      name: name.to_string(),
305      signer,
306      did: None,
307    }
308  }
309}