nym_credential_proxy_lib/shared_state/
nyxd_client.rs

1// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::error::CredentialProxyError;
5use crate::helpers::LockTimer;
6use nym_ecash_contract_common::msg::ExecuteMsg;
7use nym_validator_client::nyxd::contract_traits::NymContractsProvider;
8use nym_validator_client::nyxd::cosmwasm_client::types::ExecuteResult;
9use nym_validator_client::nyxd::{Coin, Config, CosmWasmClient, NyxdClient};
10use nym_validator_client::{DirectSigningHttpRpcNyxdClient, nyxd};
11use std::ops::Deref;
12use std::sync::Arc;
13use std::time::Duration;
14use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
15use tracing::{instrument, warn};
16
17#[derive(Clone)]
18pub struct ChainClient(Arc<RwLock<DirectSigningHttpRpcNyxdClient>>);
19
20impl ChainClient {
21    pub fn new(mnemonic: bip39::Mnemonic) -> Result<Self, CredentialProxyError> {
22        let network_details = nym_network_defaults::NymNetworkDetails::new_from_env();
23        let client_config = nyxd::Config::try_from_nym_network_details(&network_details)?;
24
25        let nyxd_url = network_details
26            .endpoints
27            .first()
28            .ok_or_else(|| CredentialProxyError::NoNyxEndpointsAvailable)?
29            .nyxd_url
30            .as_str();
31
32        Self::new_with_config(client_config, nyxd_url, mnemonic)
33    }
34
35    pub fn new_with_config(
36        client_config: Config,
37        nyxd_url: &str,
38        mnemonic: bip39::Mnemonic,
39    ) -> Result<Self, CredentialProxyError> {
40        let client = NyxdClient::connect_with_mnemonic(client_config, nyxd_url, mnemonic)?;
41
42        if client.ecash_contract_address().is_none() {
43            return Err(CredentialProxyError::UnavailableEcashContract);
44        }
45
46        if client.dkg_contract_address().is_none() {
47            return Err(CredentialProxyError::UnavailableDKGContract);
48        }
49
50        Ok(ChainClient(Arc::new(RwLock::new(client))))
51    }
52
53    pub async fn query_chain(&self) -> ChainReadPermit<'_> {
54        let _acquire_timer = LockTimer::new("acquire chain query permit");
55        self.0.read().await
56    }
57
58    pub async fn start_chain_tx(&self) -> ChainWritePermit<'_> {
59        let _acquire_timer = LockTimer::new("acquire exclusive chain write permit");
60
61        ChainWritePermit {
62            lock_timer: LockTimer::new("exclusive chain access permit"),
63            inner: self.0.write().await,
64        }
65    }
66}
67
68pub type ChainReadPermit<'a> = RwLockReadGuard<'a, DirectSigningHttpRpcNyxdClient>;
69
70// explicitly wrap the WriteGuard for extra information regarding time taken
71pub struct ChainWritePermit<'a> {
72    // it's not really dead, we only care about it being dropped
73    #[allow(dead_code)]
74    lock_timer: LockTimer,
75    inner: RwLockWriteGuard<'a, DirectSigningHttpRpcNyxdClient>,
76}
77
78impl ChainWritePermit<'_> {
79    #[instrument(skip(self, memo, info), err(Display))]
80    pub async fn make_deposits(
81        self,
82        memo: String,
83        info: Vec<(String, Coin)>,
84    ) -> Result<ExecuteResult, CredentialProxyError> {
85        let address = self.inner.address();
86        let starting_sequence = self.inner.get_sequence(&address).await?.sequence;
87
88        let ecash_contract = self
89            .inner
90            .ecash_contract_address()
91            .ok_or(CredentialProxyError::UnavailableEcashContract)?;
92        let deposit_messages = info
93            .into_iter()
94            .map(|(identity_key, amount)| {
95                (
96                    ExecuteMsg::DepositTicketBookFunds { identity_key },
97                    vec![amount],
98                )
99            })
100            .collect::<Vec<_>>();
101
102        let res = self
103            .inner
104            .execute_multiple(ecash_contract, deposit_messages, None, memo)
105            .await?;
106
107        loop {
108            let updated_sequence = self.inner.get_sequence(&address).await?.sequence;
109
110            if updated_sequence > starting_sequence {
111                break;
112            }
113            warn!("wrong sequence number... waiting before releasing chain lock");
114            tokio::time::sleep(Duration::from_millis(50)).await;
115        }
116
117        Ok(res)
118    }
119}
120
121impl Deref for ChainWritePermit<'_> {
122    type Target = DirectSigningHttpRpcNyxdClient;
123
124    fn deref(&self) -> &Self::Target {
125        self.inner.deref()
126    }
127}