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, 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 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 users.push(PolymeshUser::new(name)?);
83 }
84 self.onboard_users(&mut users).await?;
85 Ok(users)
86 }
87
88 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 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 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 user.did.is_none() {
117 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 let balance = balances[idx];
129 if balance < self.init_polyx {
130 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 if batches.len() == 0 {
164 return Ok(());
165 }
166 for mut res in batches {
168 res.wait_finalized().await?;
169 }
170 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 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}