tonlib_core/message/nft/
transfer.rs

1use num_bigint::BigUint;
2use num_traits::Zero;
3
4use super::NFT_TRANSFER;
5use crate::cell::{ArcCell, Cell, CellBuilder, EitherCellLayout, EMPTY_ARC_CELL};
6use crate::message::{HasOpcode, TonMessage, TonMessageError, WithForwardPayload, ZERO_COINS};
7use crate::TonAddress;
8
9/// Creates a body for jetton transfer according to TL-B schema:
10///
11/// ```raw
12/// transfer#5fcc3d14
13///   query_id:uint64
14///   new_owner:MsgAddress
15///   response_destination:MsgAddress
16///   custom_payload:(Maybe ^Cell)
17///   forward_amount:(VarUInteger 16)
18///   forward_payload:(Either Cell ^Cell)
19/// = InternalMsgBody;
20/// ```
21#[derive(Clone, Debug, PartialEq)]
22pub struct NftTransferMessage {
23    /// arbitrary request number.
24    pub query_id: u64,
25    /// address of the new owner of the NFT item.
26    pub new_owner: TonAddress,
27    ///  address where to send a response with confirmation of a successful transfer and the rest of the incoming message coins.
28    pub response_destination: TonAddress,
29    /// optional custom data.
30    pub custom_payload: Option<ArcCell>,
31    ///  the amount of nanotons to be sent to the destination address.
32    pub forward_ton_amount: BigUint,
33    ///  optional custom data that should be sent to the destination address.
34    pub forward_payload: ArcCell,
35
36    pub forward_payload_layout: EitherCellLayout,
37}
38
39impl NftTransferMessage {
40    pub fn new(new_owner: &TonAddress) -> Self {
41        NftTransferMessage {
42            query_id: 0,
43            new_owner: new_owner.clone(),
44            response_destination: TonAddress::NULL,
45            custom_payload: None,
46            forward_ton_amount: ZERO_COINS.clone(),
47            forward_payload: EMPTY_ARC_CELL.clone(),
48            forward_payload_layout: EitherCellLayout::Native,
49        }
50    }
51
52    pub fn with_response_destination(&mut self, response_destination: &TonAddress) -> &mut Self {
53        self.response_destination = response_destination.clone();
54        self
55    }
56
57    pub fn with_custom_payload(&mut self, custom_payload: ArcCell) -> &mut Self {
58        self.custom_payload = Some(custom_payload);
59        self
60    }
61}
62
63impl TonMessage for NftTransferMessage {
64    fn build(&self) -> Result<Cell, TonMessageError> {
65        if self.forward_ton_amount.is_zero() && self.forward_payload == EMPTY_ARC_CELL.clone() {
66            return Err(TonMessageError::ForwardTonAmountIsNegative);
67        }
68
69        let mut builder = CellBuilder::new();
70        builder.store_u32(32, Self::opcode())?;
71        builder.store_u64(64, self.query_id)?;
72
73        builder.store_address(&self.new_owner)?;
74        builder.store_address(&self.response_destination)?;
75        builder.store_ref_cell_optional(self.custom_payload.as_ref())?;
76        builder.store_coins(&self.forward_ton_amount)?;
77        builder
78            .store_either_cell_or_cell_ref(&self.forward_payload, self.forward_payload_layout)?;
79        Ok(builder.build()?)
80    }
81
82    fn parse(cell: &Cell) -> Result<Self, TonMessageError> {
83        let mut parser = cell.parser();
84
85        let opcode: u32 = parser.load_u32(32)?;
86        let query_id = parser.load_u64(64)?;
87
88        let new_owner = parser.load_address()?;
89        let response_destination = parser.load_address()?;
90        let custom_payload = parser.load_maybe_cell_ref()?;
91        let forward_ton_amount = parser.load_coins()?;
92        let forward_payload = parser.load_either_cell_or_cell_ref()?;
93        parser.ensure_empty()?;
94
95        let result = NftTransferMessage {
96            query_id,
97            new_owner,
98            response_destination,
99            custom_payload,
100            forward_ton_amount,
101            forward_payload,
102            forward_payload_layout: EitherCellLayout::Native,
103        };
104        result.verify_opcode(opcode)?;
105
106        Ok(result)
107    }
108}
109
110impl WithForwardPayload for NftTransferMessage {
111    fn set_forward_payload(&mut self, forward_payload: ArcCell, forward_ton_amount: BigUint) {
112        self.forward_payload = forward_payload;
113        self.forward_ton_amount = forward_ton_amount;
114    }
115}
116
117impl HasOpcode for NftTransferMessage {
118    fn set_query_id(&mut self, query_id: u64) {
119        self.query_id = query_id;
120    }
121
122    fn query_id(&self) -> u64 {
123        self.query_id
124    }
125
126    fn opcode() -> u32 {
127        NFT_TRANSFER
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use std::sync::Arc;
134
135    use lazy_static::lazy_static;
136    use num_bigint::BigUint;
137
138    use crate::cell::{ArcCell, BagOfCells, Cell, EitherCellLayout};
139    use crate::message::{
140        HasOpcode, NftTransferMessage, TonMessage, TonMessageError, WithForwardPayload,
141    };
142    use crate::TonAddress;
143
144    const NFT_TRANSFER_MSG: &str="b5ee9c7201010101006f0000d95fcc3d140000000000000000800e20aaf07ad251d1800fe45e3af334769b7b2069d3ab2ea6c9ee0f73dfd072a21000a1b4b24b6a66313f3e0b49d095f3e8f4294af504b3a0f7b99290129f3aaafcc47312d0040544f4e506c616e65747320676966742077697468206c6f76658";
145    const NFT_TRANSFER_PAYLOAD_DATA: &str = "40544F4E506C616E65747320676966742077697468206C6F7665";
146    const NFT_TRANSFER_PAYLOAD_BIT_LEN: usize = 208;
147
148    lazy_static! {
149        static ref NFT_TRANSFER_PAYLOAD: ArcCell = Arc::new(
150            Cell::new(
151                hex::decode(NFT_TRANSFER_PAYLOAD_DATA).unwrap(),
152                NFT_TRANSFER_PAYLOAD_BIT_LEN,
153                vec![],
154                false,
155            )
156            .unwrap()
157        );
158    }
159    #[test]
160    fn test_ft_transfer_parser() -> Result<(), TonMessageError> {
161        let boc = BagOfCells::parse_hex(NFT_TRANSFER_MSG)?;
162        let cell = boc.single_root()?;
163
164        let result_nft_transfer_msg = NftTransferMessage::parse(&cell)?;
165
166        let forward_ton_amount = BigUint::from(10000000u64);
167        let expected_nft_transfer_msg = NftTransferMessage {
168            query_id: 0,
169            new_owner: TonAddress::from_hex_str(
170                "0:71055783d6928e8c007f22f1d799a3b4dbd9034e9d5975364f707b9efe839510",
171            )
172            .unwrap(),
173            response_destination: TonAddress::from_hex_str(
174                "0:286d2c92da998c4fcf82d274257cfa3d0a52bd412ce83dee64a404a7ceaabf31",
175            )
176            .unwrap(),
177            custom_payload: None,
178            forward_ton_amount,
179            forward_payload: NFT_TRANSFER_PAYLOAD.clone(),
180            forward_payload_layout: EitherCellLayout::Native,
181        };
182
183        assert_eq!(expected_nft_transfer_msg, result_nft_transfer_msg);
184        Ok(())
185    }
186
187    #[test]
188    fn test_nft_transfer_builder() -> anyhow::Result<()> {
189        let jetton_transfer_msg = NftTransferMessage::new(&TonAddress::from_hex_str(
190            "0:71055783d6928e8c007f22f1d799a3b4dbd9034e9d5975364f707b9efe839510",
191        )?)
192        .with_query_id(0)
193        .with_response_destination(&TonAddress::from_hex_str(
194            "0:286d2c92da998c4fcf82d274257cfa3d0a52bd412ce83dee64a404a7ceaabf31",
195        )?)
196        .with_forward_payload(BigUint::from(10000000u64), NFT_TRANSFER_PAYLOAD.clone())
197        .build();
198
199        let result_boc_serialized = BagOfCells::from_root(jetton_transfer_msg?).serialize(false)?;
200        let expected_boc_serialized = hex::decode(NFT_TRANSFER_MSG)?;
201
202        assert_eq!(expected_boc_serialized, result_boc_serialized);
203        Ok(())
204    }
205}