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