1use std::fmt::{Debug, Display};
24
25use abstract_interface::{
26 Abstract, AbstractInterfaceError, AccountDetails, AccountExecFns, AccountI, AccountQueryFns,
27 DependencyCreation, IbcClient, InstallConfig, MFactoryQueryFns, RegisteredModule,
28 RegistryQueryFns,
29};
30use abstract_std::{
31 account,
32 account::{
33 state::AccountInfo, AccountModuleInfo, InfoResponse, ModuleAddressesResponse,
34 ModuleInfosResponse, ModuleInstallConfig,
35 },
36 objects::{
37 gov_type::GovernanceDetails,
38 module::{ModuleId, ModuleInfo, ModuleVersion},
39 namespace::Namespace,
40 ownership,
41 validation::verifiers,
42 AccountId,
43 },
44 registry::{self, NamespaceResponse},
45 IBC_CLIENT,
46};
47use cosmwasm_std::{to_json_binary, Coins, CosmosMsg, Uint128};
48use cw_orch::{
49 contract::Contract,
50 environment::{Environment as _, MutCwEnv},
51 prelude::*,
52};
53use serde::{de::DeserializeOwned, Serialize};
54
55use crate::{
56 client::AbstractClientResult, infrastructure::Infrastructure, AbstractClientError, Application,
57 Environment, Publisher,
58};
59
60pub struct AccountBuilder<'a, Chain: CwEnv> {
80 pub(crate) abstr: Abstract<Chain>,
81 name: Option<String>,
82 description: Option<String>,
83 link: Option<String>,
84 namespace: Option<Namespace>,
85 ownership: Option<GovernanceDetails<String>>,
86 owner_account: Option<&'a Account<Chain>>,
87 install_modules: Vec<ModuleInstallConfig>,
88 enable_ibc: bool,
89 funds: AccountCreationFunds,
90 expected_local_account_id: Option<u32>,
91}
92
93enum AccountCreationFunds {
95 #[allow(clippy::type_complexity)]
96 Auto(Box<dyn Fn(&[Coin]) -> bool>),
97 Coins(Coins),
98}
99
100impl<'a, Chain: CwEnv> AccountBuilder<'a, Chain> {
101 pub(crate) fn new(abstr: &Abstract<Chain>) -> Self {
102 Self {
103 abstr: abstr.clone(),
104 name: None,
105 description: None,
106 link: None,
107 namespace: None,
108 ownership: None,
109 owner_account: None,
110 install_modules: vec![],
111 enable_ibc: false,
112 funds: AccountCreationFunds::Coins(Coins::default()),
113 expected_local_account_id: None,
114 }
115 }
116
117 pub fn name(&mut self, name: impl Into<String>) -> &mut Self {
120 self.name = Some(name.into());
121 self
122 }
123
124 pub fn description(&mut self, description: impl Into<String>) -> &mut Self {
126 self.description = Some(description.into());
127 self
128 }
129
130 pub fn link(&mut self, link: impl Into<String>) -> &mut Self {
132 self.link = Some(link.into());
133 self
134 }
135
136 pub fn namespace(&mut self, namespace: Namespace) -> &mut Self {
139 self.namespace = Some(namespace);
140 self
141 }
142
143 pub fn sub_account(&mut self, owner_account: &'a Account<Chain>) -> &mut Self {
145 self.owner_account = Some(owner_account);
146 self
147 }
148
149 pub fn ownership(&mut self, ownership: GovernanceDetails<String>) -> &mut Self {
152 self.ownership = Some(ownership);
153 self
154 }
155
156 pub fn install_adapter<M: InstallConfig<InitMsg = Empty>>(&mut self) -> &mut Self {
158 self.install_modules
159 .push(M::install_config(&Empty {}).unwrap());
160 self
161 }
162
163 pub fn install_app<M: InstallConfig>(&mut self, configuration: &M::InitMsg) -> &mut Self {
165 self.install_modules
166 .push(M::install_config(configuration).unwrap());
167 self
168 }
169
170 pub fn install_standalone<M: InstallConfig>(
172 &mut self,
173 configuration: &M::InitMsg,
174 ) -> &mut Self {
175 self.install_modules
176 .push(M::install_config(configuration).unwrap());
177 self
178 }
179
180 pub fn install_service<M: InstallConfig>(&mut self, configuration: &M::InitMsg) -> &mut Self {
182 self.install_modules
183 .push(M::install_config(configuration).unwrap());
184 self
185 }
186
187 pub fn install_app_with_dependencies<M: DependencyCreation + InstallConfig>(
189 &mut self,
190 module_configuration: &M::InitMsg,
191 dependencies_config: M::DependenciesConfig,
192 ) -> &mut Self {
193 let deps_install_config = M::dependency_install_configs(dependencies_config).unwrap();
194 self.install_modules.extend(deps_install_config);
195 self.install_modules
196 .push(M::install_config(module_configuration).unwrap());
197 self
198 }
199
200 pub fn install_standalone_with_dependencies<M: DependencyCreation + InstallConfig>(
202 &mut self,
203 module_configuration: &M::InitMsg,
204 dependencies_config: M::DependenciesConfig,
205 ) -> &mut Self {
206 let deps_install_config = M::dependency_install_configs(dependencies_config).unwrap();
207 self.install_modules.extend(deps_install_config);
208 self.install_modules
209 .push(M::install_config(module_configuration).unwrap());
210 self
211 }
212
213 pub fn with_modules(
218 &mut self,
219 install_modules: impl IntoIterator<Item = ModuleInstallConfig>,
220 ) -> &mut Self {
221 self.install_modules.extend(install_modules);
222 self
223 }
224
225 pub fn enable_ibc(&mut self) -> &mut Self {
228 self.enable_ibc = true;
229 self
230 }
231
232 pub fn auto_fund_assert<F: Fn(&[Coin]) -> bool + 'static>(&mut self, f: F) -> &mut Self {
236 self.funds = AccountCreationFunds::Auto(Box::new(f));
237 self
238 }
239
240 pub fn auto_fund(&mut self) -> &mut Self {
243 self.funds = AccountCreationFunds::Auto(Box::new(|_| true));
244 self
245 }
246
247 pub fn funds(&mut self, funds: &[Coin]) -> AbstractClientResult<&mut Self> {
250 let coins = match &mut self.funds {
251 AccountCreationFunds::Auto(_) => return Err(AbstractClientError::FundsWithAutoFund {}),
252 AccountCreationFunds::Coins(coins) => coins,
253 };
254
255 for coin in funds {
256 coins
257 .add(coin.clone())
258 .map_err(AbstractInterfaceError::from)?;
259 }
260 Ok(self)
261 }
262
263 pub fn expected_account_id(&mut self, local_account_id: u32) -> &mut Self {
267 self.expected_local_account_id = Some(local_account_id);
268 self
269 }
270
271 pub fn build(&self) -> AbstractClientResult<Account<Chain>> {
273 let install_modules = self.install_modules();
274
275 let chain = self.abstr.registry.environment();
276 let sender = chain.sender_addr().to_string();
277 let name = self
278 .name
279 .clone()
280 .unwrap_or_else(|| String::from("Default Abstract Account"));
281 let ownership = self
282 .ownership
283 .clone()
284 .unwrap_or(GovernanceDetails::Monarchy { monarch: sender });
285
286 verifiers::validate_name(&name)?;
288 verifiers::validate_description(self.description.as_deref())?;
289 verifiers::validate_link(self.link.as_deref())?;
290
291 let funds = self.generate_funds(&install_modules, true)?;
292 let account_details = AccountDetails {
293 name,
294 description: self.description.clone(),
295 link: self.link.clone(),
296 namespace: self.namespace.as_ref().map(ToString::to_string),
297 install_modules,
298 account_id: self.expected_local_account_id,
299 };
300 let abstract_account = match self.owner_account {
301 None => AccountI::create(&self.abstr, account_details, ownership, &funds)?,
302 Some(owner_account) => owner_account
303 .abstr_account
304 .create_and_return_sub_account(account_details, &funds)?,
305 };
306 Ok(Account::new(abstract_account))
307 }
308
309 fn install_modules(&self) -> Vec<ModuleInstallConfig> {
311 let mut install_modules = self.install_modules.clone();
312 if self.enable_ibc {
313 install_modules.push(IbcClient::<Chain>::install_config(&Empty {}).unwrap());
314 }
315 install_modules.dedup();
316 install_modules
317 }
318
319 fn generate_funds(
320 &self,
321 install_modules: &[ModuleInstallConfig],
323 include_namespace_claiming: bool,
325 ) -> AbstractClientResult<Vec<Coin>> {
326 let funds = match &self.funds {
327 AccountCreationFunds::Auto(auto_funds_assert) => {
328 let modules = install_modules.iter().map(|m| m.module.clone()).collect();
329 let simulate_response = self
331 .abstr
332 .module_factory
333 .simulate_install_modules(modules)?;
334
335 let mut funds = Coins::try_from(simulate_response.total_required_funds).unwrap();
336
337 if include_namespace_claiming && self.namespace.is_some() {
339 let vc_config = self.abstr.registry.config()?;
340
341 if let Some(namespace_fee) = vc_config.namespace_registration_fee {
342 funds
343 .add(namespace_fee)
344 .map_err(AbstractInterfaceError::from)?;
345 }
346 };
347
348 let funds = funds.into_vec();
349 if !auto_funds_assert(&funds) {
351 return Err(AbstractClientError::AutoFundsAssertFailed(funds));
352 }
353 funds
354 }
355 AccountCreationFunds::Coins(coins) => coins.to_vec(),
356 };
357 Ok(funds)
358 }
359}
360
361#[derive(Clone)]
366pub struct Account<Chain: CwEnv> {
367 pub(crate) abstr_account: AccountI<Chain>,
368}
369
370impl<Chain: CwEnv> AsRef<AccountI<Chain>> for Account<Chain> {
371 fn as_ref(&self) -> &AccountI<Chain> {
372 &self.abstr_account
373 }
374}
375
376impl<Chain: CwEnv> Account<Chain> {
377 pub(crate) fn new(abstract_account: AccountI<Chain>) -> Self {
378 Self {
379 abstr_account: abstract_account,
380 }
381 }
382
383 pub(crate) fn maybe_from_namespace(
384 abstr: &Abstract<Chain>,
385 namespace: Namespace,
386 ) -> AbstractClientResult<Option<Self>> {
387 let namespace_response: NamespaceResponse = abstr.registry.namespace(namespace)?;
388
389 let NamespaceResponse::Claimed(info) = namespace_response else {
390 return Ok(None);
391 };
392
393 let abstract_account: AccountI<Chain> = AccountI::load_from(abstr, info.account_id)?;
394
395 Ok(Some(Self::new(abstract_account)))
396 }
397
398 pub fn publisher(&self) -> AbstractClientResult<Publisher<Chain>> {
402 Publisher::new(self)
403 }
404
405 pub fn id(&self) -> AbstractClientResult<AccountId> {
407 self.abstr_account.id().map_err(Into::into)
408 }
409
410 pub fn query_balance(&self, denom: impl Into<String>) -> AbstractClientResult<Uint128> {
412 let coins = self
413 .environment()
414 .bank_querier()
415 .balance(&self.address()?, Some(denom.into()))
416 .map_err(Into::into)?;
417
418 Ok(coins[0].amount)
420 }
421
422 pub fn query_balances(&self) -> AbstractClientResult<Vec<Coin>> {
424 self.environment()
425 .bank_querier()
426 .balance(&self.address()?, None)
427 .map_err(Into::into)
428 .map_err(Into::into)
429 }
430
431 pub fn info(&self) -> AbstractClientResult<AccountInfo> {
433 let info_response: InfoResponse = self.abstr_account.info()?;
434 Ok(info_response.info)
435 }
436
437 pub fn install_app<M: InstallConfig + From<Contract<Chain>>>(
440 &self,
441 configuration: &M::InitMsg,
442 funds: &[Coin],
443 ) -> AbstractClientResult<Application<Chain, M>> {
444 let modules = vec![M::install_config(configuration)?];
445
446 self.install_module_internal(modules, funds)
447 }
448
449 pub fn install_standalone<M: InstallConfig + From<Contract<Chain>>>(
453 &self,
454 configuration: &M::InitMsg,
455 funds: &[Coin],
456 ) -> AbstractClientResult<Application<Chain, M>> {
457 let modules = vec![M::install_config(configuration)?];
458
459 self.install_module_internal(modules, funds)
460 }
461
462 pub fn install_service<M: InstallConfig + From<Contract<Chain>>>(
466 &self,
467 configuration: &M::InitMsg,
468 funds: &[Coin],
469 ) -> AbstractClientResult<Application<Chain, M>> {
470 let modules = vec![M::install_config(configuration)?];
471
472 self.install_module_internal(modules, funds)
473 }
474
475 pub fn install_adapter<M: InstallConfig<InitMsg = Empty> + From<Contract<Chain>>>(
478 &self,
479 funds: &[Coin],
480 ) -> AbstractClientResult<Application<Chain, M>> {
481 let modules = vec![M::install_config(&Empty {})?];
482
483 self.install_module_internal(modules, funds)
484 }
485
486 pub fn install_app_with_dependencies<
493 M: DependencyCreation + InstallConfig + From<Contract<Chain>>,
494 >(
495 &self,
496 module_configuration: &M::InitMsg,
497 dependencies_config: M::DependenciesConfig,
498 funds: &[Coin],
499 ) -> AbstractClientResult<Application<Chain, M>> {
500 let mut install_configs: Vec<ModuleInstallConfig> =
501 M::dependency_install_configs(dependencies_config)?;
502 install_configs.push(M::install_config(module_configuration)?);
503
504 self.install_module_internal(install_configs, funds)
505 }
506
507 pub fn install_standalone_with_dependencies<
512 M: DependencyCreation + InstallConfig + From<Contract<Chain>>,
513 >(
514 &self,
515 module_configuration: &M::InitMsg,
516 dependencies_config: M::DependenciesConfig,
517 funds: &[Coin],
518 ) -> AbstractClientResult<Application<Chain, M>> {
519 let mut install_configs: Vec<ModuleInstallConfig> =
520 M::dependency_install_configs(dependencies_config)?;
521 install_configs.push(M::install_config(module_configuration)?);
522
523 self.install_module_internal(install_configs, funds)
524 }
525
526 pub fn upgrade(&self, version: ModuleVersion) -> AbstractClientResult<Chain::Response> {
528 self.abstr_account
529 .upgrade(vec![(
530 ModuleInfo::from_id(abstract_std::constants::ACCOUNT, version.clone())?,
531 Some(
532 to_json_binary(&abstract_std::account::MigrateMsg { code_id: None })
533 .map_err(Into::<CwOrchError>::into)?,
534 ),
535 )])
536 .map_err(Into::into)
537 }
538
539 pub fn ownership(&self) -> AbstractClientResult<ownership::Ownership<String>> {
541 self.abstr_account.ownership().map_err(Into::into)
542 }
543
544 pub fn owner(&self) -> AbstractClientResult<Addr> {
547 self.abstr_account
548 .top_level_owner()
549 .map(|tlo| tlo.address)
550 .map_err(Into::into)
551 }
552
553 pub fn execute(
555 &self,
556 execute_msgs: impl IntoIterator<Item = impl Into<CosmosMsg>>,
557 funds: &[Coin],
558 ) -> AbstractClientResult<Chain::Response> {
559 let msgs = execute_msgs.into_iter().map(Into::into).collect();
560 self.configure(&account::ExecuteMsg::Execute { msgs }, funds)
561 }
562
563 pub fn configure(
565 &self,
566 execute_msg: &account::ExecuteMsg,
567 funds: &[Coin],
568 ) -> AbstractClientResult<Chain::Response> {
569 self.abstr_account
570 .execute(execute_msg, funds)
571 .map_err(Into::into)
572 }
573
574 pub fn query_module<Q: Serialize + Debug, T: Serialize + DeserializeOwned>(
576 &self,
577 module_id: ModuleId,
578 msg: &Q,
579 ) -> AbstractClientResult<T> {
580 let mut module_address_response = self.module_addresses(vec![module_id.to_owned()])?;
581 let (_, module_addr) = module_address_response.modules.pop().unwrap();
582 let response = self
583 .environment()
584 .query(msg, &module_addr)
585 .map_err(Into::into)?;
586 Ok(response)
587 }
588
589 pub fn set_ibc_status(&self, enabled: bool) -> AbstractClientResult<Chain::Response> {
591 self.abstr_account
592 .set_ibc_status(enabled)
593 .map_err(Into::into)
594 }
595
596 pub fn module_infos(&self) -> AbstractClientResult<ModuleInfosResponse> {
598 let mut module_infos: Vec<AccountModuleInfo> = vec![];
599 loop {
600 let last_module_id: Option<String> = module_infos
601 .last()
602 .map(|module_info| module_info.id.clone());
603 let res: ModuleInfosResponse = self.abstr_account.module_infos(None, last_module_id)?;
604 if res.module_infos.is_empty() {
605 break;
606 }
607 module_infos.extend(res.module_infos);
608 }
609 Ok(ModuleInfosResponse { module_infos })
610 }
611
612 pub fn module_addresses(
614 &self,
615 ids: Vec<String>,
616 ) -> AbstractClientResult<ModuleAddressesResponse> {
617 self.abstr_account.module_addresses(ids).map_err(Into::into)
618 }
619
620 pub fn module_installed(&self, id: ModuleId) -> AbstractClientResult<bool> {
622 let key = account::state::ACCOUNT_MODULES.key(id).to_vec();
626 let maybe_module_addr = self
627 .environment()
628 .wasm_querier()
629 .raw_query(&self.abstr_account.address()?, key)
630 .map_err(Into::into)?;
631 Ok(!maybe_module_addr.is_empty())
632 }
633
634 pub fn module_version_installed(&self, module: ModuleInfo) -> AbstractClientResult<bool> {
636 let module_id = module.id();
637 if !self.module_installed(&module_id)? {
639 return Ok(false);
640 }
641
642 let mut module_versions_response = self.abstr_account.module_versions(vec![module_id])?;
643 let installed_version = module_versions_response.versions.pop().unwrap().version;
644 let expected_version = match &module.version {
645 ModuleVersion::Latest => {
647 let account_config = self.abstr_account.config()?;
648 let mut modules_response: registry::ModulesResponse = self
649 .environment()
650 .query(
651 ®istry::QueryMsg::Modules {
652 infos: vec![module.clone()],
653 },
654 &account_config.registry_address,
655 )
656 .map_err(Into::into)?;
657 modules_response
658 .modules
659 .pop()
660 .unwrap()
661 .module
662 .info
663 .version
664 .to_string()
665 }
666 ModuleVersion::Version(version) => version.clone(),
667 };
668 Ok(installed_version == expected_version)
669 }
670
671 pub fn ibc_status(&self) -> AbstractClientResult<bool> {
673 self.module_installed(IBC_CLIENT)
674 }
675
676 pub fn sub_account_builder(&self) -> AccountBuilder<Chain> {
678 let mut builder = AccountBuilder::new(&self.infrastructure().unwrap());
679 builder.sub_account(self);
680 builder.name("Sub Account");
681 builder
682 }
683
684 pub fn sub_accounts(&self) -> AbstractClientResult<Vec<Account<Chain>>> {
686 let mut sub_accounts = vec![];
687 let mut start_after = None;
688 let abstr_deployment = Abstract::load_from(self.environment())?;
689 loop {
690 let sub_account_ids = self
691 .abstr_account
692 .sub_account_ids(None, start_after)?
693 .sub_accounts;
694 start_after = sub_account_ids.last().cloned();
695
696 if sub_account_ids.is_empty() {
697 break;
698 }
699 sub_accounts.extend(sub_account_ids);
700 }
701
702 let sub_accounts = sub_accounts
703 .into_iter()
704 .map(|id| {
705 Ok(Account::new(AccountI::load_from(
706 &abstr_deployment,
707 AccountId::local(id),
708 )?))
709 })
710 .collect::<Result<Vec<_>, AbstractInterfaceError>>();
711
712 Ok(sub_accounts?)
713 }
714
715 pub fn address(&self) -> AbstractClientResult<Addr> {
717 Ok(self.abstr_account.address()?)
718 }
719
720 pub fn application<M: RegisteredModule + From<Contract<Chain>>>(
723 &self,
724 ) -> AbstractClientResult<Application<Chain, M>> {
725 let module = self.module()?;
726 let account = self.clone();
727
728 Application::new(account, module)
729 }
730
731 fn install_module_internal<M: RegisteredModule + From<Contract<Chain>>>(
733 &self,
734 mut modules: Vec<ModuleInstallConfig>,
735 funds: &[Coin],
736 ) -> AbstractClientResult<Application<Chain, M>> {
737 let module_infos = self.module_infos()?;
738 modules.retain(|m| {
739 !module_infos
740 .module_infos
741 .iter()
742 .any(|module_info| module_info.id == m.module.id())
743 });
744 if !modules.is_empty() {
745 self.abstr_account.install_modules(modules, funds)?;
746 }
747
748 let module = self.module::<M>()?;
749
750 Application::new(Account::new(self.abstr_account.clone()), module)
751 }
752
753 pub(crate) fn module<T: RegisteredModule + From<Contract<Chain>>>(
754 &self,
755 ) -> AbstractClientResult<T> {
756 let module_id = T::module_id();
757 let account_module_id = T::installed_module_contract_id(&self.id()?);
758 let maybe_module_addr = self.module_addresses(vec![module_id.to_string()])?.modules;
759
760 if !maybe_module_addr.is_empty() {
761 let contract = Contract::new(account_module_id, self.environment());
762 contract.set_address(&maybe_module_addr[0].1);
763 let module: T = contract.into();
764 Ok(module)
765 } else {
766 Err(AbstractClientError::ModuleNotInstalled {})
767 }
768 }
769
770 pub fn claim_namespace(
772 &self,
773 namespace: impl Into<String>,
774 ) -> Result<Chain::Response, AbstractInterfaceError> {
775 self.abstr_account.claim_namespace(namespace)
776 }
777}
778
779impl<Chain: MutCwEnv> Account<Chain> {
780 pub fn set_balance(&self, amount: &[Coin]) -> AbstractClientResult<()> {
782 self.environment()
783 .set_balance(&self.address()?, amount.to_vec())
784 .map_err(Into::into)
785 .map_err(Into::into)
786 }
787
788 pub fn add_balance(&self, amount: &[Coin]) -> AbstractClientResult<()> {
790 self.environment()
791 .add_balance(&self.address()?, amount.to_vec())
792 .map_err(Into::into)
793 .map_err(Into::into)
794 }
795}
796
797impl<Chain: CwEnv> Display for Account<Chain> {
798 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
799 write!(f, "{}", self.abstr_account)
800 }
801}
802
803impl<Chain: CwEnv> Debug for Account<Chain> {
804 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
805 f.debug_struct("Account")
806 .field("abstr_account", &self.abstr_account)
807 .finish()
808 }
809}
810
811#[cfg(test)]
812pub mod test {
813 use abstract_interface::{Abstract, RegistryQueryFns};
814 use abstract_std::objects::namespace::Namespace;
815 use cw_orch::{contract::Deploy, mock::MockBech32};
816
817 use crate::AbstractClient;
818
819 #[coverage_helper::test]
820 fn namespace_after_creation() -> cw_orch::anyhow::Result<()> {
821 let mock = MockBech32::new("mock");
822 let abstr = AbstractClient::builder(mock.clone()).build()?;
823
824 let my_namespace = "my-namespace";
825 let new_account = abstr.account_builder().build()?;
826 new_account.claim_namespace(my_namespace)?;
827
828 let abstr = Abstract::load_from(mock.clone())?;
830 let namespace_response = abstr.registry.namespace(Namespace::new(my_namespace)?)?;
831
832 match namespace_response {
833 abstract_std::registry::NamespaceResponse::Claimed(c) => {
834 assert_eq!(c.account_id, new_account.id()?)
835 }
836 abstract_std::registry::NamespaceResponse::Unclaimed {} => {
837 panic!("Expected claimed namespace")
838 }
839 }
840
841 Ok(())
842 }
843}