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}