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  pub async fn get_account_info(
189    &self,
190    account: AccountId,
191  ) -> Result<AccountInfo<u32, AccountData>> {
192    Ok(self.api.query().system().account(account).await?)
193  }
194
195  pub async fn get_account_balance(&self, account: AccountId) -> Result<u128> {
196    self
197      .get_account_info(account)
198      .await
199      .map(|info| info.data.free)
200  }
201
202  async fn get_did_and_balance(
203    &self,
204    account: AccountId,
205    need_did: bool,
206  ) -> Result<(Option<IdentityId>, u128)> {
207    let did: Option<IdentityId> = if need_did {
208      self.get_did(account).await?
209    } else {
210      None
211    };
212    let balance = self.get_account_balance(account).await?;
213    Ok((did, balance))
214  }
215
216  pub async fn register_and_fund(&mut self, account: AccountId) -> Result<IdentityId> {
217    let did = match self.get_did(account).await? {
218      Some(did) => did,
219      None => {
220        // `account` is not linked to an identity.
221        // Create a new identity with `account` as the primary key.
222        let mut res = self
223          .api
224          .call()
225          .utility()
226          .batch(vec![
227            self
228              .api
229              .call()
230              .identity()
231              .cdd_register_did_with_cdd(account, vec![], None)?
232              .into(),
233            self
234              .api
235              .call()
236              .balances()
237              .transfer(account.into(), self.init_polyx)?
238              .into(),
239          ])?
240          .execute(&mut self.cdd)
241          .await?;
242        get_identity_id(&mut res).await?.unwrap()
243      }
244    };
245    Ok(did)
246  }
247}
248
249#[derive(Clone)]
250pub struct PolymeshUser {
251  pub name: String,
252  signer: DefaultSigner,
253  pub did: Option<IdentityId>,
254}
255
256impl Deref for PolymeshUser {
257  type Target = DefaultSigner;
258
259  fn deref(&self) -> &Self::Target {
260    &self.signer
261  }
262}
263
264impl DerefMut for PolymeshUser {
265  fn deref_mut(&mut self) -> &mut Self::Target {
266    &mut self.signer
267  }
268}
269
270impl PolymeshUser {
271  pub fn new(name: &str) -> Result<Self> {
272    Ok(Self {
273      name: name.to_string(),
274      signer: DefaultSigner::from_string(&format!("//{name}"), None)?,
275      did: None,
276    })
277  }
278
279  pub fn from_signer(name: &str, signer: DefaultSigner) -> Self {
280    Self {
281      name: name.to_string(),
282      signer,
283      did: None,
284    }
285  }
286}