polymesh_api_client_extras/
user.rs1use 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 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 users.push(PolymeshUser::new(name)?);
87 }
88 self.onboard_users(&mut users).await?;
89 Ok(users)
90 }
91
92 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 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 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 user.did.is_none() {
121 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 let balance = balances[idx];
133 if balance < self.init_polyx {
134 #[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 if batches.len() == 0 {
178 return Ok(());
179 }
180 for mut res in batches {
182 res.wait_finalized().await?;
183 }
184 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 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}