1use abstract_std::{
6 account::ExecuteMsg,
7 account::ModuleInstallConfig,
8 base,
9 ibc::{Callback, ModuleQuery},
10 ibc_client::{self, ExecuteMsg as IbcClientMsg, InstalledModuleIdentification},
11 ibc_host::HostAction,
12 objects::{module::ModuleInfo, TruncatedChainId},
13 ABSTRACT_VERSION, IBC_CLIENT,
14};
15use cosmwasm_std::{
16 to_json_binary, wasm_execute, Addr, Coin, CosmosMsg, Deps, Empty, QueryRequest,
17};
18use serde::Serialize;
19
20use super::AbstractApi;
21use crate::{
22 features::{AccountExecutor, AccountIdentification, ModuleIdentification},
23 AbstractSdkResult, ModuleInterface, ModuleRegistryInterface,
24};
25
26pub trait IbcInterface:
28 AccountIdentification + ModuleRegistryInterface + ModuleIdentification + ModuleInterface
29{
30 fn ibc_client<'a>(&'a self, deps: Deps<'a>) -> IbcClient<'a, Self> {
47 IbcClient { base: self, deps }
48 }
49}
50
51impl<T> IbcInterface for T where
52 T: AccountIdentification + ModuleRegistryInterface + ModuleIdentification + ModuleInterface
53{
54}
55
56impl<T: IbcInterface> AbstractApi<T> for IbcClient<'_, T> {
57 const API_ID: &'static str = "IbcClient";
58
59 fn base(&self) -> &T {
60 self.base
61 }
62 fn deps(&self) -> Deps {
63 self.deps
64 }
65}
66
67#[derive(Clone)]
68pub struct IbcClient<'a, T: IbcInterface> {
85 base: &'a T,
86 deps: Deps<'a>,
87}
88
89impl<T: IbcInterface> IbcClient<'_, T> {
90 pub fn module_address(&self) -> AbstractSdkResult<Addr> {
92 let modules = self.base.modules(self.deps);
93 modules.assert_module_dependency(IBC_CLIENT)?;
94 self.base
95 .module_registry(self.deps)?
96 .query_module(ModuleInfo::from_id(IBC_CLIENT, ABSTRACT_VERSION.into())?)?
97 .reference
98 .unwrap_native()
99 .map_err(Into::into)
100 }
101
102 pub fn module_ibc_action<M: Serialize>(
104 &self,
105 host_chain: TruncatedChainId,
106 target_module: ModuleInfo,
107 exec_msg: &M,
108 callback: Option<Callback>,
109 ) -> AbstractSdkResult<CosmosMsg> {
110 let ibc_client_addr = self.module_address()?;
111 let msg = wasm_execute(
112 ibc_client_addr,
113 &ibc_client::ExecuteMsg::ModuleIbcAction {
114 host_chain,
115 target_module,
116 msg: to_json_binary(exec_msg)?,
117 callback,
118 },
119 vec![],
120 )?;
121 Ok(msg.into())
122 }
123
124 pub fn module_ibc_query<B: Serialize, M: Serialize>(
127 &self,
128 host_chain: TruncatedChainId,
129 target_module: InstalledModuleIdentification,
130 query_msg: &base::QueryMsg<B, M>,
131 callback: Callback,
132 ) -> AbstractSdkResult<CosmosMsg> {
133 let ibc_client_addr = self.module_address()?;
134 let msg = wasm_execute(
135 ibc_client_addr,
136 &ibc_client::ExecuteMsg::IbcQuery {
137 host_chain,
138 queries: vec![QueryRequest::Custom(ModuleQuery {
139 target_module,
140 msg: to_json_binary(query_msg)?,
141 })],
142 callback,
143 },
144 vec![],
145 )?;
146 Ok(msg.into())
147 }
148
149 pub fn ibc_query(
151 &self,
152 host_chain: TruncatedChainId,
153 query: impl Into<QueryRequest<ModuleQuery>>,
154 callback: Callback,
155 ) -> AbstractSdkResult<CosmosMsg> {
156 let ibc_client_addr = self.module_address()?;
157 let msg = wasm_execute(
158 ibc_client_addr,
159 &ibc_client::ExecuteMsg::IbcQuery {
160 host_chain,
161 queries: vec![query.into()],
162 callback,
163 },
164 vec![],
165 )?;
166 Ok(msg.into())
167 }
168
169 pub fn ibc_queries(
171 &self,
172 host_chain: TruncatedChainId,
173 queries: Vec<QueryRequest<ModuleQuery>>,
174 callback: Callback,
175 ) -> AbstractSdkResult<CosmosMsg> {
176 let ibc_client_addr = self.module_address()?;
177 let msg = wasm_execute(
178 ibc_client_addr,
179 &ibc_client::ExecuteMsg::IbcQuery {
180 host_chain,
181 queries,
182 callback,
183 },
184 vec![],
185 )?;
186 Ok(msg.into())
187 }
188
189 pub fn remote_account_addr(
192 &self,
193 host_chain: &TruncatedChainId,
194 ) -> AbstractSdkResult<Option<String>> {
195 let ibc_client_addr = self.module_address()?;
196 let account_id = self.base.account_id(self.deps)?;
197
198 let (trace, sequence) = account_id.decompose();
199 ibc_client::state::ACCOUNTS
200 .query(
201 &self.deps.querier,
202 ibc_client_addr,
203 (&trace, sequence, host_chain),
204 )
205 .map_err(Into::into)
206 }
207}
208
209impl<T: IbcInterface + AccountExecutor> IbcClient<'_, T> {
210 pub fn execute(
212 &self,
213 msg: &abstract_std::ibc_client::ExecuteMsg,
214 funds: Vec<Coin>,
215 ) -> AbstractSdkResult<CosmosMsg> {
216 let wasm_msg = wasm_execute(
217 self.base.account(self.deps)?.into_addr().into_string(),
218 &ExecuteMsg::ExecuteOnModule::<Empty> {
219 module_id: IBC_CLIENT.to_owned(),
220 exec_msg: to_json_binary(&msg)?,
221 funds,
222 },
223 vec![],
224 )?;
225 Ok(wasm_msg.into())
226 }
227 pub fn create_remote_account(
229 &self,
230 host_chain: TruncatedChainId,
232 ) -> AbstractSdkResult<CosmosMsg> {
233 self.execute(
234 &abstract_std::ibc_client::ExecuteMsg::Register {
235 host_chain,
236 namespace: None,
237 install_modules: vec![],
238 },
239 vec![],
240 )
241 }
242
243 pub fn host_action(
245 &self,
246 host_chain: TruncatedChainId,
247 action: HostAction,
248 ) -> AbstractSdkResult<CosmosMsg> {
249 self.execute(&IbcClientMsg::RemoteAction { host_chain, action }, vec![])
250 }
251
252 pub fn ics20_transfer(
254 &self,
255 host_chain: TruncatedChainId,
256 funds: Vec<Coin>,
257 memo: Option<String>,
258 receiver: Option<String>,
259 ) -> AbstractSdkResult<CosmosMsg> {
260 self.execute(
261 &IbcClientMsg::SendFunds {
262 host_chain,
263 memo,
264 receiver,
265 },
266 funds,
267 )
268 }
269
270 pub fn install_remote_app<M: Serialize>(
272 &self,
273 host_chain: TruncatedChainId,
275 module: ModuleInfo,
276 init_msg: &M,
277 ) -> AbstractSdkResult<CosmosMsg> {
278 self.host_action(
279 host_chain,
280 HostAction::Dispatch {
281 account_msgs: vec![abstract_std::account::ExecuteMsg::InstallModules {
282 modules: vec![ModuleInstallConfig::new(
283 module,
284 Some(to_json_binary(&init_msg)?),
285 )],
286 }],
287 },
288 )
289 }
290
291 pub fn install_remote_api<M: Serialize>(
293 &self,
294 host_chain: TruncatedChainId,
296 module: ModuleInfo,
297 ) -> AbstractSdkResult<CosmosMsg> {
298 self.host_action(
299 host_chain,
300 HostAction::Dispatch {
301 account_msgs: vec![abstract_std::account::ExecuteMsg::InstallModules {
302 modules: vec![ModuleInstallConfig::new(module, None)],
303 }],
304 },
305 )
306 }
307
308 pub fn execute_on_module<M: Serialize>(
312 &self,
313 host_chain: TruncatedChainId,
314 module_id: String,
315 exec_msg: &M,
316 ) -> AbstractSdkResult<CosmosMsg> {
317 self.host_action(
318 host_chain,
319 HostAction::Dispatch {
320 account_msgs: vec![abstract_std::account::ExecuteMsg::ExecuteOnModule {
321 module_id,
322 exec_msg: to_json_binary(exec_msg)?,
323 funds: vec![],
324 }],
325 },
326 )
327 }
328
329 pub fn remote_account(
332 &self,
333 host_chain: &TruncatedChainId,
334 ) -> AbstractSdkResult<Option<String>> {
335 let account_id = self.base.account_id(self.deps)?;
336 let ibc_client_addr = self.module_address()?;
337
338 let (trace, sequence) = account_id.decompose();
339 ibc_client::state::ACCOUNTS
340 .query(
341 &self.deps.querier,
342 ibc_client_addr,
343 (&trace, sequence, host_chain),
344 )
345 .map_err(Into::into)
346 }
347}
348
349#[cfg(test)]
350mod test {
351 #![allow(clippy::needless_borrows_for_generic_args)]
352 use abstract_testing::prelude::*;
353 use cosmwasm_std::*;
354
355 use super::*;
356 use crate::{apis::traits::test::abstract_api_test, mock_module::*};
357 const TEST_HOST_CHAIN: &str = "hostchain";
358
359 #[coverage_helper::test]
361 fn test_host_action_no_callback() {
362 let (deps, _, stub) = mock_module_setup();
363
364 let client = stub.ibc_client(deps.as_ref());
365 let msg = client.host_action(
366 TEST_HOST_CHAIN.parse().unwrap(),
367 HostAction::Dispatch {
368 account_msgs: vec![abstract_std::account::ExecuteMsg::UpdateStatus {
369 is_suspended: None,
370 }],
371 },
372 );
373 assert!(msg.is_ok());
374
375 let base = test_account(deps.api);
376 let expected = CosmosMsg::Wasm(WasmMsg::Execute {
377 contract_addr: base.addr().to_string(),
378 msg: to_json_binary(&ExecuteMsg::ExecuteOnModule::<cosmwasm_std::Empty> {
379 module_id: IBC_CLIENT.to_owned(),
380 exec_msg: to_json_binary(&IbcClientMsg::RemoteAction {
381 host_chain: TEST_HOST_CHAIN.parse().unwrap(),
382 action: HostAction::Dispatch {
383 account_msgs: vec![abstract_std::account::ExecuteMsg::UpdateStatus {
384 is_suspended: None,
385 }],
386 },
387 })
388 .unwrap(),
389 funds: vec![],
390 })
391 .unwrap(),
392 funds: vec![],
393 });
394 assert_eq!(msg, Ok(expected));
395 }
396
397 #[coverage_helper::test]
399 fn test_ics20_transfer() {
400 let (deps, _, stub) = mock_module_setup();
401
402 let client = stub.ibc_client(deps.as_ref());
403
404 let expected_funds = coins(100, "denom");
405
406 let msg = client.ics20_transfer(
407 TEST_HOST_CHAIN.parse().unwrap(),
408 expected_funds.clone(),
409 None,
410 None,
411 );
412 assert!(msg.is_ok());
413
414 let base = test_account(deps.api);
415 let expected = CosmosMsg::Wasm(WasmMsg::Execute {
416 contract_addr: base.addr().to_string(),
417 msg: to_json_binary(&ExecuteMsg::ExecuteOnModule::<cosmwasm_std::Empty> {
418 module_id: IBC_CLIENT.to_owned(),
419 exec_msg: to_json_binary(&IbcClientMsg::SendFunds {
420 host_chain: TEST_HOST_CHAIN.parse().unwrap(),
421 memo: None,
422 receiver: None,
423 })
424 .unwrap(),
425 funds: expected_funds,
426 })
427 .unwrap(),
428 funds: vec![],
430 });
431 assert_eq!(msg, Ok(expected));
432 }
433
434 #[coverage_helper::test]
435 fn abstract_api() {
436 let (deps, _, app) = mock_module_setup();
437 let client = app.ibc_client(deps.as_ref());
438
439 abstract_api_test(client);
440 }
441}