cosmwasm_std/ibc/
transfer_msg_builder.rs

1use crate::{
2    to_json_string, Coin, IbcCallbackRequest, IbcDstCallback, IbcMsg, IbcSrcCallback, IbcTimeout,
3};
4
5// these are the different memo types and at the same time the states
6// the TransferMsgBuilder can be in
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct EmptyMemo;
9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct WithMemo {
11    memo: String,
12}
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct WithSrcCallback {
15    src_callback: IbcSrcCallback,
16}
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct WithDstCallback {
19    dst_callback: IbcDstCallback,
20}
21#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct WithCallbacks {
23    src_callback: IbcSrcCallback,
24    dst_callback: IbcDstCallback,
25}
26
27pub trait MemoSource {
28    fn into_memo(self) -> Option<String>;
29}
30
31impl MemoSource for EmptyMemo {
32    fn into_memo(self) -> Option<String> {
33        None
34    }
35}
36
37impl MemoSource for WithMemo {
38    fn into_memo(self) -> Option<String> {
39        Some(self.memo)
40    }
41}
42
43impl MemoSource for WithSrcCallback {
44    fn into_memo(self) -> Option<String> {
45        Some(to_json_string(&IbcCallbackRequest::source(self.src_callback)).unwrap())
46    }
47}
48
49impl MemoSource for WithDstCallback {
50    fn into_memo(self) -> Option<String> {
51        Some(to_json_string(&IbcCallbackRequest::destination(self.dst_callback)).unwrap())
52    }
53}
54
55impl MemoSource for WithCallbacks {
56    fn into_memo(self) -> Option<String> {
57        Some(
58            to_json_string(&IbcCallbackRequest::both(
59                self.src_callback,
60                self.dst_callback,
61            ))
62            .unwrap(),
63        )
64    }
65}
66
67impl<M: MemoSource> TransferMsgBuilder<M> {
68    pub fn build(self) -> IbcMsg {
69        IbcMsg::Transfer {
70            channel_id: self.channel_id,
71            to_address: self.to_address,
72            amount: self.amount,
73            timeout: self.timeout,
74            memo: self.memo.into_memo(),
75        }
76    }
77}
78
79#[derive(Clone, Debug, PartialEq, Eq)]
80pub struct TransferMsgBuilder<MemoData> {
81    channel_id: String,
82    to_address: String,
83    amount: Coin,
84    timeout: IbcTimeout,
85    memo: MemoData,
86}
87
88impl TransferMsgBuilder<EmptyMemo> {
89    /// Creates a new transfer message with the given parameters and no memo.
90    pub fn new(
91        channel_id: impl Into<String>,
92        to_address: impl Into<String>,
93        amount: Coin,
94        timeout: impl Into<IbcTimeout>,
95    ) -> Self {
96        Self {
97            channel_id: channel_id.into(),
98            to_address: to_address.into(),
99            amount,
100            timeout: timeout.into(),
101            memo: EmptyMemo,
102        }
103    }
104
105    /// Adds a memo text to the transfer message.
106    pub fn with_memo(self, memo: impl Into<String>) -> TransferMsgBuilder<WithMemo> {
107        TransferMsgBuilder {
108            channel_id: self.channel_id,
109            to_address: self.to_address,
110            amount: self.amount,
111            timeout: self.timeout,
112            memo: WithMemo { memo: memo.into() },
113        }
114    }
115
116    /// Adds an IBC source callback entry to the memo field.
117    /// Use this if you want to receive IBC callbacks on the source chain.
118    ///
119    /// For more info check out [`crate::IbcSourceCallbackMsg`].
120    pub fn with_src_callback(
121        self,
122        src_callback: IbcSrcCallback,
123    ) -> TransferMsgBuilder<WithSrcCallback> {
124        TransferMsgBuilder {
125            channel_id: self.channel_id,
126            to_address: self.to_address,
127            amount: self.amount,
128            timeout: self.timeout,
129            memo: WithSrcCallback { src_callback },
130        }
131    }
132
133    /// Adds an IBC destination callback entry to the memo field.
134    /// Use this if you want to receive IBC callbacks on the destination chain.
135    ///
136    /// For more info check out [`crate::IbcDestinationCallbackMsg`].
137    pub fn with_dst_callback(
138        self,
139        dst_callback: IbcDstCallback,
140    ) -> TransferMsgBuilder<WithDstCallback> {
141        TransferMsgBuilder {
142            channel_id: self.channel_id,
143            to_address: self.to_address,
144            amount: self.amount,
145            timeout: self.timeout,
146            memo: WithDstCallback { dst_callback },
147        }
148    }
149}
150
151impl TransferMsgBuilder<WithSrcCallback> {
152    /// Adds an IBC destination callback entry to the memo field.
153    /// Use this if you want to receive IBC callbacks on the destination chain.
154    ///
155    /// For more info check out [`crate::IbcDestinationCallbackMsg`].
156    pub fn with_dst_callback(
157        self,
158        dst_callback: IbcDstCallback,
159    ) -> TransferMsgBuilder<WithCallbacks> {
160        TransferMsgBuilder {
161            channel_id: self.channel_id,
162            to_address: self.to_address,
163            amount: self.amount,
164            timeout: self.timeout,
165            memo: WithCallbacks {
166                src_callback: self.memo.src_callback,
167                dst_callback,
168            },
169        }
170    }
171}
172
173impl TransferMsgBuilder<WithDstCallback> {
174    /// Adds an IBC source callback entry to the memo field.
175    /// Use this if you want to receive IBC callbacks on the source chain.
176    ///
177    /// For more info check out [`crate::IbcSourceCallbackMsg`].
178    pub fn with_src_callback(
179        self,
180        src_callback: IbcSrcCallback,
181    ) -> TransferMsgBuilder<WithCallbacks> {
182        TransferMsgBuilder {
183            channel_id: self.channel_id,
184            to_address: self.to_address,
185            amount: self.amount,
186            timeout: self.timeout,
187            memo: WithCallbacks {
188                src_callback,
189                dst_callback: self.memo.dst_callback,
190            },
191        }
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use crate::{coin, Addr, Timestamp, Uint64};
198
199    use super::*;
200
201    #[test]
202    fn test_transfer_msg_builder() {
203        let src_callback = IbcSrcCallback {
204            address: Addr::unchecked("src"),
205            gas_limit: Some(Uint64::new(12345)),
206        };
207        let dst_callback = IbcDstCallback {
208            address: "dst".to_string(),
209            gas_limit: None,
210        };
211
212        let empty_memo_builder = TransferMsgBuilder::new(
213            "channel-0",
214            "cosmos1example",
215            coin(10, "ucoin"),
216            Timestamp::from_seconds(12345),
217        );
218
219        let empty = empty_memo_builder.clone().build();
220        let with_memo = empty_memo_builder.clone().with_memo("memo").build();
221
222        let with_src_callback_builder = empty_memo_builder
223            .clone()
224            .with_src_callback(src_callback.clone());
225        let with_src_callback = with_src_callback_builder.clone().build();
226        let with_dst_callback_builder = empty_memo_builder
227            .clone()
228            .with_dst_callback(dst_callback.clone());
229        let with_dst_callback = with_dst_callback_builder.clone().build();
230
231        let with_both_callbacks1 = with_src_callback_builder
232            .with_dst_callback(dst_callback.clone())
233            .build();
234
235        let with_both_callbacks2 = with_dst_callback_builder
236            .with_src_callback(src_callback.clone())
237            .build();
238
239        // assert all the different messages
240        assert_eq!(
241            empty,
242            IbcMsg::Transfer {
243                channel_id: "channel-0".to_string(),
244                to_address: "cosmos1example".to_string(),
245                amount: coin(10, "ucoin"),
246                timeout: Timestamp::from_seconds(12345).into(),
247                memo: None,
248            }
249        );
250        assert_eq!(
251            with_memo,
252            IbcMsg::Transfer {
253                channel_id: "channel-0".to_string(),
254                to_address: "cosmos1example".to_string(),
255                amount: coin(10, "ucoin"),
256                timeout: Timestamp::from_seconds(12345).into(),
257                memo: Some("memo".to_string()),
258            }
259        );
260        assert_eq!(
261            with_src_callback,
262            IbcMsg::Transfer {
263                channel_id: "channel-0".to_string(),
264                to_address: "cosmos1example".to_string(),
265                amount: coin(10, "ucoin"),
266                timeout: Timestamp::from_seconds(12345).into(),
267                memo: Some(
268                    to_json_string(&IbcCallbackRequest::source(src_callback.clone())).unwrap()
269                ),
270            }
271        );
272        assert_eq!(
273            with_dst_callback,
274            IbcMsg::Transfer {
275                channel_id: "channel-0".to_string(),
276                to_address: "cosmos1example".to_string(),
277                amount: coin(10, "ucoin"),
278                timeout: Timestamp::from_seconds(12345).into(),
279                memo: Some(
280                    to_json_string(&IbcCallbackRequest::destination(dst_callback.clone())).unwrap()
281                ),
282            }
283        );
284        assert_eq!(
285            with_both_callbacks1,
286            IbcMsg::Transfer {
287                channel_id: "channel-0".to_string(),
288                to_address: "cosmos1example".to_string(),
289                amount: coin(10, "ucoin"),
290                timeout: Timestamp::from_seconds(12345).into(),
291                memo: Some(
292                    to_json_string(&IbcCallbackRequest::both(src_callback, dst_callback)).unwrap()
293                ),
294            }
295        );
296        assert_eq!(with_both_callbacks1, with_both_callbacks2);
297    }
298}