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