use abstract_core::{
manager::{
state::AccountInfo, InfoResponse, ManagerModuleInfo, ModuleAddressesResponse,
ModuleInfosResponse, ModuleInstallConfig,
},
objects::{
gov_type::GovernanceDetails, namespace::Namespace, nested_admin::MAX_ADMIN_RECURSION,
validation::verifiers, AccountId, AssetEntry,
},
version_control::NamespaceResponse,
PROXY,
};
use abstract_interface::{
Abstract, AbstractAccount, AbstractInterfaceError, AccountDetails, DependencyCreation,
InstallConfig, ManagerExecFns, ManagerQueryFns, RegisteredModule, VCQueryFns,
};
use cosmwasm_std::{to_json_binary, Attribute, CosmosMsg, Empty, Uint128};
use cw_orch::prelude::*;
use cw_orch::{contract::Contract, environment::MutCwEnv};
use crate::{
client::AbstractClientResult,
infrastructure::{Environment, Infrastructure},
AbstractClientError, Application,
};
pub struct AccountBuilder<'a, Chain: CwEnv> {
pub(crate) abstr: &'a Abstract<Chain>,
name: Option<String>,
description: Option<String>,
link: Option<String>,
namespace: Option<Namespace>,
base_asset: Option<AssetEntry>,
ownership: Option<GovernanceDetails<String>>,
fetch_if_namespace_claimed: bool,
install_on_sub_account: bool,
}
impl<'a, Chain: CwEnv> AccountBuilder<'a, Chain> {
pub(crate) fn new(abstr: &'a Abstract<Chain>) -> Self {
Self {
abstr,
name: None,
description: None,
link: None,
namespace: None,
base_asset: None,
ownership: None,
fetch_if_namespace_claimed: true,
install_on_sub_account: true,
}
}
pub fn name(&mut self, name: impl Into<String>) -> &mut Self {
self.name = Some(name.into());
self
}
pub fn description(&mut self, description: impl Into<String>) -> &mut Self {
self.description = Some(description.into());
self
}
pub fn link(&mut self, link: impl Into<String>) -> &mut Self {
self.link = Some(link.into());
self
}
pub fn namespace(&mut self, namespace: Namespace) -> &mut Self {
self.namespace = Some(namespace);
self
}
pub fn base_asset(&mut self, base_asset: AssetEntry) -> &mut Self {
self.base_asset = Some(base_asset);
self
}
pub fn fetch_if_namespace_claimed(&mut self, value: bool) -> &mut Self {
self.fetch_if_namespace_claimed = value;
self
}
pub fn install_on_sub_account(&mut self, value: bool) -> &mut Self {
self.install_on_sub_account = value;
self
}
pub fn ownership(&mut self, ownership: GovernanceDetails<String>) -> &mut Self {
self.ownership = Some(ownership);
self
}
pub fn build(&self) -> AbstractClientResult<Account<Chain>> {
if self.fetch_if_namespace_claimed {
if let Some(ref namespace) = self.namespace {
let account_from_namespace_result: Option<Account<Chain>> =
Account::from_namespace(
self.abstr,
namespace.clone(),
self.install_on_sub_account,
)?;
if let Some(account_from_namespace) = account_from_namespace_result {
return Ok(account_from_namespace);
}
}
}
let chain = self.abstr.version_control.get_chain();
let sender = chain.sender().to_string();
let name = self
.name
.clone()
.unwrap_or_else(|| String::from("Default Abstract Account"));
let ownership = self
.ownership
.clone()
.unwrap_or(GovernanceDetails::Monarchy { monarch: sender });
verifiers::validate_name(&name)?;
verifiers::validate_description(self.description.as_deref())?;
verifiers::validate_link(self.link.as_deref())?;
let abstract_account = self.abstr.account_factory.create_new_account(
AccountDetails {
name,
description: self.description.clone(),
link: self.link.clone(),
namespace: self.namespace.as_ref().map(ToString::to_string),
base_asset: self.base_asset.clone(),
install_modules: vec![],
},
ownership,
Some(&[]),
)?;
Ok(Account::new(abstract_account, self.install_on_sub_account))
}
}
#[derive(Clone)]
pub struct Account<Chain: CwEnv> {
pub(crate) abstr_account: AbstractAccount<Chain>,
install_on_sub_account: bool,
}
struct ParsedAccountCreationResponse {
sub_account_id: u32,
module_address: String,
}
impl<Chain: CwEnv> Account<Chain> {
pub(crate) fn new(
abstract_account: AbstractAccount<Chain>,
install_on_sub_account: bool,
) -> Self {
Self {
abstr_account: abstract_account,
install_on_sub_account,
}
}
pub(crate) fn from_namespace(
abstr: &Abstract<Chain>,
namespace: Namespace,
install_on_sub_account: bool,
) -> AbstractClientResult<Option<Self>> {
let namespace_response: NamespaceResponse = abstr.version_control.namespace(namespace)?;
let NamespaceResponse::Claimed(info) = namespace_response else {
return Ok(None);
};
let abstract_account: AbstractAccount<Chain> = AbstractAccount::new(abstr, info.account_id);
Ok(Some(Self::new(abstract_account, install_on_sub_account)))
}
pub fn id(&self) -> AbstractClientResult<AccountId> {
self.abstr_account.id().map_err(Into::into)
}
pub fn query_balance(&self, denom: impl Into<String>) -> AbstractClientResult<Uint128> {
let coins = self
.environment()
.balance(self.proxy()?, Some(denom.into()))
.map_err(Into::into)?;
Ok(coins[0].amount)
}
pub fn query_balances(&self) -> AbstractClientResult<Vec<Coin>> {
self.environment()
.balance(self.proxy()?, None)
.map_err(Into::into)
.map_err(Into::into)
}
pub fn info(&self) -> AbstractClientResult<AccountInfo<Addr>> {
let info_response: InfoResponse = self.abstr_account.manager.info()?;
Ok(info_response.info)
}
pub fn install_app<M: ContractInstance<Chain> + InstallConfig + From<Contract<Chain>>>(
&self,
configuration: &M::InitMsg,
funds: &[Coin],
) -> AbstractClientResult<Application<Chain, M>> {
let modules = vec![M::install_config(configuration)?];
match self.install_on_sub_account {
true => self.install_module_sub_internal(modules, funds),
false => self.install_module_current_internal(modules, funds),
}
}
pub fn install_adapter<
M: ContractInstance<Chain> + InstallConfig<InitMsg = Empty> + From<Contract<Chain>>,
>(
&self,
funds: &[Coin],
) -> AbstractClientResult<Application<Chain, M>> {
let modules = vec![M::install_config(&Empty {})?];
match self.install_on_sub_account {
true => self.install_module_sub_internal(modules, funds),
false => self.install_module_current_internal(modules, funds),
}
}
pub fn install_app_with_dependencies<
M: ContractInstance<Chain>
+ DependencyCreation
+ InstallConfig
+ From<Contract<Chain>>
+ Clone,
>(
&self,
module_configuration: &M::InitMsg,
dependencies_config: M::DependenciesConfig,
funds: &[Coin],
) -> AbstractClientResult<Application<Chain, M>> {
let mut install_configs: Vec<ModuleInstallConfig> =
M::dependency_install_configs(dependencies_config)?;
install_configs.push(M::install_config(module_configuration)?);
match self.install_on_sub_account {
true => self.install_module_sub_internal(install_configs, funds),
false => self.install_module_current_internal(install_configs, funds),
}
}
pub fn ownership(&self) -> AbstractClientResult<cw_ownable::Ownership<String>> {
self.abstr_account.manager.ownership().map_err(Into::into)
}
pub fn owner(&self) -> AbstractClientResult<Addr> {
let mut governance = self.abstr_account.manager.info()?.info.governance_details;
let environment = self.environment();
for _ in 0..MAX_ADMIN_RECURSION {
match &governance {
GovernanceDetails::SubAccount { manager, .. } => {
governance = environment
.query::<_, InfoResponse>(
&abstract_core::manager::QueryMsg::Info {},
manager,
)
.map_err(|err| err.into())?
.info
.governance_details;
}
_ => break,
}
}
governance
.owner_address()
.ok_or(AbstractClientError::RenouncedAccount {})
}
pub fn execute(
&self,
execute_msgs: impl IntoIterator<Item = impl Into<CosmosMsg>>,
funds: &[Coin],
) -> AbstractClientResult<<Chain as TxHandler>::Response> {
let msgs = execute_msgs.into_iter().map(Into::into).collect();
self.abstr_account
.manager
.execute(
&abstract_core::manager::ExecuteMsg::ExecOnModule {
module_id: PROXY.to_owned(),
exec_msg: to_json_binary(&abstract_core::proxy::ExecuteMsg::ModuleAction {
msgs,
})
.map_err(AbstractInterfaceError::from)?,
},
Some(funds),
)
.map_err(Into::into)
}
pub fn module_infos(&self) -> AbstractClientResult<ModuleInfosResponse> {
let mut module_infos: Vec<ManagerModuleInfo> = vec![];
loop {
let last_module_id: Option<String> = module_infos
.last()
.map(|module_info| module_info.id.clone());
let res: ModuleInfosResponse = self
.abstr_account
.manager
.module_infos(None, last_module_id)?;
if res.module_infos.is_empty() {
break;
}
module_infos.extend(res.module_infos);
}
Ok(ModuleInfosResponse { module_infos })
}
pub fn module_addresses(
&self,
ids: Vec<String>,
) -> AbstractClientResult<ModuleAddressesResponse> {
self.abstr_account
.manager
.module_addresses(ids)
.map_err(Into::into)
}
pub fn proxy(&self) -> AbstractClientResult<Addr> {
self.abstr_account.proxy.address().map_err(Into::into)
}
pub fn manager(&self) -> AbstractClientResult<Addr> {
self.abstr_account.manager.address().map_err(Into::into)
}
fn install_module_current_internal<
M: ContractInstance<Chain> + RegisteredModule + From<Contract<Chain>>,
>(
&self,
modules: Vec<ModuleInstallConfig>,
funds: &[Coin],
) -> AbstractClientResult<Application<Chain, M>> {
let install_module_response = self
.abstr_account
.manager
.install_modules(modules, Some(funds))?;
let module_addr = Self::parse_modules_installing_response(install_module_response);
let contract = Contract::new(M::module_id().to_owned(), self.environment())
.with_address(Some(&module_addr));
let adapter: M = contract.into();
Application::new(
Account::new(self.abstr_account.clone(), self.install_on_sub_account),
adapter,
)
}
fn install_module_sub_internal<
M: ContractInstance<Chain> + RegisteredModule + From<Contract<Chain>>,
>(
&self,
modules: Vec<ModuleInstallConfig>,
funds: &[Coin],
) -> AbstractClientResult<Application<Chain, M>> {
let sub_account_response = self.abstr_account.manager.create_sub_account(
modules,
"Sub Account".to_owned(),
None,
None,
None,
None,
funds,
)?;
let parsed_account_creation_response =
Self::parse_account_creation_response(sub_account_response);
let sub_account: AbstractAccount<Chain> = AbstractAccount::new(
&self.infrastructure()?,
AccountId::local(parsed_account_creation_response.sub_account_id),
);
let contract =
Contract::new(M::module_id().to_owned(), self.environment()).with_address(Some(
&Addr::unchecked(parsed_account_creation_response.module_address),
));
let app: M = contract.into();
Application::new(Account::new(sub_account, false), app)
}
fn parse_account_creation_response(
response: <Chain as TxHandler>::Response,
) -> ParsedAccountCreationResponse {
let wasm_abstract_attributes: Vec<Attribute> = response
.events()
.into_iter()
.filter(|e| e.ty == "wasm-abstract")
.flat_map(|e| e.attributes)
.collect();
let sub_account_id: Option<u32> = wasm_abstract_attributes
.iter()
.find(|a| a.key == "sub_account_added")
.map(|a| a.value.parse().unwrap());
let module_addresses: Option<String> = wasm_abstract_attributes
.iter()
.find(|a| a.key == "new_modules")
.map(|a| a.value.parse().unwrap());
let module_address: String = module_addresses
.unwrap()
.split(',')
.last()
.unwrap()
.to_string();
ParsedAccountCreationResponse {
sub_account_id: sub_account_id.unwrap(),
module_address,
}
}
fn parse_modules_installing_response(response: <Chain as TxHandler>::Response) -> Addr {
let wasm_abstract_attributes: Vec<Attribute> = response
.events()
.into_iter()
.filter(|e| e.ty == "wasm-abstract")
.flat_map(|e| e.attributes)
.collect();
let module_addresses: String = wasm_abstract_attributes
.iter()
.find(|a| a.key == "new_modules")
.map(|a| a.value.parse().unwrap())
.unwrap();
let module_address = module_addresses.split(',').last().unwrap();
Addr::unchecked(module_address)
}
}
impl<Chain: MutCwEnv> Account<Chain> {
pub fn set_balance(&self, amount: Vec<Coin>) -> AbstractClientResult<()> {
self.environment()
.set_balance(&self.proxy()?, amount)
.map_err(Into::into)
.map_err(Into::into)
}
pub fn add_balance(&self, amount: Vec<Coin>) -> AbstractClientResult<()> {
self.environment()
.add_balance(&self.proxy()?, amount)
.map_err(Into::into)
.map_err(Into::into)
}
}