abstract_client/client.rs
1//! # Represents Abstract Client
2//!
3//! [`AbstractClient`] allows you to do everything you might need to work with the Abstract
4//! or to be more precise
5//!
6//! - Create or interact with Account
7//! - Install or interact with a module (including apps and adapters)
8//! - Publish modules
9//! - Do integration tests with Abstract
10//!
11//! Example of publishing mock app
12//!
13//! ```
14//! # use abstract_client::AbstractClientError;
15//! use abstract_app::mock::mock_app_dependency::interface::MockAppI;
16//! use cw_orch::prelude::*;
17//! use abstract_client::{AbstractClient, Publisher, Namespace};
18//! use abstract_testing::prelude::*;
19//!
20//! let chain = MockBech32::new("mock");
21//! let client = AbstractClient::builder(chain.clone()).build()?;
22//!
23//! let namespace = Namespace::new("tester")?;
24//! let publisher: Publisher<MockBech32> = client
25//! .account_builder()
26//! .namespace(namespace)
27//! .build()?
28//! .publisher()?;
29//!
30//! publisher.publish_app::<MockAppI<MockBech32>>()?;
31//! # Ok::<(), AbstractClientError>(())
32//! ```
33
34use abstract_interface::{
35 Abstract, AccountI, AnsHost, IbcClient, ModuleFactory, RegisteredModule, Registry,
36};
37use abstract_std::objects::{
38 module::{ModuleInfo, ModuleStatus, ModuleVersion},
39 module_reference::ModuleReference,
40 namespace::Namespace,
41 salt::generate_instantiate_salt,
42 AccountId,
43};
44use cosmwasm_std::{BlockInfo, Uint128};
45use cw_orch::{contract::Contract, environment::Environment as _, prelude::*};
46use rand::Rng;
47
48use crate::{
49 account::{Account, AccountBuilder},
50 source::AccountSource,
51 AbstractClientError, Environment, Publisher, Service,
52};
53
54/// Client to interact with Abstract accounts and modules
55#[derive(Clone)]
56pub struct AbstractClient<Chain: CwEnv> {
57 pub(crate) abstr: Abstract<Chain>,
58}
59
60/// The result type for the Abstract Client.
61pub type AbstractClientResult<T> = Result<T, AbstractClientError>;
62
63impl<Chain: CwEnv> AbstractClient<Chain> {
64 /// Get [`AbstractClient`] from a chosen environment. [`Abstract`] should
65 /// already be deployed to this environment.
66 ///
67 /// ```
68 /// use abstract_client::AbstractClient;
69 /// # use abstract_client::{Environment, AbstractClientError};
70 /// # use cw_orch::prelude::*;
71 /// # let chain = MockBech32::new("mock");
72 /// # let client = AbstractClient::builder(chain.clone()).build().unwrap(); // Deploy mock abstract
73 ///
74 /// let client = AbstractClient::new(chain)?;
75 /// # Ok::<(), AbstractClientError>(())
76 /// ```
77 pub fn new(chain: Chain) -> AbstractClientResult<Self> {
78 let abstr = Abstract::load_from(chain)?;
79 Ok(Self { abstr })
80 }
81
82 /// Registry contract API
83 ///
84 /// The Registry contract is a database contract that stores all module-related information.
85 /// ```
86 /// # use abstract_client::AbstractClientError;
87 /// # let chain = cw_orch::prelude::MockBech32::new("mock");
88 /// # let client = abstract_client::AbstractClient::builder(chain.clone()).build().unwrap();
89 /// use abstract_std::objects::{module_reference::ModuleReference, module::ModuleInfo};
90 /// // For getting registry address
91 /// use cw_orch::prelude::*;
92 ///
93 /// let registry = client.registry();
94 /// let vc_module = registry.module(ModuleInfo::from_id_latest("abstract:registry")?)?;
95 /// assert_eq!(vc_module.reference, ModuleReference::Native(registry.address()?));
96 /// # Ok::<(), AbstractClientError>(())
97 /// ```
98 pub fn registry(&self) -> &Registry<Chain> {
99 &self.abstr.registry
100 }
101
102 /// Abstract Name Service contract API
103 ///
104 /// The Abstract Name Service contract is a database contract that stores all asset-related information.
105 /// ```
106 /// # use abstract_client::AbstractClientError;
107 /// # use abstract_testing::prelude::*;
108 /// use abstract_client::{AbstractClient, ClientResolve};
109 /// use cw_asset::AssetInfo;
110 /// use abstract_app::objects::AssetEntry;
111 /// // For getting registry address
112 /// use cw_orch::prelude::*;
113 ///
114 /// let denom = "test_denom";
115 /// let entry = "denom";
116 /// # let chain = MockBech32::new("mock");
117 /// # let client = AbstractClient::builder(chain.clone())
118 /// # .asset(entry, cw_asset::AssetInfoBase::Native(denom.to_owned()))
119 /// # .build()?;
120 ///
121 /// let name_service = client.name_service();
122 /// let asset_entry = AssetEntry::new(entry);
123 /// let asset = asset_entry.resolve(name_service)?;
124 /// assert_eq!(asset, AssetInfo::Native(denom.to_owned()));
125 /// # Ok::<(), AbstractClientError>(())
126 /// ```
127 pub fn name_service(&self) -> &AnsHost<Chain> {
128 &self.abstr.ans_host
129 }
130
131 /// Abstract Module Factory contract API
132 pub fn module_factory(&self) -> &ModuleFactory<Chain> {
133 &self.abstr.module_factory
134 }
135
136 /// Abstract Ibc Client contract API
137 ///
138 /// The Abstract Ibc Client contract allows users to create and use Interchain Abstract Accounts
139 pub fn ibc_client(&self) -> &IbcClient<Chain> {
140 &self.abstr.ibc.client
141 }
142
143 /// Service contract API
144 pub fn service<M: RegisteredModule + From<Contract<Chain>>>(
145 &self,
146 ) -> AbstractClientResult<Service<Chain, M>> {
147 Service::new(self.registry())
148 }
149
150 /// Return current block info see [`BlockInfo`].
151 pub fn block_info(&self) -> AbstractClientResult<BlockInfo> {
152 self.environment()
153 .block_info()
154 .map_err(|e| AbstractClientError::CwOrch(e.into()))
155 }
156
157 /// Fetches an existing Abstract [`Publisher`] from chain
158 pub fn fetch_publisher(&self, namespace: Namespace) -> AbstractClientResult<Publisher<Chain>> {
159 let account = self.fetch_account(namespace)?;
160 account.publisher()
161 }
162
163 /// Builder for creating a new Abstract [`Account`].
164 pub fn account_builder(&self) -> AccountBuilder<Chain> {
165 AccountBuilder::new(&self.abstr)
166 }
167
168 /// Builder for creating a new Abstract [`Account`].
169 pub fn sub_account_builder<'a>(
170 &'a self,
171 account: &'a Account<Chain>,
172 ) -> AbstractClientResult<AccountBuilder<'a, Chain>> {
173 let mut builder = AccountBuilder::new(&self.abstr);
174 builder.sub_account(account);
175 builder.name("Sub Account");
176 Ok(builder)
177 }
178
179 /// Fetch an [`Account`] from a given source.
180 ///
181 /// This method is used to retrieve an account from a given source. It will **not** create a new account if the source is invalid.
182 ///
183 /// Sources that can be used are:
184 /// - [`Namespace`]: Will retrieve the account from the namespace if it is already claimed.
185 /// - [`AccountId`]: Will retrieve the account from the account id.
186 /// - App [`Addr`]: Will retrieve the account from an app that is installed on it.
187 pub fn fetch_account<T: Into<AccountSource>>(
188 &self,
189 source: T,
190 ) -> AbstractClientResult<Account<Chain>> {
191 let source = source.into();
192 let chain = self.abstr.registry.environment();
193
194 match source {
195 AccountSource::Namespace(namespace) => {
196 // if namespace, check if we need to claim or not.
197 // Check if namespace already claimed
198 let account_from_namespace_result: Option<Account<Chain>> =
199 Account::maybe_from_namespace(&self.abstr, namespace.clone())?;
200
201 // Only return if the account can be retrieved without errors.
202 if let Some(account_from_namespace) = account_from_namespace_result {
203 Ok(account_from_namespace)
204 } else {
205 Err(AbstractClientError::NamespaceNotClaimed {
206 namespace: namespace.to_string(),
207 })
208 }
209 }
210 AccountSource::AccountId(account_id) => {
211 let abstract_account = AccountI::load_from(&self.abstr, account_id.clone())?;
212 Ok(Account::new(abstract_account))
213 }
214 AccountSource::App(app) => {
215 // Query app for account address and get AccountId from it.
216 let app_config: abstract_std::app::AppConfigResponse = chain
217 .query(
218 &abstract_std::app::QueryMsg::<Empty>::Base(
219 abstract_std::app::BaseQueryMsg::BaseConfig {},
220 ),
221 &app,
222 )
223 .map_err(Into::into)?;
224
225 let account_config: abstract_std::account::ConfigResponse = chain
226 .query(
227 &abstract_std::account::QueryMsg::Config {},
228 &app_config.account,
229 )
230 .map_err(Into::into)?;
231 // This function verifies the account-id is valid and returns an error if not.
232 let abstract_account = AccountI::load_from(&self.abstr, account_config.account_id)?;
233 Ok(Account::new(abstract_account))
234 }
235 }
236 }
237
238 /// Fetches an existing Abstract [`Account`] from chain
239 /// If the Namespace is not claimed, creates an account with the provided account builder
240 pub fn fetch_or_build_account<T: Into<AccountSource>, F>(
241 &self,
242 source: T,
243 build_fn: F,
244 ) -> AbstractClientResult<Account<Chain>>
245 where
246 F: for<'a, 'b> FnOnce(
247 &'a mut AccountBuilder<'b, Chain>,
248 ) -> &'a mut AccountBuilder<'b, Chain>,
249 {
250 match self.fetch_account(source) {
251 Ok(account) => Ok(account),
252 Err(_) => {
253 let mut account_builder = self.account_builder();
254 build_fn(&mut account_builder).build()
255 }
256 }
257 }
258
259 /// Address of the sender
260 pub fn sender(&self) -> Addr {
261 self.environment().sender_addr()
262 }
263
264 /// Fetch an [`Account`] from a given source.
265 ///
266 /// This method is used to retrieve an account from a given source. It will **not** create a new account if the source is invalid.
267 ///
268 /// Sources that can be used are:
269 /// - [`Namespace`]: Will retrieve the account from the namespace if it is already claimed.
270 /// - [`AccountId`]: Will retrieve the account from the account id.
271 /// - App [`Addr`]: Will retrieve the account from an app that is installed on it.
272 #[deprecated(since = "0.24.2", note = "use fetch_account instead")]
273 pub fn account_from<T: Into<AccountSource>>(
274 &self,
275 source: T,
276 ) -> AbstractClientResult<Account<Chain>> {
277 self.fetch_account(source)
278 }
279
280 /// Retrieve denom balance for provided address
281 pub fn query_balance(
282 &self,
283 address: &Addr,
284 denom: impl Into<String>,
285 ) -> AbstractClientResult<Uint128> {
286 let coins = self
287 .environment()
288 .bank_querier()
289 .balance(address, Some(denom.into()))
290 .map_err(Into::into)?;
291 // There will always be a single element in this case.
292 Ok(coins[0].amount)
293 }
294
295 /// Retrieve balances of all denoms for provided address
296 pub fn query_balances(&self, address: &Addr) -> AbstractClientResult<Vec<Coin>> {
297 self.environment()
298 .bank_querier()
299 .balance(address, None)
300 .map_err(Into::into)
301 .map_err(Into::into)
302 }
303
304 /// Waits for a specified number of blocks.
305 pub fn wait_blocks(&self, amount: u64) -> AbstractClientResult<()> {
306 self.environment()
307 .wait_blocks(amount)
308 .map_err(Into::into)
309 .map_err(Into::into)
310 }
311
312 /// Waits for a specified number of blocks.
313 pub fn wait_seconds(&self, secs: u64) -> AbstractClientResult<()> {
314 self.environment()
315 .wait_seconds(secs)
316 .map_err(Into::into)
317 .map_err(Into::into)
318 }
319
320 /// Waits for next block.
321 pub fn next_block(&self) -> AbstractClientResult<()> {
322 self.environment()
323 .next_block()
324 .map_err(Into::into)
325 .map_err(Into::into)
326 }
327
328 /// Get random local account id sequence(unclaimed) in 2147483648..u32::MAX range
329 pub fn random_account_id(&self) -> AbstractClientResult<u32> {
330 let mut rng = rand::thread_rng();
331 loop {
332 let random_sequence = rng.gen_range(2147483648..u32::MAX);
333 let potential_account_id = AccountId::local(random_sequence);
334 if self.abstr.registry.account(potential_account_id).is_err() {
335 return Ok(random_sequence);
336 };
337 }
338 }
339
340 /// Get address of instantiate2 module
341 /// If used for upcoming account this supposed to be used in pair with [`AbstractClient::next_local_account_id`]
342 pub fn module_instantiate2_address<M: RegisteredModule>(
343 &self,
344 account_id: &AccountId,
345 ) -> AbstractClientResult<Addr> {
346 self.module_instantiate2_address_raw(
347 account_id,
348 ModuleInfo::from_id(
349 M::module_id(),
350 ModuleVersion::Version(M::module_version().to_owned()),
351 )?,
352 )
353 }
354
355 /// Get address of instantiate2 module
356 /// Raw version of [`AbstractClient::module_instantiate2_address`]
357 /// If used for upcoming account this supposed to be used in pair with [`AbstractClient::next_local_account_id`]
358 pub fn module_instantiate2_address_raw(
359 &self,
360 account_id: &AccountId,
361 module_info: ModuleInfo,
362 ) -> AbstractClientResult<Addr> {
363 let salt = generate_instantiate_salt(account_id);
364 let wasm_querier = self.environment().wasm_querier();
365 let module = self.registry().module(module_info)?;
366 let (code_id, creator) = match module.reference {
367 // If Account - signer is creator
368 ModuleReference::Account(id) => (id, self.environment().sender_addr()),
369 // Else module factory is creator
370 ModuleReference::App(id) | ModuleReference::Standalone(id) => {
371 (id, self.abstr.module_factory.address()?)
372 }
373 _ => {
374 return Err(AbstractClientError::Abstract(
375 abstract_std::AbstractError::Assert(
376 "module reference not account, app or standalone".to_owned(),
377 ),
378 ))
379 }
380 };
381
382 let addr = wasm_querier
383 .instantiate2_addr(code_id, &creator, salt)
384 .map_err(Into::into)?;
385 Ok(Addr::unchecked(addr))
386 }
387
388 /// Retrieves the status of a specified module.
389 ///
390 /// This function checks the status of a module within the registry contract.
391 /// and returns appropriate `Some(ModuleStatus)`. If the module is not deployed, it returns `None`.
392 pub fn module_status(&self, module: ModuleInfo) -> AbstractClientResult<Option<ModuleStatus>> {
393 self.registry().module_status(module).map_err(Into::into)
394 }
395
396 /// Clones the Abstract Client with a different sender.
397 pub fn call_as(&self, sender: &<Chain as TxHandler>::Sender) -> Self {
398 Self {
399 abstr: self.abstr.call_as(sender),
400 }
401 }
402
403 #[cfg(feature = "interchain")]
404 /// Connect this abstract client to the remote abstract client
405 /// If [`cw_orch_polytone::Polytone`] is deployed between 2 chains, it will NOT redeploy it (good for actual chains)
406 /// If Polytone is not deployed, deploys it between the 2 chains (good for integration testing)
407 pub fn connect_to(
408 &self,
409 remote_abstr: &AbstractClient<Chain>,
410 ibc: &impl cw_orch_interchain::prelude::InterchainEnv<Chain>,
411 ) -> AbstractClientResult<()>
412 where
413 Chain: cw_orch_interchain::prelude::IbcQueryHandler,
414 {
415 self.abstr.connect_to(&remote_abstr.abstr, ibc)?;
416
417 Ok(())
418 }
419}