Skip to main content

anchor_client/
nonblocking.rs

1use {
2    crate::{
3        compat::{solana_rpc_client, solana_rpc_client_api, solana_transaction},
4        AsSigner, ClientError, Config, EventContext, EventUnsubscriber, Program,
5        ProgramAccountsIterator, RequestBuilder, TxVersion,
6    },
7    anchor_lang::{prelude::Pubkey, AccountDeserialize, Discriminator},
8    solana_commitment_config::CommitmentConfig,
9    solana_rpc_client::nonblocking::rpc_client::RpcClient as AsyncRpcClient,
10    solana_rpc_client_api::{config::RpcSendTransactionConfig, filter::RpcFilterType},
11    solana_signature::Signature,
12    solana_signer::Signer,
13    solana_transaction::Transaction,
14    std::{marker::PhantomData, ops::Deref, sync::Arc},
15    tokio::sync::OnceCell,
16};
17
18impl<'a> EventUnsubscriber<'a> {
19    /// Unsubscribe gracefully.
20    pub async fn unsubscribe(self) {
21        self.unsubscribe_internal().await
22    }
23}
24
25pub trait ThreadSafeSigner: Signer + Send + Sync + 'static {
26    fn to_signer(&self) -> &dyn Signer;
27}
28
29impl<T: Signer + Send + Sync + 'static> ThreadSafeSigner for T {
30    fn to_signer(&self) -> &dyn Signer {
31        self
32    }
33}
34
35impl AsSigner for Arc<dyn ThreadSafeSigner> {
36    fn as_signer(&self) -> &dyn Signer {
37        self.to_signer()
38    }
39}
40
41impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
42    pub fn new(
43        program_id: Pubkey,
44        cfg: Config<C>,
45        #[cfg(feature = "mock")] rpc_client: AsyncRpcClient,
46    ) -> Result<Self, ClientError> {
47        #[cfg(not(feature = "mock"))]
48        let rpc_client = {
49            let comm_config = cfg.options.unwrap_or_default();
50            let cluster_url = cfg.cluster.url().to_string();
51            AsyncRpcClient::new_with_commitment(cluster_url.clone(), comm_config)
52        };
53
54        Ok(Self {
55            program_id,
56            cfg,
57            sub_client: OnceCell::new(),
58            internal_rpc_client: rpc_client,
59        })
60    }
61
62    // We disable the `rpc` method for `mock` feature because otherwise we'd either have to
63    // return a new `RpcClient` instance (which is different to the one used internally)
64    // or require the user to pass another one in for blocking (since we use the non-blocking one under the hood).
65    // The former of these would be confusing and the latter would be very annoying, especially since a user
66    // using the mock feature likely already has a `RpcClient` instance at hand anyway.
67    #[cfg(not(feature = "mock"))]
68    pub fn rpc(&self) -> AsyncRpcClient {
69        AsyncRpcClient::new_with_commitment(
70            self.cfg.cluster.url().to_string(),
71            self.cfg.options.unwrap_or_default(),
72        )
73    }
74
75    /// Returns a threadsafe request builder
76    pub fn request(&self) -> RequestBuilder<'_, C, Arc<dyn ThreadSafeSigner>> {
77        RequestBuilder::from(
78            self.program_id,
79            self.cfg.cluster.url(),
80            self.cfg.payer.clone(),
81            self.cfg.options,
82            &self.internal_rpc_client,
83        )
84    }
85
86    /// Returns the account at the given address.
87    pub async fn account<T: AccountDeserialize>(&self, address: Pubkey) -> Result<T, ClientError> {
88        self.account_internal(address).await
89    }
90
91    /// Returns all program accounts of the given type matching the given filters
92    pub async fn accounts<T: AccountDeserialize + Discriminator>(
93        &self,
94        filters: Vec<RpcFilterType>,
95    ) -> Result<Vec<(Pubkey, T)>, ClientError> {
96        self.accounts_lazy(filters).await?.collect()
97    }
98
99    /// Returns all program accounts of the given type matching the given filters as an iterator
100    /// Deserialization is executed lazily
101    pub async fn accounts_lazy<T: AccountDeserialize + Discriminator>(
102        &self,
103        filters: Vec<RpcFilterType>,
104    ) -> Result<ProgramAccountsIterator<T>, ClientError> {
105        self.accounts_lazy_internal(filters).await
106    }
107
108    /// Subscribe to program logs.
109    ///
110    /// Returns an [`EventUnsubscriber`] to unsubscribe and close connection gracefully.
111    pub async fn on<T: anchor_lang::Event + anchor_lang::AnchorDeserialize>(
112        &self,
113        f: impl FnMut(&EventContext, T) + Send + 'static,
114    ) -> Result<EventUnsubscriber<'_>, ClientError> {
115        let (handle, rx) = self.on_internal(f).await?;
116
117        Ok(EventUnsubscriber {
118            handle,
119            rx,
120            _lifetime_marker: PhantomData,
121        })
122    }
123}
124
125impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C, Arc<dyn ThreadSafeSigner>> {
126    pub fn from(
127        program_id: Pubkey,
128        cluster: &str,
129        payer: C,
130        options: Option<CommitmentConfig>,
131        rpc_client: &'a AsyncRpcClient,
132    ) -> Self {
133        Self {
134            program_id,
135            payer,
136            cluster: cluster.to_string(),
137            accounts: Vec::new(),
138            options: options.unwrap_or_default(),
139            instructions: Vec::new(),
140            instruction_data: None,
141            signers: Vec::new(),
142            internal_rpc_client: rpc_client,
143            _phantom: PhantomData,
144        }
145    }
146
147    #[must_use]
148    pub fn signer<T: ThreadSafeSigner>(mut self, signer: T) -> Self {
149        self.signers.push(Arc::new(signer));
150        self
151    }
152
153    /// Build and sign a transaction.
154    ///
155    /// Note: This will use a transaction with the legacy transaction format. If you'd like to use
156    /// a different transaction format, use [`signed_transaction_versioned`].
157    pub async fn signed_transaction(&self) -> Result<Transaction, ClientError> {
158        self.signed_transaction_internal(TxVersion::Legacy)
159            .await
160            .map(|tx| {
161                tx.into_legacy_transaction()
162                    .expect("Signed transaction with `TxVersion::Legacy`")
163            })
164    }
165
166    /// Sign and return a transaction with the specified version.
167    ///
168    /// # Arguments
169    ///
170    /// * `version` - The transaction version to use ([`TxVersion::Legacy`] or [`TxVersion::V0`]).
171    ///
172    /// # Example
173    ///
174    /// ```no_run
175    /// use anchor_client::{Client, Cluster, TxVersion};
176    /// use anchor_lang::prelude::Pubkey;
177    /// use solana_signer::null_signer::NullSigner;
178    /// use anchor_client::AddressLookupTableAccount;
179    ///
180    /// let payer = NullSigner::new(&Pubkey::default());
181    /// let client = Client::new(Cluster::Localnet, std::rc::Rc::new(payer));
182    ///
183    /// let program = client.program(Pubkey::default()).unwrap();
184    /// let lookup_table = AddressLookupTableAccount { key: Pubkey::default(), addresses: vec![] };
185    /// // Legacy transaction
186    /// let tx = request.signed_transaction_versioned(TxVersion::Legacy).unwrap();
187    ///
188    /// // V0 transaction
189    /// let tx = request.signed_transaction_versioned(TxVersion::V0(&[lookup_table])).unwrap();
190    /// ```
191    pub async fn signed_transaction_versioned(
192        &self,
193        version: TxVersion<'_>,
194    ) -> Result<solana_transaction::versioned::VersionedTransaction, ClientError> {
195        self.signed_transaction_internal(version).await
196    }
197
198    /// Send a transaction.
199    ///
200    /// Note: This will use a transaction with the legacy transaction format. If you'd like to use
201    /// a different transaction format, use [`send_versioned`].
202    pub async fn send(&self) -> Result<Signature, ClientError> {
203        self.send_internal(TxVersion::Legacy).await
204    }
205
206    /// Send a transaction with the specified version.
207    ///
208    /// # Arguments
209    ///
210    /// * `version` - The transaction version to use ([`TxVersion::Legacy`] or [`TxVersion::V0`]).
211    ///
212    /// # Example
213    ///
214    /// ```no_run
215    /// use anchor_client::{Client, Cluster, TxVersion};
216    /// use anchor_lang::prelude::Pubkey;
217    /// use solana_signer::null_signer::NullSigner;
218    /// use anchor_client::AddressLookupTableAccount;
219    ///
220    /// let payer = NullSigner::new(&Pubkey::default());
221    /// let client = Client::new(Cluster::Localnet, std::rc::Rc::new(payer));
222    ///
223    /// let program = client.program(Pubkey::default()).unwrap();
224    /// let lookup_table = AddressLookupTableAccount { key: Pubkey::default(), addresses: vec![] };
225    ///
226    /// let request = program.request();
227    /// // Legacy transaction
228    /// let sig = request.send_versioned(TxVersion::Legacy).unwrap();
229    ///
230    /// // V0 transaction with lookup tables
231    /// let sig = request.send_versioned(TxVersion::V0(&[lookup_table])).unwrap();
232    /// ```
233    pub async fn send_versioned(&self, version: TxVersion<'_>) -> Result<Signature, ClientError> {
234        self.send_internal(version).await
235    }
236
237    /// Send a transaction with spinner and config.
238    ///
239    /// Note: This will use a transaction with the legacy transaction format. If you'd like to use
240    /// a different transaction format, use [`send_with_spinner_and_config_versioned`].
241    pub async fn send_with_spinner_and_config(
242        &self,
243        config: RpcSendTransactionConfig,
244    ) -> Result<Signature, ClientError> {
245        self.send_with_spinner_and_config_internal(TxVersion::Legacy, config)
246            .await
247    }
248
249    /// Send a transaction with the specified version, spinner and config.
250    ///
251    /// # Arguments
252    ///
253    /// * `version` - The transaction version to use ([`TxVersion::Legacy`] or [`TxVersion::V0`]).
254    /// * `config` - RPC send transaction configuration.
255    pub async fn send_with_spinner_and_config_versioned(
256        &self,
257        version: TxVersion<'_>,
258        config: RpcSendTransactionConfig,
259    ) -> Result<Signature, ClientError> {
260        self.send_with_spinner_and_config_internal(version, config)
261            .await
262    }
263}