use abstract_interface::{
Abstract, AccountI, AnsHost, IbcClient, ModuleFactory, RegisteredModule, Registry,
};
use abstract_std::objects::{
module::{ModuleInfo, ModuleStatus, ModuleVersion},
module_reference::ModuleReference,
namespace::Namespace,
salt::generate_instantiate_salt,
AccountId,
};
use cosmwasm_std::{BlockInfo, Uint128};
use cw_orch::{contract::Contract, environment::Environment as _, prelude::*};
use rand::Rng;
use crate::{
account::{Account, AccountBuilder},
source::AccountSource,
AbstractClientError, Environment, Publisher, Service,
};
#[derive(Clone)]
pub struct AbstractClient<Chain: CwEnv> {
pub(crate) abstr: Abstract<Chain>,
}
pub type AbstractClientResult<T> = Result<T, AbstractClientError>;
impl<Chain: CwEnv> AbstractClient<Chain> {
pub fn new(chain: Chain) -> AbstractClientResult<Self> {
let abstr = Abstract::load_from(chain)?;
Ok(Self { abstr })
}
pub fn registry(&self) -> &Registry<Chain> {
&self.abstr.registry
}
pub fn name_service(&self) -> &AnsHost<Chain> {
&self.abstr.ans_host
}
pub fn module_factory(&self) -> &ModuleFactory<Chain> {
&self.abstr.module_factory
}
pub fn ibc_client(&self) -> &IbcClient<Chain> {
&self.abstr.ibc.client
}
pub fn service<M: RegisteredModule + From<Contract<Chain>>>(
&self,
) -> AbstractClientResult<Service<Chain, M>> {
Service::new(self.registry())
}
pub fn block_info(&self) -> AbstractClientResult<BlockInfo> {
self.environment()
.block_info()
.map_err(|e| AbstractClientError::CwOrch(e.into()))
}
pub fn fetch_publisher(&self, namespace: Namespace) -> AbstractClientResult<Publisher<Chain>> {
let account = self.fetch_account(namespace)?;
account.publisher()
}
pub fn account_builder(&self) -> AccountBuilder<Chain> {
AccountBuilder::new(&self.abstr)
}
pub fn sub_account_builder<'a>(
&'a self,
account: &'a Account<Chain>,
) -> AbstractClientResult<AccountBuilder<'a, Chain>> {
let mut builder = AccountBuilder::new(&self.abstr);
builder.sub_account(account);
builder.name("Sub Account");
Ok(builder)
}
pub fn fetch_account<T: Into<AccountSource>>(
&self,
source: T,
) -> AbstractClientResult<Account<Chain>> {
let source = source.into();
let chain = self.abstr.registry.environment();
match source {
AccountSource::Namespace(namespace) => {
let account_from_namespace_result: Option<Account<Chain>> =
Account::maybe_from_namespace(&self.abstr, namespace.clone())?;
if let Some(account_from_namespace) = account_from_namespace_result {
Ok(account_from_namespace)
} else {
Err(AbstractClientError::NamespaceNotClaimed {
namespace: namespace.to_string(),
})
}
}
AccountSource::AccountId(account_id) => {
let abstract_account = AccountI::load_from(&self.abstr, account_id.clone())?;
Ok(Account::new(abstract_account))
}
AccountSource::App(app) => {
let app_config: abstract_std::app::AppConfigResponse = chain
.query(
&abstract_std::app::QueryMsg::<Empty>::Base(
abstract_std::app::BaseQueryMsg::BaseConfig {},
),
&app,
)
.map_err(Into::into)?;
let account_config: abstract_std::account::ConfigResponse = chain
.query(
&abstract_std::account::QueryMsg::Config {},
&app_config.account,
)
.map_err(Into::into)?;
let abstract_account = AccountI::load_from(&self.abstr, account_config.account_id)?;
Ok(Account::new(abstract_account))
}
}
}
pub fn fetch_or_build_account<T: Into<AccountSource>, F>(
&self,
source: T,
build_fn: F,
) -> AbstractClientResult<Account<Chain>>
where
F: for<'a, 'b> FnOnce(
&'a mut AccountBuilder<'b, Chain>,
) -> &'a mut AccountBuilder<'b, Chain>,
{
match self.fetch_account(source) {
Ok(account) => Ok(account),
Err(_) => {
let mut account_builder = self.account_builder();
build_fn(&mut account_builder).build()
}
}
}
pub fn sender(&self) -> Addr {
self.environment().sender_addr()
}
#[deprecated(since = "0.24.2", note = "use fetch_account instead")]
pub fn account_from<T: Into<AccountSource>>(
&self,
source: T,
) -> AbstractClientResult<Account<Chain>> {
self.fetch_account(source)
}
pub fn query_balance(
&self,
address: &Addr,
denom: impl Into<String>,
) -> AbstractClientResult<Uint128> {
let coins = self
.environment()
.bank_querier()
.balance(address, Some(denom.into()))
.map_err(Into::into)?;
Ok(coins[0].amount)
}
pub fn query_balances(&self, address: &Addr) -> AbstractClientResult<Vec<Coin>> {
self.environment()
.bank_querier()
.balance(address, None)
.map_err(Into::into)
.map_err(Into::into)
}
pub fn wait_blocks(&self, amount: u64) -> AbstractClientResult<()> {
self.environment()
.wait_blocks(amount)
.map_err(Into::into)
.map_err(Into::into)
}
pub fn wait_seconds(&self, secs: u64) -> AbstractClientResult<()> {
self.environment()
.wait_seconds(secs)
.map_err(Into::into)
.map_err(Into::into)
}
pub fn next_block(&self) -> AbstractClientResult<()> {
self.environment()
.next_block()
.map_err(Into::into)
.map_err(Into::into)
}
pub fn random_account_id(&self) -> AbstractClientResult<u32> {
let mut rng = rand::thread_rng();
loop {
let random_sequence = rng.gen_range(2147483648..u32::MAX);
let potential_account_id = AccountId::local(random_sequence);
if self.abstr.registry.account(potential_account_id).is_err() {
return Ok(random_sequence);
};
}
}
pub fn module_instantiate2_address<M: RegisteredModule>(
&self,
account_id: &AccountId,
) -> AbstractClientResult<Addr> {
self.module_instantiate2_address_raw(
account_id,
ModuleInfo::from_id(
M::module_id(),
ModuleVersion::Version(M::module_version().to_owned()),
)?,
)
}
pub fn module_instantiate2_address_raw(
&self,
account_id: &AccountId,
module_info: ModuleInfo,
) -> AbstractClientResult<Addr> {
let salt = generate_instantiate_salt(account_id);
let wasm_querier = self.environment().wasm_querier();
let module = self.registry().module(module_info)?;
let (code_id, creator) = match module.reference {
ModuleReference::Account(id) => (id, self.environment().sender_addr()),
ModuleReference::App(id) | ModuleReference::Standalone(id) => {
(id, self.abstr.module_factory.address()?)
}
_ => {
return Err(AbstractClientError::Abstract(
abstract_std::AbstractError::Assert(
"module reference not account, app or standalone".to_owned(),
),
))
}
};
let addr = wasm_querier
.instantiate2_addr(code_id, &creator, salt)
.map_err(Into::into)?;
Ok(Addr::unchecked(addr))
}
pub fn module_status(&self, module: ModuleInfo) -> AbstractClientResult<Option<ModuleStatus>> {
self.registry().module_status(module).map_err(Into::into)
}
pub fn call_as(&self, sender: &<Chain as TxHandler>::Sender) -> Self {
Self {
abstr: self.abstr.call_as(sender),
}
}
#[cfg(feature = "interchain")]
pub fn connect_to(
&self,
remote_abstr: &AbstractClient<Chain>,
ibc: &impl cw_orch_interchain::prelude::InterchainEnv<Chain>,
) -> AbstractClientResult<()>
where
Chain: cw_orch_interchain::prelude::IbcQueryHandler,
{
self.abstr.connect_to(&remote_abstr.abstr, ibc)?;
Ok(())
}
}