Skip to main content

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}