1use core::future::Future;
2
3use calimero_context_config::client::env::config::ContextConfig;
4use calimero_context_config::repr::{Repr, ReprBytes, ReprTransmute};
5use calimero_context_config::types::{self as types, BlockHeight, Capability, SignedRevealPayload};
6use calimero_primitives::application::{Application, ApplicationBlob};
7use calimero_primitives::blobs::BlobId;
8use calimero_primitives::context::ContextId;
9use calimero_primitives::identity::{PrivateKey, PublicKey};
10use eyre::{bail, OptionExt};
11
12use super::ExternalClient;
13
14const MAX_RETRIES: u8 = 3;
15
16#[derive(Debug)]
17pub struct ExternalConfigClient<'a> {
18 nonce: Option<u64>,
19 client: &'a ExternalClient<'a>,
20}
21
22impl ExternalClient<'_> {
23 #[must_use]
24 pub const fn config(&self) -> ExternalConfigClient<'_> {
25 ExternalConfigClient {
26 nonce: None,
27 client: self,
28 }
29 }
30}
31
32impl ExternalConfigClient<'_> {
33 pub async fn fetch_nonce(&self, public_key: &PublicKey) -> eyre::Result<u64> {
34 let client = self.client.query::<ContextConfig>(
35 self.client.config.protocol.as_ref().into(),
36 self.client.config.network_id.as_ref().into(),
37 self.client.config.contract_id.as_ref().into(),
38 );
39
40 let context_id = self.client.context_id.rt().expect("infallible conversion");
41 let public_key = public_key.rt().expect("infallible conversion");
42
43 let nonce = client
44 .fetch_nonce(context_id, public_key)
45 .await?
46 .ok_or_eyre("not a member of the context")?;
47
48 Ok(nonce)
49 }
50
51 async fn with_nonce<T, E, F>(
52 &mut self,
53 public_key: &PublicKey,
54 f: impl Fn(u64) -> F,
55 ) -> eyre::Result<T>
56 where
57 E: Into<eyre::Report>,
58 F: Future<Output = Result<T, E>>,
59 {
60 let retries = MAX_RETRIES + u8::from(self.nonce.is_none());
61
62 for _ in 0..=retries {
63 let mut error = None;
64
65 if let Some(nonce) = self.nonce {
66 match f(nonce).await {
67 Ok(value) => return Ok(value),
68 Err(err) => error = Some(err),
69 }
70 }
71
72 let old = self.nonce;
73
74 self.nonce = Some(self.fetch_nonce(public_key).await?);
75
76 if let Some(err) = error {
77 if old == self.nonce {
78 return Err(err.into());
79 }
80 }
81 }
82
83 bail!("max retries exceeded");
84 }
85
86 pub async fn add_context(
87 &self,
88 context_secret: &PrivateKey,
89 identity: &PublicKey,
90 application: &Application,
91 ) -> eyre::Result<()> {
92 let client = self.client.mutate::<ContextConfig>(
93 self.client.config.protocol.as_ref().into(),
94 self.client.config.network_id.as_ref().into(),
95 self.client.config.contract_id.as_ref().into(),
96 );
97
98 client
99 .add_context(
100 self.client.context_id.rt().expect("infallible conversion"),
101 identity.rt().expect("infallible conversion"),
102 types::Application::new(
103 application.id.rt().expect("infallible conversion"),
104 application
105 .blob
106 .bytecode
107 .rt()
108 .expect("infallible conversion"),
109 application.size,
110 types::ApplicationSource(application.source.to_string().into()),
111 types::ApplicationMetadata(Repr::new(application.metadata.as_slice().into())),
112 ),
113 )
114 .send(**context_secret, 0)
115 .await?;
116
117 Ok(())
118 }
119
120 pub async fn update_application(
121 &mut self,
122 public_key: &PublicKey,
123 application: &Application,
124 ) -> eyre::Result<()> {
125 let identity = self
126 .client
127 .context_client()
128 .get_identity(&self.client.context_id, public_key)?
129 .ok_or_eyre("identity not found")?;
130
131 let private_key = identity.private_key()?;
132
133 self.with_nonce(public_key, async |nonce| {
134 let client = self.client.mutate::<ContextConfig>(
135 self.client.config.protocol.as_ref().into(),
136 self.client.config.network_id.as_ref().into(),
137 self.client.config.contract_id.as_ref().into(),
138 );
139
140 client
141 .update_application(
142 self.client.context_id.rt().expect("infallible conversion"),
143 types::Application::new(
144 application.id.rt().expect("infallible conversion"),
145 application
146 .blob
147 .bytecode
148 .rt()
149 .expect("infallible conversion"),
150 application.size,
151 types::ApplicationSource(application.source.to_string().into()),
152 types::ApplicationMetadata(Repr::new(
153 application.metadata.as_slice().into(),
154 )),
155 ),
156 )
157 .send(**private_key, nonce)
158 .await
159 })
160 .await?;
161
162 Ok(())
163 }
164
165 pub async fn add_members(
166 &mut self,
167 public_key: &PublicKey,
168 identities: &[PublicKey],
169 ) -> eyre::Result<()> {
170 let identity = self
171 .client
172 .context_client()
173 .get_identity(&self.client.context_id, public_key)?
174 .ok_or_eyre("identity not found")?;
175
176 let private_key = identity.private_key()?;
177
178 let identities = identities
179 .iter()
180 .map(|e| e.rt())
181 .collect::<Result<Vec<_>, _>>()
182 .expect("infallible conversion");
183
184 self.with_nonce(public_key, async |nonce| {
185 let client = self.client.mutate::<ContextConfig>(
186 self.client.config.protocol.as_ref().into(),
187 self.client.config.network_id.as_ref().into(),
188 self.client.config.contract_id.as_ref().into(),
189 );
190
191 client
192 .add_members(
193 self.client.context_id.rt().expect("infallible conversion"),
194 &identities,
195 )
196 .send(**private_key, nonce)
197 .await
198 })
199 .await?;
200
201 Ok(())
202 }
203
204 pub async fn join_context_commit_invitation(
207 &mut self,
208 public_key: &PublicKey,
209 commitment_hash: String,
210 expiration_block_height: BlockHeight,
211 ) -> eyre::Result<()> {
212 if self.client.config.protocol != "near" {
213 bail!("Only NEAR Protocol currently supports open invitaitons");
214 }
215
216 let identity = self
219 .client
220 .context_client()
221 .get_identity(&ContextId::zero(), public_key)?
222 .ok_or_eyre("identity not found")?;
223
224 let private_key = identity.private_key()?;
225
226 let client = self.client.mutate::<ContextConfig>(
227 self.client.config.protocol.as_ref().into(),
228 self.client.config.network_id.as_ref().into(),
229 self.client.config.contract_id.as_ref().into(),
230 );
231
232 let context_id = self.client.context_id.rt().expect("infallible conversion");
233
234 let nonce = 0;
235 let _ignored = client
236 .commit_invitation(context_id, commitment_hash.clone(), expiration_block_height)
237 .send(**private_key, nonce)
238 .await;
239
240 Ok(())
241 }
242
243 pub async fn join_context_reveal_invitation(
246 &mut self,
247 public_key: &PublicKey,
248 payload: SignedRevealPayload,
249 ) -> eyre::Result<()> {
250 if self.client.config.protocol != "near" {
251 bail!("Only NEAR Protocol currently supports open invitaitons");
252 }
253
254 let identity = self
257 .client
258 .context_client()
259 .get_identity(&ContextId::zero(), public_key)?
260 .ok_or_eyre("identity not found")?;
261
262 let private_key = identity.private_key()?;
263
264 let client = self.client.mutate::<ContextConfig>(
265 self.client.config.protocol.as_ref().into(),
266 self.client.config.network_id.as_ref().into(),
267 self.client.config.contract_id.as_ref().into(),
268 );
269
270 let context_id = self.client.context_id.rt().expect("infallible conversion");
271
272 let nonce = 0;
273 let _ignored = client
275 .reveal_invitation(context_id, payload.clone())
276 .send(**private_key, nonce)
277 .await;
278
279 Ok(())
280 }
281
282 pub async fn remove_members(
283 &mut self,
284 public_key: &PublicKey,
285 identities: &[PublicKey],
286 ) -> eyre::Result<()> {
287 let identity = self
288 .client
289 .context_client()
290 .get_identity(&self.client.context_id, public_key)?
291 .ok_or_eyre("identity not found")?;
292
293 let private_key = identity.private_key()?;
294
295 let identities = identities
296 .iter()
297 .map(|e| e.rt())
298 .collect::<Result<Vec<_>, _>>()
299 .expect("infallible conversion");
300
301 self.with_nonce(public_key, async |nonce| {
302 let client = self.client.mutate::<ContextConfig>(
303 self.client.config.protocol.as_ref().into(),
304 self.client.config.network_id.as_ref().into(),
305 self.client.config.contract_id.as_ref().into(),
306 );
307
308 client
309 .remove_members(
310 self.client.context_id.rt().expect("infallible conversion"),
311 &identities,
312 )
313 .send(**private_key, nonce)
314 .await
315 })
316 .await?;
317
318 Ok(())
319 }
320
321 pub async fn grant(
322 &mut self,
323 public_key: &PublicKey,
324 capabilities: &[(PublicKey, Capability)],
325 ) -> eyre::Result<()> {
326 let identity = self
327 .client
328 .context_client()
329 .get_identity(&self.client.context_id, public_key)?
330 .ok_or_eyre("identity not found")?;
331
332 let private_key = identity.private_key()?;
333
334 let capabilities = capabilities
335 .iter()
336 .map(|(who, cap)| who.rt().map(|who| (who, *cap)))
337 .collect::<Result<Vec<_>, _>>()
338 .expect("infallible conversion");
339
340 self.with_nonce(public_key, async |nonce| {
341 let client = self.client.mutate::<ContextConfig>(
342 self.client.config.protocol.as_ref().into(),
343 self.client.config.network_id.as_ref().into(),
344 self.client.config.contract_id.as_ref().into(),
345 );
346
347 client
348 .grant(
349 self.client.context_id.rt().expect("infallible conversion"),
350 &capabilities,
351 )
352 .send(**private_key, nonce)
353 .await
354 })
355 .await?;
356
357 Ok(())
358 }
359
360 pub async fn revoke(
361 &mut self,
362 public_key: &PublicKey,
363 capabilities: &[(PublicKey, Capability)],
364 ) -> eyre::Result<()> {
365 let identity = self
366 .client
367 .context_client()
368 .get_identity(&self.client.context_id, public_key)?
369 .ok_or_eyre("identity not found")?;
370
371 let private_key = identity.private_key()?;
372
373 let capabilities = capabilities
374 .iter()
375 .map(|(who, cap)| who.rt().map(|who| (who, *cap)))
376 .collect::<Result<Vec<_>, _>>()
377 .expect("infallible conversion");
378
379 self.with_nonce(public_key, async |nonce| {
380 let client = self.client.mutate::<ContextConfig>(
381 self.client.config.protocol.as_ref().into(),
382 self.client.config.network_id.as_ref().into(),
383 self.client.config.contract_id.as_ref().into(),
384 );
385
386 client
387 .revoke(
388 self.client.context_id.rt().expect("infallible conversion"),
389 &capabilities,
390 )
391 .send(**private_key, nonce)
392 .await
393 })
394 .await?;
395
396 Ok(())
397 }
398
399 pub async fn update_proxy_contract(&mut self, public_key: &PublicKey) -> eyre::Result<()> {
400 let identity = self
401 .client
402 .context_client()
403 .get_identity(&self.client.context_id, public_key)?
404 .ok_or_eyre("identity not found")?;
405
406 let private_key = identity.private_key()?;
407
408 self.with_nonce(public_key, async |nonce| {
409 let client = self.client.mutate::<ContextConfig>(
410 self.client.config.protocol.as_ref().into(),
411 self.client.config.network_id.as_ref().into(),
412 self.client.config.contract_id.as_ref().into(),
413 );
414
415 client
416 .update_proxy_contract(self.client.context_id.rt().expect("infallible conversion"))
417 .send(**private_key, nonce)
418 .await
419 })
420 .await?;
421
422 Ok(())
423 }
424
425 pub async fn application(&self) -> eyre::Result<Application> {
426 let client = self.client.query::<ContextConfig>(
427 self.client.config.protocol.as_ref().into(),
428 self.client.config.network_id.as_ref().into(),
429 self.client.config.contract_id.as_ref().into(),
430 );
431
432 let application = client
433 .application(self.client.context_id.rt().expect("infallible conversion"))
434 .await?;
435
436 let application = Application::new(
437 application.id.as_bytes().into(),
438 ApplicationBlob {
439 bytecode: application.blob.as_bytes().into(),
440 compiled: BlobId::from([0; 32]),
441 },
442 application.size,
443 application.source.0.parse()?,
444 application.metadata.0.into_inner().into_owned(),
445 );
446
447 Ok(application)
448 }
449
450 pub async fn application_revision(&self) -> eyre::Result<u64> {
451 let client = self.client.query::<ContextConfig>(
452 self.client.config.protocol.as_ref().into(),
453 self.client.config.network_id.as_ref().into(),
454 self.client.config.contract_id.as_ref().into(),
455 );
456
457 let revision = client
458 .application_revision(self.client.context_id.rt().expect("infallible conversion"))
459 .await?;
460
461 Ok(revision)
462 }
463
464 pub async fn members(&self, offset: usize, length: usize) -> eyre::Result<Vec<PublicKey>> {
465 let client = self.client.query::<ContextConfig>(
466 self.client.config.protocol.as_ref().into(),
467 self.client.config.network_id.as_ref().into(),
468 self.client.config.contract_id.as_ref().into(),
469 );
470
471 let members = client
472 .members(
473 self.client.context_id.rt().expect("infallible conversion"),
474 offset,
475 length,
476 )
477 .await?;
478
479 let members = members
480 .into_iter()
481 .map(|identity| identity.as_bytes().into())
482 .collect();
483
484 Ok(members)
485 }
486
487 pub async fn has_member(&self, identity: &PublicKey) -> eyre::Result<bool> {
488 let client = self.client.query::<ContextConfig>(
489 self.client.config.protocol.as_ref().into(),
490 self.client.config.network_id.as_ref().into(),
491 self.client.config.contract_id.as_ref().into(),
492 );
493
494 let has_member = client
495 .has_member(
496 self.client.context_id.rt().expect("infallible conversion"),
497 identity.rt().expect("infallible conversion"),
498 )
499 .await?;
500
501 Ok(has_member)
502 }
503
504 pub async fn members_revision(&self) -> eyre::Result<u64> {
505 let client = self.client.query::<ContextConfig>(
506 self.client.config.protocol.as_ref().into(),
507 self.client.config.network_id.as_ref().into(),
508 self.client.config.contract_id.as_ref().into(),
509 );
510
511 let revision = client
512 .members_revision(self.client.context_id.rt().expect("infallible conversion"))
513 .await?;
514
515 Ok(revision)
516 }
517
518 pub async fn get_proxy_contract(&self) -> eyre::Result<String> {
551 let client = self.client.query::<ContextConfig>(
552 self.client.config.protocol.as_ref().into(),
553 self.client.config.network_id.as_ref().into(),
554 self.client.config.contract_id.as_ref().into(),
555 );
556
557 let proxy_contract = client
558 .get_proxy_contract(self.client.context_id.rt().expect("infallible conversion"))
559 .await?;
560
561 Ok(proxy_contract)
562 }
563}