hedera/client/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use std::borrow::Cow;
4use std::collections::HashMap;
5use std::fmt;
6use std::num::{
7    NonZeroU64,
8    NonZeroUsize,
9};
10use std::sync::atomic::{
11    AtomicBool,
12    AtomicU64,
13    Ordering,
14};
15use std::time::Duration;
16
17pub(crate) use network::{
18    Network,
19    NetworkData,
20};
21pub(crate) use operator::Operator;
22use parking_lot::RwLock;
23use tokio::sync::watch;
24use triomphe::Arc;
25
26use self::network::managed::ManagedNetwork;
27use self::network::mirror::MirrorNetwork;
28pub(crate) use self::network::mirror::MirrorNetworkData;
29use crate::ping_query::PingQuery;
30use crate::signer::AnySigner;
31use crate::{
32    AccountId,
33    ArcSwapOption,
34    Error,
35    Hbar,
36    LedgerId,
37    NodeAddressBook,
38    NodeAddressBookQuery,
39    PrivateKey,
40    PublicKey,
41};
42
43#[cfg(feature = "serde")]
44mod config;
45
46mod network;
47mod operator;
48
49/// Default gRPC deadline for requests and channel connection timeouts
50pub(crate) const DEFAULT_GRPC_DEADLINE: Duration = Duration::from_secs(10);
51
52/// Default request timeout for the entire operation (including retries)
53pub(crate) const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(120);
54
55#[derive(Copy, Clone)]
56pub(crate) struct ClientBackoff {
57    pub(crate) max_backoff: Duration,
58    // min backoff.
59    pub(crate) initial_backoff: Duration,
60    pub(crate) max_attempts: usize,
61    pub(crate) request_timeout: Option<Duration>,
62    pub(crate) grpc_deadline: Duration,
63}
64
65impl Default for ClientBackoff {
66    fn default() -> Self {
67        Self {
68            max_backoff: Duration::from_millis(backoff::default::MAX_INTERVAL_MILLIS),
69            initial_backoff: Duration::from_millis(backoff::default::INITIAL_INTERVAL_MILLIS),
70            max_attempts: 10,
71            request_timeout: Some(DEFAULT_REQUEST_TIMEOUT),
72            grpc_deadline: DEFAULT_GRPC_DEADLINE,
73        }
74    }
75}
76
77// yes, client is complicated enough for this, even if it's only internal.
78struct ClientBuilder {
79    network: ManagedNetwork,
80    operator: Option<Operator>,
81    max_transaction_fee: Option<NonZeroU64>,
82    max_query_payment: Option<NonZeroU64>,
83    ledger_id: Option<LedgerId>,
84    auto_validate_checksums: bool,
85    regenerate_transaction_ids: bool,
86    update_network: bool,
87    backoff: ClientBackoff,
88}
89
90impl ClientBuilder {
91    #[must_use]
92    fn new(network: ManagedNetwork) -> Self {
93        Self {
94            network,
95            operator: None,
96            max_transaction_fee: None,
97            max_query_payment: None,
98            ledger_id: None,
99            auto_validate_checksums: false,
100            regenerate_transaction_ids: true,
101            update_network: true,
102            backoff: ClientBackoff::default(),
103        }
104    }
105
106    fn disable_network_updating(self) -> Self {
107        Self { update_network: false, ..self }
108    }
109
110    fn ledger_id(self, ledger_id: Option<LedgerId>) -> Self {
111        Self { ledger_id, ..self }
112    }
113
114    fn build(self) -> Client {
115        let Self {
116            network,
117            operator,
118            max_transaction_fee,
119            max_query_payment,
120            ledger_id,
121            auto_validate_checksums,
122            regenerate_transaction_ids,
123            update_network,
124            backoff,
125        } = self;
126
127        let network_update_tx = match update_network {
128            true => network::managed::spawn_network_update(
129                network.clone(),
130                Some(Duration::from_secs(24 * 60 * 60)),
131            ),
132            // yeah, we just drop the rx.
133            false => watch::channel(None).0,
134        };
135
136        Client(Arc::new(ClientInner {
137            network,
138            operator: ArcSwapOption::new(operator.map(Arc::new)),
139            max_transaction_fee_tinybar: AtomicU64::new(
140                max_transaction_fee.map_or(0, NonZeroU64::get),
141            ),
142            max_query_payment_tinybar: AtomicU64::new(max_query_payment.map_or(0, NonZeroU64::get)),
143            ledger_id: ArcSwapOption::new(ledger_id.map(Arc::new)),
144            auto_validate_checksums: AtomicBool::new(auto_validate_checksums),
145            regenerate_transaction_ids: AtomicBool::new(regenerate_transaction_ids),
146            network_update_tx,
147            backoff: RwLock::new(backoff),
148        }))
149    }
150}
151
152struct ClientInner {
153    network: ManagedNetwork,
154    operator: ArcSwapOption<Operator>,
155    max_transaction_fee_tinybar: AtomicU64,
156    max_query_payment_tinybar: AtomicU64,
157    ledger_id: ArcSwapOption<LedgerId>,
158    auto_validate_checksums: AtomicBool,
159    regenerate_transaction_ids: AtomicBool,
160    network_update_tx: watch::Sender<Option<Duration>>,
161    backoff: RwLock<ClientBackoff>,
162}
163
164/// Managed client for use on the Hiero network.
165#[derive(Clone)]
166pub struct Client(Arc<ClientInner>);
167
168impl fmt::Debug for Client {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        // todo: put anything important here.
171        f.debug_struct("Client").finish_non_exhaustive()
172    }
173}
174
175impl Client {
176    #[cfg(feature = "serde")]
177    fn from_config_data(config: config::ClientConfig) -> crate::Result<Self> {
178        let config::ClientConfig { operator, network, mirror_network } = config;
179
180        // fixme: check to ensure net and mirror net are the same when they're a network name (no other SDK actually checks this though)
181        let client = match network {
182            config::Either::Left(network) => Client::for_network(network)?,
183            config::Either::Right(it) => match it {
184                config::NetworkName::Mainnet => Client::for_mainnet(),
185                config::NetworkName::Testnet => Client::for_testnet(),
186                config::NetworkName::Previewnet => Client::for_previewnet(),
187            },
188        };
189
190        let mirror_network = mirror_network.map(|mirror_network| match mirror_network {
191            config::Either::Left(mirror_network) => {
192                MirrorNetwork::from_addresses(mirror_network.into_iter().map(Cow::Owned).collect())
193            }
194            config::Either::Right(it) => match it {
195                config::NetworkName::Mainnet => MirrorNetwork::mainnet(),
196                config::NetworkName::Testnet => MirrorNetwork::testnet(),
197                config::NetworkName::Previewnet => MirrorNetwork::previewnet(),
198            },
199        });
200
201        if let Some(operator) = operator {
202            client.0.operator.store(Some(Arc::new(operator)));
203        }
204
205        if let Some(mirror_network) = mirror_network {
206            client.set_mirror_network(mirror_network.load().addresses());
207        }
208
209        Ok(client)
210    }
211
212    /// Create a client from the given json config.
213    ///
214    /// # Errors
215    /// - [`Error::BasicParse`] if an error occurs parsing the configuration.
216    #[cfg(feature = "serde")]
217    pub fn from_config(json: &str) -> crate::Result<Self> {
218        let config = serde_json::from_str::<config::ClientConfigInner>(json)
219            .map_err(crate::Error::basic_parse)?
220            .into();
221
222        Self::from_config_data(config)
223    }
224
225    /// Returns the addresses for the configured mirror network.
226    ///
227    /// Unless _explicitly_ set, the return value isn't guaranteed to be anything in particular in order to allow future changes without breaking semver.
228    /// However, when a function such as `for_testnet` is used, _some_ valid value will be returned.
229    ///
230    /// Current return values (reminder that these are semver exempt)
231    ///
232    /// - mainnet: `["mainnet-public.mirrornode.hedera.com:443"]`
233    /// - testnet: `["testnet.mirrornode.hedera.com:443"]`
234    /// - previewnet: `["previewnet.mirrornode.hedera.com:443"]`
235    ///
236    /// # Examples
237    ///
238    /// ```
239    /// # #[tokio::main]
240    /// # async fn main() {
241    /// use hedera::Client;
242    ///
243    /// let client = Client::for_testnet();
244    ///
245    /// // note: This isn't *guaranteed* in a semver sense, but this is the current result.
246    /// let expected = Vec::from(["testnet.mirrornode.hedera.com:443".to_owned()]);
247    /// assert_eq!(expected, client.mirror_network());
248    ///
249    /// # }
250    /// ```
251    #[must_use]
252    pub fn mirror_network(&self) -> Vec<String> {
253        self.mirrornet().load().addresses().collect()
254    }
255
256    /// Sets the addresses to use for the mirror network.
257    ///
258    /// This is mostly useful if you used [`Self::for_network`] and need to set a mirror network.
259    pub fn set_mirror_network<I: IntoIterator<Item = String>>(&self, addresses: I) {
260        self.mirrornet().store(
261            MirrorNetworkData::from_addresses(addresses.into_iter().map(Cow::Owned).collect())
262                .into(),
263        );
264    }
265
266    /// Construct a client with the given nodes configured.
267    ///
268    /// Note that this disables network auto-updating.
269    ///
270    /// # Errors
271    /// - [`Error::BasicParse`] if an error occurs parsing the configuration.
272    // allowed for API compatibility.
273    #[allow(clippy::needless_pass_by_value)]
274    pub fn for_network(network: HashMap<String, AccountId>) -> crate::Result<Self> {
275        let network =
276            ManagedNetwork::new(Network::from_addresses(&network)?, MirrorNetwork::default());
277
278        Ok(ClientBuilder::new(network).disable_network_updating().build())
279    }
280
281    /// Construct a client from a select mirror network
282    pub async fn for_mirror_network(mirror_networks: Vec<String>) -> crate::Result<Self> {
283        Self::for_mirror_network_with_shard_realm(mirror_networks, 0, 0).await
284    }
285
286    /// Construct a client from a select mirror network with a specific shard and realm.
287    pub async fn for_mirror_network_with_shard_realm(
288        mirror_networks: Vec<String>,
289        shard: u64,
290        realm: u64,
291    ) -> crate::Result<Self> {
292        let network_addresses: HashMap<String, AccountId> = HashMap::new();
293        let network = ManagedNetwork::new(
294            Network::from_addresses(&network_addresses)?,
295            MirrorNetwork::from_addresses(mirror_networks.into_iter().map(Cow::Owned).collect()),
296        );
297
298        let client = ClientBuilder::new(network).build();
299        let address_book = if shard == 0 && realm == 0 {
300            NodeAddressBookQuery::default().execute(&client).await?
301        } else {
302            NodeAddressBookQuery::new().shard(shard).realm(realm).execute(&client).await?
303        };
304
305        client.set_network_from_address_book(address_book);
306
307        Ok(client)
308    }
309
310    /// Construct a Hiero client pre-configured for mainnet access.
311    #[must_use]
312    pub fn for_mainnet() -> Self {
313        ClientBuilder::new(ManagedNetwork::mainnet()).ledger_id(Some(LedgerId::mainnet())).build()
314    }
315
316    /// Construct a Hiero client pre-configured for testnet access.
317    #[must_use]
318    pub fn for_testnet() -> Self {
319        ClientBuilder::new(ManagedNetwork::testnet()).ledger_id(Some(LedgerId::testnet())).build()
320    }
321
322    /// Construct a Hiero client pre-configured for previewnet access.
323    #[must_use]
324    pub fn for_previewnet() -> Self {
325        ClientBuilder::new(ManagedNetwork::previewnet())
326            .ledger_id(Some(LedgerId::previewnet()))
327            .build()
328    }
329
330    /// Updates the network to use the given address book.
331    ///
332    /// Note: This is only really useful if you used `for_network`, because the network can auto-update.
333    ///
334    /// If network auto-updating is enabled this will eventually be overridden.
335    // allowed for API compatibility.
336    #[allow(clippy::needless_pass_by_value)]
337    pub fn set_network_from_address_book(&self, address_book: NodeAddressBook) {
338        self.net().update_from_address_book(&address_book);
339    }
340
341    /// Updates the network to use the given addresses.
342    ///
343    /// Note: This is only really useful if you used `for_network`, because the network can auto-update.
344    ///
345    /// If network auto-updating is enabled this will eventually be overridden.
346    ///
347    /// Tend to prefer [`set_network_from_address_book`](Self::set_network_from_address_book) where possible.
348    ///
349    /// # Errors
350    /// [`Error::BasicParse`](crate::Error::BasicParse) If any node address is unparsable.
351    // allowed for API compatibility.
352    #[allow(clippy::needless_pass_by_value)]
353    pub fn set_network(&self, network: HashMap<String, AccountId>) -> crate::Result<()> {
354        self.net().update_from_addresses(&network)?;
355
356        Ok(())
357    }
358
359    /// Returns the nodes associated with this client.
360    #[must_use]
361    pub fn network(&self) -> HashMap<String, AccountId> {
362        self.net().0.load().addresses()
363    }
364
365    /// Returns the max number of times a node can be retried before removing it from the network.
366    pub fn max_node_attempts(&self) -> Option<NonZeroUsize> {
367        self.net().0.load().max_node_attempts()
368    }
369
370    /// Set the max number of times a node can return a bad gRPC status before we remove it from the list.
371    pub fn set_max_node_attempts(&self, attempts: usize) {
372        self.net().0.load().set_max_node_attempts(NonZeroUsize::new(attempts))
373    }
374
375    /// Returns the max backoff interval for network nodes if gRPC response fail.    
376    pub fn max_node_backoff(&self) -> Duration {
377        self.net().0.load().max_backoff()
378    }
379
380    /// Sets max backoff interval for network nodes
381    pub fn set_max_node_backoff(&self, max_node_backoff: Duration) {
382        self.net().0.load().set_max_backoff(max_node_backoff)
383    }
384
385    /// Returns the initial backoff interval for network nodes if gRPC response fail.    
386    pub fn min_node_backoff(&self) -> Duration {
387        self.net().0.load().min_backoff()
388    }
389
390    /// Sets initial backoff interval for network nodes
391    pub fn set_min_node_backoff(&self, min_node_backoff: Duration) {
392        self.net().0.load().set_min_backoff(min_node_backoff)
393    }
394
395    /// Construct a hedera client pre-configured for access to the given network.
396    ///
397    /// Currently supported network names are `"mainnet"`, `"testnet"`, and `"previewnet"`.
398    ///
399    /// # Errors
400    /// - [`Error::BasicParse`] if the network name is not a supported network name.
401    pub fn for_name(name: &str) -> crate::Result<Self> {
402        match name {
403            "mainnet" => Ok(Self::for_mainnet()),
404            "testnet" => Ok(Self::for_testnet()),
405            "previewnet" => Ok(Self::for_previewnet()),
406            "localhost" => {
407                let mut network: HashMap<String, AccountId> = HashMap::new();
408                network.insert("127.0.0.1:50211".to_string(), AccountId::new(0, 0, 3));
409
410                let client = Client::for_network(network).unwrap();
411                client.set_mirror_network(["127.0.0.1:5600".to_string()]);
412                Ok(client)
413            }
414            _ => Err(Error::basic_parse(format!("Unknown network name {name}"))),
415        }
416    }
417
418    // optimized function to avoid allocations/pointer chasing.
419    // this shouldn't be exposed because it exposes repr.
420    pub(crate) fn ledger_id_internal(&self) -> arc_swap::Guard<Option<Arc<LedgerId>>> {
421        self.0.ledger_id.load()
422    }
423
424    /// Sets the ledger ID for the Client's network.
425    pub fn set_ledger_id(&self, ledger_id: Option<LedgerId>) {
426        self.0.ledger_id.store(ledger_id.map(Arc::new));
427    }
428
429    /// Returns true if checksums should be automatically validated.
430    #[must_use]
431    pub fn auto_validate_checksums(&self) -> bool {
432        self.0.auto_validate_checksums.load(Ordering::Relaxed)
433    }
434
435    /// Enable or disable automatic entity ID checksum validation.
436    pub fn set_auto_validate_checksums(&self, value: bool) {
437        self.0.auto_validate_checksums.store(value, Ordering::Relaxed);
438    }
439
440    /// Returns true if transaction IDs should be automatically regenerated.
441    ///
442    /// This is `true` by default.
443    #[must_use]
444    pub fn default_regenerate_transaction_id(&self) -> bool {
445        self.0.regenerate_transaction_ids.load(Ordering::Relaxed)
446    }
447
448    /// Enable or disable transaction ID regeneration.
449    pub fn set_default_regenerate_transaction_id(&self, value: bool) {
450        self.0.regenerate_transaction_ids.store(value, Ordering::Relaxed);
451    }
452
453    /// Sets the account that will, by default, be paying for transactions and queries built with
454    /// this client.
455    ///
456    /// The operator account ID is used to generate the default transaction ID for all transactions
457    /// executed with this client.
458    ///
459    /// The operator private key is used to sign all transactions executed by this client.
460    pub fn set_operator(&self, id: AccountId, key: PrivateKey) {
461        self.0
462            .operator
463            .store(Some(Arc::new(Operator { account_id: id, signer: AnySigner::PrivateKey(key) })));
464    }
465
466    /// Sets the account that will, by default, be paying for transactions and queries built with
467    /// this client.
468    ///
469    /// The operator account ID is used to generate the default transaction ID for all transactions
470    /// executed with this client.
471    ///
472    /// The operator signer is used to sign all transactions executed by this client.
473    pub fn set_operator_with<F: Fn(&[u8]) -> Vec<u8> + Send + Sync + 'static>(
474        &self,
475        id: AccountId,
476        public_key: PublicKey,
477        f: F,
478    ) {
479        self.0.operator.store(Some(Arc::new(Operator {
480            account_id: id,
481            signer: AnySigner::arbitrary(Box::new(public_key), f),
482        })));
483    }
484
485    /// Gets a reference to the configured network.
486    pub(crate) fn net(&self) -> &Network {
487        &self.0.network.primary
488    }
489
490    /// Gets a reference to the configured mirror network.
491    pub(crate) fn mirrornet(&self) -> &MirrorNetwork {
492        &self.0.network.mirror
493    }
494
495    /// Sets the maximum transaction fee to be used when no explicit max transaction fee is set.
496    ///
497    /// Note: Setting `amount` to zero is "unlimited"
498    /// # Panics
499    /// - if amount is negative
500    pub fn set_default_max_transaction_fee(&self, amount: Hbar) {
501        assert!(amount >= Hbar::ZERO);
502        self.0.max_transaction_fee_tinybar.store(amount.to_tinybars() as u64, Ordering::Relaxed);
503    }
504
505    /// Gets the maximum transaction fee the paying account is willing to pay.
506    #[must_use]
507    pub fn default_max_transaction_fee(&self) -> Option<Hbar> {
508        let val = self.0.max_transaction_fee_tinybar.load(Ordering::Relaxed);
509
510        (val > 0).then(|| Hbar::from_tinybars(val as i64))
511    }
512
513    /// Gets the maximum query fee the paying account is willing to pay.
514    #[must_use]
515    pub fn default_max_query_payment(&self) -> Option<Hbar> {
516        let val = self.0.max_query_payment_tinybar.load(Ordering::Relaxed);
517
518        (val > 0).then(|| Hbar::from_tinybars(val as i64))
519    }
520
521    /// Sets the maximum query payment to be used when no explicit max query payment is set.
522    ///
523    /// Note: Setting `amount` to zero is "unlimited"
524    /// # Panics
525    /// - if amount is negative
526    pub fn set_default_max_query_payment(&self, amount: Hbar) {
527        assert!(amount >= Hbar::ZERO);
528        self.0.max_query_payment_tinybar.store(amount.to_tinybars() as u64, Ordering::Relaxed);
529    }
530
531    /// Returns the maximum amount of time that will be spent on a request.
532    #[must_use]
533    pub fn request_timeout(&self) -> Option<Duration> {
534        self.backoff().request_timeout
535    }
536
537    /// Sets the maximum amount of time that will be spent on a request.
538    pub fn set_request_timeout(&self, timeout: Option<Duration>) {
539        self.0.backoff.write().request_timeout = timeout;
540    }
541
542    /// Returns the maximum number of attempts for a request.
543    #[must_use]
544    pub fn max_attempts(&self) -> usize {
545        self.backoff().max_attempts
546    }
547
548    /// Sets the maximum number of attempts for a request.
549    pub fn set_max_attempts(&self, max_attempts: usize) {
550        self.0.backoff.write().max_attempts = max_attempts;
551    }
552
553    /// The initial backoff for a request being executed.
554    #[doc(alias = "initial_backoff")]
555    #[must_use]
556    pub fn min_backoff(&self) -> Duration {
557        self.backoff().initial_backoff
558    }
559
560    /// Sets the initial backoff for a request being executed.
561    #[doc(alias = "set_initial_backoff")]
562    pub fn set_min_backoff(&self, max_backoff: Duration) {
563        self.0.backoff.write().max_backoff = max_backoff;
564    }
565
566    /// Returns the maximum amount of time a request will wait between attempts.
567    #[must_use]
568    pub fn max_backoff(&self) -> Duration {
569        self.backoff().max_backoff
570    }
571
572    /// Sets the maximum amount of time a request will wait between attempts.
573    pub fn set_max_backoff(&self, max_backoff: Duration) {
574        self.0.backoff.write().max_backoff = max_backoff;
575    }
576
577    /// Returns the gRPC deadline for individual requests.
578    #[must_use]
579    pub fn grpc_deadline(&self) -> Duration {
580        self.backoff().grpc_deadline
581    }
582
583    /// Sets the gRPC deadline for individual requests.
584    ///
585    /// This timeout is used both for establishing connections to nodes and for individual gRPC calls.
586    pub fn set_grpc_deadline(&self, grpc_deadline: Duration) {
587        self.0.backoff.write().grpc_deadline = grpc_deadline;
588    }
589
590    #[must_use]
591    pub(crate) fn backoff(&self) -> ClientBackoff {
592        *self.0.backoff.read()
593    }
594
595    // keep this internal (repr)
596    pub(crate) fn load_operator(&self) -> arc_swap::Guard<Option<Arc<Operator>>> {
597        self.0.operator.load()
598    }
599
600    // keep this internal (repr)
601    pub(crate) fn full_load_operator(&self) -> Option<Arc<Operator>> {
602        self.0.operator.load_full()
603    }
604
605    /// Send a ping to the given node.
606    pub async fn ping(&self, node_account_id: AccountId) -> crate::Result<()> {
607        PingQuery::new(node_account_id).execute(self, None).await
608    }
609
610    /// Send a ping to the given node, canceling the ping after `timeout` has elapsed.
611    pub async fn ping_with_timeout(
612        &self,
613        node_account_id: AccountId,
614        timeout: Duration,
615    ) -> crate::Result<()> {
616        PingQuery::new(node_account_id).execute(self, Some(timeout)).await
617    }
618
619    /// Send a ping to all nodes.
620    pub async fn ping_all(&self) -> crate::Result<()> {
621        futures_util::future::try_join_all(
622            self.net().0.load().node_ids().iter().map(|it| self.ping(*it)),
623        )
624        .await?;
625
626        Ok(())
627    }
628
629    /// Send a ping to all nodes, canceling the ping after `timeout` has elapsed.
630    pub async fn ping_all_with_timeout(&self, timeout: Duration) -> crate::Result<()> {
631        futures_util::future::try_join_all(
632            self.net().0.load().node_ids().iter().map(|it| self.ping_with_timeout(*it, timeout)),
633        )
634        .await?;
635
636        Ok(())
637    }
638
639    /// Returns the frequency at which the network will update (if it will update at all).
640    #[must_use = "this function has no side-effects"]
641    pub fn network_update_period(&self) -> Option<Duration> {
642        *self.0.network_update_tx.borrow()
643    }
644
645    /// Sets the frequency at which the network will update.
646    ///
647    /// Note that network updates will not affect any in-flight requests.
648    pub fn set_network_update_period(&self, period: Option<Duration>) {
649        self.0.network_update_tx.send_if_modified(|place| {
650            let changed = *place == period;
651            if changed {
652                *place = period;
653            }
654
655            changed
656        });
657    }
658
659    /// Triggers an immediate network update from the address book.
660    /// Note: This method is not part of the public API and may be changed or removed in future versions.
661    pub(crate) async fn refresh_network(&self) {
662        match NodeAddressBookQuery::new()
663            .execute_mirrornet(self.mirrornet().load().channel(self.grpc_deadline()), None)
664            .await
665        {
666            Ok(address_book) => {
667                log::info!("Successfully updated network address book");
668                self.set_network_from_address_book(address_book);
669            }
670            Err(e) => {
671                log::warn!("Failed to update network address book: {e:?}");
672            }
673        }
674    }
675
676    /// Returns the Account ID for the operator.
677    #[must_use]
678    pub fn get_operator_account_id(&self) -> Option<AccountId> {
679        self.load_operator().as_deref().map(|it| it.account_id)
680    }
681
682    /// Returns the `PublicKey` for the current operator.
683    #[must_use]
684    pub fn get_operator_public_key(&self) -> Option<PublicKey> {
685        self.load_operator().as_deref().map(|it| it.signer.public_key())
686    }
687}